claudeSessionClientToolsModel.ts ×10

Frontier kind: Code frontier

unlabeled · c_339292ff9265

213 tests · 6784 LOC · 62 files · introduces 0 tests · 87 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
10 ranges87 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
989 ranges6784 lines · 62 files · Browse complete extent
All tests (intent)
213 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.

1 file ranked by introduced lines: 87 introduced LOC across 10 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/claude/clientTools/claudeSessionClientToolsModel.ts 87 introduced LOC · 10 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- claudeSessionClientToolsModel.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 { Disposable } from '../../../../../base/common/lifecycle.js';
7 > import { autorun, IObservable, ISettableObservable, observableValueOpts } from '../../../../../base/common/observable.js';
8 > import type { ToolDefinition } from '../../../common/state/protocol/state.js';
9 > import { ActiveClientToolSet, structuralToolsEqual } from '../../activeClientState.js';
10 >
11 > /**
12 > * Pure state holder for the workbench-registered client-tool snapshots
13 > * contributed by potentially several active clients, keyed by `clientId`.
14 > * Exposes the merged tool set as a single {@link IObservable} (deduplicated
15 > * by name, first-inserted client wins) so consumers can react to changes
16 > * without polling, and {@link ownerOf} so tool calls can be routed back to
17 > * the contributing client.
18 > *
19 > * The `merged` observable dedupes structurally-equivalent writes: tool
20 > * snapshots compare on `name + description + inputSchema` (order-insensitive,
21 > * `undefined` equivalent to `[]`). A re-send of a structurally identical set
22 > * therefore does NOT fire downstream subscribers.
23 > *
24 > * Knows nothing about diffing or the SDK — pair with
25 > * {@link SessionClientToolsDiff} to track "has the merged snapshot changed
26 > * since the last successful SDK build".
27 > */
28 > export class SessionClientToolsModel {
29
30 private readonly _toolSet = new ActiveClientToolSet();
34 );
35 readonly merged: IObservable<readonly ToolDefinition[]> = this._merged;
37 > /** Replace `clientId`'s contributed tools (full replacement). */
38 > setTools(clientId: string, tools: readonly ToolDefinition[]): void {
39 this._toolSet.set(clientId, tools);
40 this._merged.set(this._toolSet.merged(), undefined);
41 }
43 > /** This client's contributed tools (empty when absent). */
44 > getTools(clientId: string): readonly ToolDefinition[] {
45 return this._toolSet.get(clientId);
46 }
48 > /** Remove a client's tool contribution. */
49 > removeClient(clientId: string): void {
50 if (this._toolSet.delete(clientId)) {
51 this._merged.set(this._toolSet.merged(), undefined);
52 }
53 }
55 > /** The `clientId` that owns the tool named `toolName`, or `undefined`. */
56 > ownerOf(toolName: string, preferredClientId?: string): string | undefined {
57 return this._toolSet.ownerOf(toolName, preferredClientId);
58 }
60 >
61 > /**
62 > * Tracks "has {@link SessionClientToolsModel.merged} changed since the
63 > * last successful {@link consume}?". Subscribes to the model's observable
64 > * and flips a private dirty bit on every change; {@link consume} captures
65 > * the current merged snapshot and clears the bit — preserving the C6 pin
66 > * invariant from the previous `ClientToolDiff` implementation: a `setTools`
67 > * call that races the in-flight builder re-flips the bit via the autorun, so
68 > * the next sendMessage detects the stale set and triggers another rebind.
69 > *
70 > * On builder throw the bit is left set — the SDK is still running with
71 > * the previous snapshot, so the next sendMessage should retry.
72 > */
73 > export class SessionClientToolsDiff extends Disposable {
74 >
75 > readonly model: SessionClientToolsModel = new SessionClientToolsModel();
76 >
77 > private _dirty = false;
78 > // `autorun` invokes its callback once at registration for dependency
79 > // tracking. Skip that initial run so a brand-new diff doesn't report
80 > // dirty before any `setTools` has happened.
81 > private _ignoreNextFire = true;
82 > // Structural tool snapshot last marked applied (via {@link consume}).
83 > private _lastAppliedTools: readonly ToolDefinition[] = [];
84 >
85 > constructor() {
86 super();
87 this._register(autorun(reader => {
97 }));
98 }
100 > get hasDifference(): boolean {
101 return this._dirty;
102 }
104 > /**
105 > * Read the current merged tool set and mark it as the applied snapshot.
106 > * A subsequent {@link SessionClientToolsModel.setTools} re-flips dirty
107 > * via the autorun, so callers do NOT need to compare snapshots
108 > * themselves to detect a race. If the caller's downstream work
109 > * (e.g. SDK rebuild) fails, call {@link markDirty} to surface the
110 > * stale state so the next sendMessage retries.
111 > */
112 > consume(): readonly ToolDefinition[] {
113 const merged = this.model.merged.get();
114 this._dirty = false;
116 return merged;
117 }
119 > /**
120 > * Force the dirty bit on. Use when a caller's async work that
121 > * followed {@link consume} failed and the SDK is therefore still on
122 > * the previous snapshot.
123 > */
124 > markDirty(): void {
125 this._dirty = true;
126 }