pi-cursor-sdk 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +53 -1
  2. package/README.md +2 -2
  3. package/docs/cursor-live-smoke-checklist.md +54 -41
  4. package/docs/cursor-model-ux-spec.md +4 -3
  5. package/docs/cursor-testing-lessons.md +199 -0
  6. package/package.json +14 -5
  7. package/scripts/isolated-cursor-smoke.sh +226 -0
  8. package/scripts/steering-rpc-smoke.mjs +238 -0
  9. package/scripts/tmux-live-smoke.sh +418 -0
  10. package/scripts/validate-smoke-jsonl.mjs +207 -0
  11. package/src/cursor-context-tools.ts +6 -0
  12. package/src/cursor-display-text.ts +10 -0
  13. package/src/cursor-edit-diff.ts +11 -0
  14. package/src/cursor-env-boolean.ts +22 -0
  15. package/src/cursor-live-run-coordinator.ts +483 -0
  16. package/src/cursor-native-replay-routing.ts +48 -0
  17. package/src/cursor-native-replay-trace.ts +29 -0
  18. package/src/cursor-native-tool-display-registration.ts +103 -0
  19. package/src/cursor-native-tool-display-replay.ts +465 -0
  20. package/src/cursor-native-tool-display-state.ts +78 -0
  21. package/src/cursor-native-tool-display-tools.ts +102 -0
  22. package/src/cursor-native-tool-display.ts +10 -648
  23. package/src/cursor-partial-content-emitter.ts +121 -0
  24. package/src/cursor-pi-tool-bridge-abort.ts +133 -0
  25. package/src/cursor-pi-tool-bridge-diagnostics.ts +179 -0
  26. package/src/cursor-pi-tool-bridge-mcp.ts +118 -0
  27. package/src/cursor-pi-tool-bridge-run.ts +384 -0
  28. package/src/cursor-pi-tool-bridge-server.ts +182 -0
  29. package/src/cursor-pi-tool-bridge-snapshot.ts +88 -0
  30. package/src/cursor-pi-tool-bridge-types.ts +80 -0
  31. package/src/cursor-pi-tool-bridge.ts +42 -1104
  32. package/src/cursor-provider-live-run-drain.ts +405 -0
  33. package/src/cursor-provider-turn-coordinator.ts +460 -0
  34. package/src/cursor-provider.ts +77 -1103
  35. package/src/cursor-question-tool.ts +9 -1
  36. package/src/cursor-record-utils.ts +26 -0
  37. package/src/cursor-sdk-output-filter.ts +100 -0
  38. package/src/cursor-sensitive-text.ts +37 -0
  39. package/src/cursor-tool-transcript.ts +28 -1229
  40. package/src/cursor-transcript-tool-formatters.ts +641 -0
  41. package/src/cursor-transcript-tool-specs.ts +441 -0
  42. package/src/cursor-transcript-utils.ts +276 -0
