Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
104 changes: 5 additions & 99 deletions src/vs/platform/agentHost/browser/agentHostProtocolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { DeferredPromise, TimeoutTimer } from '../../../base/common/async.js';
import { CancellationError } from '../../../base/common/errors.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { Disposable, DisposableStore, MutableDisposable, IReference, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
import { equals } from '../../../base/common/objects.js';
import { Schemas } from '../../../base/common/network.js';
import { hasKey } from '../../../base/common/types.js';
import { URI } from '../../../base/common/uri.js';
Expand All @@ -19,9 +18,9 @@ import { localize } from '../../../nls.js';
import { ILogService } from '../../log/common/log.js';
import { FileSystemProviderErrorCode, toFileSystemProviderErrorCode } from '../../files/common/files.js';
import { ConfigurationTarget, ConfigurationTargetToString, IConfigurationService } from '../../configuration/common/configuration.js';
import { AgentCanvasAvailability, AgentSession, IAgentCreateChatRequestOptions, IAgentCreateSessionConfig, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, AuthenticateParams, AuthenticateResult, IMcpNotification, type IAgentCanvas, type IAgentCanvasSnapshot } from '../common/agent.js';
import { AGENT_HOST_DEBUG_LOGS_CHUNK_BYTES, AGENT_HOST_DEBUG_LOGS_MAX_ENTRIES, IAgentConnection, IAgentHostManagedSettingsDiagnostics, IAgentHostNetworkDiagnosticsInfo, IAgentHostNetworkFetchResult, type AgentHostDebugLogsArtifactKind, type IAgentHostCanvases, type IAgentHostDebugLogsArtifact, type IAgentHostDebugLogsChunk } from '../common/agentService.js';
import { AgentHostCanvasesChangedNotification, agentHostCanvasesChangedParamsValidator, ClaimAgentHostDetachedWorktreeExtensionMethod, CollectAgentHostDebugLogsExtensionMethod, CreateAgentHostDetachedWorktreeExtensionMethod, DeleteAgentHostDetachedWorktreeExtensionMethod, GetAgentHostSessionStateFileExtensionMethod, ImportSessionExtensionMethod, isValidAgentHostCanvasesChangedParams, ReadAgentHostDebugLogsChunkExtensionMethod, ReconcileAgentHostDetachedWorktreesExtensionMethod, RemoveSessionArtifactExtensionMethod, ReportAgentHostFirstResponseExtensionMethod, ReportChatUserInteractionExtensionMethod, RequestAgentHostMcpAuthenticationExtensionMethod, RequestAgentHostWorkspaceTrustExtensionMethod, resolveAgentHostCanvasSourceResultValidator, ResolveAgentHostCanvasSourceExtensionMethod, SetAgentHostDetachedWorktreeArchivedExtensionMethod, supportsAgentHostCanvases, supportsAgentHostChatStateFile, supportsAgentHostDevContainers, type IAgentHostExtensionCommandMap, type IAgentHostExtensionInitializeResult, type IAgentHostExtensionServerCommandMap, type IAgentHostMcpAuthenticationRequest } from '../common/agentHostExtensionProtocol.js';
import { AgentSession, IAgentCreateChatRequestOptions, IAgentCreateSessionConfig, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, AuthenticateParams, AuthenticateResult, IMcpNotification } from '../common/agent.js';
import { AGENT_HOST_DEBUG_LOGS_CHUNK_BYTES, AGENT_HOST_DEBUG_LOGS_MAX_ENTRIES, IAgentConnection, IAgentHostManagedSettingsDiagnostics, IAgentHostNetworkDiagnosticsInfo, IAgentHostNetworkFetchResult, type AgentHostDebugLogsArtifactKind, type IAgentHostDebugLogsArtifact, type IAgentHostDebugLogsChunk } from '../common/agentService.js';
import { ClaimAgentHostDetachedWorktreeExtensionMethod, CollectAgentHostDebugLogsExtensionMethod, CreateAgentHostDetachedWorktreeExtensionMethod, DeleteAgentHostDetachedWorktreeExtensionMethod, GetAgentHostSessionStateFileExtensionMethod, ImportSessionExtensionMethod, ReadAgentHostDebugLogsChunkExtensionMethod, ReconcileAgentHostDetachedWorktreesExtensionMethod, RemoveSessionArtifactExtensionMethod, ReportAgentHostFirstResponseExtensionMethod, ReportChatUserInteractionExtensionMethod, RequestAgentHostMcpAuthenticationExtensionMethod, RequestAgentHostWorkspaceTrustExtensionMethod, SetAgentHostDetachedWorktreeArchivedExtensionMethod, supportsAgentHostChatStateFile, supportsAgentHostDevContainers, type IAgentHostExtensionCommandMap, type IAgentHostExtensionInitializeResult, type IAgentHostExtensionServerCommandMap, type IAgentHostMcpAuthenticationRequest } from '../common/agentHostExtensionProtocol.js';
import { McpAuthRequiredReason } from '../common/state/protocol/channels-session/state.js';
import { supportsAgentHostTiming, supportsChatUserInteractionTiming } from '../common/meta/agentHostTimingMeta.js';
import { readCodexSessionModel } from '../common/meta/codexSessionModel.js';
Expand All @@ -34,7 +33,7 @@ import { AGENT_HOST_SCHEME, agentHostAuthority, createAgentHostResourceUriMapper
import { AgentHostResourceIdentity, AgentHostResourcePermissionError, IAgentHostResourceService, LOCAL_AGENT_HOST_RESOURCE_IDENTITY } from '../common/agentHostResourceService.js';
import type { ClientNotificationMap, CommandMap, JsonRpcErrorResponse, JsonRpcRequest, JsonRpcResponse } from '../common/state/protocol/messages.js';
import { ActionType, type ActionEnvelope, type ChatAction, type ClientAnnotationsAction, type ClientAutomationAction, type ClientAutomationRunAction, type ClientChangesetAction, type INotification, type IRootConfigChangedAction, type SessionAction, type TerminalAction } from '../common/state/sessionActions.js';
import { MessageAttachmentKind, SessionSummary, ROOT_STATE_URI, StateComponents, isAhpRootChannel, isDefaultChatUri, parseChatUri, type ClientPluginCustomization, type Message, type RootState } from '../common/state/sessionState.js';
import { MessageAttachmentKind, SessionSummary, ROOT_STATE_URI, StateComponents, isAhpRootChannel, isDefaultChatUri, isSessionStatusArchived, type ClientPluginCustomization, type Message, type RootState } from '../common/state/sessionState.js';
import { normalizeLegacyActionEnvelope } from '../common/state/legacyProtocolCompatibility.js';
import { SUPPORTED_PROTOCOL_VERSIONS } from '../common/state/protocol/version/registry.js';
import { isJsonRpcNotification, isJsonRpcRequest, isJsonRpcResponse, ProtocolError, ReconnectResultType, type ProtocolMessage, type IStateSnapshot } from '../common/state/sessionProtocol.js';
Expand Down Expand Up @@ -278,23 +277,6 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
: undefined;
}

