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
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dd91062
eafb1d7
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
// DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts

import { ActionType } from '../common/actions.js';
import { softAssertNever } from '../common/reducer-helpers.js';
import type { ResourceWatchState } from './state.js';
import type { ResourceWatchAction } from '../action-origin.generated.js';

Expand All @@ -34,6 +33,6 @@ export function resourceWatchReducer(state: ResourceWatchState, action: Resource
return state;
}

softAssertNever(action as never, log);
(log ?? console.warn)(`Unhandled action type: ${JSON.stringify(action)}`);
return state;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { ActionType } from '../common/actions.js';
import type { StringOrMarkdown, ErrorInfo, FileEdit, UsageInfo } from '../common/state.js';
import { ToolCallConfirmationReason, ToolCallCancellationReason, PendingMessageKind, type UserMessage, type ResponsePart, type ToolCallResult, type ToolResultContent, type ToolDefinition, type SessionActiveClient, type Customization, type SessionInputAnswer, type SessionInputRequest, type SessionInputResponseKind, type ConfirmationOption, type AgentSelection } from './state.js';
import { ToolCallConfirmationReason, ToolCallCancellationReason, PendingMessageKind, type Message, type ResponsePart, type ToolCallResult, type ToolResultContent, type ToolDefinition, type SessionActiveClient, type Customization, type SessionInputAnswer, type SessionInputRequest, type SessionInputResponseKind, type ConfirmationOption, type AgentSelection } from './state.js';
import type { ModelSelection } from '../channels-root/state.js';
import type { ChangesetSummary } from '../channels-changeset/state.js';

Expand Down Expand Up @@ -62,7 +62,9 @@ export interface SessionCreationFailedAction {
}

/**
* User sent a message; server starts agent processing.
* A new message has been sent to the agent, and a new turn starts.
*
* A client is only allowed to send {@link MessageKind.User} messages.
*
* @category Session Actions
* @version 1
Expand All @@ -72,8 +74,8 @@ export interface SessionTurnStartedAction {
type: ActionType.SessionTurnStarted;
/** Turn identifier */
turnId: string;
/** User's message */
userMessage: UserMessage;
/** The new message */
message: Message;
/** If this turn was auto-started from a queued message, the ID of that message */
queuedMessageId?: string;
}
Expand Down Expand Up @@ -225,7 +227,7 @@ export interface SessionToolCallDeniedAction extends ToolCallActionBase {
/** Why the tool was cancelled */
reason: ToolCallCancellationReason.Denied | ToolCallCancellationReason.Skipped;
/** What the user suggested doing instead */
userSuggestion?: UserMessage;
userSuggestion?: Message;
/** Optional explanation for the denial */
reasonMessage?: StringOrMarkdown;
/** ID of the selected confirmation option, if the server provided options */
Expand Down Expand Up @@ -684,6 +686,8 @@ export interface SessionTruncatedAction {
* idle when a queued message is set, the server SHOULD immediately consume it
* and start a new turn.
*
* A client is only allowed to send {@link MessageKind.User} messages.
*
* @category Session Actions
* @version 1
* @clientDispatchable
Expand All @@ -695,7 +699,7 @@ export interface SessionPendingMessageSetAction {
/** Unique identifier for this pending message */
id: string;
/** The message content */
userMessage: UserMessage;
message: Message;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export interface FetchTurnsResult {
*/
export const enum CompletionItemKind {
/**
* Completions for the text of a {@link UserMessage} the user is composing.
* Completions for the text of a {@link Message} the user is composing.
* Each returned item carries an attachment that gets associated with the
* message when accepted.
*/
Expand Down Expand Up @@ -235,7 +235,7 @@ export interface CompletionsParams extends BaseParams {
* When the user accepts an item, the client SHOULD:
* 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText`
* (or insert `insertText` at the cursor when the range is omitted).
* 2. Associate the item's `attachment` with the resulting {@link UserMessage}.
* 2. Associate the item's `attachment` with the resulting {@link Message}.
*
* @category Commands
*/
Expand All @@ -255,7 +255,7 @@ export interface CompletionItem {
*
* Note: this range refers to positions in the *current* input. The
* attachment's own `rangeStart`/`rangeEnd` (when present) refer to
* positions in the final {@link UserMessage.text} after the item is
* positions in the final {@link Message.text} after the item is
* accepted.
*/
rangeStart?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ function endTurn(

const turn: Turn = {
id: active.id,
userMessage: active.userMessage,
message: active.message,
responseParts,
usage: active.usage,
state: turnState,
Expand Down Expand Up @@ -269,7 +269,7 @@ export function sessionReducer(state: SessionState, action: SessionAction, log?:
...state,
activeTurn: {
id: action.turnId,
userMessage: action.userMessage,
message: action.message,
responseParts: [],
usage: undefined,
},
Expand Down Expand Up @@ -734,7 +734,7 @@ export function sessionReducer(state: SessionState, action: SessionAction, log?:
// ── Pending Messages ──────────────────────────────────────────────────

case ActionType.SessionPendingMessageSet: {
const entry: PendingMessage = { id: action.id, userMessage: action.userMessage };
const entry: PendingMessage = { id: action.id, message: action.message };
if (action.kind === PendingMessageKind.Steering) {
return { ...state, steeringMessage: entry };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export const enum PendingMessageKind {
export interface PendingMessage {
/** Unique identifier for this pending message */
id: string;
/** The message content */
userMessage: UserMessage;
/** The message that will start the next turn */
message: Message;
}

// ─── Session State ───────────────────────────────────────────────────────────
Expand Down Expand Up @@ -552,8 +552,8 @@ export const enum MessageAttachmentKind {
export interface Turn {
/** Turn identifier */
id: string;
/** The user's input */
userMessage: UserMessage;
/** The message that initiated the turn */
message: Message;
/**
* All response content in stream order: text, tool calls, reasoning, and content refs.
*
Expand All @@ -577,8 +577,8 @@ export interface Turn {
export interface ActiveTurn {
/** Turn identifier */
id: string;
/** The user's input */
userMessage: UserMessage;
/** The message that initiated the turn */
message: Message;
/**
* All response content in stream order: text, tool calls, reasoning, and content refs.
*
Expand All @@ -590,20 +590,41 @@ export interface ActiveTurn {
}

/**
* A user message and its associated attachments.
* Discriminant for Message types.
*
* Attachments MAY be referenced inside {@link UserMessage.text} via their
* @category Turn Types
*/
export enum MessageKind {
User = 'user',
SystemNotification = 'systemNotification',
}

/**
* A message that initiates or steers a turn. Messages can originate from the
* user or be system-generated (see {@link MessageKind}).
*
* Attachments MAY be referenced inside {@link Message.text} via their
* {@link MessageAttachmentBase.range} field. Attachments without a range are
* still associated with the message but do not correspond to a specific span
* in the text.
*
* @category Turn Types
*/
export interface UserMessage {
export interface Message {
/** Message text */
text: string;
/** The origin of the message */
origin: { kind: MessageKind };
/** File/selection attachments */
attachments?: MessageAttachment[];
/**
* Additional provider-specific metadata for this message.
*
* Clients MAY look for well-known keys here to provide enhanced UI, and
* agent hosts MAY use it to carry context that does not fit any other
* field. Mirrors the MCP `_meta` convention.
*/
_meta?: Record<string, unknown>;
}

/**
Expand All @@ -619,7 +640,7 @@ export interface MessageAttachmentBase {
label: string;

/**
* If defined, the range in {@link UserMessage.text} that references this
* If defined, the range in {@link Message.text} that references this
* attachment. This is a text range, not a byte range.
*/
range?: TextRange;
Expand Down Expand Up @@ -711,7 +732,7 @@ export interface MessageResourceAttachment extends MessageAttachmentBase, Conten
}

/**
* An attachment associated with a {@link UserMessage}.
* An attachment associated with a {@link Message}.
*
* @category Turn Types
*/
Expand Down Expand Up @@ -1059,7 +1080,7 @@ export interface ToolCallCancelledState extends ToolCallBase, ToolCallParameterF
/** Optional message explaining the cancellation */
reasonMessage?: StringOrMarkdown;
/** What the user suggested doing instead */
userSuggestion?: UserMessage;
userSuggestion?: Message;
/** The confirmation option the user selected, if confirmation options were provided */
selectedOption?: ConfirmationOption;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export interface InitializeResult {
/** Suggested default directory for remote filesystem browsing */
defaultDirectory?: URI;
/**
* Characters that, when typed in a {@link UserMessage} input, SHOULD cause
* Characters that, when typed in a {@link Message} input, SHOULD cause
* the client to issue a `completions` request with
* {@link CompletionItemKind.UserMessage}. Typically includes characters like
* `'@'` or `'/'`.
Expand Down Expand Up @@ -351,6 +351,7 @@ export interface ResourceReadResult {
* How {@link ResourceWriteParams.data} is placed within the target file.
*
* Each mode interprets {@link ResourceWriteParams.position} differently:
*
* - `truncate` (default): rooted at the **start** of the file. The file is
* truncated at `position` (0 by default) and `data` is written from that
* offset, so the resulting file is `existing[0..position] + data`. With
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { ServerNotificationMap } from '../messages.js';
*
* Formatted as a [SemVer](https://semver.org) `MAJOR.MINOR.PATCH` string.
*/
export const PROTOCOL_VERSION = '0.2.0';
export const PROTOCOL_VERSION = '0.3.0';

/**
* Every protocol version a client built from this source tree is willing
Expand All @@ -35,7 +35,7 @@ export const PROTOCOL_VERSION = '0.2.0';
* `scripts/verify-release-metadata.ts`.
*/
export const SUPPORTED_PROTOCOL_VERSIONS: readonly string[] = Object.freeze([
PROTOCOL_VERSION,
'0.3.0',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why is the constant not referenced here?

@anthonykim1 anthonykim1 May 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ah, good point. I think it might have to come from upstream first though. Let me go file issue / tackle

]);

// ─── SemVer Comparison ───────────────────────────────────────────────────────
Expand Down
10 changes: 5 additions & 5 deletions src/vs/platform/agentHost/common/state/sessionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ import {
type ToolResultContent,
type ToolResultSubagentContent,
type ToolResultTextContent,
type UserMessage,
type Message,
} from './protocol/state.js';

// Re-export everything from the protocol state module
export {
ChangesetOperationScope, ChangesetStatus, CustomizationLoadStatus,
CustomizationType, MessageAttachmentKind,
CustomizationType, MessageAttachmentKind, MessageKind,
PendingMessageKind,
PolicyState,
ResponsePartKind,
Expand Down Expand Up @@ -76,7 +76,7 @@ export {
type ToolResultSubagentContent,
type ToolResultTextContent,
type Turn, type URI, type UsageInfo,
type UserMessage
type Message
} from './protocol/state.js';

export {
Expand Down Expand Up @@ -370,10 +370,10 @@ export function createSessionState(summary: SessionSummary): SessionState {
};
}

export function createActiveTurn(id: string, userMessage: UserMessage): ActiveTurn {
export function createActiveTurn(id: string, message: Message): ActiveTurn {
return {
id,
userMessage,
message,
responseParts: [],
usage: undefined,
};
Expand Down
6 changes: 3 additions & 3 deletions src/vs/platform/agentHost/node/agentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,7 @@ export class AgentService extends Disposable implements IAgentService {
return false;
}
const attachmentsRootStr = this._attachmentsRoot(channel).toString();
return !!action.userMessage.attachments?.some(a => this._isRewritableAttachment(a, attachmentsRootStr));
return !!action.message.attachments?.some(a => this._isRewritableAttachment(a, attachmentsRootStr));
}
private _isRewritableAttachment(attachment: MessageAttachment, attachmentsRootStr: string): boolean {
if (attachment.type === MessageAttachmentKind.EmbeddedResource) {
Expand Down Expand Up @@ -1172,7 +1172,7 @@ export class AgentService extends Disposable implements IAgentService {
* chance to make use of it.
*/
private async _rewriteUserMessageAttachments<T extends SessionTurnStartedAction | SessionPendingMessageSetAction>(channel: string, action: T, clientId: string): Promise<T> {
const attachments = action.userMessage.attachments;
const attachments = action.message.attachments;
if (!attachments?.length) {
return action;
}
Expand All @@ -1181,7 +1181,7 @@ export class AgentService extends Disposable implements IAgentService {
const rewritten = await Promise.all(attachments.map(a => this._rewriteSingleAttachment(a, attachmentsRoot, attachmentsRootStr, clientId)));
return {
...action,
userMessage: { ...action.userMessage, attachments: rewritten },
message: { ...action.message, attachments: rewritten },
};
}

Expand Down
15 changes: 8 additions & 7 deletions src/vs/platform/agentHost/node/agentSideEffects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ActionType, StateAction, type SessionToolCallCompleteAction } from '../
import {
buildSubagentSessionUri,
getToolFileEdits,
MessageKind,
PendingMessageKind,
ResponsePartKind,
ROOT_STATE_URI,
Expand Down Expand Up @@ -507,7 +508,7 @@ export class AgentSideEffects extends Disposable {
this._stateManager.dispatchServerAction(subagentSessionUri, {
type: ActionType.SessionTurnStarted,
turnId,
userMessage: { text: '' },
message: { text: '', origin: { kind: MessageKind.User } },
});

this._subagentSessions.set(subagentKey, subagentSessionUri);
Expand Down Expand Up @@ -706,7 +707,7 @@ export class AgentSideEffects extends Disposable {
// title is still the default placeholder to avoid clobbering a
// title set by the user or provider before the first turn.
const state = this._stateManager.getSessionState(channel);
const fallbackTitle = action.userMessage.text.trim().replace(/\s+/g, ' ').slice(0, 200);
const fallbackTitle = action.message.text.trim().replace(/\s+/g, ' ').slice(0, 200);
if (state && state.turns.length === 0 && !state.summary.title && fallbackTitle.length > 0) {
this._stateManager.dispatchServerAction(channel, {
type: ActionType.SessionTitleChanged,
Expand All @@ -723,9 +724,9 @@ export class AgentSideEffects extends Disposable {
});
return;
}
const attachments = action.userMessage.attachments;
const attachments = action.message.attachments;
this._telemetryReporter.userMessageSent(agent.id, channel, state, 'direct', attachments);
agent.sendMessage(URI.parse(channel), action.userMessage.text, attachments, action.turnId).catch(err => {
agent.sendMessage(URI.parse(channel), action.message.text, attachments, action.turnId).catch(err => {
const errCode = (err as { code?: number })?.code;
this._logService.error(`[AgentSideEffects] sendMessage failed for session=${channel}: code=${errCode}, message=${err instanceof Error ? err.message : String(err)}, type=${err?.constructor?.name}`, err);
this._stateManager.dispatchServerAction(channel, {
Expand Down Expand Up @@ -943,7 +944,7 @@ export class AgentSideEffects extends Disposable {
this._stateManager.dispatchServerAction(session, {
type: ActionType.SessionTurnStarted,
turnId,
userMessage: msg.userMessage,
message: msg.message,
queuedMessageId: msg.id,
});

Expand All @@ -957,9 +958,9 @@ export class AgentSideEffects extends Disposable {
});
return;
}
const attachments = msg.userMessage.attachments;
const attachments = msg.message.attachments;
this._telemetryReporter.userMessageSent(agent.id, session, this._stateManager.getSessionState(session), 'queued', attachments);
agent.sendMessage(URI.parse(session), msg.userMessage.text, attachments, turnId).catch(err => {
agent.sendMessage(URI.parse(session), msg.message.text, attachments, turnId).catch(err => {
this._logService.error('[AgentSideEffects] sendMessage failed (queued)', err);
this._stateManager.dispatchServerAction(session, {
type: ActionType.SessionError,
Expand Down
Loading
Loading