@@ -0,0 +1,460 @@
1
+ import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai";
2
+ import type { InteractionUpdate } from "@cursor/sdk";
3
+ import type { CursorLiveRun } from "./cursor-live-run-coordinator.js";
4
+ import { cursorLiveRuns } from "./cursor-provider-live-run-drain.js";
5
+ import { formatInactiveCursorReplayTrace } from "./cursor-native-replay-trace.js";
6
+ import { resolveNativeReplayDisposition } from "./cursor-native-replay-routing.js";
7
+ import { truncateCursorDisplayLine } from "./cursor-display-text.js";
8
+ import { CursorPartialContentEmitter } from "./cursor-partial-content-emitter.js";
9
+ import { asRecord, getField, hasUsableText } from "./cursor-record-utils.js";
10
+ import { scrubPiToolDisplay, scrubSensitiveText } from "./cursor-sensitive-text.js";
11
+ import { buildCursorPiToolDisplay, formatCursorToolTranscript, getCursorCreatePlanText, mergeCursorToolCalls } from "./cursor-tool-transcript.js";
12
+ import { getString, getToolArgs, getToolName } from "./cursor-transcript-utils.js";
13
+
14
+ function formatCursorToolName(toolCall: unknown): string {
15
+ return truncateCursorDisplayLine(getToolName(toolCall), 80) || "unknown";
16
+ }
17
+
18
+ interface CursorShellOutputDelta {
19
+ stream: "stdout" | "stderr";
20
+ data: string;
21
+ }
22
+
23
+ interface CursorShellOutputDeltas {
24
+ stdout: string[];
25
+ stderr: string[];
26
+ }
27
+
28
+ function isCursorShellToolCall(toolCall: unknown): boolean {
29
+ const normalizedName = getToolName(toolCall).replace(/\s+/g, " ").trim().toLowerCase();
30
+ return normalizedName === "shell" || normalizedName === "run_terminal_cmd" || normalizedName === "terminal" || normalizedName === "bash";
31
+ }
32
+
33
+ function isCursorTaskToolCall(toolCall: unknown): boolean {
34
+ return getToolName(toolCall).replace(/\s+/g, " ").trim().toLowerCase() === "task";
35
+ }
36
+
37
+ function extractCursorTaskProgressLabel(toolCall: unknown, apiKey?: string): string | undefined {
38
+ if (!isCursorTaskToolCall(toolCall)) return undefined;
39
+ const description = getString(getToolArgs(toolCall), "description");
40
+ if (!description?.trim()) return undefined;
41
+ return truncateCursorDisplayLine(scrubSensitiveText(description, apiKey));
42
+ }
43
+
44
+ function getCursorShellOutputDelta(update: InteractionUpdate): CursorShellOutputDelta | undefined {
45
+ if (update.type !== "shell-output-delta") return undefined;
46
+ const event = getField(update, "event");
47
+ const eventCase = getField(event, "case");
48
+ if (eventCase !== "stdout" && eventCase !== "stderr") return undefined;
49
+ const value = getField(event, "value");
50
+ const data = getField(value, "data");
51
+ if (typeof data !== "string" || data.length === 0) return undefined;
52
+ return { stream: eventCase, data };
53
+ }
54
+
55
+ function mergeShellOutputDeltasIntoCursorToolCall(toolCall: unknown, deltas: CursorShellOutputDeltas | undefined): unknown {
56
+ if (!deltas) return toolCall;
57
+ const stdout = deltas.stdout.join("");
58
+ const stderr = deltas.stderr.join("");
59
+ if (!hasUsableText(stdout) && !hasUsableText(stderr)) return toolCall;
60
+
61
+ const toolRecord = asRecord(toolCall);
62
+ const result = getField(toolRecord, "result");
63
+ const resultRecord = asRecord(result);
64
+ if (!toolRecord || !resultRecord || resultRecord.status !== "success") return toolCall;
65
+
66
+ const value = getField(resultRecord, "value");
67
+ const valueRecord = asRecord(value);
68
+ const completedStdout = getField(valueRecord, "stdout");
69
+ const completedStderr = getField(valueRecord, "stderr");
70
+ if (hasUsableText(typeof completedStdout === "string" ? completedStdout : undefined)) return toolCall;
71
+ if (hasUsableText(typeof completedStderr === "string" ? completedStderr : undefined)) return toolCall;
72
+
73
+ return {
74
+ ...toolRecord,
75
+ result: {
76
+ ...resultRecord,
77
+ value: {
78
+ ...(valueRecord ?? {}),
79
+ stdout,
80
+ stderr,
81
+ },
82
+ },
83
+ };
84
+ }
85
+
86
+ type ToolCompletionResolution =
87
+ | { action: "ignore-bridge"; identity?: string }
88
+ | {
89
+ action: "handle";
90
+ toolCall: unknown;
91
+ identity?: string;
92
+ source?: "started" | "fallback";
93
+ matchedStartedCallId?: string;
94
+ };
95
+
96
+ export interface CursorSdkTurnCoordinatorOptions {
97
+ stream: AssistantMessageEventStream;
98
+ partial: AssistantMessage;
99
+ cwd: string;
100
+ resolvedApiKey?: string;
101
+ liveRun?: CursorLiveRun;
102
+ useNativeToolReplay: boolean;
103
+ activeToolNames?: ReadonlySet<string>;
104
+ nativeReplayId: string;
105
+ textDeltas: string[];
106
+ }
107
+
108
+ export class CursorSdkTurnCoordinator {
109
+ readonly stream: AssistantMessageEventStream;
110
+ readonly partial: AssistantMessage;
111
+ readonly cwd: string;
112
+ readonly resolvedApiKey?: string;
113
+ readonly liveRun?: CursorLiveRun;
114
+ readonly useNativeToolReplay: boolean;
115
+ readonly activeToolNames?: ReadonlySet<string>;
116
+ readonly nativeReplayId: string;
117
+ readonly textDeltas: string[];
118
+
119
+ private readonly contentEmitter: CursorPartialContentEmitter;
120
+ private nativeToolDisplayCounter = 0;
121
+ private nativeToolReplayStarted = false;
122
+ private cursorPlanTextCandidate: string | undefined;
123
+ private readonly startedToolCalls = new Map<string, unknown>();
124
+ private readonly bridgeStartedToolCallIds = new Set<string>();
125
+ private readonly activeShellCallIds = new Set<string>();
126
+ private readonly ambiguousShellOutputCallIds = new Set<string>();
127
+ private readonly shellOutputDeltasByCallId = new Map<string, CursorShellOutputDeltas>();
128
+ private readonly completedToolIdentities = new Set<string>();
129
+ private readonly completedStartedToolFingerprints = new Set<string>();
130
+ private readonly completedFallbackToolFingerprints = new Set<string>();
131
+ private readonly emittedTaskProgressCallIds = new Set<string>();
132
+
133
+ constructor(options: CursorSdkTurnCoordinatorOptions) {
134
+ this.stream = options.stream;
135
+ this.partial = options.partial;
136
+ this.cwd = options.cwd;
137
+ this.resolvedApiKey = options.resolvedApiKey;
138
+ this.liveRun = options.liveRun;
139
+ this.useNativeToolReplay = options.useNativeToolReplay;
140
+ this.activeToolNames = options.activeToolNames;
141
+ this.nativeReplayId = options.nativeReplayId;
142
+ this.textDeltas = options.textDeltas;
143
+ this.contentEmitter = new CursorPartialContentEmitter(options.stream, options.partial, undefined, false);
144
+ }
145
+
146
+ get planTextCandidate(): string | undefined {
147
+ return this.cursorPlanTextCandidate;
148
+ }
149
+
150
+ get replayStarted(): boolean {
151
+ return this.nativeToolReplayStarted;
152
+ }
153
+
154
+ discardIncompleteStartedToolCalls(): void {
155
+ this.startedToolCalls.clear();
156
+ this.bridgeStartedToolCallIds.clear();
157
+ this.activeShellCallIds.clear();
158
+ this.ambiguousShellOutputCallIds.clear();
159
+ this.shellOutputDeltasByCallId.clear();
160
+ this.emittedTaskProgressCallIds.clear();
161
+ }
162
+
163
+ closeTraceBlock(): void {
164
+ this.contentEmitter.closeThinking();
165
+ }
166
+
167
+ flushText(deltas: string[]): string {
168
+ return this.contentEmitter.flushText(deltas);
169
+ }
170
+
171
+ handleDelta(update: InteractionUpdate): void {
172
+ if (update.type === "text-delta") {
173
+ this.textDeltas.push(update.text);
174
+ if (this.liveRun) {
175
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "text-delta", text: update.text });
176
+ } else {
177
+ this.contentEmitter.appendTextDelta(update.text);
178
+ }
179
+ return;
180
+ }
181
+ if (update.type === "thinking-delta") {
182
+ if (this.liveRun) {
183
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: update.text });
184
+ } else {
185
+ this.contentEmitter.appendThinkingDelta(update.text);
186
+ }
187
+ return;
188
+ }
189
+ if (update.type === "thinking-completed") {
190
+ if (this.liveRun) {
191
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-completed" });
192
+ } else {
193
+ this.contentEmitter.closeThinking();
194
+ }
195
+ return;
196
+ }
197
+ if (update.type === "partial-tool-call") {
198
+ this.maybeEmitCursorTaskProgress(update.callId, update.toolCall);
199
+ return;
200
+ }
201
+ if (update.type === "tool-call-started") {
202
+ if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(update.toolCall)) {
203
+ if (typeof update.callId === "string") this.bridgeStartedToolCallIds.add(update.callId);
204
+ } else {
205
+ this.maybeEmitCursorTaskProgress(update.callId, update.toolCall);
206
+ this.startedToolCalls.set(update.callId, update.toolCall);
207
+ if (isCursorShellToolCall(update.toolCall)) this.activeShellCallIds.add(update.callId);
208
+ }
209
+ return;
210
+ }
211
+ if (update.type === "tool-call-completed") {
212
+ const resolution = this.resolveToolCompletion({
213
+ source: "delta",
214
+ callId: update.callId,
215
+ toolCall: update.toolCall,
216
+ startedToolCall: this.startedToolCalls.get(update.callId),
217
+ });
218
+ if (resolution.action === "ignore-bridge") return;
219
+ this.handleCompletedToolCall(resolution.toolCall, {
220
+ identity: resolution.identity,
221
+ source: resolution.source,
222
+ });
223
+ return;
224
+ }
225
+ if (update.type === "shell-output-delta") {
226
+ const delta = getCursorShellOutputDelta(update);
227
+ if (delta) this.appendShellOutputDelta(delta);
228
+ return;
229
+ }
230
+ if (update.type === "summary") {
231
+ const summary = `Cursor summary: ${truncateCursorDisplayLine(update.summary)}\n`;
232
+ if (this.liveRun) {
233
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: summary });
234
+ } else {
235
+ this.contentEmitter.appendThinkingDelta(summary);
236
+ }
237
+ }
238
+ }
239
+
240
+ handleStep(stepEnvelope: unknown): void {
241
+ const stepType = getField(stepEnvelope, "type");
242
+ const step = getField(stepEnvelope, "message") ? stepEnvelope : undefined;
243
+ const rawStepToolCall = getField(step, "message");
244
+ if (stepType !== "toolCall") return;
245
+ const toolCall = rawStepToolCall;
246
+ const stepId = getField(stepEnvelope, "id") ?? getField(toolCall, "id") ?? getField(toolCall, "callId");
247
+ if (!toolCall) return;
248
+
249
+ const resolution = this.resolveToolCompletion({
250
+ source: "step",
251
+ callId: stepId,
252
+ toolCall,
253
+ });
254
+ if (resolution.action === "ignore-bridge") return;
255
+ this.handleCompletedToolCall(resolution.toolCall, {
256
+ identity: resolution.identity,
257
+ source: resolution.source,
258
+ });
259
+ if (resolution.matchedStartedCallId && resolution.matchedStartedCallId !== stepId) {
260
+ this.completedToolIdentities.add(`cursor-tool:${resolution.matchedStartedCallId}`);
261
+ }
262
+ }
263
+
264
+ private resolveToolCompletion(options: {
265
+ source: "delta" | "step";
266
+ callId: unknown;
267
+ toolCall: unknown;
268
+ startedToolCall?: unknown;
269
+ }): ToolCompletionResolution {
270
+ const bridgeStartedCallId = this.takeBridgeStartedToolCallId(options.callId);
271
+ if (bridgeStartedCallId) {
272
+ this.completedToolIdentities.add(`cursor-tool:${bridgeStartedCallId}`);
273
+ return { action: "ignore-bridge", identity: `cursor-tool:${bridgeStartedCallId}` };
274
+ }
275
+
276
+ let matchedStartedCallId: string | undefined;
277
+ let resolvedToolCall: unknown;
278
+ let identity: string | undefined;
279
+ let source: "started" | "fallback" | undefined;
280
+
281
+ if (options.source === "delta") {
282
+ const callId = options.callId;
283
+ identity = typeof callId === "string" ? `cursor-tool:${callId}` : undefined;
284
+ resolvedToolCall = mergeCursorToolCalls(options.startedToolCall, options.toolCall);
285
+ if (typeof callId === "string") {
286
+ this.clearStartedToolCall(callId);
287
+ }
288
+ resolvedToolCall = mergeShellOutputDeltasIntoCursorToolCall(
289
+ resolvedToolCall,
290
+ typeof callId === "string" ? this.takeShellOutputDeltas(callId) : undefined,
291
+ );
292
+ source = identity ? "started" : "fallback";
293
+ } else {
294
+ matchedStartedCallId = this.removeStartedToolCallForStep(options.toolCall, options.callId);
295
+ resolvedToolCall = mergeShellOutputDeltasIntoCursorToolCall(
296
+ options.toolCall,
297
+ matchedStartedCallId ? this.takeShellOutputDeltas(matchedStartedCallId) : undefined,
298
+ );
299
+ const identityId = typeof options.callId === "string" ? options.callId : matchedStartedCallId;
300
+ identity = identityId ? `cursor-tool:${identityId}` : undefined;
301
+ }
302
+
303
+ if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(resolvedToolCall)) {
304
+ const bridgeIdentity = options.source === "step" && matchedStartedCallId
305
+ ? `cursor-tool:${matchedStartedCallId}`
306
+ : identity;
307
+ if (bridgeIdentity) this.completedToolIdentities.add(bridgeIdentity);
308
+ return { action: "ignore-bridge", identity: bridgeIdentity };
309
+ }
310
+
311
+ if (options.source === "delta") {
312
+ return { action: "handle", toolCall: resolvedToolCall, identity, source };
313
+ }
314
+ return {
315
+ action: "handle",
316
+ toolCall: resolvedToolCall,
317
+ identity,
318
+ matchedStartedCallId,
319
+ };
320
+ }
321
+
322
+ private handleCompletedToolCall(
323
+ toolCall: unknown,
324
+ options: { identity?: string; source?: "started" | "fallback" } = {},
325
+ ): void {
326
+ const planText = getCursorCreatePlanText(toolCall);
327
+ if (planText) this.cursorPlanTextCandidate = scrubSensitiveText(planText, this.resolvedApiKey);
328
+
329
+ const transcript = scrubSensitiveText(formatCursorToolTranscript(toolCall, { cwd: this.cwd }), this.resolvedApiKey);
330
+ const display = buildCursorPiToolDisplay(toolCall, { cwd: this.cwd });
331
+ const fingerprint = this.getToolFingerprint({ toolName: display.toolName, args: display.args, result: display.result });
332
+ if (options.identity && this.completedToolIdentities.has(options.identity)) return;
333
+ if (options.source === "started") {
334
+ if (this.completedFallbackToolFingerprints.has(fingerprint)) return;
335
+ } else if (this.completedStartedToolFingerprints.has(fingerprint) || this.completedFallbackToolFingerprints.has(fingerprint)) {
336
+ return;
337
+ }
338
+ if (options.identity) this.completedToolIdentities.add(options.identity);
339
+ if (options.source === "started") {
340
+ this.completedStartedToolFingerprints.add(fingerprint);
341
+ } else {
342
+ this.completedFallbackToolFingerprints.add(fingerprint);
343
+ }
344
+
345
+ const disposition = resolveNativeReplayDisposition({
346
+ toolName: display.toolName,
347
+ useNativeToolReplay: this.useNativeToolReplay,
348
+ activeToolNames: this.activeToolNames,
349
+ hasLiveRun: this.liveRun !== undefined,
350
+ });
351
+
352
+ if (disposition === "queue_replay" && this.liveRun) {
353
+ this.nativeToolReplayStarted = true;
354
+ const id = `${this.nativeReplayId}-tool-${++this.nativeToolDisplayCounter}`;
355
+ const scrubbedDisplay = scrubPiToolDisplay(display, this.resolvedApiKey);
356
+ cursorLiveRuns.queueEvent(this.liveRun, {
357
+ type: "tool",
358
+ tool: { ...scrubbedDisplay, id },
359
+ });
360
+ return;
361
+ }
362
+
363
+ const traceText =
364
+ disposition === "inactive_trace"
365
+ ? formatInactiveCursorReplayTrace(display)
366
+ : transcript || `Cursor tool: ${formatCursorToolName(toolCall)} completed`;
367
+ this.emitCursorToolTrace(traceText);
368
+ }
369
+
370
+ private emitCursorToolTrace(text: string): void {
371
+ const traceText = text.endsWith("\n") ? text : `${text}\n`;
372
+ if (this.liveRun) {
373
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: traceText });
374
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-completed" });
375
+ return;
376
+ }
377
+ this.contentEmitter.appendThinkingBlock(traceText);
378
+ }
379
+
380
+ private maybeEmitCursorTaskProgress(callId: unknown, toolCall: unknown): void {
381
+ if (typeof callId !== "string" || this.emittedTaskProgressCallIds.has(callId)) return;
382
+ if (this.liveRun?.bridgeRun?.isBridgeMcpToolCall(toolCall)) return;
383
+ const label = extractCursorTaskProgressLabel(toolCall, this.resolvedApiKey);
384
+ if (!label) return;
385
+ this.emittedTaskProgressCallIds.add(callId);
386
+ this.emitCursorTaskProgress(label);
387
+ }
388
+
389
+ private emitCursorTaskProgress(label: string): void {
390
+ const progressText = `Cursor task: ${label}\n`;
391
+ if (this.liveRun) {
392
+ cursorLiveRuns.queueEvent(this.liveRun, { type: "thinking-delta", text: progressText });
393
+ return;
394
+ }
395
+ this.contentEmitter.appendThinkingDelta(progressText);
396
+ }
397
+
398
+ private getToolFingerprint(value: unknown): string {
399
+ try {
400
+ return JSON.stringify(value);
401
+ } catch {
402
+ return String(value);
403
+ }
404
+ }
405
+
406
+ private getStartedToolCallFingerprint(toolCall: unknown): string {
407
+ return this.getToolFingerprint({ toolName: getToolName(toolCall), args: getField(toolCall, "args") });
408
+ }
409
+
410
+ private clearStartedToolCall(callId: string): void {
411
+ this.startedToolCalls.delete(callId);
412
+ this.bridgeStartedToolCallIds.delete(callId);
413
+ this.activeShellCallIds.delete(callId);
414
+ this.ambiguousShellOutputCallIds.delete(callId);
415
+ }
416
+
417
+ private takeBridgeStartedToolCallId(callId: unknown): string | undefined {
418
+ if (typeof callId !== "string" || !this.bridgeStartedToolCallIds.has(callId)) return undefined;
419
+ this.bridgeStartedToolCallIds.delete(callId);
420
+ return callId;
421
+ }
422
+
423
+ private takeShellOutputDeltas(callId: string): CursorShellOutputDeltas | undefined {
424
+ const deltas = this.shellOutputDeltasByCallId.get(callId);
425
+ this.shellOutputDeltasByCallId.delete(callId);
426
+ return deltas;
427
+ }
428
+
429
+ private appendShellOutputDelta(delta: CursorShellOutputDelta): void {
430
+ if (this.activeShellCallIds.size !== 1) {
431
+ for (const activeCallId of this.activeShellCallIds) {
432
+ this.ambiguousShellOutputCallIds.add(activeCallId);
433
+ this.shellOutputDeltasByCallId.delete(activeCallId);
434
+ }
435
+ return;
436
+ }
437
+ const [callId] = this.activeShellCallIds;
438
+ if (!callId || this.ambiguousShellOutputCallIds.has(callId)) return;
439
+ let deltas = this.shellOutputDeltasByCallId.get(callId);
440
+ if (!deltas) {
441
+ deltas = { stdout: [], stderr: [] };
442
+ this.shellOutputDeltasByCallId.set(callId, deltas);
443
+ }
444
+ deltas[delta.stream].push(delta.data);
445
+ }
446
+
447
+ private removeStartedToolCallForStep(toolCall: unknown, stepId: unknown): string | undefined {
448
+ if (typeof stepId === "string" && this.startedToolCalls.has(stepId)) {
449
+ this.clearStartedToolCall(stepId);
450
+ return stepId;
451
+ }
452
+ const fingerprint = this.getStartedToolCallFingerprint(toolCall);
453
+ for (const [callId, startedToolCall] of this.startedToolCalls) {
454
+ if (this.getStartedToolCallFingerprint(startedToolCall) !== fingerprint) continue;
455
+ this.clearStartedToolCall(callId);
456
+ return callId;
457
+ }
458
+ return undefined;
459
+ }
460
+ }