private readonly _canvasSnapshots = new Map<string, IAgentCanvasSnapshot>();
private readonly _onDidChangeCanvases = this._register(new Emitter<IAgentCanvasSnapshot>());
private readonly _canvasService: IAgentHostCanvases = {
onDidChange: this._onDidChangeCanvases.event,
getSnapshots: () => [...this._canvasSnapshots.values()],
resolveSource: (chat, instanceId, revision) => this._resolveCanvasSource(chat, instanceId, revision),
};

get canvases(): IAgentHostCanvases | undefined {
return this._resourceIdentity === LOCAL_AGENT_HOST_RESOURCE_IDENTITY
&& this._state.kind !== AgentHostClientState.Closed
&& this._state.kind !== AgentHostClientState.Incompatible
&& supportsAgentHostCanvases(this._initializeResult.get())
? this._canvasService
: undefined;
}

private readonly _subscriptionManager: AgentSubscriptionManager;

private readonly _onDidAction = this._register(new Emitter<ActionEnvelope>());
Expand Down Expand Up @@ -692,7 +674,6 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
this._logService.warn('[AgentHostProtocolClient] Dispose callback failed', error);
}
}
this._clearCanvasSnapshots();
this._handleClose(connectionDisposedError(this._address));
super.dispose();
}
Expand Down Expand Up @@ -1086,7 +1067,6 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
}

