Path: blob/main/src/vs/workbench/api/common/extHostChatSessions.ts
5239 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4/* eslint-disable local/code-no-native-private */56import type * as vscode from 'vscode';7import { coalesce } from '../../../base/common/arrays.js';8import { DeferredPromise } from '../../../base/common/async.js';9import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';10import { CancellationError } from '../../../base/common/errors.js';11import { Emitter, Event } from '../../../base/common/event.js';12import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';13import { ResourceMap } from '../../../base/common/map.js';14import { MarshalledId } from '../../../base/common/marshallingIds.js';15import { basename } from '../../../base/common/resources.js';16import { URI, UriComponents } from '../../../base/common/uri.js';17import { SymbolKind, SymbolKinds } from '../../../editor/common/languages.js';18import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js';19import { ILogService } from '../../../platform/log/common/log.js';20import { IChatRequestVariableEntry, IDiagnosticVariableEntryFilterData, IPromptFileVariableEntry, ISymbolVariableEntry, PromptFileVariableKind } from '../../contrib/chat/common/attachments/chatVariableEntries.js';21import { ChatSessionStatus, IChatSessionItem, IChatSessionProviderOptionItem } from '../../contrib/chat/common/chatSessionsService.js';22import { ChatAgentLocation } from '../../contrib/chat/common/constants.js';23import { IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/participants/chatAgents.js';24import { Proxied } from '../../services/extensions/common/proxyIdentifier.js';25import { ChatSessionDto, ExtHostChatSessionsShape, IChatAgentProgressShape, IChatSessionProviderOptions, MainContext, MainThreadChatSessionsShape } from './extHost.protocol.js';26import { ChatAgentResponseStream } from './extHostChatAgents2.js';27import { CommandsConverter, ExtHostCommands } from './extHostCommands.js';28import { ExtHostLanguageModels } from './extHostLanguageModels.js';29import { IExtHostRpcService } from './extHostRpcService.js';30import * as typeConvert from './extHostTypeConverters.js';31import { Diagnostic } from './extHostTypeConverters.js';32import * as extHostTypes from './extHostTypes.js';33import * as objects from '../../../base/common/objects.js';3435type ChatSessionTiming = vscode.ChatSessionItem['timing'];3637// #region Chat Session Item Controller3839class ChatSessionItemImpl implements vscode.ChatSessionItem {40#label: string;41#iconPath?: vscode.IconPath;42#description?: string | vscode.MarkdownString;43#badge?: string | vscode.MarkdownString;44#status?: vscode.ChatSessionStatus;45#archived?: boolean;46#tooltip?: string | vscode.MarkdownString;47#timing?: ChatSessionTiming;48#changes?: readonly vscode.ChatSessionChangedFile[];49#metadata?: { readonly [key: string]: unknown };50#onChanged: () => void;5152readonly resource: vscode.Uri;5354constructor(resource: vscode.Uri, label: string, onChanged: () => void) {55this.resource = resource;56this.#label = label;57this.#onChanged = onChanged;58}5960get label(): string {61return this.#label;62}6364set label(value: string) {65if (this.#label !== value) {66this.#label = value;67this.#onChanged();68}69}7071get iconPath(): vscode.IconPath | undefined {72return this.#iconPath;73}7475set iconPath(value: vscode.IconPath | undefined) {76if (this.#iconPath !== value) {77this.#iconPath = value;78this.#onChanged();79}80}8182get description(): string | vscode.MarkdownString | undefined {83return this.#description;84}8586set description(value: string | vscode.MarkdownString | undefined) {87if (this.#description !== value) {88this.#description = value;89this.#onChanged();90}91}9293get badge(): string | vscode.MarkdownString | undefined {94return this.#badge;95}9697set badge(value: string | vscode.MarkdownString | undefined) {98if (this.#badge !== value) {99this.#badge = value;100this.#onChanged();101}102}103104get status(): vscode.ChatSessionStatus | undefined {105return this.#status;106}107108set status(value: vscode.ChatSessionStatus | undefined) {109if (this.#status !== value) {110this.#status = value;111this.#onChanged();112}113}114115get archived(): boolean | undefined {116return this.#archived;117}118119set archived(value: boolean | undefined) {120if (this.#archived !== value) {121this.#archived = value;122this.#onChanged();123}124}125126get tooltip(): string | vscode.MarkdownString | undefined {127return this.#tooltip;128}129130set tooltip(value: string | vscode.MarkdownString | undefined) {131if (this.#tooltip !== value) {132this.#tooltip = value;133this.#onChanged();134}135}136137get timing(): ChatSessionTiming | undefined {138return this.#timing;139}140141set timing(value: ChatSessionTiming | undefined) {142if (this.#timing !== value) {143this.#timing = value;144this.#onChanged();145}146}147148get changes(): readonly vscode.ChatSessionChangedFile[] | undefined {149return this.#changes;150}151152set changes(value: readonly vscode.ChatSessionChangedFile[] | undefined) {153if (this.#changes !== value) {154this.#changes = value;155this.#onChanged();156}157}158159get metadata(): { readonly [key: string]: unknown } | undefined {160return this.#metadata;161}162163set metadata(value: { readonly [key: string]: unknown } | undefined) {164if (value !== undefined) {165try {166JSON.stringify(value);167} catch {168throw new Error('metadata must be JSON-serializable');169}170}171if (!objects.equals(this.#metadata, value)) {172this.#metadata = value;173this.#onChanged();174}175}176}177178class ChatSessionItemCollectionImpl implements vscode.ChatSessionItemCollection {179readonly #items = new ResourceMap<vscode.ChatSessionItem>();180#onItemsChanged: () => void;181182constructor(onItemsChanged: () => void) {183this.#onItemsChanged = onItemsChanged;184}185186get size(): number {187return this.#items.size;188}189190replace(items: readonly vscode.ChatSessionItem[]): void {191if (items.length === 0 && this.#items.size === 0) {192return;193}194195this.#items.clear();196for (const item of items) {197this.#items.set(item.resource, item);198}199this.#onItemsChanged();200}201202forEach(callback: (item: vscode.ChatSessionItem, collection: vscode.ChatSessionItemCollection) => unknown, thisArg?: any): void {203for (const [_, item] of this.#items) {204callback.call(thisArg, item, this);205}206}207208add(item: vscode.ChatSessionItem): void {209this.#items.set(item.resource, item);210this.#onItemsChanged();211}212213delete(resource: vscode.Uri): void {214this.#items.delete(resource);215this.#onItemsChanged();216}217218get(resource: vscode.Uri): vscode.ChatSessionItem | undefined {219return this.#items.get(resource);220}221222[Symbol.iterator](): Iterator<readonly [id: URI, chatSessionItem: vscode.ChatSessionItem]> {223return this.#items.entries();224}225}226227// #endregion228229class ExtHostChatSession {230private _stream: ChatAgentResponseStream;231// Empty map since question carousel is designed for chat agents, not chat sessions232private readonly _pendingCarouselResolvers = new Map<string, Map<string, DeferredPromise<Record<string, unknown> | undefined>>>();233234constructor(235public readonly session: vscode.ChatSession,236public readonly extension: IExtensionDescription,237request: IChatAgentRequest,238public readonly proxy: IChatAgentProgressShape,239public readonly commandsConverter: CommandsConverter,240public readonly sessionDisposables: DisposableStore241) {242this._stream = new ChatAgentResponseStream(extension, request, proxy, commandsConverter, sessionDisposables, this._pendingCarouselResolvers, CancellationToken.None);243}244245get activeResponseStream() {246return this._stream;247}248249getActiveRequestStream(request: IChatAgentRequest) {250return new ChatAgentResponseStream(this.extension, request, this.proxy, this.commandsConverter, this.sessionDisposables, this._pendingCarouselResolvers, CancellationToken.None);251}252}253254export class ExtHostChatSessions extends Disposable implements ExtHostChatSessionsShape {255private static _sessionHandlePool = 0;256257private readonly _proxy: Proxied<MainThreadChatSessionsShape>;258259private _itemProviderHandlePool = 0;260private readonly _chatSessionItemProviders = new Map</* handle */ number, {261readonly sessionType: string;262readonly provider: vscode.ChatSessionItemProvider;263readonly extension: IExtensionDescription;264readonly disposable: DisposableStore;265}>();266267private _itemControllerHandlePool = 0;268private readonly _chatSessionItemControllers = new Map</* handle */ number, {269readonly sessionType: string;270readonly controller: vscode.ChatSessionItemController;271readonly extension: IExtensionDescription;272readonly disposable: DisposableStore;273readonly onDidChangeChatSessionItemStateEmitter: Emitter<vscode.ChatSessionItem>;274}>();275276private _contentProviderHandlePool = 0;277private readonly _chatSessionContentProviders = new Map</* handle */ number, {278readonly provider: vscode.ChatSessionContentProvider;279readonly extension: IExtensionDescription;280readonly capabilities?: vscode.ChatSessionCapabilities;281readonly disposable: DisposableStore;282}>();283284/**285* Map of uri -> chat session items286*287* TODO: this isn't cleared/updated properly288*/289private readonly _sessionItems = new ResourceMap<vscode.ChatSessionItem>();290291/**292* Map of uri -> chat sessions infos293*/294private readonly _extHostChatSessions = new ResourceMap<{ readonly sessionObj: ExtHostChatSession; readonly disposeCts: CancellationTokenSource }>();295/**296* Store option groups with onSearch callbacks per provider handle297*/298private readonly _providerOptionGroups = new Map<number, vscode.ChatSessionProviderOptionGroup[]>();299300constructor(301private readonly commands: ExtHostCommands,302private readonly _languageModels: ExtHostLanguageModels,303@IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService,304@ILogService private readonly _logService: ILogService,305) {306super();307this._proxy = this._extHostRpc.getProxy(MainContext.MainThreadChatSessions);308309commands.registerArgumentProcessor({310processArgument: (arg) => {311if (arg && arg.$mid === MarshalledId.AgentSessionContext) {312const id = arg.session.resource || arg.sessionId;313const sessionContent = this._sessionItems.get(id);314if (sessionContent) {315return sessionContent;316} else {317this._logService.warn(`No chat session found for ID: ${id}`);318return arg;319}320}321322return arg;323}324});325}326327registerChatSessionItemProvider(extension: IExtensionDescription, chatSessionType: string, provider: vscode.ChatSessionItemProvider): vscode.Disposable {328const handle = this._itemProviderHandlePool++;329const disposables = new DisposableStore();330331this._chatSessionItemProviders.set(handle, { provider, extension, disposable: disposables, sessionType: chatSessionType });332this._proxy.$registerChatSessionItemProvider(handle, chatSessionType);333if (provider.onDidChangeChatSessionItems) {334disposables.add(provider.onDidChangeChatSessionItems(() => {335this._logService.trace(`ExtHostChatSessions. Firing $onDidChangeChatSessionItems for ${chatSessionType}`);336this._proxy.$onDidChangeChatSessionItems(handle);337}));338}339340if (provider.onDidCommitChatSessionItem) {341disposables.add(provider.onDidCommitChatSessionItem((e) => {342const { original, modified } = e;343this._proxy.$onDidCommitChatSessionItem(handle, original.resource, modified.resource);344}));345}346347return {348dispose: () => {349this._chatSessionItemProviders.delete(handle);350disposables.dispose();351this._proxy.$unregisterChatSessionItemProvider(handle);352}353};354}355356357createChatSessionItemController(extension: IExtensionDescription, id: string, refreshHandler: (token: vscode.CancellationToken) => Thenable<void>): vscode.ChatSessionItemController {358const controllerHandle = this._itemControllerHandlePool++;359const disposables = new DisposableStore();360361let isDisposed = false;362let refreshIdPool = 0;363let activeRefreshId: number | undefined = undefined;364365const onDidChangeItemsEmitter = disposables.add(new Emitter<void>());366const onDidChangeChatSessionItemStateEmitter = disposables.add(new Emitter<vscode.ChatSessionItem>());367368const notifyItemsChanged = () => {369// Suppress updates when a refresh is already happening370if (typeof activeRefreshId === 'undefined') {371onDidChangeItemsEmitter.fire();372}373};374375const collection = new ChatSessionItemCollectionImpl(() => {376notifyItemsChanged();377});378379const controller = Object.freeze<vscode.ChatSessionItemController>({380id,381refreshHandler: async (refreshToken: CancellationToken) => {382if (isDisposed) {383throw new Error('ChatSessionItemController has been disposed');384}385386const opId = ++refreshIdPool;387activeRefreshId = opId;388389try {390this._logService.trace(`ExtHostChatSessions. Controller(${id}).refresh()`);391await refreshHandler(refreshToken);392} finally {393if (activeRefreshId === opId) {394activeRefreshId = undefined;395}396}397},398items: collection,399onDidChangeChatSessionItemState: onDidChangeChatSessionItemStateEmitter.event,400createChatSessionItem: (resource: vscode.Uri, label: string) => {401if (isDisposed) {402throw new Error('ChatSessionItemController has been disposed');403}404405return new ChatSessionItemImpl(resource, label, () => {406// TODO: Optimize to only update the specific item407notifyItemsChanged();408});409},410dispose: () => {411isDisposed = true;412disposables.dispose();413},414});415416this._chatSessionItemControllers.set(controllerHandle, { controller, extension, disposable: disposables, sessionType: id, onDidChangeChatSessionItemStateEmitter });417418// Controllers are implemented using providers on the ext host side for now419disposables.add(this.registerChatSessionItemProvider(extension, id, {420onDidChangeChatSessionItems: onDidChangeItemsEmitter.event,421onDidCommitChatSessionItem: Event.None,422provideChatSessionItems: async (token: CancellationToken): Promise<vscode.ChatSessionItem[]> => {423await controller.refreshHandler(token);424return Array.from(controller.items, x => x[1]);425},426}));427428disposables.add(toDisposable(() => {429this._chatSessionItemControllers.delete(controllerHandle);430this._proxy.$unregisterChatSessionItemProvider(controllerHandle);431}));432433return controller;434}435436registerChatSessionContentProvider(extension: IExtensionDescription, chatSessionScheme: string, chatParticipant: vscode.ChatParticipant, provider: vscode.ChatSessionContentProvider, capabilities?: vscode.ChatSessionCapabilities): vscode.Disposable {437const handle = this._contentProviderHandlePool++;438const disposables = new DisposableStore();439440this._chatSessionContentProviders.set(handle, { provider, extension, capabilities, disposable: disposables });441this._proxy.$registerChatSessionContentProvider(handle, chatSessionScheme);442443if (provider.onDidChangeChatSessionOptions) {444disposables.add(provider.onDidChangeChatSessionOptions(evt => {445this._proxy.$onDidChangeChatSessionOptions(handle, evt.resource, evt.updates);446}));447}448449if (provider.onDidChangeChatSessionProviderOptions) {450disposables.add(provider.onDidChangeChatSessionProviderOptions(() => {451this._proxy.$onDidChangeChatSessionProviderOptions(handle);452}));453}454455return new extHostTypes.Disposable(() => {456this._chatSessionContentProviders.delete(handle);457disposables.dispose();458this._proxy.$unregisterChatSessionContentProvider(handle);459});460}461462private convertChatSessionStatus(status: vscode.ChatSessionStatus | undefined): ChatSessionStatus | undefined {463if (status === undefined) {464return undefined;465}466467switch (status) {468case 0: // vscode.ChatSessionStatus.Failed469return ChatSessionStatus.Failed;470case 1: // vscode.ChatSessionStatus.Completed471return ChatSessionStatus.Completed;472case 2: // vscode.ChatSessionStatus.InProgress473return ChatSessionStatus.InProgress;474// Need to support NeedsInput status if we ever export it to the extension API475default:476return undefined;477}478}479480private convertChatSessionItem(sessionContent: vscode.ChatSessionItem): IChatSessionItem {481// Support both new (created, lastRequestStarted, lastRequestEnded) and old (startTime, endTime) timing properties482const timing = sessionContent.timing;483const created = timing?.created ?? timing?.startTime ?? 0;484const lastRequestStarted = timing?.lastRequestStarted ?? timing?.startTime;485const lastRequestEnded = timing?.lastRequestEnded ?? timing?.endTime;486487return {488resource: sessionContent.resource,489label: sessionContent.label,490description: sessionContent.description ? typeConvert.MarkdownString.from(sessionContent.description) : undefined,491badge: sessionContent.badge ? typeConvert.MarkdownString.from(sessionContent.badge) : undefined,492status: this.convertChatSessionStatus(sessionContent.status),493archived: sessionContent.archived,494tooltip: typeConvert.MarkdownString.fromStrict(sessionContent.tooltip),495timing: {496created,497lastRequestStarted,498lastRequestEnded,499},500changes: sessionContent.changes instanceof Array ? sessionContent.changes : undefined,501metadata: sessionContent.metadata,502};503}504505async $provideChatSessionItems(handle: number, token: vscode.CancellationToken): Promise<IChatSessionItem[]> {506const itemProvider = this._chatSessionItemProviders.get(handle);507if (!itemProvider) {508this._logService.error(`No provider registered for handle ${handle}`);509return [];510}511512this._logService.trace(`ExtHostChatSessions:$provideChatSessionItems(${itemProvider.sessionType})`);513const items = await itemProvider.provider.provideChatSessionItems(token) ?? [];514if (token.isCancellationRequested) {515return [];516}517518const response: IChatSessionItem[] = [];519for (const sessionContent of items) {520this._sessionItems.set(sessionContent.resource, sessionContent);521response.push(this.convertChatSessionItem(sessionContent));522}523return response;524}525526async $provideChatSessionContent(handle: number, sessionResourceComponents: UriComponents, token: CancellationToken): Promise<ChatSessionDto> {527const provider = this._chatSessionContentProviders.get(handle);528if (!provider) {529throw new Error(`No provider for handle ${handle}`);530}531532const sessionResource = URI.revive(sessionResourceComponents);533534const session = await provider.provider.provideChatSessionContent(sessionResource, token);535if (token.isCancellationRequested) {536throw new CancellationError();537}538539const sessionDisposables = new DisposableStore();540const sessionId = ExtHostChatSessions._sessionHandlePool++;541const id = sessionResource.toString();542const chatSession = new ExtHostChatSession(session, provider.extension, {543sessionResource,544requestId: 'ongoing',545agentId: id,546message: '',547variables: { variables: [] },548location: ChatAgentLocation.Chat,549}, {550$handleProgressChunk: (requestId, chunks) => {551return this._proxy.$handleProgressChunk(handle, sessionResource, requestId, chunks);552},553$handleAnchorResolve: (requestId, requestHandle, anchor) => {554this._proxy.$handleAnchorResolve(handle, sessionResource, requestId, requestHandle, anchor);555},556}, this.commands.converter, sessionDisposables);557558const disposeCts = sessionDisposables.add(new CancellationTokenSource());559this._extHostChatSessions.set(sessionResource, { sessionObj: chatSession, disposeCts });560561// Call activeResponseCallback immediately for best user experience562if (session.activeResponseCallback) {563Promise.resolve(session.activeResponseCallback(chatSession.activeResponseStream.apiObject, disposeCts.token)).finally(() => {564// complete565this._proxy.$handleProgressComplete(handle, sessionResource, 'ongoing');566});567}568const { capabilities } = provider;569return {570id: sessionId + '',571resource: URI.revive(sessionResource),572hasActiveResponseCallback: !!session.activeResponseCallback,573hasRequestHandler: !!session.requestHandler,574supportsInterruption: !!capabilities?.supportsInterruptions,575options: session.options,576history: session.history.map(turn => {577if (turn instanceof extHostTypes.ChatRequestTurn) {578return this.convertRequestTurn(turn);579} else {580return this.convertResponseTurn(turn as extHostTypes.ChatResponseTurn2, sessionDisposables);581}582})583};584}585586async $provideHandleOptionsChange(handle: number, sessionResourceComponents: UriComponents, updates: ReadonlyArray<{ optionId: string; value: string | IChatSessionProviderOptionItem | undefined }>, token: CancellationToken): Promise<void> {587const sessionResource = URI.revive(sessionResourceComponents);588const provider = this._chatSessionContentProviders.get(handle);589if (!provider) {590this._logService.warn(`No provider for handle ${handle}`);591return;592}593594if (!provider.provider.provideHandleOptionsChange) {595this._logService.debug(`Provider for handle ${handle} does not implement provideHandleOptionsChange`);596return;597}598599try {600const updatesToSend = updates.map(update => ({601optionId: update.optionId,602value: update.value === undefined ? undefined : (typeof update.value === 'string' ? update.value : update.value.id)603}));604await provider.provider.provideHandleOptionsChange(sessionResource, updatesToSend, token);605} catch (error) {606this._logService.error(`Error calling provideHandleOptionsChange for handle ${handle}, sessionResource ${sessionResource}:`, error);607}608}609610async $provideChatSessionProviderOptions(handle: number, token: CancellationToken): Promise<IChatSessionProviderOptions | undefined> {611const entry = this._chatSessionContentProviders.get(handle);612if (!entry) {613this._logService.warn(`No provider for handle ${handle} when requesting chat session options`);614return;615}616617const provider = entry.provider;618if (!provider.provideChatSessionProviderOptions) {619return;620}621622try {623const { optionGroups } = await provider.provideChatSessionProviderOptions(token);624if (!optionGroups) {625return;626}627this._providerOptionGroups.set(handle, optionGroups);628return {629optionGroups,630};631} catch (error) {632this._logService.error(`Error calling provideChatSessionProviderOptions for handle ${handle}:`, error);633return;634}635}636637async $interruptChatSessionActiveResponse(providerHandle: number, sessionResource: UriComponents, requestId: string): Promise<void> {638const entry = this._extHostChatSessions.get(URI.revive(sessionResource));639entry?.disposeCts.cancel();640}641642async $disposeChatSessionContent(providerHandle: number, sessionResource: UriComponents): Promise<void> {643const entry = this._extHostChatSessions.get(URI.revive(sessionResource));644if (!entry) {645this._logService.warn(`No chat session found for resource: ${sessionResource}`);646return;647}648649entry.disposeCts.cancel();650entry.sessionObj.sessionDisposables.dispose();651this._extHostChatSessions.delete(URI.revive(sessionResource));652}653654async $invokeChatSessionRequestHandler(handle: number, sessionResource: UriComponents, request: IChatAgentRequest, history: any[], token: CancellationToken): Promise<IChatAgentResult> {655const entry = this._extHostChatSessions.get(URI.revive(sessionResource));656if (!entry || !entry.sessionObj.session.requestHandler) {657return {};658}659660const chatRequest = typeConvert.ChatAgentRequest.to(request, undefined, await this.getModelForRequest(request, entry.sessionObj.extension), [], new Map(), entry.sessionObj.extension, this._logService);661662const stream = entry.sessionObj.getActiveRequestStream(request);663await entry.sessionObj.session.requestHandler(chatRequest, { history, yieldRequested: false }, stream.apiObject, token);664665// TODO: do we need to dispose the stream object?666return {};667}668669private async getModelForRequest(request: IChatAgentRequest, extension: IExtensionDescription): Promise<vscode.LanguageModelChat> {670let model: vscode.LanguageModelChat | undefined;671if (request.userSelectedModelId) {672model = await this._languageModels.getLanguageModelByIdentifier(extension, request.userSelectedModelId);673}674if (!model) {675model = await this._languageModels.getDefaultLanguageModel(extension);676if (!model) {677throw new Error('Language model unavailable');678}679}680681return model;682}683684private convertRequestTurn(turn: extHostTypes.ChatRequestTurn) {685const variables = turn.references.map(ref => this.convertReferenceToVariable(ref));686return {687type: 'request' as const,688id: turn.id,689prompt: turn.prompt,690participant: turn.participant,691command: turn.command,692variableData: variables.length > 0 ? { variables } : undefined693};694}695696private convertReferenceToVariable(ref: vscode.ChatPromptReference): IChatRequestVariableEntry {697const value = ref.value && typeof ref.value === 'object' && 'uri' in ref.value && 'range' in ref.value698? typeConvert.Location.from(ref.value as vscode.Location)699: ref.value;700const range = ref.range ? { start: ref.range[0], endExclusive: ref.range[1] } : undefined;701702if (value && value instanceof extHostTypes.ChatReferenceDiagnostic && Array.isArray(value.diagnostics) && value.diagnostics.length && value.diagnostics[0][1].length) {703const marker = Diagnostic.from(value.diagnostics[0][1][0]);704const refValue: IDiagnosticVariableEntryFilterData = {705filterRange: { startLineNumber: marker.startLineNumber, startColumn: marker.startColumn, endLineNumber: marker.endLineNumber, endColumn: marker.endColumn },706filterSeverity: marker.severity,707filterUri: value.diagnostics[0][0],708problemMessage: value.diagnostics[0][1][0].message709};710return IDiagnosticVariableEntryFilterData.toEntry(refValue);711}712713if (extHostTypes.Location.isLocation(ref.value) && ref.name.startsWith(`sym:`)) {714const loc = typeConvert.Location.from(ref.value);715return {716id: ref.id,717name: ref.name,718fullName: ref.name.substring(4),719value: { uri: ref.value.uri, range: loc.range },720// We never send this information to extensions, so default to Property721symbolKind: SymbolKind.Property,722// We never send this information to extensions, so default to Property723icon: SymbolKinds.toIcon(SymbolKind.Property),724kind: 'symbol',725range,726} satisfies ISymbolVariableEntry;727}728729if (URI.isUri(value) && ref.name.startsWith(`prompt:`) &&730ref.id.startsWith(PromptFileVariableKind.PromptFile) &&731ref.id.endsWith(value.toString())) {732return {733id: ref.id,734name: `prompt:${basename(value)}`,735value,736kind: 'promptFile',737modelDescription: 'Prompt instructions file',738isRoot: true,739automaticallyAdded: false,740range,741} satisfies IPromptFileVariableEntry;742}743744const isFile = URI.isUri(value) || (value && typeof value === 'object' && 'uri' in value);745const isFolder = isFile && URI.isUri(value) && value.path.endsWith('/');746return {747id: ref.id,748name: ref.name,749value,750modelDescription: ref.modelDescription,751range,752kind: isFolder ? 'directory' as const : isFile ? 'file' as const : 'generic' as const753};754}755756private convertResponseTurn(turn: extHostTypes.ChatResponseTurn2, sessionDisposables: DisposableStore) {757const parts = coalesce(turn.response.map(r => typeConvert.ChatResponsePart.from(r, this.commands.converter, sessionDisposables)));758return {759type: 'response' as const,760parts,761participant: turn.participant762};763}764765async $invokeOptionGroupSearch(providerHandle: number, optionGroupId: string, query: string, token: CancellationToken): Promise<IChatSessionProviderOptionItem[]> {766const optionGroups = this._providerOptionGroups.get(providerHandle);767if (!optionGroups) {768this._logService.warn(`No option groups found for provider handle ${providerHandle}`);769return [];770}771772const group = optionGroups.find((g: vscode.ChatSessionProviderOptionGroup) => g.id === optionGroupId);773if (!group || !group.onSearch) {774this._logService.warn(`No onSearch callback found for option group ${optionGroupId}`);775return [];776}777778try {779const results = await group.onSearch(query, token);780return results ?? [];781} catch (error) {782this._logService.error(`Error calling onSearch for option group ${optionGroupId}:`, error);783return [];784}785}786787$onDidChangeChatSessionItemState(controllerHandle: number, sessionResourceComponents: UriComponents, archived: boolean): void {788const controllerData = this._chatSessionItemControllers.get(controllerHandle);789if (!controllerData) {790this._logService.warn(`No controller found for handle ${controllerHandle}`);791return;792}793794const sessionResource = URI.revive(sessionResourceComponents);795const item = controllerData.controller.items.get(sessionResource);796if (!item) {797this._logService.warn(`No item found for session resource ${sessionResource.toString()}`);798return;799}800801item.archived = archived;802controllerData.onDidChangeChatSessionItemStateEmitter.fire(item);803}804}805806807