diff --git a/scripts/sync-agent-host-protocol.ts b/scripts/sync-agent-host-protocol.ts index aba4fcfa346..87db5d7ab0e 100644 --- a/scripts/sync-agent-host-protocol.ts +++ b/scripts/sync-agent-host-protocol.ts @@ -156,6 +156,10 @@ function convertIndentation(content: string): string { * `import { B, type A }` to satisfy the no-duplicate-imports lint rule. */ function mergeDuplicateImports(content: string): string { + // Normalize line endings so the `$`-anchored import regexes below match + // regardless of whether the source was checked out with CRLF or LF. + content = content.replace(/\r\n/g, '\n'); + // Collapse multi-line imports into single lines first content = content.replace(/import\s+(type\s+)?\{([^}]+)\}\s+from\s+'([^']+)';/g, (_match, typeKeyword, names, mod) => { const collapsed = names.replace(/\s+/g, ' ').trim(); diff --git a/src/vs/platform/agentHost/common/state/protocol/.ahp-version b/src/vs/platform/agentHost/common/state/protocol/.ahp-version index 164ff310031..3b22c90103a 100644 --- a/src/vs/platform/agentHost/common/state/protocol/.ahp-version +++ b/src/vs/platform/agentHost/common/state/protocol/.ahp-version @@ -1 +1 @@ -185675f +9b1fb91 diff --git a/src/vs/platform/agentHost/common/state/protocol/action-origin.generated.ts b/src/vs/platform/agentHost/common/state/protocol/action-origin.generated.ts index e8254fb5bc3..a536bc07aa5 100644 --- a/src/vs/platform/agentHost/common/state/protocol/action-origin.generated.ts +++ b/src/vs/platform/agentHost/common/state/protocol/action-origin.generated.ts @@ -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 SessionTurnStartedAction, type SessionDeltaAction, type SessionResponsePartAction, type SessionToolCallStartAction, type SessionToolCallDeltaAction, type SessionToolCallReadyAction, type SessionToolCallConfirmedAction, type SessionToolCallCompleteAction, type SessionToolCallResultConfirmedAction, type SessionToolCallContentChangedAction, type SessionTurnCompleteAction, type SessionTurnCancelledAction, type SessionErrorAction, type SessionTitleChangedAction, type SessionUsageAction, type SessionReasoningAction, type SessionModelChangedAction, type SessionAgentChangedAction, type SessionServerToolsChangedAction, type SessionActiveClientChangedAction, type SessionActiveClientToolsChangedAction, type SessionPendingMessageSetAction, type SessionPendingMessageRemovedAction, type SessionQueuedMessagesReorderedAction, type SessionInputRequestedAction, type SessionInputAnswerChangedAction, type SessionInputCompletedAction, type SessionCustomizationsChangedAction, type SessionCustomizationToggledAction, type SessionCustomizationUpdatedAction, type SessionCustomizationRemovedAction, type SessionMcpServerStateChangedAction, type SessionTruncatedAction, type SessionIsReadChangedAction, type SessionIsArchivedChangedAction, type SessionActivityChangedAction, type SessionChangesetsChangedAction, type SessionConfigChangedAction, type SessionMetaChangedAction, type ChangesetStatusChangedAction, type ChangesetFileSetAction, type ChangesetFileRemovedAction, type ChangesetOperationsChangedAction, type ChangesetOperationStatusChangedAction, type ChangesetClearedAction, type TerminalDataAction, type TerminalInputAction, type TerminalResizedAction, type TerminalClaimedAction, type TerminalTitleChangedAction, type TerminalCwdChangedAction, type TerminalExitedAction, type TerminalClearedAction, type TerminalCommandDetectionAvailableAction, type TerminalCommandExecutedAction, type TerminalCommandFinishedAction, type ResourceWatchChangedAction } from './actions.js'; +import { ActionType, type StateAction, type RootAgentsChangedAction, type RootActiveSessionsChangedAction, type RootTerminalsChangedAction, type RootConfigChangedAction, type SessionReadyAction, type SessionCreationFailedAction, type SessionTurnStartedAction, type SessionDeltaAction, type SessionResponsePartAction, type SessionToolCallStartAction, type SessionToolCallDeltaAction, type SessionToolCallReadyAction, type SessionToolCallConfirmedAction, type SessionToolCallCompleteAction, type SessionToolCallResultConfirmedAction, type SessionToolCallContentChangedAction, type SessionTurnCompleteAction, type SessionTurnCancelledAction, type SessionErrorAction, type SessionTitleChangedAction, type SessionUsageAction, type SessionReasoningAction, type SessionModelChangedAction, type SessionAgentChangedAction, type SessionServerToolsChangedAction, type SessionActiveClientChangedAction, type SessionActiveClientToolsChangedAction, type SessionPendingMessageSetAction, type SessionPendingMessageRemovedAction, type SessionQueuedMessagesReorderedAction, type SessionInputRequestedAction, type SessionInputAnswerChangedAction, type SessionInputCompletedAction, type SessionCustomizationsChangedAction, type SessionCustomizationToggledAction, type SessionCustomizationUpdatedAction, type SessionCustomizationRemovedAction, type SessionMcpServerStateChangedAction, type SessionTruncatedAction, type SessionIsReadChangedAction, type SessionIsArchivedChangedAction, type SessionActivityChangedAction, type SessionChangesetsChangedAction, type SessionConfigChangedAction, type SessionMetaChangedAction, type ChangesetStatusChangedAction, type ChangesetFileSetAction, type ChangesetFileRemovedAction, 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 } from './actions.js'; // ─── Root vs Session vs Terminal vs Changeset Action Unions ───────────────── @@ -187,6 +187,29 @@ export type ServerChangesetAction = | ChangesetClearedAction ; +/** Union of all annotations-scoped actions. */ +export type AnnotationsAction = + | AnnotationsSetAction + | AnnotationsUpdatedAction + | AnnotationsRemovedAction + | AnnotationsEntrySetAction + | AnnotationsEntryRemovedAction + ; + +/** Union of annotations actions that clients may dispatch. */ +export type ClientAnnotationsAction = + | AnnotationsSetAction + | AnnotationsUpdatedAction + | AnnotationsRemovedAction + | AnnotationsEntrySetAction + | AnnotationsEntryRemovedAction + ; + +/** Union of annotations actions that only the server may produce. */ +export type ServerAnnotationsAction = + never + ; + /** Union of all resource-watch-scoped actions. */ export type ResourceWatchAction = | ResourceWatchChangedAction @@ -260,6 +283,11 @@ export const IS_CLIENT_DISPATCHABLE: { readonly [K in StateAction['type']]: bool [ActionType.ChangesetOperationsChanged]: false, [ActionType.ChangesetOperationStatusChanged]: false, [ActionType.ChangesetCleared]: false, + [ActionType.AnnotationsSet]: true, + [ActionType.AnnotationsUpdated]: true, + [ActionType.AnnotationsRemoved]: true, + [ActionType.AnnotationsEntrySet]: true, + [ActionType.AnnotationsEntryRemoved]: true, [ActionType.TerminalData]: false, [ActionType.TerminalInput]: true, [ActionType.TerminalResized]: true, diff --git a/src/vs/platform/agentHost/common/state/protocol/actions.ts b/src/vs/platform/agentHost/common/state/protocol/actions.ts index b4358a98991..91a9c7a4918 100644 --- a/src/vs/platform/agentHost/common/state/protocol/actions.ts +++ b/src/vs/platform/agentHost/common/state/protocol/actions.ts @@ -11,4 +11,5 @@ export * from './channels-root/actions.js'; export * from './channels-session/actions.js'; export * from './channels-terminal/actions.js'; export * from './channels-changeset/actions.js'; +export * from './channels-annotations/actions.js'; export * from './channels-resource-watch/actions.js'; diff --git a/src/vs/platform/agentHost/common/state/protocol/channels-annotations/actions.ts b/src/vs/platform/agentHost/common/state/protocol/channels-annotations/actions.ts new file mode 100644 index 00000000000..7c8bc55b9e2 --- /dev/null +++ b/src/vs/platform/agentHost/common/state/protocol/channels-annotations/actions.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// allow-any-unicode-comment-file +// DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts + +import { ActionType } from '../common/actions.js'; +import type { URI, TextRange } from '../common/state.js'; +import type { AnnotationEntry, Annotation } from './state.js'; + +// ─── Annotations Actions ───────────────────────────────────────────────────── + +/** + * Upsert an {@link Annotation} in the annotations channel — adds a new + * annotation, or replaces an existing one identified by + * {@link Annotation.id}. + * + * Dispatched by a client to create an annotation (together with its + * mandatory first entry) or to re-anchor / resolve an existing one; the + * dispatching client assigns the {@link Annotation.id} and the id of any + * new entry. When replacing, the full annotation payload (including its + * {@link Annotation.entries | entries} list) is substituted; producers + * SHOULD prefer {@link AnnotationsEntrySetAction} for per-entry edits, and + * {@link AnnotationsUpdatedAction} to resolve / re-anchor an existing + * annotation, to keep wire updates small. + * + * @category Annotations Actions + * @version 3 + * @clientDispatchable + */ +export interface AnnotationsSetAction { + type: ActionType.AnnotationsSet; + /** The new or replacement annotation. MUST contain at least one entry. */ + annotation: Annotation; +} + +/** + * Partially update an existing {@link Annotation}'s own properties — a narrow + * alternative to {@link AnnotationsSetAction} for the common case of resolving + * / re-opening or re-anchoring an annotation without resending its + * {@link Annotation.entries | entries}. + * + * Targets one annotation by its {@link annotationId}. Only the fields present + * on the action are written; omitted fields leave the corresponding + * {@link Annotation} property unchanged. The annotation's + * {@link Annotation.entries | entries}, {@link Annotation.id | id}, and + * {@link Annotation._meta | _meta} are never touched — dispatch + * {@link AnnotationsSetAction} to replace those, to clear {@link range} + * (re-anchor to the whole file), or {@link AnnotationsEntrySetAction} / + * {@link AnnotationsEntryRemovedAction} to edit individual entries. + * + * If {@link annotationId} does not match any current annotation the action is + * a no-op. + * + * @category Annotations Actions + * @version 4 + * @clientDispatchable + */ +export interface AnnotationsUpdatedAction { + type: ActionType.AnnotationsUpdated; + /** The {@link Annotation.id} of the annotation to update. */ + annotationId: string; + /** + * Re-anchors the annotation to the file versions this turn produced. + * Matches a {@link Turn.id} on the owning session. Omit to leave the + * current {@link Annotation.turnId} unchanged. + */ + turnId?: string; + /** + * Re-anchors the annotation to this file. Omit to leave the current + * {@link Annotation.resource} unchanged. + */ + resource?: URI; + /** + * Narrows the annotation to this range within {@link resource}. Omit to + * leave the current {@link Annotation.range} unchanged; this action cannot + * clear an existing range — dispatch {@link AnnotationsSetAction} to + * re-anchor to the whole file. + */ + range?: TextRange; + /** + * Marks the annotation resolved (`true`) or re-opens it (`false`). Omit to + * leave the current {@link Annotation.resolved} state unchanged. + */ + resolved?: boolean; +} + +/** + * Remove an {@link Annotation} from the channel by its id. + * + * Dispatched to delete an entire annotation and every entry it contains. + * Because the protocol forbids empty annotations, a client that wants to + * remove the last remaining entry dispatches this action — collapsing the + * annotation — rather than {@link AnnotationsEntryRemovedAction}. + * + * @category Annotations Actions + * @version 3 + * @clientDispatchable + */ +export interface AnnotationsRemovedAction { + type: ActionType.AnnotationsRemoved; + /** The {@link Annotation.id} of the annotation to remove. */ + annotationId: string; +} + +/** + * Upsert an {@link AnnotationEntry} within an existing annotation — adds a + * new entry, or replaces one identified by {@link AnnotationEntry.id}. The + * dispatching client assigns the {@link AnnotationEntry.id} of a new entry. + * If {@link annotationId} does not match any current annotation the action + * is a no-op. + * + * @category Annotations Actions + * @version 3 + * @clientDispatchable + */ +export interface AnnotationsEntrySetAction { + type: ActionType.AnnotationsEntrySet; + /** The {@link Annotation.id} the entry belongs to. */ + annotationId: string; + /** The new or replacement entry. */ + entry: AnnotationEntry; +} + +/** + * Remove a single {@link AnnotationEntry} from an annotation without + * collapsing the annotation itself. Used when more than one entry remains — + * to remove the last entry a client dispatches {@link AnnotationsRemovedAction} + * instead, since the protocol forbids empty annotations. + * + * If either {@link annotationId} or {@link entryId} does not match the + * current state the action is a no-op. + * + * @category Annotations Actions + * @version 3 + * @clientDispatchable + */ +export interface AnnotationsEntryRemovedAction { + type: ActionType.AnnotationsEntryRemoved; + /** The {@link Annotation.id} the entry belongs to. */ + annotationId: string; + /** The {@link AnnotationEntry.id} to remove. */ + entryId: string; +} diff --git a/src/vs/platform/agentHost/common/state/protocol/channels-annotations/reducer.ts b/src/vs/platform/agentHost/common/state/protocol/channels-annotations/reducer.ts new file mode 100644 index 00000000000..7223ac5849e --- /dev/null +++ b/src/vs/platform/agentHost/common/state/protocol/channels-annotations/reducer.ts @@ -0,0 +1,115 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// allow-any-unicode-comment-file +// DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts + +import { ActionType } from '../common/actions.js'; +import type { AnnotationEntry, Annotation, AnnotationsState } from './state.js'; +import type { AnnotationsAction } from '../action-origin.generated.js'; +import { softAssertNever } from '../common/reducer-helpers.js'; + +/** + * Pure reducer for annotations state. Handles every {@link AnnotationsAction} + * variant. + * + * Per the spec, every annotations action is client-dispatchable; the reducer + * runs identically on the client (optimistic, write-ahead) and the server. It + * preserves the dispatch order of annotations (and of entries within an + * annotation): new entries are appended; `*Set` actions with a matching id + * replace in place, while actions whose target id is unknown are no-ops + * (mirroring `changeset/fileRemoved` semantics). The single-entry + * minimum invariant is enforced by producers, not the reducer — removing an + * annotation's last entry via {@link AnnotationsEntryRemovedAction} (instead + * of {@link AnnotationsRemovedAction}) would leave an empty annotation, + * which is observable but not catastrophic. + */ +export function annotationsReducer(state: AnnotationsState, action: AnnotationsAction, log?: (msg: string) => void): AnnotationsState { + switch (action.type) { + case ActionType.AnnotationsSet: { + const idx = state.annotations.findIndex(t => t.id === action.annotation.id); + if (idx < 0) { + return { ...state, annotations: [...state.annotations, action.annotation] }; + } + const next: Annotation[] = [...state.annotations]; + next[idx] = action.annotation; + return { ...state, annotations: next }; + } + + case ActionType.AnnotationsUpdated: { + const idx = state.annotations.findIndex(t => t.id === action.annotationId); + if (idx < 0) { + return state; + } + const annotation = state.annotations[idx]; + const updated: Annotation = { ...annotation }; + if (action.turnId !== undefined) { + updated.turnId = action.turnId; + } + if (action.resource !== undefined) { + updated.resource = action.resource; + } + if (action.range !== undefined) { + updated.range = action.range; + } + if (action.resolved !== undefined) { + updated.resolved = action.resolved; + } + const next: Annotation[] = [...state.annotations]; + next[idx] = updated; + return { ...state, annotations: next }; + } + + case ActionType.AnnotationsRemoved: { + const idx = state.annotations.findIndex(t => t.id === action.annotationId); + if (idx < 0) { + return state; + } + const next: Annotation[] = [...state.annotations]; + next.splice(idx, 1); + return { ...state, annotations: next }; + } + + case ActionType.AnnotationsEntrySet: { + const tIdx = state.annotations.findIndex(t => t.id === action.annotationId); + if (tIdx < 0) { + return state; + } + const annotation = state.annotations[tIdx]; + const cIdx = annotation.entries.findIndex(c => c.id === action.entry.id); + let nextEntries: AnnotationEntry[]; + if (cIdx < 0) { + nextEntries = [...annotation.entries, action.entry]; + } else { + nextEntries = [...annotation.entries]; + nextEntries[cIdx] = action.entry; + } + const nextAnnotations: Annotation[] = [...state.annotations]; + nextAnnotations[tIdx] = { ...annotation, entries: nextEntries }; + return { ...state, annotations: nextAnnotations }; + } + + case ActionType.AnnotationsEntryRemoved: { + const tIdx = state.annotations.findIndex(t => t.id === action.annotationId); + if (tIdx < 0) { + return state; + } + const annotation = state.annotations[tIdx]; + const cIdx = annotation.entries.findIndex(c => c.id === action.entryId); + if (cIdx < 0) { + return state; + } + const nextEntries: AnnotationEntry[] = [...annotation.entries]; + nextEntries.splice(cIdx, 1); + const nextAnnotations: Annotation[] = [...state.annotations]; + nextAnnotations[tIdx] = { ...annotation, entries: nextEntries }; + return { ...state, annotations: nextAnnotations }; + } + + default: + softAssertNever(action, log); + return state; + } +} diff --git a/src/vs/platform/agentHost/common/state/protocol/channels-annotations/state.ts b/src/vs/platform/agentHost/common/state/protocol/channels-annotations/state.ts new file mode 100644 index 00000000000..12e8a62cfdf --- /dev/null +++ b/src/vs/platform/agentHost/common/state/protocol/channels-annotations/state.ts @@ -0,0 +1,130 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// allow-any-unicode-comment-file +// DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts + +import type { URI, StringOrMarkdown, TextRange } from '../common/state.js'; + +// ─── Annotations Summary ───────────────────────────────────────────────────── + +/** + * Lightweight per-session summary of the annotations channel, surfaced on + * {@link SessionSummary.annotations} so badge UI can render annotation / + * entry counts without subscribing to the channel itself. + * + * @category Annotations + */ +export interface AnnotationsSummary { + /** + * The subscribable annotations channel URI for the owning session + * (typically `ahp-session://annotations`). Surfaced explicitly even + * though it is derivable from the session URI so badge UI does not need + * to know the derivation rule. + */ + resource: URI; + /** Total number of {@link Annotation} entries in the channel. */ + annotationCount: number; + /** Total number of {@link AnnotationEntry} entries across every annotation. */ + entryCount: number; +} + +// ─── Annotations State ─────────────────────────────────────────────────────── + +/** + * Full state for a session's annotations channel, returned when a client + * subscribes to an `ahp-session://annotations` URI. + * + * @category Annotations + */ +export interface AnnotationsState { + /** Annotations in this channel, keyed by {@link Annotation.id}. */ + annotations: Annotation[]; +} + +// ─── Annotation ────────────────────────────────────────────────────────────── + +/** + * A conversation anchored to a specific file produced by a specific turn, + * optionally narrowed to a range within that file. + * + * {@link turnId} anchors the annotation to the file versions that turn + * produced, so a later turn that rewrites the same file does not silently + * invalidate the annotation's anchor — clients can resolve {@link resource} + * and {@link range} against the turn's changeset. When {@link range} is + * omitted the annotation is anchored to the entire file. + * + * Every annotation MUST contain at least one {@link AnnotationEntry}. An + * {@link AnnotationsSetAction} that creates an annotation therefore carries + * its mandatory first entry, and removing the last remaining entry collapses + * the annotation via {@link AnnotationsRemovedAction} rather than leaving an + * empty annotation behind. + * + * @category Annotations + */ +export interface Annotation { + /** + * Stable identifier within the annotations channel. Assigned by the client + * that dispatches the creating {@link AnnotationsSetAction}. + */ + id: string; + /** + * Turn that produced the file versions this annotation is anchored to. + * Matches a {@link Turn.id} on the owning session. + */ + turnId: string; + /** The file the annotation is anchored to. */ + resource: URI; + /** + * Range within {@link resource} the annotation is anchored to. When + * omitted the annotation is anchored to the entire file. + */ + range?: TextRange; + /** + * Whether the annotation has been resolved. Newly created annotations are + * always unresolved (`false`); a client marks an annotation resolved (or + * re-opens it) by dispatching an {@link AnnotationsUpdatedAction} carrying + * the updated flag (or an {@link AnnotationsSetAction} when replacing the + * whole annotation). + */ + resolved: boolean; + /** + * Entries in this annotation, in dispatch order (oldest first). MUST + * contain at least one entry. + */ + entries: AnnotationEntry[]; + /** + * Producer-defined opaque metadata, surfaced to tooling but not + * interpreted by the protocol. + */ + _meta?: Record; +} + +// ─── Annotation Entry ──────────────────────────────────────────────────────── + +/** + * A single entry within an {@link Annotation}. + * + * @category Annotations + */ +export interface AnnotationEntry { + /** + * Stable identifier within the enclosing annotation. Assigned by the client + * that dispatches the {@link AnnotationsEntrySetAction} (or the enclosing + * {@link AnnotationsSetAction}) introducing the entry. + */ + id: string; + /** + * Entry body. A bare `string` is rendered as plain text; pass + * `{ markdown: "…" }` to opt into Markdown rendering. See + * {@link StringOrMarkdown}. + */ + text: StringOrMarkdown; + /** + * Producer-defined opaque metadata, surfaced to tooling but not + * interpreted by the protocol. + */ + _meta?: Record; +} diff --git a/src/vs/platform/agentHost/common/state/protocol/channels-session/state.ts b/src/vs/platform/agentHost/common/state/protocol/channels-session/state.ts index 0a9afa2e14a..53241811cbb 100644 --- a/src/vs/platform/agentHost/common/state/protocol/channels-session/state.ts +++ b/src/vs/platform/agentHost/common/state/protocol/channels-session/state.ts @@ -7,6 +7,7 @@ // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts import type { Changeset } from '../channels-changeset/state.js'; +import type { AnnotationsSummary } from '../channels-annotations/state.js'; import type { ModelSelection } from '../channels-root/state.js'; import type { ConfigPropertySchema, ContentRef, ErrorInfo, FileEdit, Icon, ProtectedResourceMetadata, StringOrMarkdown, TextRange, TextSelection, URI, UsageInfo } from '../common/state.js'; @@ -221,6 +222,13 @@ export interface SessionSummary { * client to subscribe to a changeset. */ changes?: ChangesSummary; + /** + * Lightweight summary of this session's inline annotations channel + * (`ahp-session://annotations`). Surfaced so badge UI can render + * annotation / entry counts without subscribing. Absent when the session + * does not expose an annotations channel. + */ + annotations?: AnnotationsSummary; } /** @@ -574,6 +582,8 @@ export const enum MessageAttachmentKind { EmbeddedResource = 'embeddedResource', /** An attachment that references a resource by URI. */ Resource = 'resource', + /** An attachment that references annotations on an annotations channel. */ + Annotations = 'annotations', } /** @@ -763,6 +773,31 @@ export interface MessageResourceAttachment extends MessageAttachmentBase, Conten selection?: TextSelection; } +/** + * An attachment that references annotations on a session's annotations + * channel (see {@link AnnotationsState}). + * + * When {@link annotationIds} is omitted the attachment references every + * annotation on the channel; when present it references only the listed + * {@link Annotation.id | annotation ids}. + * + * @category Turn Types + */ +export interface MessageAnnotationsAttachment extends MessageAttachmentBase { + /** Discriminant */ + type: MessageAttachmentKind.Annotations; + /** + * The annotations channel URI (typically `ahp-session://annotations`). + * Matches {@link AnnotationsSummary.resource}. + */ + resource: URI; + /** + * Specific {@link Annotation.id | annotation ids} to reference. When + * omitted, the attachment references all annotations on the channel. + */ + annotationIds?: string[]; +} + /** * An attachment associated with a {@link Message}. * @@ -771,7 +806,8 @@ export interface MessageResourceAttachment extends MessageAttachmentBase, Conten export type MessageAttachment = | SimpleMessageAttachment | MessageEmbeddedResourceAttachment - | MessageResourceAttachment; + | MessageResourceAttachment + | MessageAnnotationsAttachment; // ─── Response Parts ────────────────────────────────────────────────────────── diff --git a/src/vs/platform/agentHost/common/state/protocol/common/actions.ts b/src/vs/platform/agentHost/common/state/protocol/common/actions.ts index 2eeccf45dd3..a5015938f75 100644 --- a/src/vs/platform/agentHost/common/state/protocol/common/actions.ts +++ b/src/vs/platform/agentHost/common/state/protocol/common/actions.ts @@ -14,6 +14,8 @@ import type { SessionReadyAction, SessionCreationFailedAction, SessionTurnStarte import type { ChangesetStatusChangedAction, ChangesetFileSetAction, ChangesetFileRemovedAction, ChangesetOperationsChangedAction, ChangesetOperationStatusChangedAction, ChangesetClearedAction } from '../channels-changeset/actions.js'; +import type { AnnotationsSetAction, AnnotationsUpdatedAction, AnnotationsRemovedAction, AnnotationsEntrySetAction, AnnotationsEntryRemovedAction } from '../channels-annotations/actions.js'; + import type { TerminalDataAction, TerminalInputAction, TerminalResizedAction, TerminalClaimedAction, TerminalTitleChangedAction, TerminalCwdChangedAction, TerminalExitedAction, TerminalClearedAction, TerminalCommandDetectionAvailableAction, TerminalCommandExecutedAction, TerminalCommandFinishedAction } from '../channels-terminal/actions.js'; import type { ResourceWatchChangedAction } from '../channels-resource-watch/actions.js'; @@ -75,6 +77,11 @@ export const enum ActionType { ChangesetOperationsChanged = 'changeset/operationsChanged', ChangesetOperationStatusChanged = 'changeset/operationStatusChanged', ChangesetCleared = 'changeset/cleared', + AnnotationsSet = 'annotations/set', + AnnotationsUpdated = 'annotations/updated', + AnnotationsRemoved = 'annotations/removed', + AnnotationsEntrySet = 'annotations/entrySet', + AnnotationsEntryRemoved = 'annotations/entryRemoved', RootTerminalsChanged = 'root/terminalsChanged', RootConfigChanged = 'root/configChanged', TerminalData = 'terminal/data', @@ -176,6 +183,11 @@ export type StateAction = | ChangesetOperationsChangedAction | ChangesetOperationStatusChangedAction | ChangesetClearedAction + | AnnotationsSetAction + | AnnotationsUpdatedAction + | AnnotationsRemovedAction + | AnnotationsEntrySetAction + | AnnotationsEntryRemovedAction | TerminalDataAction | TerminalInputAction | TerminalResizedAction diff --git a/src/vs/platform/agentHost/common/state/protocol/common/reducer-helpers.ts b/src/vs/platform/agentHost/common/state/protocol/common/reducer-helpers.ts index 35f7f981c19..568e7bce470 100644 --- a/src/vs/platform/agentHost/common/state/protocol/common/reducer-helpers.ts +++ b/src/vs/platform/agentHost/common/state/protocol/common/reducer-helpers.ts @@ -6,7 +6,7 @@ // allow-any-unicode-comment-file // DO NOT EDIT -- auto-generated by scripts/sync-agent-host-protocol.ts -import { IS_CLIENT_DISPATCHABLE, type RootAction, type ClientRootAction, type SessionAction, type ClientSessionAction, type TerminalAction, type ClientTerminalAction, type ChangesetAction, type ClientChangesetAction } from '../action-origin.generated.js'; +import { IS_CLIENT_DISPATCHABLE, type RootAction, type ClientRootAction, type SessionAction, type ClientSessionAction, type TerminalAction, type ClientTerminalAction, type ChangesetAction, type ClientChangesetAction, type AnnotationsAction, type ClientAnnotationsAction } from '../action-origin.generated.js'; /** * Soft assertion for exhaustiveness checking. Place in the `default` branch of @@ -29,6 +29,6 @@ export function softAssertNever(value: never, log?: (msg: string) => void): void * Servers SHOULD call this to validate incoming `dispatchAction` requests * and reject any action the client is not allowed to originate. */ -export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction { +export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction | AnnotationsAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction | ClientAnnotationsAction { return IS_CLIENT_DISPATCHABLE[action.type]; } diff --git a/src/vs/platform/agentHost/common/state/protocol/common/state.ts b/src/vs/platform/agentHost/common/state/protocol/common/state.ts index e0b95cc5cc6..7ebfcaba7cb 100644 --- a/src/vs/platform/agentHost/common/state/protocol/common/state.ts +++ b/src/vs/platform/agentHost/common/state/protocol/common/state.ts @@ -9,8 +9,9 @@ import type { RootState } from '../channels-root/state.js'; import type { SessionState } from '../channels-session/state.js'; import type { TerminalState } from '../channels-terminal/state.js'; -import type { ResourceWatchState } from '../channels-resource-watch/state.js'; import type { ChangesetState } from '../channels-changeset/state.js'; +import type { ResourceWatchState } from '../channels-resource-watch/state.js'; +import type { AnnotationsState } from '../channels-annotations/state.js'; // ─── Type Aliases ──────────────────────────────────────────────────────────── @@ -323,7 +324,7 @@ export interface Snapshot { /** The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`) */ resource: URI; /** The current state of the resource */ - state: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; + state: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState; /** The `serverSeq` at which this snapshot was taken. Subsequent actions will have `serverSeq > fromSeq`. */ fromSeq: number; } diff --git a/src/vs/platform/agentHost/common/state/protocol/reducers.ts b/src/vs/platform/agentHost/common/state/protocol/reducers.ts index 29ee3541bfb..6ac00093ab4 100644 --- a/src/vs/platform/agentHost/common/state/protocol/reducers.ts +++ b/src/vs/platform/agentHost/common/state/protocol/reducers.ts @@ -10,5 +10,6 @@ export { rootReducer } from './channels-root/reducer.js'; export { sessionReducer } from './channels-session/reducer.js'; export { terminalReducer } from './channels-terminal/reducer.js'; export { changesetReducer } from './channels-changeset/reducer.js'; +export { annotationsReducer } from './channels-annotations/reducer.js'; export { resourceWatchReducer } from './channels-resource-watch/reducer.js'; export { softAssertNever, isClientDispatchable } from './common/reducer-helpers.js'; diff --git a/src/vs/platform/agentHost/common/state/protocol/state.ts b/src/vs/platform/agentHost/common/state/protocol/state.ts index f952f4d4a1d..cfb0ece43f3 100644 --- a/src/vs/platform/agentHost/common/state/protocol/state.ts +++ b/src/vs/platform/agentHost/common/state/protocol/state.ts @@ -11,5 +11,6 @@ export * from './channels-root/state.js'; export * from './channels-session/state.js'; export * from './channels-terminal/state.js'; export * from './channels-changeset/state.js'; +export * from './channels-annotations/state.js'; export * from './channels-otlp/state.js'; export * from './channels-resource-watch/state.js'; diff --git a/src/vs/platform/agentHost/common/state/protocol/version/registry.ts b/src/vs/platform/agentHost/common/state/protocol/version/registry.ts index a3706e6501a..9d83a6c2e45 100644 --- a/src/vs/platform/agentHost/common/state/protocol/version/registry.ts +++ b/src/vs/platform/agentHost/common/state/protocol/version/registry.ts @@ -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.3.0'; +export const PROTOCOL_VERSION = '0.4.0'; /** * Every protocol version a client built from this source tree is willing @@ -35,6 +35,7 @@ export const PROTOCOL_VERSION = '0.3.0'; * `scripts/verify-release-metadata.ts`. */ export const SUPPORTED_PROTOCOL_VERSIONS: readonly string[] = Object.freeze([ + '0.4.0', '0.3.0', ]); @@ -125,6 +126,11 @@ export const ACTION_INTRODUCED_IN: { readonly [K in StateAction['type']]: string [ActionType.ChangesetOperationsChanged]: '0.2.0', [ActionType.ChangesetOperationStatusChanged]: '0.3.0', [ActionType.ChangesetCleared]: '0.2.0', + [ActionType.AnnotationsSet]: '0.3.0', + [ActionType.AnnotationsUpdated]: '0.4.0', + [ActionType.AnnotationsRemoved]: '0.3.0', + [ActionType.AnnotationsEntrySet]: '0.3.0', + [ActionType.AnnotationsEntryRemoved]: '0.3.0', [ActionType.RootTerminalsChanged]: '0.1.0', [ActionType.RootConfigChanged]: '0.1.0', [ActionType.TerminalData]: '0.1.0', diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/stateToProgressAdapter.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/stateToProgressAdapter.ts index 878b438cfb8..f4fbd2f63af 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/stateToProgressAdapter.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/stateToProgressAdapter.ts @@ -320,11 +320,12 @@ function messageAttachmentToVariableEntry(attachment: MessageAttachment, connect }; } + const modelRepresentation = attachment.type === MessageAttachmentKind.Simple ? attachment.modelRepresentation : undefined; return { kind: 'generic', id: generateUuid(), name: attachment.label, - value: attachment.modelRepresentation || attachment.label, + value: modelRepresentation || attachment.label, _meta: attachment._meta, }; }