this._logService.info(`[RemoteAgentHostProtocol] Server forgot client ${this._clientId}; initializing a fresh connection.`);
this._clearCanvasSnapshots();
const initializeResult = await this._dispatchRequest<IAgentHostExtensionInitializeResult>('initialize', {
channel: ROOT_STATE_URI,
protocolVersions: [...CLIENT_SUPPORTED_PROTOCOL_VERSIONS],
Expand Down Expand Up @@ -1839,27 +1819,6 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
await this._sendRequest('disposeChat', { channel: chat.toString() });
}

private async _resolveCanvasSource(chat: URI, instanceId: string, revision: number): Promise<string> {
if (this.canvases !== this._canvasService) {
throw new Error('Agent Host canvases are not available for this connection.');
}
const snapshot = this._canvasSnapshots.get(chat.toString());
const canvas = snapshot?.canvases.find(canvas => canvas.instanceId === instanceId);
if (!canvas || canvas.revision !== revision || canvas.availability !== AgentCanvasAvailability.Ready) {
throw new Error(`Canvas '${instanceId}' is not available at revision ${revision}.`);
}
const result = await this._sendExtensionRequest(ResolveAgentHostCanvasSourceExtensionMethod, {
chat: chat.toString(),
instanceId,
revision,
});
const validated = resolveAgentHostCanvasSourceResultValidator.validate(result);
if (validated.error || !validated.content.url.trim()) {
throw new ProtocolError(JsonRpcErrorCodes.InvalidParams, 'Agent Host returned an invalid canvas source.');
}
return validated.content.url;
}

/**
* Create a new terminal on the remote agent host.
*/
Expand Down Expand Up @@ -1917,7 +1876,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
kind: s.defaultChat === chat.resource || isDefaultChatUri(chat.resource) ? 'default' : 'peer',
origin: chat.origin,
...(chat.interactivity !== undefined ? { interactivity: chat.interactivity } : {}),
...(chat.archived === true ? { archived: true } : {}),
...(isSessionStatusArchived(chat.status) || chat.archived === true ? { archived: true } : {}),
...(chat.changes !== undefined ? { changes: chat.changes } : {}),
})),
// Carry durable host provenance for sessions first materialized from a listing.
Expand Down Expand Up @@ -2094,10 +2053,6 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
this._logService.warn(`[RemoteAgentHostProtocol] Received response for unknown request id ${msg.id}`);
}
} else if (isJsonRpcNotification(msg)) {
if ((msg as { method: string }).method === AgentHostCanvasesChangedNotification) {
this._handleCanvasSnapshot((msg as { params?: unknown }).params);
return;
}
if (this._devContainerService.handleNotification(msg.method, msg.params)) {
return;
}
Expand Down Expand Up @@ -2147,55 +2102,6 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
}
}

private _handleCanvasSnapshot(params: unknown): void {
if (this._resourceIdentity !== LOCAL_AGENT_HOST_RESOURCE_IDENTITY) {
return;
}
const initializeResult = this._initializeResult.get();
if (initializeResult && !supportsAgentHostCanvases(initializeResult)) {
return;
}
const validated = agentHostCanvasesChangedParamsValidator.validate(params);
if (validated.error || !isValidAgentHostCanvasesChangedParams(validated.content)) {
this._logService.warn('[AgentHostProtocolClient] Ignoring invalid canvas snapshot.');
return;
}
let chat: URI;
try {
chat = URI.parse(validated.content.chat, true);
} catch {
this._logService.warn('[AgentHostProtocolClient] Ignoring canvas snapshot with an invalid chat URI.');
return;
}
if (!parseChatUri(chat)) {
this._logService.warn('[AgentHostProtocolClient] Ignoring canvas snapshot for a non-chat URI.');
return;
}
const canvases: readonly IAgentCanvas[] = validated.content.canvases;
const key = chat.toString();
const previous = this._canvasSnapshots.get(key);
if (previous && equals(previous.canvases, canvases)) {
return;
}
if (canvases.length === 0) {
if (!previous) {
return;
}
this._canvasSnapshots.delete(key);
} else {
this._canvasSnapshots.set(key, { chat, canvases });
}
this._onDidChangeCanvases.fire({ chat, canvases });
}

private _clearCanvasSnapshots(): void {
const snapshots = [...this._canvasSnapshots.values()];
this._canvasSnapshots.clear();
for (const snapshot of snapshots) {
this._onDidChangeCanvases.fire({ chat: snapshot.chat, canvases: [] });
}
}

