Skip to content

Commit 6cde2e9

Browse files
sandy081Copilot
andauthored
sessions: scope model and agent selection per chat (#323744)
Sessions - scope model and agent selection per chat In the Agents window a session can contain multiple peer chats, and the model/agent decision is meant to be per chat. The provider already wrote the selection to the active chat, but `VisibleSession` (the `IActiveSession` the pickers bind to) exposed the session/default chat's `modelId`/`mode`, so every chat tab read and displayed the same selection. Derive `modelId`/`mode` from the active chat so reads and writes are both scoped to the focused chat. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 04b756b commit 6cde2e9

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

src/vs/sessions/services/sessions/browser/visibleSessions.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ export class VisibleSession extends Disposable implements IActiveSession {
3232
private readonly _activeChat: ISettableObservable<IChat>;
3333
readonly activeChat: IObservable<IChat>;
3434

35+
/**
36+
* Model and mode are scoped to the active chat so the Agents window pickers
37+
* read and write the selection of the currently focused chat, not the
38+
* session/default chat. Sessions with multiple peer chats keep an
39+
* independent model/agent per chat.
40+
*/
41+
private readonly _activeChatModelId: IObservable<string | undefined>;
42+
private readonly _activeChatMode: IObservable<{ readonly id: string; readonly kind: string } | undefined>;
43+
3544
/** Resource strings of chats that have been closed (hidden from the tab strip). */
3645
private readonly _closedChatUris: ISettableObservable<ReadonlySet<string>>;
3746
readonly openChats: IObservable<readonly IChat[]>;
@@ -47,6 +56,9 @@ export class VisibleSession extends Disposable implements IActiveSession {
4756
this._activeChat = observableValue<IChat>(`activeChat-${_session.sessionId}`, initialChat);
4857
this.activeChat = this._activeChat;
4958

59+
this._activeChatModelId = derived(this, reader => this._activeChat.read(reader).modelId.read(reader));
60+
this._activeChatMode = derived(this, reader => this._activeChat.read(reader).mode.read(reader));
61+
5062
// Seed the closed set from persisted state, but never hide the chat that
5163
// is being restored as active, nor the main chat (which can never be
5264
// closed and must always remain in the tab strip).
@@ -137,8 +149,8 @@ export class VisibleSession extends Disposable implements IActiveSession {
137149
get status() { return this._session.status; }
138150
get changes() { return this._session.changes; }
139151
get changesets() { return this._session.changesets; }
140-
get modelId() { return this._session.modelId; }
141-
get mode() { return this._session.mode; }
152+
get modelId() { return this._activeChatModelId; }
153+
get mode() { return this._activeChatMode; }
142154
get loading() { return this._session.loading; }
143155
get isArchived() { return this._session.isArchived; }
144156
get isRead() { return this._session.isRead; }

src/vs/sessions/services/sessions/test/browser/visibleSessions.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,3 +1055,38 @@ suite('VisibleSession - visibleChatTabs', () => {
10551055
assert.deepStrictEqual(visible.visibleChatTabs.get().map(c => c.title.get()), ['main', 'draft', 'second']);
10561056
});
10571057
});
1058+
1059+
suite('VisibleSession - per-chat model/mode', () => {
1060+
1061+
const disposables = ensureNoDisposablesAreLeakedInTestSuite();
1062+
1063+
function makeChat(id: string, modelId: string | undefined, modeId: string | undefined): IChat {
1064+
return {
1065+
...stubChat,
1066+
resource: URI.parse(`test:///chat/${id}`),
1067+
title: constObservable(id),
1068+
modelId: constObservable(modelId),
1069+
mode: constObservable(modeId ? { id: modeId, kind: 'agent' } : undefined),
1070+
};
1071+
}
1072+
1073+
test('modelId and mode follow the active chat, not the session/default chat', () => {
1074+
const first = makeChat('first', 'model-1', 'agent-1');
1075+
const second = makeChat('second', 'model-2', 'agent-2');
1076+
const base = stubSession('S');
1077+
const session: ISession = { ...base, chats: constObservable([first, second]), mainChat: constObservable(first) };
1078+
const visible = disposables.add(new VisibleSession(session, first));
1079+
1080+
assert.deepStrictEqual(
1081+
{ modelId: visible.modelId.get(), mode: visible.mode.get() },
1082+
{ modelId: 'model-1', mode: { id: 'agent-1', kind: 'agent' } },
1083+
);
1084+
1085+
visible.setActiveChat(second);
1086+
1087+
assert.deepStrictEqual(
1088+
{ modelId: visible.modelId.get(), mode: visible.mode.get() },
1089+
{ modelId: 'model-2', mode: { id: 'agent-2', kind: 'agent' } },
1090+
);
1091+
});
1092+
});

0 commit comments

Comments
 (0)