chatServiceImpl.ts ×25

Frontier kind: Code frontier

unlabeled · c_a10dde441b65

34 tests · 72005 LOC · 309 files · introduces 0 tests · 274 LOC · 5 files

Introduces — evidence that enters the hierarchy at this concept

Code
48 ranges274 lines · 5 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
6141 ranges72005 lines · 309 files · Browse complete extent
All tests (intent)
34 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.

5 files ranked by introduced lines: 274 introduced LOC across 48 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts 181 introduced LOC · 25 ranges

Open complete file

99 dispose() {
100 if (this.requestId) {
101 > this.toolsService.cancelToolCallsForRequest(this.requestId); chatServiceImpl.ts
102 > }
103 this.cancellationTokenSource.dispose();
104 }
1456 }
1457 }
1459 > // Collect hooks from hook .json files
1460 > const collectHooks = async (): Promise<{ hooks: ChatRequestHooks | undefined; hasDisabledClaudeHooks: boolean }> => {
1461 > let collectedHooks: ChatRequestHooks | undefined;
1462 > let hasDisabledClaudeHooks = false;
1463 > try {
1464 > const hooksInfo = await this.promptsService.getHooks(token);
1465 if (hooksInfo) {
1466 collectedHooks = hooksInfo.hooks;
1467 hasDisabledClaudeHooks = hooksInfo.hasDisabledClaudeHooks;
1468 }
1469 > } catch (error) { chatServiceImpl.ts
1470 this.logService.warn('[ChatService] Failed to collect hooks:', error);
1471 }
1473 > // Merge hooks from the selected custom agent's frontmatter (if any)
1474 > const agentName = options?.modeInfo?.modeInstructions?.name;
1475 > if (agentName) {
1476 try {
1477 const agents = await this.promptsService.getCustomAgents(token);
1484 }
1485 }
1486 > return { hooks: collectedHooks, hasDisabledClaudeHooks }; chatServiceImpl.ts
1487 > };
1488 >
1489 > // Collect automatic instructions (.instructions.md, skills, etc.)
1490 > const collectInstructions = async (): Promise<IChatRequestVariableEntry[]> => {
1491 > const ctx = options?.instructionContext;
1492 > if (!ctx) {
1493 > return [];
1494 > }
1495 // When the extension is responsible for instruction collection, skip the core path entirely.
1496 if (this.configurationService.getValue<boolean>(ChatConfiguration.CollectInstructionsInExtension) === true) {
1503 // resolution can see them. We filter them back out below
1504 // to return only the entries that were newly added.
1505 > const variableSet = new ChatRequestVariableSet(options?.attachedContext); chatServiceImpl.ts
1506 > const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, ctx.modeKind, ctx.enabledTools, ctx.enabledSubAgents, getChatSessionType(sessionResource));
1507 > await computer.collect(variableSet, token);
1508 // Return only the entries that were added by instruction collection
1509 > const originalIds = new Set((options?.attachedContext ?? []).map(v => v.id)); chatServiceImpl.ts
1510 > return variableSet.asArray().filter(v => !originalIds.has(v.id));
1511 > } catch (err) {
1512 this.logService.error('[ChatService] Failed to collect instructions:', err);
1513 return [];
1515 markChat(sessionResource, ChatPerfMark.DidCollectInstructions);
1516 }
1517 > }; chatServiceImpl.ts
1518 >
1519 > const stopWatch = new StopWatch(false);
1520 > store.add(token.onCancellationRequested(() => {
1521 this.trace('sendRequest', `Request for session ${model.sessionResource} was cancelled`);
1522 if (!request) {
1535
1536 model.cancelRequest(request);
1537 > })); chatServiceImpl.ts
1538 >
1539 > try {
1540 > let rawResult: IChatAgentResult | null | undefined;
1541 > let agentOrCommandFollowups: Promise<IChatFollowup[] | undefined> | undefined = undefined;
1542 if (agentPart || (defaultAgent && !commandPart)) {
1543 > // --- Step 1: Create the request model immediately (before any awaits) --- chatServiceImpl.ts
1544 > // This fires RequestUiUpdated synchronously so the user sees their message right away.
1545 > const initialAgent = agentPart?.agent ?? defaultAgent;
1546 > const initialCommand = agentSlashCommandPart?.command;
1547 > const initVariableData: IChatRequestVariableData = { variables: [] };
1548 > request = model.addRequest(parsedRequest, initVariableData, attempt, options?.modeInfo, initialAgent, initialCommand, options?.confirmation, options?.locationData, options?.attachedContext, undefined, options?.userSelectedModelId, options?.userSelectedTools?.get(), undefined, options?.isSystemInitiated, options?.systemInitiatedLabel, options?.terminalExecutionId, isTerminalCommand);
1549 > const thisRequest = request;
1550 > completeResponseCreated();
1551 >
1552 > // --- Step 2: Collect hooks + instructions in parallel (after UI is shown) ---
1553 > const [hooksResult, instructionEntries] = await Promise.all([
1554 > collectHooks(),
1555 > collectInstructions(),
1556 > ]);
1557 > const collectedHooks = hooksResult.hooks;
1558 > const hasDisabledClaudeHooks = hooksResult.hasDisabledClaudeHooks;
1559 >
1560 > // --- Step 3: Merge instructions and resolved variables into variableData ---
1561 > const allContext = this.prepareContext(request.attachedContext);
1562 > if (instructionEntries.length > 0) {
1563 allContext.push(...instructionEntries);
1564 }
1566 > // Store only non-instruction variables on the model.
1567 > // Automatically-added promptText entries (~33 KB each) are
1568 > // ephemeral — re-collected every turn, never rendered in
1569 > // the UI, and not needed in serialized session history.
1570 > const storedVariables = allContext.filter(v => !(isPromptTextVariableEntry(v) && v.automaticallyAdded));
1571 > model.updateRequest(request, { variables: storedVariables });
1572 >
1573 > // The full set (including instructions) is passed to the
1574 > // agent request only — not stored on the request model.
1575 > let variableData: IChatRequestVariableData = { variables: allContext };
1576 >
1577 > // Merge resolved variables (e.g. images from directories) for the
1578 > // agent request only - they are not stored on the request model.
1579 > if (options?.resolvedVariables?.length) {
1580 variableData = { variables: [...variableData.variables, ...options.resolvedVariables] };
1581 }
1583 > const promptTextResult = getPromptText(request.message);
1584 > variableData = updateRanges(variableData, promptTextResult.diff); // TODO bit of a hack
1585 > const message = promptTextResult.message;
1586 >
1587 > // --- Step 4: Build the agent request object ---
1588 > const buildAgentRequest = (agent: IChatAgentData, command?: IChatAgentCommand, enableCommandDetection?: boolean, isParticipantDetected?: boolean): IChatAgentRequest => {
1589 > const agentRequest: IChatAgentRequest = {
1590 > sessionResource: model.sessionResource,
1591 > requestId: thisRequest.id,
1592 > agentId: agent.id,
1593 > message,
1594 > command: command?.name,
1595 > variables: variableData,
1596 > enableCommandDetection,
1597 > isParticipantDetected,
1598 > attempt,
1599 > location,
1600 > locationData: thisRequest.locationData,
1601 > acceptedConfirmationData: options?.acceptedConfirmationData,
1602 > rejectedConfirmationData: options?.rejectedConfirmationData,
1603 > agentHostSessionConfig: options?.agentHostSessionConfig,
1604 > userSelectedModelId: options?.userSelectedModelId,
1605 > modelConfiguration: options?.userSelectedModelConfiguration ?? (options?.userSelectedModelId ? this.languageModelsService.getModelConfiguration(options.userSelectedModelId) : undefined),
1606 > userSelectedTools: options?.userSelectedTools?.get(),
1607 > modeInstructions: options?.modeInfo?.modeInstructions,
1608 > permissionLevel: options?.modeInfo?.permissionLevel,
1609 > editedFileEvents: thisRequest.editedFileEvents,
1610 > hooks: collectedHooks,
1611 > hasHooksEnabled: !!collectedHooks && Object.values(collectedHooks).some(arr => arr.length > 0),
1612 > isSystemInitiated: options?.isSystemInitiated,
1613 > workingDirectory: model.workingDirectory,
1614 > };
1615 >
1616 > let isInitialTools = true;
1617 >
1618 > store.add(autorun(reader => {
1619 > const tools = options?.userSelectedTools?.read(reader);
1620 > if (isInitialTools) {
1621 > isInitialTools = false;
1622 > return;
1623 > }
1624
1625 > if (tools && request) { chatServiceImpl.ts
1626 this.chatAgentService.setRequestTools(agent.id, request.id, tools);
1627 // in case the request has not been sent out yet:
1628 agentRequest.userSelectedTools = tools;
1629 }
1630 > })); chatServiceImpl.ts
1631 >
1632 > return agentRequest;
1633 > };
1634 >
1635 > // --- Step 5: Participant detection ---
1636 > if (
1637 > this.configurationService.getValue('chat.detectParticipant.enabled') !== false &&
1638 > this.chatAgentService.hasChatParticipantDetectionProviders() &&
1639 !agentPart &&
1640 !commandPart &&
1645 options?.modeInfo?.kind !== ChatModeKind.Edit &&
1646 !options?.agentIdSilent
1647 > ) { chatServiceImpl.ts
1648 // We have no agent or command to scope history with, pass the full history to the participant detection provider
1649 const defaultAgentHistory = this.getHistoryEntriesFromModel(requests, location, defaultAgent.id);
1658 }
1659 }
1661 > const agent = (detectedAgent ?? agentPart?.agent ?? defaultAgent)!;
1662 > const command = detectedCommand ?? agentSlashCommandPart?.command;
1663 >
1664 > await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`);
1665 >
1666 > // Recompute history in case the agent or command changed
1667 > const history = this.getHistoryEntriesFromModel(requests, location, agent.id);
1668 > const requestProps = buildAgentRequest(agent, command, enableCommandDetection, !!detectedAgent);
1669 > this.generateInitialChatTitleIfNeeded(model, requestProps, defaultAgent, token);
1670 > const pendingRequest = this._pendingRequests.get(sessionResource);
1671 > if (pendingRequest) {
1672 > store.add(autorun(reader => {
1673 > const yieldRequested = pendingRequest.yieldRequested.read(reader);
1674 > if (request) {
1675 > this.chatAgentService.setYieldRequested(agent.id, request.id, yieldRequested);
1676 > }
1677 > }));
1678 > pendingRequest.requestId ??= requestProps.requestId;
1679 > if (pendingRequest.requestId) {
1680 > this.telemetryService.publicLog2<ChatPendingRequestChangeEvent, ChatPendingRequestChangeClassification>(ChatPendingRequestChangeEventName, { action: 'add', source: 'sendRequestId', requestId: pendingRequest.requestId, chatSessionId: chatSessionResourceToId(sessionResource) });
1681 > }
1682 > }
1683 >
1684 > // Check for disabled Claude Code hooks and notify the user once per workspace.
1685 > // Only set the flag when actually showing the hint, so the setup agent flow
1686 > // (which may resend requests) doesn't consume the flag before the real request runs.
1687 > const disabledClaudeHooksDismissedKey = 'chat.disabledClaudeHooks.notification';
1688 > if (hasDisabledClaudeHooks && !this.storageService.getBoolean(disabledClaudeHooksDismissedKey, StorageScope.WORKSPACE)) {
1689 this.storageService.store(disabledClaudeHooksDismissedKey, true, StorageScope.WORKSPACE, StorageTarget.USER);
1690 progressCallback([{ kind: 'disabledClaudeHooks' }]);
1691 }
1693 > // MCP autostart: only run for native VS Code sessions (sidebar, new editors) but not for extension contributed sessions that have inputType set.
1694 > if (model.canUseTools) {
1695 const autostartResult = new ChatMcpServersStarting(this.mcpService.autostart(token));
1696 if (!autostartResult.isEmpty) {
1699 }
1700 }
1702 > const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token);
1703 rawResult = agentResult;
1704 agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken);
1794 request.response?.complete();
1795 }
1796 > } finally { chatServiceImpl.ts
1797 > store.dispose();
1798 > }
1799 };
1800 let shouldProcessPending = false;
1950
1951 private generateInitialChatTitleIfNeeded(model: ChatModel, request: IChatAgentRequest, defaultAgent: IChatAgentData, token: CancellationToken): void {
1952 > // Generate a title only for the first request, and only via the default agent. chatServiceImpl.ts
1953 > // Use a single-entry history based on the current request (no full chat history).
1954 > if (model.getRequests().length !== 1 || model.customTitle) {
1955 return;
1956 }
1968 };
1969 void generate();
1971
1972 private prepareContext(attachedContextVariables: IChatRequestVariableEntry[] | undefined): IChatRequestVariableEntry[] {
1973 > attachedContextVariables ??= []; chatServiceImpl.ts
1974 >
1975 > // "reverse", high index first so that replacement is simple
1976 > attachedContextVariables.sort((a, b) => {
1977 // If either range is undefined, sort it to the back
1978 if (!a.range && !b.range) {
1986 }
1987 return b.range.start - a.range.start;
1988 > }); chatServiceImpl.ts
1989 >
1990 > return attachedContextVariables;
1991 > }
1992
1993 private getHistoryEntriesFromModel(requests: IChatRequestModel[], location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] {
1994 > const history: IChatAgentHistoryEntry[] = []; chatServiceImpl.ts
1995 > const agent = this.chatAgentService.getAgent(forAgentId);
1996 > for (const request of requests) {
1997 if (!request.response) {
1998 continue;
2024 history.push({ request: historyRequest, response: toChatHistoryContent(request.response.response.value), result: request.response.result ?? {} });
2025 }
2027 > return history;
2028 > }
2029
2030 async removeRequest(sessionResource: URI, requestId: string): Promise<void> {
src/vs/workbench/contrib/chat/common/chatService/chatServiceTelemetry.ts 65 introduced LOC · 10 ranges

Open complete file

252 }
253
254 > function getCodeBlocks(text: string): string[] { chatServiceTelemetry.ts
255 > const lines = text.split('\n');
256 > const codeBlockLanguages: string[] = [];
257 >
258 > let codeBlockState: undefined | { readonly delimiter: string; readonly languageId: string };
259 > for (let i = 0; i < lines.length; i++) {
260 > const line = lines[i];
261 >
262 > if (codeBlockState) {
263 if (new RegExp(`^\\s*${codeBlockState.delimiter}\\s*$`).test(line)) {
264 codeBlockLanguages.push(codeBlockState.languageId);
265 codeBlockState = undefined;
266 }
267 > } else { chatServiceTelemetry.ts
268 > const match = line.match(/^(\s*)(`{3,}|~{3,})(\w*)/);
269 > if (match) {
270 codeBlockState = { delimiter: match[2], languageId: match[3] };
271 }
273 > }
274 > return codeBlockLanguages;
275 > }
276
277 export class ChatRequestTelemetry {
292
293 complete({ timeToFirstProgress, totalTime, result, requestType, request, detectedAgent }: {
294 > timeToFirstProgress: number | undefined; chatServiceTelemetry.ts
295 > totalTime: number | undefined;
296 > result: ChatProviderInvokedEvent['result'];
297 > requestType: ChatProviderInvokedEvent['requestType'];
298 > // Should rearrange so these 2 can be in the constructor
299 > request: ChatRequestModel;
300 > detectedAgent: IChatAgentData | undefined;
301 > }) {
302 > if (this.isComplete) {
303 return;
304 }
306 > this.isComplete = true;
307 > this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', {
308 > timeToFirstProgress,
309 > totalTime,
310 > result,
311 > requestType,
312 > requestId: request.id,
313 > agent: detectedAgent?.id ?? this.opts.agent.id,
314 > agentExtensionId: detectedAgent?.extensionId.value ?? this.opts.agent.extensionId.value,
315 > slashCommand: this.opts.agentSlashCommandPart ? this.opts.agentSlashCommandPart.command.name : this.opts.commandPart?.slashCommand.command,
316 > chatSessionId: chatSessionResourceToId(this.opts.sessionResource),
317 > enableCommandDetection: this.opts.enableCommandDetection,
318 > isParticipantDetected: !!detectedAgent,
319 > location: this.opts.location,
320 > citations: request.response?.codeCitations.length ?? 0,
321 > numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length,
322 > attachmentKinds: this.attachmentKindsForTelemetry(request.variableData),
323 > model: this.resolveModelId(this.opts.options?.userSelectedModelId),
324 > permissionLevel: this.opts.options?.modeInfo?.kind === ChatModeKind.Ask ? undefined : this.opts.options?.modeInfo?.permissionLevel,
325 > chatMode: this.opts.options?.modeInfo?.telemetryModeName ?? this.opts.options?.modeInfo?.telemetryModeId,
326 > sessionType: getChatSessionTypeForTelemetry(this.opts.sessionResource),
327 > harness: getHarnessForTelemetry(this.opts.sessionResource),
328 > });
329 > }
330
331 private attachmentKindsForTelemetry(variableData: IChatRequestVariableData): string[] {
332 > // this shows why attachments still have to be cleaned up somewhat chatServiceTelemetry.ts
333 > return variableData.variables.map(v => {
334 if (v.kind === 'implicit') {
335 return 'implicit';
364 }
365 }
367 > }
368
369 private resolveModelId(userSelectedModelId: string | undefined): string | undefined {
370 > return userSelectedModelId && this.languageModelsService.lookupLanguageModel(userSelectedModelId)?.id; chatServiceTelemetry.ts
371 > }
372 }
373
374 > function getChatSessionTypeForTelemetry(sessionResource: URI): string { chatServiceTelemetry.ts
375 > const sessionType = getChatSessionType(sessionResource);
376 > // Collapse the high-cardinality, host-specific authority into a single
377 > // value (the authority is PII); the harness is reported separately.
378 > return isRemoteAgentHostSessionType(sessionType) ? 'remote-agent-host' : sessionType;
379 > }
380
381 /**
384 * telemetry gap #2 in #8209. Undefined for non-remote sessions.
385 */
386 > function getHarnessForTelemetry(sessionResource: URI): string | undefined { chatServiceTelemetry.ts
387 > return parseRemoteAgentHostHarness(getChatSessionType(sessionResource));
388 > }
src/vs/workbench/contrib/chat/common/model/chatModel.ts 17 introduced LOC · 7 ranges

