mockAgent.ts ×55

Frontier kind: Code frontier

unlabeled · c_67d9c219eff3

325 tests · 26680 LOC · 131 files · introduces 0 tests · 260 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
55 ranges260 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
2379 ranges26680 lines · 131 files · Browse complete extent
All tests (intent)
325 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 260 introduced LOC across 55 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/test/node/mockAgent.ts 260 introduced LOC · 55 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- mockAgent.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { timeout } from '../../../../base/common/async.js';
7 > import { Emitter } from '../../../../base/common/event.js';
8 > import { observableValue } from '../../../../base/common/observable.js';
9 > import type { IAuthorizationProtectedResourceMetadata } from '../../../../base/common/oauth.js';
10 > import { URI } from '../../../../base/common/uri.js';
11 > import { type ISyncedCustomization } from '../../common/agentPluginManager.js';
12 > import { AgentSession, type AgentProvider, type AgentSignal, type IActiveClient, type IAgent, type IAgentActionSignal, type IAgentChats, type IAgentCreateChatForkSource, type IAgentCreateChatOptions, type IAgentCreateChatResult, type IAgentCreateSessionConfig, type IAgentCreateSessionResult, type IAgentDescriptor, type IAgentModelInfo, type IAgentResolveSessionConfigParams, type IAgentSessionConfigCompletionsParams, type IAgentSessionMetadata, type IAgentToolPendingConfirmationSignal } from '../../common/agentService.js';
13 > import { buildSubagentTurnsFromHistory, buildTurnsFromHistory, type IHistoryRecord } from './historyRecordFixtures.js';
14 > import { ProtectedResourceMetadata, ToolCallContributorKind, type AgentSelection, type MessageAttachment, type ModelSelection, type ToolDefinition } from '../../common/state/protocol/state.js';
15 > import type { ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../../common/state/protocol/commands.js';
16 > import { ActionType } from '../../common/state/sessionActions.js';
17 > import { ResponsePartKind, ToolCallConfirmationReason, ToolCallStatus, ToolResultContentType, CustomizationLoadStatus, buildDefaultChatUri, isAhpChatChannel, parseChatUri, parseSubagentSessionUri, type ClientPluginCustomization, type Customization, type PendingMessage, type StringOrMarkdown, type ToolCallResult, type Turn, type UsageInfo } from '../../common/state/sessionState.js';
18 > import { hasKey } from '../../../../base/common/types.js';
19 >
20 > /** Well-known auto-generated title used by the 'with-title' prompt. */
21 > export const MOCK_AUTO_TITLE = 'Automatically generated title';
22 >
23 function uriKey(session: URI): string {
24 // Build a stable key from raw URI fields without invoking `toString()`,
28 return `${session.scheme}://${session.authority}${session.path}${session.query ? '?' + session.query : ''}${session.fragment ? '#' + session.fragment : ''}`;
29 }
31 function mockProject(provider: AgentProvider) {
32 return { uri: URI.from({ scheme: 'mock-project', path: `/${provider}` }), displayName: `Agent ${provider}` };
33 }
35 > interface IMockSendMessageCall {
36 > readonly session: URI;
37 > readonly prompt: string;
38 > readonly attachments?: readonly MessageAttachment[];
39 > readonly chat?: URI;
40 > readonly senderClientId?: string;
41 > }
42 >
43 > /**
44 > * General-purpose mock agent for unit tests. Tracks all method calls
45 > * for assertion and exposes {@link fireProgress} to inject progress events.
46 > */
47 > export class MockAgent implements IAgent {
48 > private readonly _onDidSessionProgress = new Emitter<AgentSignal>();
49 > readonly onDidSessionProgress = this._onDidSessionProgress.event;
50 > private readonly _onDidSendMessage = new Emitter<IMockSendMessageCall>();
51 > readonly onDidSendMessage = this._onDidSendMessage.event;
52 > private readonly _models = observableValue<readonly IAgentModelInfo[]>(this, []);
53 > readonly models = this._models;
54 >
55 > private readonly _sessions = new Map<string, URI>();
56 > private _nextId = 1;
57 > /** Active turn IDs per session, captured from sendMessage(). */
58 > private readonly _activeTurnIds = new Map<string, string>();
59 >
60 >
61 > readonly sendMessageCalls: IMockSendMessageCall[] = [];
62 > readonly setPendingMessagesCalls: { chat: URI; steeringMessage: PendingMessage | undefined; queuedMessages: readonly PendingMessage[] }[] = [];
63 > readonly disposeSessionCalls: URI[] = [];
64 > readonly releaseSessionCalls: URI[] = [];
65 > readonly abortSessionCalls: URI[] = [];
66 > readonly respondToPermissionCalls: { requestId: string; approved: boolean }[] = [];
67 > readonly changeModelCalls: { session: URI; model: ModelSelection; chat?: URI }[] = [];
68 > readonly changeAgentCalls: { session: URI; agent: AgentSelection | undefined; chat?: URI }[] = [];
69 > readonly authenticateCalls: { resource: string; token: string }[] = [];
70 > readonly setClientCustomizationsCalls: { clientId: string; customizations: ClientPluginCustomization[] }[] = [];
71 > readonly setClientToolsCalls: { clientId: string; tools: readonly ToolDefinition[] }[] = [];
72 > readonly removeActiveClientCalls: { clientId: string }[] = [];
73 > readonly clientToolCallCompleteCalls: { session: URI; chat: URI; toolCallId: string; result: ToolCallResult }[] = [];
74 > readonly truncateSessionCalls: { session: URI; turnId: string | undefined; chat: URI | undefined }[] = [];
75 > /** Configurable return value for getCustomizations. */
76 > customizations: Customization[] = [];
77 > private readonly _onDidCustomizationsChange = new Emitter<void>();
78 > readonly onDidCustomizationsChange = this._onDidCustomizationsChange.event;
79 > getSessionCustomizations?: (session: URI) => Promise<readonly Customization[]>;
80 >
81 > /**
82 > * Configurable session history. Tests construct {@link IHistoryRecord}
83 > * entries (the agent-internal intermediate shape) and the mock converts
84 > * them to {@link Turn}s on demand. Subagent URIs are routed to filtered
85 > * subagent turns via {@link buildSubagentTurnsFromHistory}.
86 > */
87 > sessionMessages: IHistoryRecord[] = [];
88 >
89 > /** Optional overrides applied to session metadata from listSessions. */
90 > sessionMetadataOverrides: Partial<Omit<IAgentSessionMetadata, 'session'>> = {};
91 >
92 > constructor(readonly id: AgentProvider = 'mock') { }
93
94 getDescriptor(): IAgentDescriptor {
262 },
263 };
264 > mockAgent.ts
265 > async authenticate(resource: string, token: string): Promise<boolean> {
266 this.authenticateCalls.push({ resource, token });
267 return true;
268 }
269 > mockAgent.ts
270 > getCustomizations(): Customization[] {
271 return this.customizations;
272 }
273 > mockAgent.ts
274 > syncClientCustomizations(session: URI, clientId: string, customizations: ClientPluginCustomization[]): ISyncedCustomization[] {
275 this.setClientCustomizationsCalls.push({ clientId, customizations });
276 const results: ISyncedCustomization[] = customizations.map(c => ({
290 return results;
291 }
292 > mockAgent.ts
293 > getOrCreateActiveClient(session: URI, client: { readonly clientId: string; readonly displayName?: string }): IActiveClient {
294 const self = this;
295 let tools: readonly ToolDefinition[] = [];
310 };
311 }
312 > mockAgent.ts
313 > removeActiveClient(_session: URI, clientId: string): void {
314 this.removeActiveClientCalls.push({ clientId });
315 }
316 > mockAgent.ts
317 > onClientToolCallComplete(session: URI, chat: URI, toolCallId: string, result: ToolCallResult): void {
318 this.clientToolCallCompleteCalls.push({ session, chat, toolCallId, result });
319 }
320 > mockAgent.ts
321 > async shutdown(): Promise<void> { }
322 >
323 > /**
324 > * Fires an {@link AgentSignal} on this agent.
325 > */
326 > fireProgress(signal: AgentSignal): void {
327 this._onDidSessionProgress.fire(signal);
328 }
329 > mockAgent.ts
330 > /**
331 > * Looks up the active turn id captured from the most recent
332 > * {@link sendMessage} call for a given session. Returns `undefined` if
333 > * the session has no active turn yet (e.g. tests that fire progress
334 > * without first calling sendMessage).
335 > */
336 > getActiveTurnId(session: URI): string | undefined {
337 return this._activeTurnIds.get(uriKey(session));
338 }
339 > mockAgent.ts
340 > fireCustomizationsChange(): void {
341 this._onDidCustomizationsChange.fire();
342 }
343 > mockAgent.ts
344 > dispose(): void {
345 this._onDidSessionProgress.dispose();
346 this._onDidSendMessage.dispose();
347 this._onDidCustomizationsChange.dispose();
348 }
349 > } mockAgent.ts
350 >
351 > /**
352 > * Well-known URI of a pre-existing session seeded in {@link ScriptedMockAgent}.
353 > * This session appears in `listSessions()` and has message history via
354 > * `getSessionMessages()`, but was never created through the server's
355 > * `handleCreateSession`. It simulates a session from a previous server
356 > * lifetime for testing the restore-on-subscribe path.
357 > */
358 > export const PRE_EXISTING_SESSION_URI = AgentSession.uri('mock', 'pre-existing-session');
359 >
360 > export class ScriptedMockAgent implements IAgent {
361 > readonly id: AgentProvider = 'mock';
362 >
363 > private readonly _onDidSessionProgress = new Emitter<AgentSignal>();
364 > readonly onDidSessionProgress = this._onDidSessionProgress.event;
365 > private readonly _models = observableValue<readonly IAgentModelInfo[]>(this, [{ provider: 'mock', id: 'mock-model', name: 'Mock Model', maxContextWindow: 128000, supportsVision: false }]);
366 > readonly models = this._models;
367 >
368 > private readonly _sessions = new Map<string, URI>();
369 > private _nextId = 1;
370 >
371 > /**
372 > * Message history for the pre-existing session: a single user→assistant
373 > * turn with a tool call.
374 > */
375 > private readonly _preExistingMessages: IHistoryRecord[] = [
376 > { type: 'message', role: 'user', session: PRE_EXISTING_SESSION_URI, messageId: 'h-msg-1', content: 'What files are here?' },
377 > { type: 'tool_start', session: PRE_EXISTING_SESSION_URI, toolCallId: 'h-tc-1', toolName: 'list_files', displayName: 'List Files', invocationMessage: 'Listing files...' },
378 > { type: 'tool_complete', session: PRE_EXISTING_SESSION_URI, toolCallId: 'h-tc-1', result: { pastTenseMessage: 'Listed files', content: [{ type: ToolResultContentType.Text, text: 'file1.ts\nfile2.ts' }], success: true } satisfies ToolCallResult },
379 > { type: 'message', role: 'assistant', session: PRE_EXISTING_SESSION_URI, messageId: 'h-msg-2', content: 'Here are the files: file1.ts and file2.ts' },
380 > ];
381 >
382 > // Track pending permission requests
383 > private readonly _pendingPermissions = new Map<string, (approved: boolean) => void>();
384 > // Track the active turn ID per session, captured from sendMessage().
385 > private readonly _activeTurnIds = new Map<string, string>();
386 > // Track pending abort callbacks for slow responses
387 > private readonly _pendingAborts = new Map<string, () => void>();
388 >
389 > constructor() {
390 // Seed the pre-existing session so it appears in listSessions()
391 this._sessions.set(AgentSession.id(PRE_EXISTING_SESSION_URI), PRE_EXISTING_SESSION_URI);
406 }
407 }
408 > mockAgent.ts
409 > getDescriptor(): IAgentDescriptor {
410 return { provider: 'mock', displayName: 'Mock Agent', description: 'Scripted test agent' };
411 }
412 > mockAgent.ts
413 > getProtectedResources(): IAuthorizationProtectedResourceMetadata[] {
414 return [];
415 }
416 > mockAgent.ts
417 > async listSessions(): Promise<IAgentSessionMetadata[]> {
418 return [...this._sessions.values()].map(s => ({
419 session: s,
424 }));
425 }
426 > mockAgent.ts
427 > async getSessionMetadata(session: URI): Promise<IAgentSessionMetadata | undefined> {
428 if (!this._sessions.has(AgentSession.id(session))) {
429 return undefined;
437 };
438 }
439 > mockAgent.ts
440 > async createSession(config?: IAgentCreateSessionConfig): Promise<IAgentCreateSessionResult> {
441 const session = config?.session ?? AgentSession.uri('mock', `mock-session-${this._nextId++}`);
442 const rawId = AgentSession.id(session);
444 return { session, project: mockProject(this.id) };
445 }
446 > mockAgent.ts
447 > async resolveSessionConfig(params: IAgentResolveSessionConfigParams): Promise<ResolveSessionConfigResult> {
448 const isolation = params.config?.isolation === 'folder' || params.config?.isolation === 'worktree' ? params.config.isolation : 'worktree';
449 const branch = isolation === 'worktree' && typeof params.config?.branch === 'string' ? params.config.branch : 'main';
475 };
476 }
477 > mockAgent.ts
478 > async sessionConfigCompletions(params: IAgentSessionConfigCompletionsParams): Promise<SessionConfigCompletionsResult> {
479 if (params.property !== 'branch') {
480 return { items: [] };
484 return { items: branches.map(branch => ({ value: branch, label: branch })) };
485 }
486 > mockAgent.ts
487 > async sendMessage(session: URI, chat: URI, prompt: string, _attachments?: readonly MessageAttachment[], turnId?: string): Promise<void> {
488 if (turnId) {
489 this._activeTurnIds.set(uriKey(session), turnId);
821 }
822 }
823 > mockAgent.ts
824 > setPendingMessages(chat: URI, steeringMessage: PendingMessage | undefined, _queuedMessages: readonly PendingMessage[]): void {
825 // When steering is set, consume it on the next tick
826 if (steeringMessage) {
830 }
831 }
832 > mockAgent.ts
833 > getOrCreateActiveClient(_session: URI, client: { readonly clientId: string; readonly displayName?: string }): IActiveClient {
834 let tools: readonly ToolDefinition[] = [];
835 let customizations: readonly ClientPluginCustomization[] = [];
843 };
844 }
845 > mockAgent.ts
846 > removeActiveClient(): void { }
847 >
848 > private didCompleteToolCalls = new Set<string>();
849 >
850 > onClientToolCallComplete(session: URI, chat: URI, toolCallId: string, result: ToolCallResult): void {
851 // The mock's event model is chat-channel oriented (sendMessage fires
852 // every turn signal on the chat URI). Emit the completion on the chat
867 }
868 }
869 > mockAgent.ts
870 > async getSessionMessages(session: URI): Promise<readonly Turn[]> {
871 const subagentInfo = parseSubagentSessionUri(session);
872 if (subagentInfo) {
882 return [];
883 }
884 > mockAgent.ts
885 > async disposeSession(session: URI): Promise<void> {
886 this._sessions.delete(AgentSession.id(session));
887 }
888 > mockAgent.ts
889 > async abortSession(session: URI): Promise<void> {
890 const callback = this._pendingAborts.get(session.toString());
891 if (callback) {
894 }
895 }
896 > mockAgent.ts
897 > async changeModel(_session: URI, _model: ModelSelection): Promise<void> {
898 // Mock agent doesn't track model state
899 }
900 > mockAgent.ts
901 > /**
902 > * Map an already-resolved chat URI to the `(session, chat)` pair the
903 > * scripted mock's per-chat context is keyed by.
904 > */
905 > private _resolveChatTarget(chat: URI): { session: URI; chat: URI } {
906 const parsed = parseChatUri(chat);
907 if (!parsed) {
910 return { session: URI.parse(parsed.session), chat: URI.parse(chat.toString()) };
911 }
912 > mockAgent.ts
913 > readonly chats: IAgentChats = {
914 > createChat: (_chat: URI, _options?: IAgentCreateChatOptions): Promise<IAgentCreateChatResult | void> => {
915 throw new Error('Scripted mock agent does not support multiple chats');
916 },
917 > fork: (_chat: URI, _source: IAgentCreateChatForkSource, _options?: IAgentCreateChatOptions): Promise<IAgentCreateChatResult | void> => { mockAgent.ts
918 throw new Error('Scripted mock agent does not support chat forking');
919 },
920 > disposeChat: (_chat: URI): Promise<void> => { mockAgent.ts
921 return Promise.resolve();
922 },
923 > sendMessage: (chatUri: URI, prompt: string, _workingDirectory: URI | undefined, attachments?: readonly MessageAttachment[], turnId?: string, _senderClientId?: string): Promise<void> => { mockAgent.ts
924 const { session, chat } = this._resolveChatTarget(chatUri);
925 return this.sendMessage(session, chat, prompt, attachments, turnId);
926 },
927 > abort: (chat: URI): Promise<void> => { mockAgent.ts
928 const { session } = this._resolveChatTarget(chat);
929 return this.abortSession(session);
930 },
931 > changeModel: (chat: URI, model: ModelSelection): Promise<void> => { mockAgent.ts
932 const { session } = this._resolveChatTarget(chat);
933 return this.changeModel(session, model);
934 },
935 > changeAgent: (_chat: URI, _agent: AgentSelection | undefined): Promise<void> => { mockAgent.ts
936 // Scripted mock does not track agent selection.
937 return Promise.resolve();
938 },
939 > getMessages: (chat: URI): Promise<readonly Turn[]> => { mockAgent.ts
940 return this.getSessionMessages(chat);
941 },
942 > }; mockAgent.ts
943 >
944 > async truncateSession(_session: URI, _turnId?: string): Promise<void> {
945 // Mock agent accepts truncation without side effects
946 }
947 > mockAgent.ts
948 > respondToPermissionRequest(toolCallId: string, approved: boolean): void {
949 const callback = this._pendingPermissions.get(toolCallId);
950 if (callback) {
953 }
954 }
955 > mockAgent.ts
956 > respondToUserInputRequest(): void {
957 // no-op for tests
958 }
959 > mockAgent.ts
960 > async authenticate(_resource: string, _token: string): Promise<boolean> {
961 return true;
962 }
963 > mockAgent.ts
964 > async shutdown(): Promise<void> { }
965 >
966 > dispose(): void {
967 this._onDidSessionProgress.dispose();
968 }
969 > mockAgent.ts
970 > /**
971 > * Fires a sequence of {@link AgentSignal}s with staggered 10 ms delays
972 > * so the state manager processes them in order.
973 > */
974 > private _fireSequence(signals: AgentSignal[]): void {
975 let delay = 0;
976 for (const signal of signals) {
979 }
980 }
981 > mockAgent.ts
982 > /** Builds the session-string + turnId context for signal construction. */
983 > private _ctx(session: URI): { sessionStr: string; turnId: string } {
984 return {
985 sessionStr: session.toString(),
987 };
988 }
989 > } mockAgent.ts
990 >
991 > // =============================================================================
992 > // Test-event helpers
993 > // =============================================================================
994 >
995 > // =============================================================================
996 > // Signal factory helpers
997 > // =============================================================================
998 >
999 > let _mockPartIdCounter = 0;
1000 >
1001 > /** Wraps a session action into an {@link IAgentActionSignal}. */
1002 function _action(session: URI, action: import('../../common/state/sessionActions.js').SessionAction | import('../../common/state/sessionActions.js').ChatAction, parentToolCallId?: string): IAgentActionSignal {
1003 return { kind: 'action', resource: session, action, parentToolCallId };
1004 }
1005 > mockAgent.ts
1006 > /** Creates a markdown {@link ResponsePartKind.Markdown} response part signal. */
1007 function _markdown(session: URI, sessionStr: string, turnId: string, content: string, parentToolCallId?: string): IAgentActionSignal {
1008 return _action(session, {
1012 }, parentToolCallId);
1013 }
1014 > mockAgent.ts
1015 > /** Creates a reasoning {@link ResponsePartKind.Reasoning} response part signal. */
1016 function _reasoning(session: URI, sessionStr: string, turnId: string, content: string): IAgentActionSignal {
1017 return _action(session, {
1021 });
1022 }
1023 > mockAgent.ts
1024 > /** Creates a {@link ActionType.ChatTurnComplete} signal. */
1025 function _idle(session: URI, sessionStr: string, turnId: string): IAgentActionSignal {
1026 return _action(session, { type: ActionType.ChatTurnComplete, turnId, duration: 1 });
1027 }
1028 > mockAgent.ts
1029 > /** Creates a {@link ActionType.ChatError} signal. */
1030 function _error(session: URI, sessionStr: string, turnId: string, errorType: string, message: string, stack?: string): IAgentActionSignal {
1031 return _action(session, { type: ActionType.ChatError, turnId, duration: 1, error: { errorType, message, stack } });
1032 }
1033 > mockAgent.ts
1034 > /** Creates a {@link ActionType.SessionTitleChanged} signal. */
1035 function _titleChanged(session: URI, sessionStr: string, title: string): IAgentActionSignal {
1036 return _action(session, { type: ActionType.SessionTitleChanged, title });
1037 }
1038 > mockAgent.ts
1039 > /** Creates a {@link ActionType.ChatUsage} signal. */
1040 function _usage(session: URI, sessionStr: string, turnId: string, usage: UsageInfo): IAgentActionSignal {
1041 return _action(session, { type: ActionType.ChatUsage, turnId, usage });
1042 }
1043 > mockAgent.ts
1044 > /**
1045 > * Creates tool-start signals: a {@link ActionType.ChatToolCallStart} and,
1046 > * for non-client tools, an auto-ready {@link ActionType.ChatToolCallReady}.
1047 > */
1048 function _toolStart(session: URI, sessionStr: string, turnId: string, toolCallId: string, toolName: string, displayName: string, invocationMessage: StringOrMarkdown, opts?: {
1049 toolInput?: string;
1085 return signals;
1086 }
1087 > mockAgent.ts
1088 > /** Creates a {@link ActionType.ChatToolCallComplete} signal. */
1089 function _toolComplete(session: URI, sessionStr: string, turnId: string, toolCallId: string, result: ToolCallResult, parentToolCallId?: string): IAgentActionSignal {
1090 return _action(session, { type: ActionType.ChatToolCallComplete, turnId, toolCallId, result }, parentToolCallId);
1091 }
1092 > mockAgent.ts
1093 > /** Creates a {@link IAgentToolPendingConfirmationSignal}. */
1094 function _pendingConfirmation(session: URI, toolCallId: string, invocationMessage: StringOrMarkdown, opts?: {
1095 toolInput?: string;