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
5 changes: 3 additions & 2 deletions src/vs/platform/agentHost/browser/agentHostProtocolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { AgentHostResourceIdentity, AgentHostResourcePermissionError, IAgentHost
import type { ClientNotificationMap, CommandMap, JsonRpcErrorResponse, JsonRpcRequest } from '../common/state/protocol/messages.js';
import { ActionType, type ActionEnvelope, type ChatAction, type ClientAnnotationsAction, type ClientChangesetAction, type INotification, type IRootConfigChangedAction, type SessionAction, type TerminalAction } from '../common/state/sessionActions.js';
import { MessageAttachmentKind, SessionSummary, ROOT_STATE_URI, StateComponents, isAhpRootChannel, 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';
import { type IVscodeUpgradeResult } from '../common/state/protocolUpgrade.js';
Expand Down Expand Up @@ -862,7 +863,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
if (envelope.serverSeq > maxSeq) {
maxSeq = envelope.serverSeq;
}
this._onDidAction.fire(envelope);
this._onDidAction.fire(normalizeLegacyActionEnvelope(envelope));
}
this._serverSeq = maxSeq;
if (result.missing.length > 0) {
Expand Down Expand Up @@ -1471,7 +1472,7 @@ export class AgentHostProtocolClient extends Disposable implements IAgentConnect
// Protocol envelope → VS Code envelope (superset of action types)
const envelope = msg.params;
this._serverSeq = Math.max(this._serverSeq, envelope.serverSeq);
this._onDidAction.fire(envelope);
this._onDidAction.fire(normalizeLegacyActionEnvelope(envelope));
break;
}
case 'root/sessionAdded':
Expand Down
5 changes: 5 additions & 0 deletions src/vs/platform/agentHost/common/state/agentSubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { RootAction, SessionAction as IProtocolSessionAction, ChatAction as
import type { AnnotationsState, ChangesetState, ChatState, RootState, SessionState, TerminalState } from './protocol/state.js';
import type { IStateSnapshot } from './sessionProtocol.js';
import { isAhpRootChannel, ROOT_STATE_URI, StateComponents } from './sessionState.js';
import { normalizeLegacyChatStateErrors } from './legacyProtocolCompatibility.js';

// --- Public API --------------------------------------------------------------

Expand Down Expand Up @@ -426,6 +427,10 @@ export class ChatStateSubscription extends BaseAgentSubscription<ChatState> {
this._seqAllocator = seqAllocator;
}

override handleSnapshot(state: ChatState, fromSeq: number): void {
super.handleSnapshot(normalizeLegacyChatStateErrors(state), fromSeq);
}

/**
* Optimistically apply a chat action. Returns the clientSeq to send to
* the server so it can echo back for reconciliation.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { hasKey } from '../../../../base/common/types.js';
import { ActionType, type ActionEnvelope, type ChatErrorAction, type StateAction } from './protocol/actions.js';
import { ResponsePartKind, TurnState, type ChatState, type ErrorInfo, type Turn } from './protocol/state.js';

interface ILegacyChatErrorAction extends Omit<ChatErrorAction, 'part'> {
readonly error: ErrorInfo;
}

type CompatibleTurn = Turn | (Turn & { readonly error: ErrorInfo });

type CompatibleActionEnvelope = Omit<ActionEnvelope, 'action'> & {
readonly action: StateAction | ILegacyChatErrorAction;
};

/**
* Reads the top-level error field emitted by AHP hosts before durable error
* response parts were introduced.
*/
export function readLegacyTurnError(turn: CompatibleTurn): ErrorInfo | undefined {
if (!hasKey(turn, { error: true })) {
return undefined;
}
return turn.error;
}

/**
* Moves a legacy completed-turn error into its durable response-part position.
*/
export function normalizeLegacyTurnError(turn: CompatibleTurn): Turn {
if (turn.state !== TurnState.Error || !hasKey(turn, { error: true })) {
return turn;
}

const { error, ...normalizedTurn } = turn;
const finalPart = turn.responseParts[turn.responseParts.length - 1];
return {
...normalizedTurn,
responseParts: finalPart?.kind === ResponsePartKind.Error
? turn.responseParts
: [...turn.responseParts, { kind: ResponsePartKind.Error, error }],
};
}

/**
* Normalizes legacy completed-turn errors in a chat snapshot.
*/
export function normalizeLegacyChatStateErrors(state: ChatState): ChatState {
const turns = state.turns.map(normalizeLegacyTurnError);
return turns.some((turn, index) => turn !== state.turns[index])
? { ...state, turns }
: state;
}

/**
* Normalizes legacy error payloads before a server action reaches reducers or
* action observers.
*/
export function normalizeLegacyActionEnvelope(envelope: CompatibleActionEnvelope): ActionEnvelope {
const action = envelope.action;
switch (action.type) {
case ActionType.ChatError:
if (hasKey(action, { error: true })) {
const { error, ...normalizedAction } = action;
return {
...envelope,
action: {
...normalizedAction,
part: { kind: ResponsePartKind.Error, error },
},
};
}
return { ...envelope, action };
case ActionType.ChatTurnsLoaded: {
const turns = action.turns.map(normalizeLegacyTurnError);
return turns.some((turn, index) => turn !== action.turns[index])
? { ...envelope, action: { ...action, turns } }
: { ...envelope, action };
}
default:
return { ...envelope, action };
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
f770e26b
b4016c0e
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// Generated from types/actions.ts — do not edit
// Run `npm run generate` to regenerate.

import { ActionType, type StateAction, type RootAgentsChangedAction, type RootActiveSessionsChangedAction, type RootTerminalsChangedAction, type RootConfigChangedAction, type SessionReadyAction, type SessionCreationFailedAction, type SessionChatAddedAction, type SessionChatRemovedAction, type SessionChatUpdatedAction, type SessionDefaultChatChangedAction, type SessionTitleChangedAction, type SessionServerToolsChangedAction, type SessionActiveClientSetAction, type SessionActiveClientRemovedAction, type SessionWorkingDirectorySetAction, type SessionWorkingDirectoryRemovedAction, type SessionWorkingDirectoryReplacedAction, type SessionInputNeededSetAction, type SessionInputNeededRemovedAction, type SessionCustomizationsChangedAction, type SessionCustomizationToggledAction, type SessionCustomizationUpdatedAction, type SessionCustomizationRemovedAction, type SessionMcpServerStateChangedAction, type SessionMcpServerStartRequestedAction, type SessionMcpServerStopRequestedAction, type SessionIsReadChangedAction, type SessionIsArchivedChangedAction, type SessionActivityChangedAction, type SessionChangesetsChangedAction, type SessionConfigChangedAction, type SessionMetaChangedAction, type ChatTurnStartedAction, type ChatDeltaAction, type ChatResponsePartAction, type ChatToolCallStartAction, type ChatToolCallDeltaAction, type ChatToolCallReadyAction, type ChatToolCallConfirmedAction, type ChatToolCallCompleteAction, type ChatToolCallResultConfirmedAction, type ChatToolCallContentChangedAction, type ChatToolCallAuthRequiredAction, type ChatToolCallAuthResolvedAction, type ChatTurnCompleteAction, type ChatTurnCancelledAction, type ChatErrorAction, type ChatActivityChangedAction, type ChatWorkingDirectorySetAction, type ChatWorkingDirectoryRemovedAction, type ChatUsageAction, type ChatReasoningAction, type ChatPendingMessageSetAction, type ChatPendingMessageRemovedAction, type ChatQueuedMessagesReorderedAction, type ChatDraftChangedAction, type ChatInputRequestedAction, type ChatInputAnswerChangedAction, type ChatInputCompletedAction, type ChatTruncatedAction, type ChatTurnsLoadedAction, type ChangesetStatusChangedAction, type ChangesetFileSetAction, type ChangesetFileRemovedAction, type ChangesetFilesReviewChangedAction, type ChangesetContentChangedAction, type ChangesetOperationsChangedAction, type ChangesetOperationStatusChangedAction, type ChangesetClearedAction, type AnnotationsSetAction, type AnnotationsUpdatedAction, type AnnotationsRemovedAction, type AnnotationsEntrySetAction, type AnnotationsEntryRemovedAction, type TerminalDataAction, type TerminalInputAction, type TerminalResizedAction, type TerminalClaimedAction, type TerminalTitleChangedAction, type TerminalCwdChangedAction, type TerminalExitedAction, type TerminalClearedAction, type TerminalCommandDetectionAvailableAction, type TerminalCommandExecutedAction, type TerminalCommandFinishedAction, type ResourceWatchChangedAction, type AutomationCreateRequestedAction, type AutomationUpdateRequestedAction, type AutomationSetAction, type AutomationRemovedAction, type AutomationRunLifecycleChangedAction, type AutomationRunSessionSetAction, type AutomationRunSessionRemovedAction, type AutomationRunPrimarySessionChangedAction, type AutomationRunCancelRequestedAction } from './actions.js';
import { ActionType, type StateAction, type RootAgentsChangedAction, type RootActiveSessionsChangedAction, type RootTerminalsChangedAction, type RootConfigChangedAction, type SessionReadyAction, type SessionCreationFailedAction, type SessionChatAddedAction, type SessionChatRemovedAction, type SessionChatUpdatedAction, type SessionDefaultChatChangedAction, type SessionTitleChangedAction, type SessionServerToolsChangedAction, type SessionActiveClientSetAction, type SessionActiveClientRemovedAction, type SessionWorkingDirectorySetAction, type SessionWorkingDirectoryRemovedAction, type SessionWorkingDirectoryReplacedAction, type SessionInputNeededSetAction, type SessionInputNeededRemovedAction, type SessionCustomizationsChangedAction, type SessionCustomizationToggledAction, type SessionCustomizationUpdatedAction, type SessionCustomizationRemovedAction, type SessionMcpServerStateChangedAction, type SessionMcpServerStartRequestedAction, type SessionMcpServerStopRequestedAction, type SessionIsReadChangedAction, type SessionIsArchivedChangedAction, type SessionActivityChangedAction, type SessionChangesetsChangedAction, type SessionConfigChangedAction, type SessionMetaChangedAction, type ChatTurnStartedAction, type ChatDeltaAction, type ChatResponsePartAction, type ChatToolCallStartAction, type ChatToolCallDeltaAction, type ChatToolCallReadyAction, type ChatToolCallConfirmedAction, type ChatToolCallCompleteAction, type ChatToolCallResultConfirmedAction, type ChatToolCallContentChangedAction, type ChatToolCallAuthRequiredAction, type ChatToolCallAuthResolvedAction, type ChatTurnCompleteAction, type ChatTurnCancelledAction, type ChatErrorAction, type ChatTurnResumeAction, type ChatActivityChangedAction, type ChatWorkingDirectorySetAction, type ChatWorkingDirectoryRemovedAction, type ChatUsageAction, type ChatReasoningAction, type ChatPendingMessageSetAction, type ChatPendingMessageRemovedAction, type ChatQueuedMessagesReorderedAction, type ChatDraftChangedAction, type ChatInputRequestedAction, type ChatInputAnswerChangedAction, type ChatInputCompletedAction, type ChatTruncatedAction, type ChatTurnsLoadedAction, type ChangesetStatusChangedAction, type ChangesetFileSetAction, type ChangesetFileRemovedAction, type ChangesetFilesReviewChangedAction, type ChangesetContentChangedAction, type ChangesetOperationsChangedAction, type ChangesetOperationStatusChangedAction, type ChangesetClearedAction, type AnnotationsSetAction, type AnnotationsUpdatedAction, type AnnotationsRemovedAction, type AnnotationsEntrySetAction, type AnnotationsEntryRemovedAction, type TerminalDataAction, type TerminalInputAction, type TerminalResizedAction, type TerminalClaimedAction, type TerminalTitleChangedAction, type TerminalCwdChangedAction, type TerminalExitedAction, type TerminalClearedAction, type TerminalCommandDetectionAvailableAction, type TerminalCommandExecutedAction, type TerminalCommandFinishedAction, type ResourceWatchChangedAction, type AutomationCreateRequestedAction, type AutomationUpdateRequestedAction, type AutomationSetAction, type AutomationRemovedAction, type AutomationRunLifecycleChangedAction, type AutomationRunSessionSetAction, type AutomationRunSessionRemovedAction, type AutomationRunPrimarySessionChangedAction, type AutomationRunCancelRequestedAction } from './actions.js';


// ─── Root vs Session vs Chat vs Terminal vs Changeset Action Unions ─────────────────
Expand Down Expand Up @@ -119,6 +119,7 @@ export type ChatAction =
| ChatTurnCompleteAction
| ChatTurnCancelledAction
| ChatErrorAction
| ChatTurnResumeAction
| ChatActivityChangedAction
| ChatWorkingDirectorySetAction
| ChatWorkingDirectoryRemovedAction
Expand All @@ -143,6 +144,7 @@ export type ClientChatAction =
| ChatToolCallResultConfirmedAction
| ChatToolCallContentChangedAction
| ChatTurnCancelledAction
| ChatTurnResumeAction
Comment thread
roblourens marked this conversation as resolved.
| ChatWorkingDirectorySetAction
| ChatWorkingDirectoryRemovedAction
| ChatPendingMessageSetAction
Expand Down Expand Up @@ -368,6 +370,7 @@ export const IS_CLIENT_DISPATCHABLE: { readonly [K in StateAction['type']]: bool
[ActionType.ChatTurnComplete]: false,
[ActionType.ChatTurnCancelled]: true,
[ActionType.ChatError]: false,
[ActionType.ChatTurnResume]: true,
[ActionType.ChatActivityChanged]: false,
[ActionType.ChatWorkingDirectorySet]: true,
[ActionType.ChatWorkingDirectoryRemoved]: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { SessionState } from '../channels-session/state.js';
* state is authoritative for those interactions.
*
* @category Automation Run State
* @exhaustive
*/
export const enum AutomationRunStatus {
/** The durable run record exists but execution has not started. */
Expand All @@ -37,6 +38,7 @@ export const enum AutomationRunStatus {
* Discriminant describing what created an automation run.
*
* @category Automation Run State
* @exhaustive
*/
export const enum AutomationRunOriginKind {
/** A client explicitly invoked {@link RunAutomationParams | runAutomation}. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type { FetchAutomationRunsParams, ListAutomationTriggerDefinitionsParams,
* operations describe what is allowed for this particular automation now.
*
* @category Automation State
* @nonexhaustive
*/
export const enum AutomationOperation {
/** Replace editable fields using {@link AutomationUpdateRequestedAction | `automation/updateRequested`}. */
Expand Down Expand Up @@ -80,6 +81,7 @@ export interface AutomationSchedule {
* unavailable.
*
* @category Automation State
* @nonexhaustive
*/
export const enum AutomationMisfirePolicy {
/** Discard missed occurrences and wait for the next future occurrence. */
Expand All @@ -95,6 +97,7 @@ export const enum AutomationMisfirePolicy {
* Discriminant for automatic trigger definitions.
*
* @category Automation State
* @exhaustive
*/
export const enum AutomationTriggerKind {
/** A portable recurring {@link AutomationSchedule}. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { BaseParams } from '../common/commands.js';
* `Changeset` scope has no target.
*
* @category Commands
* @nonexhaustive
*/
export const enum ChangesetOperationTargetKind {
/** Operation acts on a single file. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export interface ChangesetCapabilities {
* Computation lifecycle of a {@link ChangesetState}.
*
* @category Changesets
* @nonexhaustive
*/
export const enum ChangesetStatus {
/** The server is still computing the contents of this changeset. */
Expand Down Expand Up @@ -191,6 +192,7 @@ export interface ChangesetFile {
* Pull Request" button, or an inline error after a failed "revert").
*
* @category Changesets
* @nonexhaustive
*/
export const enum ChangesetOperationStatus {
/**
Expand All @@ -215,6 +217,7 @@ export const enum ChangesetOperationStatus {
* Where a {@link ChangesetOperation} can be invoked.
*
* @category Changesets
* @nonexhaustive
*/
export const enum ChangesetOperationScope {
/** Applies to the whole changeset. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
// DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts

import { ActionType } from '../common/actions.js';
import type { StringOrMarkdown, ErrorInfo, FileEdit, UsageInfo, URI } from '../common/state.js';
import type { StringOrMarkdown, FileEdit, UsageInfo, URI } from '../common/state.js';
import type { McpAuthRequirement } from '../channels-session/state.js';
import { ToolCallConfirmationReason, ToolCallCancellationReason, PendingMessageKind, type Message, type ResponsePart, type ToolCallResult, type ToolResultContent, type ChatInputAnswer, type ChatInputRequest, type ChatInputResponseKind, type ConfirmationOption, type ToolCallContributor, type ToolCallRiskAssessment, type ToolInput, type Turn } from './state.js';
import { ToolCallConfirmationReason, ToolCallCancellationReason, PendingMessageKind, type Message, type ResponsePart, type ToolCallResult, type ToolResultContent, type ChatInputAnswer, type ChatInputRequest, type ChatInputResponseKind, type ConfirmationOption, type ErrorResponsePart, type ToolCallContributor, type ToolCallRiskAssessment, type ToolInput, type Turn } from './state.js';

// ─── Tool Call Action Base ───────────────────────────────────────────────────

Expand Down Expand Up @@ -74,7 +74,7 @@ export interface ChatTurnStartedAction {
* Streaming text chunk from the assistant, appended to a specific response part.
*
* The server MUST first emit a `chat/responsePart` to create the target
* part (markdown or reasoning), then use this action to append text to it.
* markdown part, then use this action to append text to it.
*
* @category Chat Actions
* @version 1
Expand Down Expand Up @@ -102,14 +102,17 @@ export interface ChatDeltaAction {
/**
* Structured content appended to the response.
*
* An {@link ErrorResponsePart} MUST be appended with {@link ChatErrorAction}
* instead so adding the part and ending the turn are one atomic transition.
*
* @category Chat Actions
* @version 1
*/
export interface ChatResponsePartAction {
type: ActionType.ChatResponsePart;
/** Turn identifier */
turnId: string;
/** Response part (markdown or content ref) */
/** Response part to append; error parts are ignored. */
part: ResponsePart;
/**
* Additional provider-specific metadata for this action.
Expand Down Expand Up @@ -472,8 +475,11 @@ export interface ChatErrorAction {
* data.
*/
duration: number;
/** Error details */
error: ErrorInfo;
/**
* Error part to append to the response stream before finalizing the turn.
* Its optional `resumable` flag indicates whether the turn can be resumed.
*/
part: ErrorResponsePart;
/**
* Additional provider-specific metadata for this action.
*
Expand All @@ -486,6 +492,24 @@ export interface ChatErrorAction {
_meta?: Record<string, unknown>;
}

/**
* Resumes the latest errored turn without adding another message.
*
* The turn MUST be the latest turn, its state MUST be `error`, and its final
* response part MUST be a resumable error. The reducer reopens the same turn
* with its existing message, response parts, and usage intact. The host then
* resumes the provider's execution for that turn.
*
* @category Chat Actions
* @version 1
* @clientDispatchable
*/
export interface ChatTurnResumeAction {
type: ActionType.ChatTurnResume;
/** Identifier of the errored turn. */
turnId: string;
}

/**
* The activity description of this chat changed.
*
Expand Down Expand Up @@ -805,6 +829,7 @@ export type ChatAction =
| ChatTurnCompleteAction
| ChatTurnCancelledAction
| ChatErrorAction
| ChatTurnResumeAction
| ChatActivityChangedAction
| ChatWorkingDirectorySetAction
| ChatWorkingDirectoryRemovedAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { Message, SideChatSelection } from './state.js';

/**
* How a new chat uses its source chat and turn.
* @nonexhaustive
*/
export const enum ChatSourceKind {
/** Copy source history through the referenced turn into the new chat. */
Expand Down
Loading
Loading