Skip to content

Remove ChatSessionService.notifySessionOptionsChange#303060

Merged
mjbvz merged 1 commit intomicrosoft:mainfrom
mjbvz:dev/mjbvz/conscious-aphid
Mar 19, 2026
Merged

Remove ChatSessionService.notifySessionOptionsChange#303060
mjbvz merged 1 commit intomicrosoft:mainfrom
mjbvz:dev/mjbvz/conscious-aphid

Conversation

@mjbvz
Copy link
Collaborator

@mjbvz mjbvz commented Mar 19, 2026

I'm trying to fix bugs in the chat session service and improve the extension api but am running into a lot of debt. Added comments noting the problems around notifySessionOptionsChange and why I want to remove it

- Use single event for options change

- We should not expose notify type methods on services because callers should not control event flows directly like this

- Remove use of async event. Afaik this was not actually being used for anything
Copilot AI review requested due to automatic review settings March 19, 2026 04:19
@vs-code-engineering vs-code-engineering bot added this to the 1.113.0 milestone Mar 19, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR removes ChatSessionService.notifySessionOptionsChange and consolidates session-option change propagation around a single onDidChangeSessionOptions event + a new updateSessionOptions(...) API, aiming to reduce event debt and make option-change firing conditional on real changes.

Changes:

  • Replaced the async “notify extension then update” flow with synchronous setSessionOption/updateSessionOptions that updates state and fires a single change event.
  • Changed onDidChangeSessionOptions payload from URI to a structured { sessionResource, updates } event.
  • Updated main thread forwarding to listen to onDidChangeSessionOptions instead of a dedicated “notify extension” async event.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts Updates mock to new option-change event shape and removes notify API usage.
src/vs/workbench/contrib/chat/common/chatSessionsService.ts Adjusts IChatSessionsService contract: new event payload + adds updateSessionOptions.
src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts Switches option updates from notifySessionOptionsChange to updateSessionOptions / setSessionOption.
src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts Implements updateSessionOptions, removes async notify event/method, and updates event firing semantics.
src/vs/workbench/api/test/browser/mainThreadChatSessions.test.ts Updates tests to use setSessionOption/updateSessionOptions.
src/vs/workbench/api/browser/mainThreadChatSessions.ts Forwards option changes via onDidChangeSessionOptions and removes waitUntil usage.
src/vs/sessions/contrib/chat/browser/newSession.ts Replaces notify calls with setSessionOption for Sessions window new-session option selection.
Comments suppressed due to low confidence (2)

src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts:1148

  • Even when only some entries in updates actually change, the event payload currently re-emits the full updates array. This means listeners/extensions may receive “changes” for options that were no-ops, undermining the goal of only firing on real changes. Consider building a changedUpdates array (only the entries that differed) and firing with that instead.
		let didChange = false;
		for (const { optionId, value } of updates) {
			const existingValue = session.getOption(optionId);
			if (existingValue !== value) {
				session.setOption(optionId, value);
				didChange = true;
			}
		}

		if (didChange) {
			this._onDidChangeSessionOptions.fire({ sessionResource, updates: updates });
		}

src/vs/sessions/contrib/chat/browser/newSession.ts:270

  • Same issue as above for remote new sessions: option selections are made before a session exists, but setSessionOption only works once ChatSessionsService has created session state. Without notifySessionOptionsChange, these updates can be dropped. Consider deferring/batching these updates until after the session is created or having the service cache pending options per resource.
	setOption(optionId: string, value: IChatSessionProviderOptionItem | string): void {
		if (typeof value !== 'string') {
			this.selectedOptions.set(optionId, value);
		}
		this._onDidChange.fire('options');
		this._onDidChange.fire('disabled');
		this.chatSessionsService.setSessionOption(this.resource, optionId, value);
	}

You can also share your feedback on Copilot code review. Take the survey.

@mjbvz mjbvz merged commit 7a43958 into microsoft:main Mar 19, 2026
22 checks passed
* MainThreadChatSessions subscribes to this to forward changes to the extension host.
* Uses IWaitUntil pattern to allow listeners to register async work.
*/
readonly onRequestNotifyExtension: Event<IChatSessionOptionsWillNotifyExtensionEvent>;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Services should generally not know about the main thread or extensions

Instead the main thread should use apis exposed by the service

* Uses IWaitUntil pattern to allow listeners to register async work.
*/
readonly onRequestNotifyExtension: Event<IChatSessionOptionsWillNotifyExtensionEvent>;
notifySessionOptionsChange(sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string | IChatSessionProviderOptionItem }>): Promise<void>;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Notify type methods are generally a code smell because they let callers control event flows

It's better to hide event firing inside of service. Calls to notify methods also tend to get added unnecessary to workaround issues, which results in way too many events getting fired

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

notify is also a lying name here because this method actually changes state instead of just notifying

* Fired when options for a chat session change.
*/
readonly onDidChangeSessionOptions: Event<URI>;
readonly onDidChangeSessionOptions: Event<IChatSessionOptionsChangeEvent>;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Consolidating because we have two different events for session option changes. These were fired at different times

this.chatSessionsService.notifySessionOptionsChange(
this.resource,
[{ optionId, value }]
).catch((err) => this.logService.error(`Failed to notify session option ${optionId} change:`, err));
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This error handling logic was copy and pasted around. If we need this, it should done in a single central place instead

* Extends IWaitUntil to allow listeners to register async work that will be awaited.
*/
export interface IChatSessionOptionsWillNotifyExtensionEvent extends IWaitUntil {
export interface IChatSessionOptionsChangeEvent {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

An async event was used which adds complexity but it doesn't actually seem to needed

AsyncEvent should be used very sparingly. They should not be treated as replacement for normal async function calls.

If you're just posting something to the ext host and don't care about the response, don't need to await it

}
};

(proxy.$provideChatSessionContent as sinon.SinonStub).resolves(sessionContent);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I didn't fix it here but these casts are actually really bad because they are basically like any casts. It means resolve can be passed anything

Instead there's a Sinon type helper that is strongly typed that we should use

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants