agentFeedbackServerTools.ts ×23

Frontier kind: Code frontier

unlabeled · c_f57a55b3e1e4

1280 tests · 12540 LOC · 48 files · introduces 0 tests · 381 LOC · 2 files

Introduces — evidence that enters the hierarchy at this concept

Code
28 ranges381 lines · 2 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
911 ranges12540 lines · 48 files · Browse complete extent
All tests (intent)
1280 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

2 files ranked by introduced lines: 381 introduced LOC across 28 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/shared/agentFeedbackServerTools.ts 273 introduced LOC · 23 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- agentFeedbackServerTools.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { generateUuid } from '../../../../base/common/uuid.js';
7 > import { localize } from '../../../../nls.js';
8 > import { FEEDBACK_ANNOTATION_META_KEY, readFeedbackAnnotationMeta, VIEW_UNREVIEWED_COMMENTS_TOOL_NAME, ADD_COMMENT_TOOL_NAME, type IFeedbackAnnotationMeta } from '../../common/meta/agentFeedbackAnnotations.js';
9 > import { buildAnnotationsUri } from '../../common/annotationsUri.js';
10 > import type { AnnotationsAction } from '../../common/state/sessionActions.js';
11 > import { ActionType } from '../../common/state/protocol/common/actions.js';
12 > import { parseChatUri, type Annotation, type AnnotationsState, type StringOrMarkdown, type TextRange, type ToolDefinition } from '../../common/state/sessionState.js';
13 > import type { IServerToolDisplay, IServerToolDisplayResult, IServerToolGroup } from './agentServerToolHost.js';
14 >
15 > /**
16 > * Server-side implementation of the agent feedback ("comments") tools.
17 > *
18 > * These tools used to be registered on the client (agents window) and keyed
19 > * off an in-memory store. For agent-host sessions they now execute on the
20 > * server against the session's annotations channel: each comment is an
21 > * {@link Annotation} on `<session>/annotations`, with feedback semantics
22 > * carried in {@link Annotation._meta} under {@link FEEDBACK_ANNOTATION_META_KEY}
23 > * (see `agentFeedbackAnnotations.ts`). The functions here are pure — they read
24 > * the current {@link AnnotationsState} and return the annotation actions to
25 > * dispatch plus a textual tool result — so they can be unit tested without a
26 > * running state manager. The host wiring (reading the snapshot, dispatching
27 > * the actions) lives in the caller.
28 > */
29 >
30 > export const addCommentToolName = ADD_COMMENT_TOOL_NAME;
31 > export const listCommentsToolName = 'listComments';
32 > export const deleteCommentsToolName = 'deleteComments';
33 > export const resolveCommentsToolName = 'resolveComments';
34 > export const viewUnreviewedCommentsToolName = VIEW_UNREVIEWED_COMMENTS_TOOL_NAME;
35 >
36 > /**
37 > * Feedback kinds that originate from a review the user is expected to triage
38 > * (a pull request review or an in-product code review) rather than being
39 > * authored by the user directly. Comments of these kinds that are still in the
40 > * `created` state are surfaced to the agent via the {@link listCommentsToolName}
41 > * note and revealed through {@link viewUnreviewedCommentsToolName}.
42 > */
43 > const REVIEWABLE_FEEDBACK_KINDS: ReadonlySet<string> = new Set(['prReview', 'codeReview']);
44 >
45 > /**
46 > * Server tools that must not be auto-approved: invoking them surfaces a
47 > * confirmation to the user (rendered by a custom client content part) before
48 > * the tool body runs. Providers consult {@link feedbackToolRequiresConfirmation}
49 > * (via the host) to exclude these from their server-tool auto-approve lists.
50 > */
51 > const feedbackConfirmationToolNames: ReadonlySet<string> = new Set([viewUnreviewedCommentsToolName]);
52 >
53 > /** Whether the given feedback server tool requires user confirmation before it runs. */
54 > export function feedbackToolRequiresConfirmation(toolName: string): boolean {
55 return feedbackConfirmationToolNames.has(toolName);
56 }
58 > const addCommentInputSchema: ToolDefinition['inputSchema'] = {
59 > type: 'object',
60 > properties: {
61 > resourceUri: { type: 'string', description: 'URI of the file to add a comment to.' },
62 > range: {
63 > type: 'object',
64 > description: 'One-based text range to comment on.',
65 > properties: {
66 > startLineNumber: { type: 'number', description: 'One-based start line number.' },
67 > startColumn: { type: 'number', description: 'One-based start column.' },
68 > endLineNumber: { type: 'number', description: 'One-based end line number.' },
69 > endColumn: { type: 'number', description: 'One-based end column.' },
70 > },
71 > required: ['startLineNumber', 'startColumn', 'endLineNumber', 'endColumn'],
72 > },
73 > text: { type: 'string', description: 'Comment text to add.' },
74 > },
75 > required: ['resourceUri', 'range', 'text'],
76 > };
77 >
78 > const listCommentsInputSchema: ToolDefinition['inputSchema'] = {
79 > type: 'object',
80 > properties: {},
81 > };
82 >
83 > const viewUnreviewedCommentsInputSchema: ToolDefinition['inputSchema'] = {
84 > type: 'object',
85 > properties: {},
86 > };
87 >
88 > const deleteCommentsInputSchema: ToolDefinition['inputSchema'] = {
89 > type: 'object',
90 > properties: {
91 > commentIds: { type: 'array', items: { type: 'string' }, description: 'Comment IDs to delete.' },
92 > },
93 > required: ['commentIds'],
94 > };
95 >
96 > const resolveCommentsInputSchema: ToolDefinition['inputSchema'] = {
97 > type: 'object',
98 > properties: {
99 > commentIds: { type: 'array', items: { type: 'string' }, description: 'Comment IDs to update.' },
100 > resolved: { type: 'boolean', description: 'Whether the comments should be marked as resolved. Defaults to true.' },
101 > },
102 > required: ['commentIds'],
103 > };
104 >
105 > /**
106 > * Protocol {@link ToolDefinition}s for the feedback server tools, advertised on
107 > * {@link SessionState.serverTools} so clients know these tools are owned and
108 > * executed by the agent host.
109 > */
110 > export const feedbackServerToolDefinitions: ToolDefinition[] = [
111 > {
112 > name: addCommentToolName,
113 > title: 'Add Comment (Agent Feedback)',
114 > description: 'Add a comment to a file range.',
115 > inputSchema: addCommentInputSchema,
116 > annotations: { readOnlyHint: false },
117 > },
118 > {
119 > name: listCommentsToolName,
120 > title: 'List Comments (Agent Feedback)',
121 > description: 'List comments for this session.',
122 > inputSchema: listCommentsInputSchema,
123 > annotations: { readOnlyHint: true },
124 > },
125 > {
126 > name: deleteCommentsToolName,
127 > title: 'Delete Comments (Agent Feedback)',
128 > description: 'Delete comments for this session.',
129 > inputSchema: deleteCommentsInputSchema,
130 > annotations: { readOnlyHint: false, destructiveHint: true },
131 > },
132 > {
133 > name: resolveCommentsToolName,
134 > title: 'Resolve Comments (Agent Feedback)',
135 > description: 'Mark comments for this session as resolved or unresolved.',
136 > inputSchema: resolveCommentsInputSchema,
137 > annotations: { readOnlyHint: false },
138 > },
139 > {
140 > name: viewUnreviewedCommentsToolName,
141 > title: 'View Unreviewed Comments (Agent Feedback)',
142 > description: 'View pull request or code review comments that the user has not reviewed yet. Calling this asks the user to choose which of those comments to reveal; only the comments the user reveals are returned.',
143 > inputSchema: viewUnreviewedCommentsInputSchema,
144 > annotations: { readOnlyHint: true },
145 > },
146 > ];
147 >
148 > // --- Argument validation ------------------------------------------------------
149 >
150 > interface IOneBasedRange {
151 > readonly startLineNumber: number;
152 > readonly startColumn: number;
153 > readonly endLineNumber: number;
154 > readonly endColumn: number;
155 > }
156 >
157 > interface IAddCommentArgs {
158 > readonly resourceUri?: unknown;
159 > readonly range?: unknown;
160 > readonly text?: unknown;
161 > }
162 >
163 > interface IDeleteCommentsArgs {
164 > readonly commentIds?: unknown;
165 > }
166 >
167 > interface IResolveCommentsArgs {
168 > readonly commentIds?: unknown;
169 > readonly resolved?: unknown;
170 > }
171 >
172 function getRequiredString(value: unknown, field: string, toolName: string): string {
173 if (typeof value !== 'string' || value.length === 0) {
176 return value;
177 }
179 function getRequiredPositiveInteger(value: unknown, field: string, toolName: string): number {
180 if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
183 return value;
184 }
186 function getAddCommentArgs(rawArgs: unknown): { resourceUri: string; range: IOneBasedRange; text: string } {
187 const args = (rawArgs ?? {}) as IAddCommentArgs;
203 };
204 }
206 function getUniqueCommentIds(value: unknown, toolName: string): readonly string[] {
207 if (!Array.isArray(value) || value.length === 0) {
214 return [...new Set(ids)];
215 }
217 function getResolvedFlag(value: unknown): boolean {
218 if (value === undefined) {
224 return value;
225 }
227 > // --- Annotation <-> feedback conversion ---------------------------------------
228 >
229 function toTextRange(range: IOneBasedRange): TextRange {
230 return {
233 };
234 }
236 function fromTextRange(range: TextRange | undefined): IOneBasedRange {
237 if (!range) {
245 };
246 }
248 function entryText(text: StringOrMarkdown): string {
249 return typeof text === 'string' ? text : text.markdown;
250 }
252 function readMeta(annotation: Annotation): IFeedbackAnnotationMeta | undefined {
253 return readFeedbackAnnotationMeta(annotation);
254 }
256 > interface ISerializedComment {
257 > readonly id: string;
258 > readonly resourceUri: string;
259 > readonly range: IOneBasedRange;
260 > readonly text: string;
261 > readonly kind: string;
262 > readonly resolved: boolean;
263 > readonly replies?: readonly string[];
264 > }
265 >
266 function serializeComment(annotation: Annotation): ISerializedComment {
267 const entries = annotation.entries ?? [];
278 };
279 }
281 > /**
282 > * Comments visible to the agent: everything except items still in the
283 > * `created` state (the agent added them but the user has not accepted them
284 > * yet). Mirrors the client `getListableFeedback` behavior.
285 > */
286 function listableAnnotations(state: AnnotationsState): Annotation[] {
287 return state.annotations.filter(annotation => {
298 });
299 }
301 > /**
302 > * Feedback annotations of a {@link REVIEWABLE_FEEDBACK_KINDS reviewable kind}
303 > * the user has flagged for reveal to the agent (via the confirmation of the
304 > * {@link viewUnreviewedCommentsToolName} tool). These are exactly the comments
305 > * the user chose to reveal for the current invocation; everything else
306 > * (including review comments that happen to be accepted from a previous reveal
307 > * or a manual accept) is excluded.
308 > */
309 function pendingRevealAnnotations(state: AnnotationsState): Annotation[] {
310 return state.annotations.filter(annotation => {
316 });
317 }
319 > /** Returns a copy of {@link annotation} with the {@link IFeedbackAnnotationMeta.pendingAgentReveal} flag cleared. */
320 function clearPendingReveal(annotation: Annotation): Annotation {
321 const meta = readMeta(annotation);
326 return { ...annotation, _meta: { ...annotation._meta, [FEEDBACK_ANNOTATION_META_KEY]: nextMeta } };
327 }
329 > /**
330 > * Reviewable (PR / code review) feedback annotations the user has not reviewed
331 > * yet, i.e. still in the `created` state. Used to build the
332 > * {@link listCommentsToolName} note.
333 > */
334 function createdReviewableAnnotations(state: AnnotationsState): Annotation[] {
335 return state.annotations.filter(annotation => {
341 });
342 }
344 > /**
345 > * A short note appended to the {@link listCommentsToolName} result when there
346 > * are reviewable comments the user has not accepted yet, pointing the agent at
347 > * {@link viewUnreviewedCommentsToolName}. Returns `undefined` (no note) when
348 > * there are no such comments.
349 > */
350 function buildUnreviewedCommentsNote(state: AnnotationsState): string | undefined {
351 const created = createdReviewableAnnotations(state);
374 return `There ${verb} ${subject} which the user has not reviewed yet. If the user wants you to tackle them, call the \`${viewUnreviewedCommentsToolName}\` tool to view them.`;
375 }
377 > // --- Tool execution -----------------------------------------------------------
378 >
379 > export interface IFeedbackToolOutcome {
380 > /** Annotation actions to dispatch on the session's annotations channel. */
381 > readonly actions: readonly AnnotationsAction[];
382 > /** Textual tool result returned to the agent. */
383 > readonly result: string;
384 > }
385 >
386 > /**
387 > * Executes a feedback server tool against the current annotation state.
388 > *
389 > * Pure: it does not mutate {@link state}, instead returning the annotation
390 > * actions the caller should dispatch (so the authoritative state manager
391 > * remains the single writer) along with the textual tool result.
392 > *
393 > * @throws if {@link toolName} is unknown or the arguments are invalid.
394 > */
395 > export function applyFeedbackTool(state: AnnotationsState, sessionResource: string, toolName: string, rawArgs: unknown): IFeedbackToolOutcome {
396 switch (toolName) {
397 case addCommentToolName: {
502 }
503 }
505 > /**
506 > * Parses the number of comments returned by the {@link listCommentsToolName}
507 > * tool from its JSON result (`{ comments: [...] }`). Returns `undefined` when
508 > * the result is missing or not in the expected shape, so the caller can fall
509 > * back to a count-less message.
510 > */
511 function parseListedCommentCount(resultText: string | undefined): number | undefined {
512 if (!resultText) {
520 }
521 }
523 > /**
524 > * Display strings for the feedback ("comments") tools, authored here so every
525 > * provider (Copilot, Claude, Codex, …) renders them identically instead of
526 > * each provider's display layer re-deriving the strings from the tool name.
527 > * Returns `undefined` for tools this group does not own, so the caller falls
528 > * back to its generic display.
529 > *
530 > * {@link toolName} is the bare tool name (any transport prefix such as Claude's
531 > * `mcp__<server>__` has already been stripped by the dispatcher).
532 > */
533 function getFeedbackToolDisplay(toolName: string, _args: unknown, result?: IServerToolDisplayResult): IServerToolDisplay | undefined {
534 switch (toolName) {
577 }
578 }
580 > /**
581 > * The feedback ("comments") server-tool group, contributed to the
582 > * {@link AgentServerToolHost} at startup (see `node/agentService.ts`). Wraps
583 > * the pure {@link applyFeedbackTool} executor with the annotations-channel I/O:
584 > * it reads the session's current {@link AnnotationsState}, applies the tool,
585 > * and dispatches the resulting annotation actions through the state manager
586 > * (the single writer).
587 > */
588 > export const feedbackServerToolGroup: IServerToolGroup = {
589 > definitions: feedbackServerToolDefinitions,
590 > requiresConfirmation(toolName): boolean {
591 return feedbackToolRequiresConfirmation(toolName);
592 },
593 > getDisplay(toolName, args, result): IServerToolDisplay | undefined { agentFeedbackServerTools.ts
594 return getFeedbackToolDisplay(toolName, args, result);
595 },
596 > execute(stateManager, chatUri, toolName, rawArgs): string { agentFeedbackServerTools.ts
597 // A session can contain multiple chats, each addressed by its own
598 // `ahp-chat` URI but sharing the same context/workspace. Comments belong
src/vs/platform/agentHost/common/meta/agentFeedbackAnnotations.ts 108 introduced LOC · 5 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- agentFeedbackAnnotations.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import type { Mutable } from '../../../../base/common/types.js';
7 > import type { Annotation } from '../state/protocol/state.js';
8 >
9 > /**
10 > * Shared convention for carrying agent-feedback semantics inside an
11 > * {@link Annotation._meta} on the agent host annotations channel.
12 > *
13 > * Feedback items round-trip as annotations on `<session>/annotations`; the
14 > * annotation's own fields cover id / resource / range / resolved, and
15 > * everything else (lifecycle state, origin kind, code context, PR linkage)
16 > * lives under {@link FEEDBACK_ANNOTATION_META_KEY}. This module is the single
17 > * place both the server (agent host, which writes feedback annotations from
18 > * its server tools) and the client (agents window, which reads them back)
19 > * agree on the key and shape, so the two sides cannot drift.
20 > */
21 >
22 > /** Namespaced key under {@link Annotation._meta} carrying feedback semantics. */
23 > export const FEEDBACK_ANNOTATION_META_KEY = 'vscode.agentFeedback';
24 >
25 > /**
26 > * Name of the agent host server tool that reveals review comments the user has
27 > * not accepted yet. Shared here (in the layer-neutral `common` module) so the
28 > * node-side server tool implementation and the browser-side chat adapter that
29 > * renders its confirmation agree on the name without drifting. The agent sees
30 > * this name directly (Copilot) or prefixed as `mcp__host__<name>` (Claude).
31 > */
32 > export const VIEW_UNREVIEWED_COMMENTS_TOOL_NAME = 'viewUnreviewedComments';
33 >
34 > /**
35 > * Name of the agent host server tool that adds a comment (agent feedback) to a
36 > * file range. Shared here (in the layer-neutral `common` module) so the
37 > * node-side server tool implementation and the browser-side chat adapter that
38 > * renders its tool call agree on the name without drifting. The agent sees this
39 > * name directly (Copilot) or prefixed as `mcp__host__<name>` (Claude).
40 > */
41 > export const ADD_COMMENT_TOOL_NAME = 'addComment';
42 >
43 > /**
44 > * Whether {@link toolName} (a tool name as seen on a tool call) refers to the
45 > * {@link VIEW_UNREVIEWED_COMMENTS_TOOL_NAME} server tool. Accepts both the bare
46 > * name and the Claude `mcp__<server>__<name>` prefixed form.
47 > */
48 > export function isViewUnreviewedCommentsTool(toolName: string): boolean {
49 return toolName === VIEW_UNREVIEWED_COMMENTS_TOOL_NAME || toolName.endsWith(`__${VIEW_UNREVIEWED_COMMENTS_TOOL_NAME}`);
50 }
52 > /**
53 > * Whether {@link toolName} (a tool name as seen on a tool call) refers to the
54 > * {@link ADD_COMMENT_TOOL_NAME} server tool. Accepts both the bare name and the
55 > * Claude `mcp__<server>__<name>` prefixed form.
56 > */
57 > export function isAddCommentTool(toolName: string): boolean {
58 return toolName === ADD_COMMENT_TOOL_NAME || toolName.endsWith(`__${ADD_COMMENT_TOOL_NAME}`);
59 }
61 > /**
62 > * Origin of a feedback item. String values match the client-side
63 > * `AgentFeedbackKind` enum so a value written by either side decodes on the
64 > * other without translation.
65 > */
66 > export type AgentFeedbackKindValue = 'user' | 'codeReview' | 'prReview';
67 >
68 > /**
69 > * Lifecycle state of a feedback item. String values match the client-side
70 > * `AgentFeedbackState` enum.
71 > */
72 > export type AgentFeedbackStateValue = 'created' | 'accepted' | 'submitted' | 'resolved';
73 >
74 > /**
75 > * Feedback semantics carried in an annotation's {@link Annotation._meta}.
76 > *
77 > * The optional client-only fields ({@link suggestion}, {@link codeSelection},
78 > * {@link diffHunks}, {@link sourcePRReviewCommentId}) are populated when a
79 > * feedback item is converted from a code- or PR-review comment on the client;
80 > * server tools only ever write {@link kind} / {@link state} /
81 > * {@link sessionResource}. {@link suggestion} is typed loosely here because
82 > * its concrete shape lives in the client (sessions) layer.
83 > */
84 > export interface IFeedbackAnnotationMeta {
85 > readonly kind: AgentFeedbackKindValue;
86 > readonly state: AgentFeedbackStateValue;
87 > readonly sessionResource: string;
88 > readonly suggestion?: unknown;
89 > readonly codeSelection?: string;
90 > readonly diffHunks?: string;
91 > readonly sourcePRReviewCommentId?: string;
92 > /**
93 > * Transient marker set by the client when the user reveals this comment to
94 > * the agent via the `viewUnreviewedComments` tool. The server tool returns
95 > * exactly the comments carrying this flag (so the result is scoped to the
96 > * comments selected for that invocation rather than every accepted review
97 > * comment) and clears it once they have been delivered, so a later
98 > * invocation does not re-return them.
99 > */
100 > readonly pendingAgentReveal?: boolean;
101 > }
102 >
103 function isAgentFeedbackKindValue(value: unknown): value is AgentFeedbackKindValue {
104 return value === 'user' || value === 'codeReview' || value === 'prReview';
105 }
107 function isAgentFeedbackStateValue(value: unknown): value is AgentFeedbackStateValue {
108 return value === 'created' || value === 'accepted' || value === 'submitted' || value === 'resolved';
109 }
111 > /**
112 > * Reads the well-known {@link IFeedbackAnnotationMeta} from an annotation's
113 > * `_meta` bag (under {@link FEEDBACK_ANNOTATION_META_KEY}). The annotations
114 > * channel is shared, so this validates the required `kind` / `state` /
115 > * `sessionResource` fields and returns `undefined` for annotations that aren't
116 > * feedback items. Read through this rather than casting the namespaced slot.
117 > */
118 > export function readFeedbackAnnotationMeta(annotation: Annotation): IFeedbackAnnotationMeta | undefined {
119 const meta = annotation._meta;
120 const slot = meta?.[FEEDBACK_ANNOTATION_META_KEY];