1
>
/*---------------------------------------------------------------------------------------------
claudeToolCallRegistry.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 { ILogService } from '../../../log/common/log.js';
7
>
import type { StringOrMarkdown } from '../../common/state/protocol/state.js';
8
>
import { getClaudeInvocationMessage, getClaudeToolDisplayName, getClaudeToolInputString } from './claudeToolDisplay.js';
9
>
10
>
/**
11
>
* Phase 8.5 — per-tool-call info computed at `content_block_stop` and
12
>
* reused at `tool_result` time. Mirrors Copilot's `IToolStartInfo`
13
>
* shape: Copilot stashes it at `tool.execution_start` (where the
14
>
* Copilot SDK hands over complete args); Claude stashes it at the
15
>
* analogous `content_block_stop` (the first point where the
16
>
* `input_json_delta` buffer is complete and parseable).
17
>
*/
18
>
export interface IClaudeToolStartInfo {
19
>
readonly toolName: string;
20
>
readonly displayName: string;
21
>
readonly parsedInput: Record<string, unknown> | undefined;
22
>
readonly invocationMessage: StringOrMarkdown;
23
>
readonly toolInput: string | undefined;
24
>
}
25
>
26
>
interface IRegistryEntry {
27
>
readonly toolName: string;
28
>
readonly turnId: string;
29
>
inputBuffer: string;
30
>
info: IClaudeToolStartInfo | undefined;
31
>
}
32
>
33
>
/**
34
>
* Phase 8.5 — per-session, cross-message tool-call tracking for the
35
>
* live mapper. Owns:
36
>
*
37
>
* - **Attribution** — `tool_use_id → { toolName, turnId }`. A
38
>
* `tool_use` lands in one assistant message; the matching
39
>
* `tool_result` arrives in a later synthetic `user` message. The
40
>
* registry resolves the originating turn so each Complete action
41
>
* lands on the correct turn.
42
>
* - **Input accumulation** — `input_json_delta` chunks arrive across
43
>
* `content_block_start` → `delta*` → `content_block_stop`. The
44
>
* registry concatenates them and parses once at `finalize`.
45
>
* - **Computed start-info** — `displayName`, rich `invocationMessage`,
46
>
* `toolInput` string, parsed input. Computed once at `finalize`
47
>
* and looked up at `tool_result` time so `pastTenseMessage` can
48
>
* include the original parameters.
49
>
*
50
>
* Mirror of Copilot's `pendingTools: Map<toolCallId, IToolStartInfo>`
51
>
* pattern in
52
>
* [`mapSessionEvents.ts`](../copilot/mapSessionEvents.ts) — only the
53
>
* seam differs (Copilot's SDK hands over complete args at
54
>
* `tool.execution_start`; Claude's SDK streams them in deltas, so the
55
>
* "ready" seam is `content_block_stop`).
56
>
*
57
>
* Encapsulated as a class with named lifecycle methods so the maps'
58
>
* mutators are not part of the public surface — Phase 6.1's lesson.
59
>
* One instance lives per `ClaudeAgentSession` and is composed by
60
>
* `ClaudeMapperState`; the mapper threads `state` (which exposes the
61
>
* registry as `state.toolCalls`) into every invocation.
62
>
*/
63
>
export class ClaudeToolCallRegistry {
64
private readonly _entries = new Map<string, IRegistryEntry>();
66
>
/**
67
>
* Begin tracking a tool call. Called from `content_block_start`
68
>
* for a `tool_use` block. Allocates the delta buffer; the
69
>
* computed info bag is filled in by {@link finalize}.
70
>
*/
71
>
begin(toolUseId: string, toolName: string, turnId: string): void {
72
this._entries.set(toolUseId, {
73
toolName,