Open complete file

434
435 public set variableData(v: IChatRequestVariableData) {
436 > this._version++; chatModel.ts
437 > this._variableData = v;
438 > }
439
440 public get confirmation(): string | undefined {
443
444 public get locationData(): IChatLocationData | undefined {
445 > return this._locationData; chatModel.ts
446 > }
447
448 public get attachedContext(): IChatRequestVariableEntry[] | undefined {
449 > return this._attachedContext; chatModel.ts
450 > }
451
452 public get editedFileEvents(): IChatAgentEditedFileEvent[] | undefined {
2529 private readonly _canUseTools: boolean = true;
2530 get canUseTools(): boolean {
2531 > return this._canUseTools; chatModel.ts
2532 > }
2533
2534 private _disableBackgroundKeepAlive: boolean;
2998
2999 updateRequest(request: ChatRequestModel, variableData: IChatRequestVariableData) {
3000 > request.variableData = variableData; chatModel.ts
3001 > this._onDidChange.fire({ kind: 'changedRequest', request });
3002 > }
3003
3004 adoptRequest(request: ChatRequestModel): void {
3173
3174 export function updateRanges(variableData: IChatRequestVariableData, diff: number): IChatRequestVariableData {
3175 > return { chatModel.ts
3176 > variables: variableData.variables.map(v => ({
3177 ...v,
3178 range: v.range && {
3180 endExclusive: v.range.endExclusive - diff
3181 }
3182 > })) chatModel.ts
3183 > };
3184 > }
3185
3186 export function canMergeMarkdownStrings(md1: IMarkdownString, md2: IMarkdownString): boolean {
src/vs/workbench/contrib/chat/common/participants/chatAgents.ts 9 introduced LOC · 5 ranges

Open complete file

535
536 async invokeAgent(id: string, request: IChatAgentRequest, progress: (parts: IChatProgress[]) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {
537 > markChat(request.sessionResource, ChatPerfMark.AgentWillInvoke); chatAgents.ts
538 > const data = this._agents.get(id);
539 > if (!data?.impl) {
540 throw new Error(`No activated agent with id "${id}"`);
541 }
545 markChat(request.sessionResource, ChatPerfMark.AgentDidInvoke);
546 return result;
547 > } chatAgents.ts
548
549 setRequestTools(id: string, requestId: string, tools: UserSelectedTools): void {
557
558 setYieldRequested(id: string, requestId: string, value: boolean): void {
559 > const data = this._agents.get(id); chatAgents.ts
560 > if (!data?.impl) {
561 return;
562 }
563
564 data.impl.setYieldRequested?.(requestId, value);
565 > } chatAgents.ts
566
567 async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]> {
600
601 hasChatParticipantDetectionProviders() {
602 > return this._chatParticipantDetectionProviders.size > 0; chatAgents.ts
603 > }
604
605 async detectAgentOrCommand(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation }, token: CancellationToken): Promise<{ agent: IChatAgentData; command?: IChatAgentCommand } | undefined> {
src/vs/workbench/contrib/chat/test/common/tools/mockLanguageModelToolsService.ts 2 introduced LOC · 1 range

Open complete file

65
66 cancelToolCallsForRequest(requestId: string): void {
68 > }
69
70 // eslint-disable-next-line @typescript-eslint/no-explicit-any