Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
de0809b
agentHost: sync multi-provider chat protocol and add shared chat harn…
sandy081 Oct 19, 2026
5bf431c
sessions: model peer chats backed by different agent harnesses
sandy081 Oct 19, 2026
a9097a1
sessions: pick a new peer chat's harness and show chat harnesses
sandy081 Oct 19, 2026
aa3db84
sessions: document harness presentation and untitled chat reuse
sandy081 Oct 19, 2026
b88d04f
agentHost: route chats to their own agent behind multipleChatProviders
sandy081 Oct 19, 2026
840950b
Merge host multi-agent chat support
sandy081 Oct 19, 2026
3f25daf
Merge client multiple chat harness support
sandy081 Oct 19, 2026
61e6625
Sync final agent host protocol and use real chat provider fields
sandy081 Oct 19, 2026
626bfb3
sessions: address review of multi-harness peer chats
sandy081 Oct 19, 2026
0266d52
agentHost: harden chats with their own agent after review
sandy081 Oct 19, 2026
97b4981
sessions: polish chat harness markers
sandy081 Oct 19, 2026
df459d8
agentHost: keep a chat's agent outside the catalog payloads
sandy081 Oct 19, 2026
cfa9a25
agentHost: tighten recorded chat agents after re-review
sandy081 Oct 19, 2026
668c34e
Merge host multi-agent chat cleanup
sandy081 Oct 19, 2026
2ebefcf
Merge client multiple chat harness fixes
sandy081 Oct 19, 2026
589c459
Add Agent Host e2e coverage for chats with different agents
sandy081 Oct 19, 2026
11830de
agentHost: offer protocol 1.1.0 while chats may use their own agents
sandy081 Oct 19, 2026
1909b93
Merge host protocol 1.1.0 offer fix
sandy081 Oct 19, 2026
2bfa3a7
agentHost: re-initialize once when the host enables chats with their …
sandy081 Oct 19, 2026
446bd7c
sessions: hide chat harnesses while the host's gate is off
sandy081 Oct 19, 2026
a8640da
Merge host client re-initialize for chat providers
sandy081 Oct 19, 2026
04b6416
chat: present and select models for a peer chat by its own harness
sandy081 Oct 19, 2026
43849b9
chat: harden peer chat respondent presentation and model pools
sandy081 Oct 19, 2026
d7bbbef
chat: never revalidate an outgoing chat against another model pool
sandy081 Oct 19, 2026
8b07ab0
sessions: pick a peer chat's harness with the shared session-type picker
sandy081 Oct 20, 2026
0469b24
sessions: label a peer chat row by its own harness
sandy081 Oct 20, 2026
84f6266
sessions: polish the peer chat harness picker
sandy081 Oct 20, 2026
3a2ca00
sessions: show the new chat's harness picker as the new-session pill
sandy081 Oct 20, 2026
f08360b
sessions: match the new-session pill and send to the picked harness's…
sandy081 Oct 20, 2026
b387e4d
Merge origin/main into multi-harness foundation
sandy081 Oct 20, 2026
c1a0107
Merge origin/main (via foundation) into multi-agent chat host
sandy081 Oct 20, 2026
3df92c8
Merge origin/main (via host) into multiple chat harness client
sandy081 Oct 20, 2026
65d13b7
sessions: drop the chat tab harness icon and the mixed-harness indicator
sandy081 Oct 20, 2026
e0b1f2a
agentHost: let session tools create chats on another harness
sandy081 Oct 20, 2026
42a5684
Merge host branch (shared chat harness helpers) into multiple chat ha…
sandy081 Oct 20, 2026
276b7d3
Use shared toAgentHostChatHarness helper in agent host sessions provider
sandy081 Oct 20, 2026
21861f0
agentHost: tighten cross-harness chat creation after review
sandy081 Oct 20, 2026
c00c65a
Apply multiple chat providers setting without a window reload
sandy081 Oct 20, 2026
10e9549
Keep PING_INTERVAL_MS documentation on its constant
sandy081 Oct 20, 2026
6b45bed
agentHost: adopt AHP be8b56b1 and keep a chat's agent schema host-side
sandy081 Oct 20, 2026
9b29df0
Merge host branch (AHP be8b56b1, resolved per-chat config) into multi…
sandy081 Oct 20, 2026
97fd667
Resolve a different-agent chat's config with resolveSessionConfig
sandy081 Oct 20, 2026
f95b227
Turn the agent host chat agent config cache into a service
sandy081 Oct 20, 2026
bb15745
Merge origin/main (12cd33793ea) into multiple chat harness client
sandy081 Oct 22, 2026
21116d6
Resolve a nested chat's workspace choices with the new chat's own agent
sandy081 Oct 22, 2026
a7bcf45
Check a chat's own workspace against the chat's own agent
sandy081 Oct 22, 2026
df2c29c
Merge commit '12cd33793ea' into sandy081/agents/multi-agent-chat-supp…
sandy081 Oct 23, 2026
16e1ec0
Check a chat's own workspace against the chat's own agent
sandy081 Oct 22, 2026
e3350ca
Adopt complete chat config (AHP 6fc80fff)
sandy081 Oct 23, 2026
38ee691
Publish a chat's config whenever its session has config
sandy081 Oct 23, 2026
49c2f20
Merge host branch (AHP #534 chat config model) into multiple chat har…
sandy081 Oct 23, 2026
a745345
Sync agent host protocol f8067f73 (chat config doc comments)
sandy081 Oct 23, 2026
9388dc6
Read a chat's config from the approved chat config model (AHP #534)
sandy081 Oct 23, 2026
492711d
Let a session config write replace the default chat's own values
sandy081 Oct 23, 2026
1a1afa9
Keep a chat's model when a turn names one its agent does not offer
sandy081 Oct 23, 2026
c9eb8d0
Announce moved chats with chat/moved (AHP 382fd311)
sandy081 Oct 26, 2026
31e62e9
Merge branch 'sandy081/agents/multi-agent-chat-support-implementation…
sandy081 Oct 26, 2026
3a0665f
Merge origin/main into multi-agent chat support; sync AHP to 5872a476
sandy081 Oct 27, 2026
2bae86e
Merge host branch (origin/main 5893cd55828, AHP 5872a476) into multip…
sandy081 Oct 27, 2026
5179d0e
Show the nested composer's harness picker in its session options, lik…
sandy081 Oct 27, 2026
2b9907a
Offer move destinations of another agent that offer the moved chat's …
sandy081 Oct 27, 2026
3fc904d
Allow moving chats across sessions of different agents
sandy081 Oct 27, 2026
8e224e3
Keep a moved chat's model selection on its own agent
sandy081 Oct 27, 2026
b86ec06
Merge branch 'sandy081/agents/multi-agent-chat-support-implementation…
sandy081 Oct 27, 2026
1bfe6af
Merge commit 'b86ec067cd5' into sandy081/agents/multi-agent-chat-supp…
sandy081 Oct 27, 2026
5fd986c
Write default-chat config to the session only on hosts that apply it
sandy081 Oct 27, 2026
2502993
Fix review findings in multi-agent chats (aeb9b133)
sandy081 Oct 27, 2026
993d8d5
Merge host branch (review fixes M1-M3, L1-L5, move refusal reasons) i…
sandy081 Oct 27, 2026
bca653d
Explain move refusals with the host's reasons in one mapping
sandy081 Oct 27, 2026
61e230a
Merge commit 'bca653dd471' into sandy081/agents/multi-agent-chat-supp…
sandy081 Oct 27, 2026
c9841b1
Word move_session for chats with their own agents only where they may
sandy081 Oct 27, 2026
9a7a92f
Merge host branch (gate-aware move_session description) into multiple…
sandy081 Oct 27, 2026
18a231e
Skip peer chats again when the agents of chats can't be read
sandy081 Oct 27, 2026
6a2337b
Merge host branch (N1: skip peers when chat agents can't be read) int…
sandy081 Oct 27, 2026
eff7dd1
Merge commit '6a2337b1081' into sandy081/agents/multi-agent-chat-supp…
sandy081 Oct 27, 2026
3e88cbf
Restore a default chat's inherited config without a dispatch or write
sandy081 Oct 27, 2026
40893ef
Merge host branch (seed a restored default chat's config without disp…
sandy081 Oct 27, 2026
a51219c
Merge commit '40893efbccc' into sandy081/agents/multi-agent-chat-supp…
sandy081 Oct 27, 2026
eb63a3a
Publish a restored default chat's inherited config; skip only the red…
sandy081 Oct 27, 2026
4b99ce3
Merge host branch (restore dispatches a default chat's config, skippi…
sandy081 Oct 27, 2026
2addd79
Agent host: name chat subscription holders around the chat-providers …
sandy081 Oct 27, 2026
e4776cb
Merge commit '2addd79051e' into sandy081/agents/multi-agent-chat-supp…
sandy081 Oct 27, 2026
35f188b
Seed a local agent host with chats with their own agents before its h…
sandy081 Oct 27, 2026
914c558
Merge branch 'sandy081/agents/multi-agent-chat-support-implementation…
sandy081 Oct 27, 2026
0493022
Agent host: resolve an unresolved response chat from session state in…
sandy081 Oct 27, 2026
f9cf20d
Agent host: unsubscribe a subscribe that resolves after its subscript…
sandy081 Oct 27, 2026
557ea42
Agent host: unsubscribe a refresh that resolves after its subscriptio…
sandy081 Oct 27, 2026
140504e
agentHost: give test provider-service mocks getProviderForChat
sandy081 Oct 28, 2026
5a1239b
Merge commit '140504e95d5' into sandy081/agents/multi-agent-chat-supp…
sandy081 Oct 28, 2026
26b3c06
Resolve a fork's agent through its restored source chat's owner
sandy081 Oct 28, 2026
03814e0
Merge branch 'sandy081/agents/multi-agent-chat-support-implementation…
sandy081 Oct 28, 2026
f36b6e2
Merge remote-tracking branch 'origin/main' into sandy081/agents/multi…
sandy081 Oct 28, 2026
de7d252
Test a fork of a chat of another agent's session
sandy081 Oct 28, 2026
86cf91d
Merge branch 'sandy081/agents/multi-agent-chat-support-implementation…
sandy081 Oct 28, 2026
0bd6e13
Merge branch 'sandy081/agents/multiple-chat-harness-support' into san…
sandy081 Oct 28, 2026
07ac73b
Merge remote-tracking branch 'origin/main' into sandy081/agents/multi…
sandy081 Oct 28, 2026
be0fadd
Keep per-chat config as an overlay while chats can't use their own ag…
sandy081 Oct 28, 2026
eae45cd
Agent host: fix CI test/fixture mocks, wait for a nested chat folder …
sandy081 Oct 28, 2026
00ad7a5
Merge branch 'sandy081/agents/multi-agent-chat-support-implementation…
sandy081 Oct 28, 2026
747060d
Agent host: read complete chat config only from a host that declares …
sandy081 Oct 28, 2026
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
17 changes: 14 additions & 3 deletions src/vs/platform/agentHost/AGENTS.md

Large diffs are not rendered by default.

118 changes: 113 additions & 5 deletions src/vs/platform/agentHost/browser/agentHostProtocolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ import { MessageAttachmentKind, SessionSummary, ROOT_STATE_URI, StateComponents,
import type { AgentChatMoveDestination } from '../common/agentHostChatMove.js';
import { ChatMoveDestinationKind } from '../common/state/protocol/channels-chat/commands.js';
import { normalizeLegacyActionEnvelope } from '../common/state/legacyProtocolCompatibility.js';
import { getAgentHostSupportedProtocolVersions } from '../common/agentHostProtocolCompatibility.js';
import { getAgentHostClientProtocolVersions } from '../common/agentHostProtocolCompatibility.js';
import { ChatProvidersConfigKey, ChatProvidersSettingId } from '../common/agentHostChatProviders.js';
import { compareProtocolVersions } from '../common/state/protocol/version/registry.js';
import { isJsonRpcNotification, isJsonRpcRequest, isJsonRpcResponse, ProtocolError, ReconnectResultType, JSON_RPC_INTERNAL_ERROR, type ProtocolMessage, type IStateSnapshot, type SubscribeResult, type JsonRpcNotification } from '../common/state/sessionProtocol.js';
import { type IVscodeUpgradeResult } from '../common/state/protocolUpgrade.js';
import { isClientTransport, NonReconnectableTransportError, type AgentHostTransportFailureReason, type IProtocolTransport } from '../common/state/sessionTransport.js';
Expand Down Expand Up @@ -100,6 +102,9 @@ import type { IDevContainerAgentHostMainService } from '../common/devContainerAg

const AHP_CLIENT_CONNECTION_CLOSED = -32000;

/** The first protocol version whose hosts may advertise `multipleChatProviders`. */
const MULTIPLE_CHAT_PROVIDERS_PROTOCOL_VERSION = '1.1.0';

function toAgentExtensionMode(value: unknown): AgentExtensionMode | undefined {
if (typeof value !== 'string') {
return undefined;
Expand Down Expand Up @@ -337,6 +342,12 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
private _relayRootRefresh: { readonly transport: IProtocolTransport; readonly promise: Promise<void> } | undefined;
/** Disposable holding the listeners attached to the current transport. */
private readonly _transportListeners = this._register(new MutableDisposable<DisposableStore>());
/** The next reconnect sends `initialize` instead of `reconnect`, so the host decides its capabilities again. */
private _freshInitializeRequested = false;
/** Whether to name the chat subscriptions held once the re-initialize for chats with their own agents connects. */
private _describeChatSubscriptionsOnReconnect = false;
/** Whether this connection already re-initialized for `multipleChatProviders` since the host's gate last turned on. */
private _reinitializedForChatProviders = false;
private readonly _connectionAuthority: string;
readonly resourceUris: IAgentHostResourceUriMapper;
private _serverSeq = 0;
Expand Down Expand Up @@ -562,6 +573,11 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
: undefined;
}

/** The versions offered in `initialize`; read on every connection, so the setting applies from the next one. */
private _offeredProtocolVersions(): string[] {
return getAgentHostClientProtocolVersions(this._configurationService.getValue<boolean>(ChatProvidersSettingId.Enabled) === true);
}

/**
* The latest `initialize` response from the host, or `undefined` if
* the handshake has not completed yet. Exposed observably so callers can
Expand Down Expand Up @@ -627,6 +643,9 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
this._register(this.onDidAction(envelope => {
this._subscriptionManager.receiveEnvelope(envelope);
}));
// The host's gate for chats with their own agents usually turns on through this client's own mirror, after the handshake.
this._register(this._subscriptionManager.rootState.onDidChange(() => this._maybeReinitializeForChatProviders()));
this._register(this.onDidChangeConnectionState(() => this._maybeReinitializeForChatProviders()));

this._register(this._configurationService.onDidChangeConfiguration(e => {
if (this._state.kind !== AgentHostClientState.Connected || this._isWebPubSubRelay()) {
Expand Down Expand Up @@ -656,6 +675,10 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
this._logService.info(`[RemoteAgentHostProtocol] Mirroring configuration to host root config from ${ConfigurationTargetToString(e.source)}: ${mirrored.join(', ')}`);
this._dispatchRootConfig(patch);
}
if (e.affectsConfiguration(ChatProvidersSettingId.Enabled)) {
// The host's gate may already be on (e.g. mirrored by another window), so its root state won't change.
this._maybeReinitializeForChatProviders();
}
if (e.affectsConfiguration(GLOBAL_AUTO_APPROVE_SETTING_ID)) {
this._updateAutoApprovePolicyRestriction();
}
Expand Down Expand Up @@ -860,7 +883,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
// Advertise every compatible version, most-preferred first, so an
// older host (a cloud sandbox running a 0.5.x `copilotd`) can negotiate down
// instead of rejecting the connection. A current host still picks the newest.
protocolVersions: getAgentHostSupportedProtocolVersions(),
protocolVersions: this._offeredProtocolVersions(),
clientId: this._clientId,
clientInfo: this._clientInfo,
_meta: this._clientMeta(),
Expand Down Expand Up @@ -1221,7 +1244,12 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
}

private async _reconnectOrInitialize(lastSeenServerSeq: number, subscriptions: string[]): Promise<{ result: CommandMap['reconnect']['result'] & { readonly _meta?: Record<string, unknown> }; freshInitialize: boolean }> {
const freshInitialize = this._freshInitializeRequested;
this._freshInitializeRequested = false;
try {
if (freshInitialize) {
throw new ProtocolError(AhpErrorCodes.NotFound, 'A fresh initialize was requested');
}
const result = await this._dispatchRequest<CommandMap['reconnect']['result'] & { readonly _meta?: Record<string, unknown> }>('reconnect', {
channel: ROOT_STATE_URI,
clientId: this._clientId,
Expand All @@ -1236,10 +1264,12 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
}
}

this._logService.info(`[RemoteAgentHostProtocol] Server forgot client ${this._clientId}; initializing a fresh connection.`);
this._logService.info(freshInitialize
? `[RemoteAgentHostProtocol] Re-initializing ${this._address} so the host decides its capabilities again.`
: `[RemoteAgentHostProtocol] Server forgot client ${this._clientId}; initializing a fresh connection.`);
const initializeResult = await this._dispatchRequest<IAgentHostExtensionInitializeResult>('initialize', {
channel: ROOT_STATE_URI,
protocolVersions: getAgentHostSupportedProtocolVersions(),
protocolVersions: this._offeredProtocolVersions(),
clientId: this._clientId,
clientInfo: this._clientInfo,
_meta: this._clientMeta(),
Expand Down Expand Up @@ -1539,6 +1569,68 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
}
}

/**
* The host decides `multipleChatProviders` from its root config at the
* handshake, but this client mirrors `multipleChatProviders.enabled` only
* after it, and offers the protocol version that may advertise it only
* while the setting is on. When the setting is on, the host's gate is on
* (as confirmed by the host's root state), and the handshake didn't
* advertise the capability, re-initialize once so the host decides again,
* including when the handshake negotiated an older version because the
* setting was off then. At most once until the host's gate or the setting
* turns off again, so it never loops; nothing happens with the setting off.
*/
/**
* The chat subscriptions this client holds, by holder: every one is restored after a re-initialize, so this
* names the components that make a re-initialize restore many chats.
*/
private _describeChatSubscriptions(): string {
const holders = new Map<string, number>();
let count = 0;
for (const subscription of this._subscriptionManager.getActiveSubscriptions()) {
if (subscription.kind === StateComponents.Chat) {
count++;
for (const holder of subscription.holders) {
holders.set(holder.owner, (holders.get(holder.owner) ?? 0) + holder.count);
}
}
}
return `${count} chat subscription(s) held by: ${[...holders].map(([owner, holderCount]) => `${owner}=${holderCount}`).join(', ') || 'none'}`;
}

private _maybeReinitializeForChatProviders(): void {
if (this._describeChatSubscriptionsOnReconnect && this._state.kind === AgentHostClientState.Connected) {
this._describeChatSubscriptionsOnReconnect = false;
this._logService.trace(`[RemoteAgentHostProtocol] ${this._address} re-initialized for chats with their own agents with ${this._describeChatSubscriptions()}.`);
}
if (this._configurationService.getValue<boolean>(ChatProvidersSettingId.Enabled) !== true) {
this._reinitializedForChatProviders = false;
return;
}
if (this._state.kind !== AgentHostClientState.Connected || !this._transportFactory) {
return;
}
const root = this._subscriptionManager.rootState.value;
if (!root || root instanceof Error || root.config?.values[ChatProvidersConfigKey.Enabled] !== true) {
this._reinitializedForChatProviders = false;
return;
}
const result = this._initializeResult.get();
if (!result || result.multipleChatProviders || this._reinitializedForChatProviders
|| !this._offeredProtocolVersions().some(version => compareProtocolVersions(version, MULTIPLE_CHAT_PROVIDERS_PROTOCOL_VERSION) >= 0)) {
return;
}
this._reinitializedForChatProviders = true;
this._freshInitializeRequested = true;
this._logService.info(`[RemoteAgentHostProtocol] ${this._address} enabled chats with their own agents after the handshake; re-initializing.`);
this._logService.trace(`[RemoteAgentHostProtocol] ${this._address} re-initializing with ${this._describeChatSubscriptions()}.`);
this._describeChatSubscriptionsOnReconnect = true;
// Disposing the listeners closes the transport; route the close through the soft-reconnect path, as the watchdog does.
this._transportListeners.clear();
this._handleTransportClose();
}


private _updateDynamicAutoTiers(): void {
const enabled = getGlobalConfigurationValue<boolean>(this._configurationService, AgentHostDynamicAutoTiersEnabledSettingId) === true;
this._dispatchRootConfig({ [CopilotCliConfigKey.DynamicAutoTiers]: enabled });
Expand Down Expand Up @@ -1853,7 +1945,21 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect

private async _subscribe(resource: URI): Promise<IStateSnapshot> {
this._logService.trace(`[RemoteAgentHostProtocol] subscribe start: ${resource.toString()}`);
const result = await this._sendRequest('subscribe', { channel: resource.toString() });
let result: CommandMap['subscribe']['result'];
while (true) {
try {
result = await this._sendRequest('subscribe', { channel: resource.toString() });
break;
} catch (error) {
// A subscribe in flight when the transport drops (a soft reconnect, or the re-initialize that enables
// chats with their own agents) is rejected only because its reply was lost: send it again once the
// client reconnects, as any request issued while reconnecting is, instead of failing the subscription.
if (!isConnectionClosedError(error) || (this._state as ClientState).kind !== AgentHostClientState.Reconnecting) {
throw error;
}
this._logService.trace(`[RemoteAgentHostProtocol] subscribe lost to a reconnect, retrying once reconnected: ${resource.toString()}`);
}
}
if (!result.snapshot) {
throw new Error(`subscribe to ${resource.toString()} returned no snapshot`);
}
Expand Down Expand Up @@ -2542,6 +2648,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
channel: session.toString(),
chat: chat.toString(),
...(options?.title !== undefined ? { title: options.title } : {}),
...(options?.provider !== undefined ? { provider: options.provider } : {}),
...(initialMessage ? { initialMessage } : {}),
...(options?.workingDirectories !== undefined && !options.fork
? { workingDirectories: options.workingDirectories.map(directory => fromAgentHostUri(directory).toString()) }
Expand Down Expand Up @@ -2635,6 +2742,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
summary: chat.title,
kind: s.defaultChat === chat.resource ? 'default' : 'peer',
origin: chat.origin,
...(chat.provider !== undefined ? { provider: chat.provider } : {}),
...(chat.interactivity !== undefined ? { interactivity: chat.interactivity } : {}),
...(isSessionChatArchived(chat) ? { archived: true } : {}),
...(chat.status !== undefined ? { status: chat.status, isRead: isSessionStatusRead(chat.status) } : {}),
Expand Down
9 changes: 9 additions & 0 deletions src/vs/platform/agentHost/common/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ export interface IAgentSessionChatMetadata {
readonly kind: 'default' | 'peer';
readonly origin?: ChatOrigin;
readonly interactivity?: ChatInteractivity;
/** The chat's own agent when it differs from its session's; absence means the session's agent. */
readonly provider?: string;
readonly archived?: boolean;
/** Exact chat read state when known; absence means the provider did not supply it. */
readonly isRead?: boolean;
Expand Down Expand Up @@ -1013,6 +1015,13 @@ export interface IAgentCreateChatRequestOptions extends IAgentCreateChatOptions
* the provider, while retaining the side-chat provenance itself.
*/
readonly sideChat?: IAgentCreateChatSideChatSource;
/**
* The agent requested for the new chat (`CreateChatParams.provider`). The
* host resolves and validates it and creates the chat in that agent;
* absent means the source chat's agent for a fork or side chat, else the
* session's. Never passed to providers.
*/
readonly provider?: AgentProvider;
/**
* Host-owned workspace requested for a new peer chat. The host prepares it
* (adding the folder to the session, creating a worktree or a scratch
Expand Down
18 changes: 18 additions & 0 deletions src/vs/platform/agentHost/common/agentHostChatMove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,24 @@ export const enum AgentChatMoveRefusal {
* credentials than the destination session."
*/
DifferentCredentialOwner = 'differentCredentialOwner',
/**
* Chats with their own agents are on, but the chat's agent is unknown or disabled on the host, so it can't move to a
* session of another agent or become a session of its own agent. Client text: enable the chat's agent. Distinct from
* {@link DifferentProvider}, which means chats with their own agents are off.
*/
AgentUnavailable = 'agentUnavailable',
/**
* Chats with their own agents are on, but the destination's agent can't host chats of other agents, or the chat's
* agent can't join a session of another agent: one of them doesn't support multiple chats. Client text: pick a
* session of an agent that supports chats of other agents.
*/
AgentWithoutChats = 'agentWithoutChats',
/**
* The chat's agent couldn't describe its settings for the chat when it moved into a session of another agent or
* into a new session of its own agent. Transient: client text asks to try again. Distinct from {@link Unsupported}
* (no conversation of its own to take into a new session).
*/
AgentConfigUnavailable = 'agentConfigUnavailable',
}

/** A chat move the host refused before changing anything. */
Expand Down
39 changes: 39 additions & 0 deletions src/vs/platform/agentHost/common/agentHostChatProviders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { localize } from '../../../nls.js';
import { createSchema, schemaProperty } from './agentHostSchema.js';

/** Host root config keys for chats of one session using different agents. */
export const ChatProvidersConfigKey = {
Enabled: 'multipleChatProviders.enabled',
} as const;

/** Workbench settings mirrored into {@link ChatProvidersConfigKey}. */
export const ChatProvidersSettingId = {
Enabled: 'chat.agentHost.multipleChatProviders.enabled',
} as const;

export const chatProvidersRootConfigSchema = createSchema({
[ChatProvidersConfigKey.Enabled]: schemaProperty<boolean>({
type: 'boolean',
title: localize('multipleChatProviders.config.enabled', "Multiple Chat Providers"),
default: false,
}),
});

/**
* The user-facing harness id of an agent provider, as clients present it:
* Copilot's `copilotcli` agent is the `copilot` harness; every other agent id
* is its own harness id.
*/
export function toAgentHostChatHarness(agentProvider: string): string {
return agentProvider === 'copilotcli' ? 'copilot' : agentProvider;
}

/** The agent provider of a user-facing harness id; the inverse of {@link toAgentHostChatHarness}. */
export function fromAgentHostChatHarness(harness: string): string {
return harness === 'copilot' ? 'copilotcli' : harness;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { getErrorMessage } from '../../../base/common/errors.js';
import { compareProtocolVersions, negotiateProtocolVersion, SUPPORTED_PROTOCOL_VERSIONS } from './state/protocol/version/registry.js';
import { compareProtocolVersions, negotiateProtocolVersion, PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from './state/protocol/version/registry.js';
import { JsonRpcErrorCodes, ProtocolError } from './state/sessionProtocol.js';

const legacyCompatibleProtocolVersion = '0.10.0';
Expand All @@ -25,3 +25,15 @@ export function negotiateAgentHostProtocolVersion(offered: readonly string[]): s
export function getAgentHostSupportedProtocolVersions(): string[] {
return [...SUPPORTED_PROTOCOL_VERSIONS, legacyCompatibleProtocolVersion].sort((a, b) => compareProtocolVersions(b, a));
}

/**
* The protocol versions a client offers in `initialize`, most-preferred first.
* With chats of one session using different agents enabled, the client also
* offers the current {@link PROTOCOL_VERSION} first, which hosts require
* before advertising `multipleChatProviders`; otherwise the offer is exactly
* {@link getAgentHostSupportedProtocolVersions}.
*/
export function getAgentHostClientProtocolVersions(multipleChatProviders: boolean): string[] {
const supported = getAgentHostSupportedProtocolVersions();
return multipleChatProviders && !supported.includes(PROTOCOL_VERSION) ? [PROTOCOL_VERSION, ...supported] : supported;
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
AgentHostWorkspaceSnapshotEnabledConfigKey,
} from './agentHostSchema.js';
import { AgentMergeConfigKey, AgentMergeSettingId, AGENT_MERGE_SETTING_TAG } from './agentMerge.js';
import { ChatProvidersConfigKey, ChatProvidersSettingId } from './agentHostChatProviders.js';
import { artifactToolsConfigurationProperties } from './artifactToolsConfiguration.js';
import { chatMoveConfigurationProperties } from './chatMoveConfiguration.js';
import { titleGenerationConfigurationProperties } from './titleGenerationConfiguration.js';
Expand Down Expand Up @@ -217,6 +218,14 @@ configurationRegistry.registerConfiguration({
tags: ['experimental', AGENT_MERGE_SETTING_TAG],
agentHost: { key: AgentMergeConfigKey.ReplyAttribution },
},
[ChatProvidersSettingId.Enabled]: {
type: 'boolean',
description: nls.localize('chat.agentHost.multipleChatProviders.enabled', "Enables chats in one Agent Host session to use different agents."),
default: false,
scope: ConfigurationScope.APPLICATION,
tags: ['experimental'],
agentHost: { key: ChatProvidersConfigKey.Enabled },
},
...chatMoveConfigurationProperties,
...titleGenerationConfigurationProperties,
...artifactToolsConfigurationProperties,
Expand Down
Loading
Loading