Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHostChatAgents2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
$releaseSession(sessionResourceDto: UriComponents): void {
const sessionResource = URI.revive(sessionResourceDto);
this._sessionDisposables.deleteAndDispose(sessionResource);
this._chatSessions.clearInputStateCache(sessionResource);
const sessionId = LocalChatSessionUri.parseLocalSessionId(sessionResource);
if (sessionId) {
this._onDidDisposeChatSession.fire(sessionId);
Expand Down
65 changes: 60 additions & 5 deletions src/vs/workbench/api/common/extHostChatSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
*/
private readonly _proxyCommands = new Map</* proxyId */ string, { readonly originalCommandId: string; readonly controllerHandle: number }>();

/**
* Cache of resolved input states per session resource
*/
private readonly _inputStateCache = new ResourceMap<vscode.ChatSessionInputState>();

constructor(
private readonly commands: ExtHostCommands,
private readonly _languageModels: ExtHostLanguageModels,
Expand Down Expand Up @@ -453,6 +458,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
const disposable: vscode.Disposable = {
dispose: () => {
this._chatSessionItemControllers.delete(controllerHandle);
this._clearInputStateCacheForScheme(chatSessionType);
disposables.dispose();
this._proxy.$unregisterChatSessionItemController(controllerHandle);
}
Expand Down Expand Up @@ -518,6 +524,12 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
if (entry) {
entry.optionGroups = inputState.groups;
}
// Invalidate only the cache entries that hold this specific inputState object
for (const [uri, cached] of this._inputStateCache) {
if (cached === inputState) {
this._inputStateCache.delete(uri);
}
}
Comment on lines +536 to +541
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When inputState.groups is set, this callback updates entry.optionGroups (controller-wide) but only invalidates cache entries whose cached value is the same inputState object. If multiple session resources for the same scheme have cached input states, they can become inconsistent with the new optionGroups/defaults. Consider clearing cached input states for the whole scheme (or otherwise ensuring all affected cached states are refreshed) when controller-level option groups change.

This issue also appears on line 904 of the same file.

Suggested change
// Invalidate only the cache entries that hold this specific inputState object
for (const [uri, cached] of this._inputStateCache) {
if (cached === inputState) {
this._inputStateCache.delete(uri);
}
}
// Option groups are controller-wide, so clear cached input states to force refresh.
this._inputStateCache.clear();

Copilot uses AI. Check for mistakes.
const wrappedGroups = this._wrapOptionGroupCommands(controllerHandle, inputState.groups);
const serializableGroups = wrappedGroups.map(g => ({
id: g.id,
Expand Down Expand Up @@ -547,6 +559,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio

disposables.add(toDisposable(() => {
this._chatSessionItemControllers.delete(controllerHandle);
this._clearInputStateCacheForScheme(id);
this._proxy.$unregisterChatSessionItemController(controllerHandle);
}));

Expand Down Expand Up @@ -605,6 +618,9 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
controllerData?.optionGroups ?? [], context.initialSessionOptions
);

// Seed the cache so the first $invokeAgent call after session open gets a cache hit
this._inputStateCache.set(sessionResource, inputState);

Comment on lines +634 to +636
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$provideChatSessionContent seeds _inputStateCache before awaiting provider.provider.provideChatSessionContent(...). If that provider call throws or the token is cancelled before the session is fully created, the cache entry for sessionResource will remain and can retain references unnecessarily. Consider seeding the cache only after provideChatSessionContent succeeds, or deleting the cache entry in an error/cancellation path.

Copilot uses AI. Check for mistakes.
const session = await provider.provider.provideChatSessionContent(sessionResource, token, {
sessionOptions: context?.initialSessionOptions ?? [],
inputState,
Expand Down Expand Up @@ -735,6 +751,8 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
if (controllerData) {
controllerData.optionGroups = optionGroups;
}
// Invalidate cached input states since option groups changed
this._clearInputStateCacheForScheme(entry.chatSessionScheme);
}
return {
optionGroups,
Expand All @@ -752,15 +770,17 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
}

async $disposeChatSessionContent(providerHandle: number, sessionResource: UriComponents): Promise<void> {
const entry = this._extHostChatSessions.get(URI.revive(sessionResource));
const uri = URI.revive(sessionResource);
const entry = this._extHostChatSessions.get(uri);
if (!entry) {
this._logService.warn(`No chat session found for resource: ${sessionResource}`);
return;
}

entry.disposeCts.cancel();
entry.sessionObj.sessionDisposables.dispose();
this._extHostChatSessions.delete(URI.revive(sessionResource));
this._extHostChatSessions.delete(uri);
this._inputStateCache.delete(uri);
}

async $invokeChatSessionRequestHandler(handle: number, sessionResource: UriComponents, request: IChatAgentRequest, history: any[], token: CancellationToken): Promise<IChatAgentResult> {
Expand Down Expand Up @@ -852,14 +872,42 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
}

/**
* Gets the input state for a session. This calls the controller's `getChatSessionInputState` handler if available,
* otherwise falls back to creating an input state from the session options.
* Clears the cached input state for a specific session resource.
* Called from ExtHostChatAgents2 when a session is released.
*/
clearInputStateCache(resource: URI): void {
this._inputStateCache.delete(resource);
}

/**
* Clears all cached input states for sessions whose URI scheme matches the given scheme.
* Used when controller-wide changes occur (e.g., controller unregistered, option groups changed).
*/
private _clearInputStateCacheForScheme(scheme: string): void {
for (const [uri] of this._inputStateCache) {
if (uri.scheme === scheme) {
this._inputStateCache.delete(uri);
}
}
}

/**
* Gets the input state for a session. Returns a cached result if available,
* otherwise calls the controller's `getChatSessionInputState` handler and caches the result.
* Falls back to creating an input state from the session options.
*/
async getInputStateForSession(
sessionResource: URI | undefined,
initialSessionOptions: ReadonlyArray<{ optionId: string; value: string }> | undefined,
token: CancellationToken,
): Promise<vscode.ChatSessionInputState> {
if (sessionResource) {
const cached = this._inputStateCache.get(sessionResource);
if (cached) {
return cached;
}
}

const scheme = sessionResource?.scheme;
const controllerData = scheme ? this.getChatSessionItemController(scheme) : undefined;
if (controllerData?.controller.getChatSessionInputState) {
Expand All @@ -869,10 +917,17 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
token,
);
if (result) {
if (sessionResource) {
this._inputStateCache.set(sessionResource, result);
}
return result;
}
}
return this._createInputStateFromOptions(controllerData?.optionGroups ?? [], initialSessionOptions);
const fallback = this._createInputStateFromOptions(controllerData?.optionGroups ?? [], initialSessionOptions);
if (sessionResource) {
this._inputStateCache.set(sessionResource, fallback);
}
return fallback;
Comment on lines 944 to +980
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New caching behavior in getInputStateForSession (cache hits, scheme-wide invalidation, and seeding on session open) is not covered by existing tests. There is already an ExtHostChatSessions test suite (src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts), so it would be good to add regression tests that (1) verify cached values are reused to avoid repeated controller calls and (2) verify cache invalidation when session options/input state change.

Copilot uses AI. Check for mistakes.
}

/**
Expand Down
Loading