private _handleFatalClose(error: ProtocolError, reason?: AgentHostTransportFailureReason): void {
this._onDidFatalClose.fire(error);
this._handleClose(error, reason);
Expand Down
40 changes: 10 additions & 30 deletions src/vs/platform/agentHost/common/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { AgentHostClientType } from './agentHostClientInfo.js';
import type { CodexModelProvider, IAgentTurnTelemetryCorrelation, IAgentHostClientTelemetryContext, IAgentProviderSendStageRecorder, IAgentProviderTurnTelemetryContext } from './agentHostTelemetry.js';
import type { AgentPermissionDecisionSource } from './meta/agentPermissionResponseMeta.js';
import type { ResolveSessionConfigResult, SessionConfigCompletionsResult } from './state/protocol/commands.js';
import type { CanvasState } from './state/protocol/channels-canvas/state.js';
import { ProtectedResourceMetadata, type BackgroundWork, type Changeset, type ChatInteractivity, type ChatOrigin, type ConfigSchema, type MessageAttachment, type ModelSelection, type AgentSelection, type SessionActiveClient, type ToolCallPendingConfirmationState, type ToolDefinition, ChangesSummary } from './state/protocol/state.js';
import type { ActionOrigin, AuthRequiredParams, SessionAction, ChatAction } from './state/sessionActions.js';
import { ChatInputResponseKind, ChatOriginKind, SessionStatus, buildSubagentChatUri, parseRequiredSessionUriFromChatUri, type AgentCapabilities, type ClientPluginCustomization, type Customization, type ErrorInfo, type ISessionFolderPickerDecision, type Message, type PendingMessage, type ChatInputAnswer, type SessionMeta, type ToolCallResult, type Turn, type PolicyState } from './state/sessionState.js';
Expand Down Expand Up @@ -707,31 +708,6 @@ export interface IAgentChatDataChange {
readonly providerData: string;
}

export const AgentCanvasAvailability = {
Ready: 'ready',
Unavailable: 'unavailable',
} as const;

export type AgentCanvasAvailability = typeof AgentCanvasAvailability[keyof typeof AgentCanvasAvailability];

/** A live model-opened canvas projected by an agent provider. */
export interface IAgentCanvas {
readonly instanceId: string;
readonly extensionId: string;
readonly extensionName?: string;
readonly canvasId: string;
readonly title?: string;
readonly status?: string;
readonly revision: number;
readonly availability: AgentCanvasAvailability;
}

/** Full replacement of the live canvas collection for one exact chat. */
export interface IAgentCanvasSnapshot {
readonly chat: URI;
readonly canvases: readonly IAgentCanvas[];
}

/** A legacy concrete chat backing enumerated by {@link IAgent.listLegacyChatBackings} for migration. */
export interface IAgentLegacyChat {
/** The concrete chat's channel URI (see {@link buildChatUri}). */
Expand Down Expand Up @@ -901,8 +877,6 @@ export interface IAgentChats {
/** Reconstruct the turns for `chat` (used on restore). */
getMessages(chat: URI, context: AgentChatOperationContext): Promise<readonly Turn[]>;

/** Resolve the current source of a live model-opened canvas. */
resolveCanvasSource?(chat: URI, instanceId: string, revision: number, context: AgentChatOperationContext): Promise<string>;
}

export interface IAgentResolveChatConfigParams {
Expand Down Expand Up @@ -946,6 +920,7 @@ export interface IAgentModelInfo {
*/
export type AgentSignal =
| IAgentActionSignal
| IAgentCanvasSignal
| IAgentModelCallCompletedSignal
| IAgentModelCallFinishedSignal
| IAgentToolPendingConfirmationSignal
Expand All @@ -972,6 +947,14 @@ export interface IAgentActionSignal {
readonly parentToolCallId?: string;
}

/** Materializes or updates a chat-owned live canvas channel. */
export interface IAgentCanvasSignal {
readonly kind: 'canvas';
readonly chat: URI;
readonly resource: URI;
readonly state: CanvasState;
}

/** Reports one completed upstream model response for host-owned turn telemetry. */
export interface IAgentModelCallCompletedSignal {
readonly kind: 'model_call_completed';
Expand Down Expand Up @@ -1314,9 +1297,6 @@ export interface IAgent {
/** Fires when an opaque chat backing changes and must be persisted again. */
readonly onDidChangeChatData: Event<IAgentChatDataChange>;

/** Full-replacement live canvas snapshots for exact chats owned by this provider. */
readonly onDidChangeCanvases?: Event<IAgentCanvasSnapshot>;

/** Fires when the provider creates a chat, such as a delegated subagent. */
readonly onDidSpawnChat: Event<IAgentSpawnChatEvent>;

Expand Down
Loading
Loading