@tanstack/ai-claude-code 0.6.12 → 0.7.2

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.
@@ -78,12 +78,15 @@ async function* translateSdkStream(sdkMessages, ctx) {
78
78
  let partialIsStructuredOutput = false;
79
79
  /** Anthropic message ids whose text/thinking already streamed via partials. */
80
80
  const streamedMessageIds = /* @__PURE__ */ new Set();
81
+ /** Tool-call ids already streamed via partials, so the complete message dedupes them. */
82
+ const streamedToolCallIds = /* @__PURE__ */ new Set();
81
83
  let partialMessageId = null;
82
- let partialBlockType = null;
84
+ const partialBlockTypes = /* @__PURE__ */ new Map();
83
85
  let partialTextMessageId = null;
84
86
  let partialTextContent = "";
85
87
  let partialTextStarted = false;
86
88
  let partialReasoningId = null;
89
+ const partialToolCalls = /* @__PURE__ */ new Map();
87
90
  function* startRun() {
88
91
  if (runStarted) return;
89
92
  runStarted = true;
@@ -135,6 +138,32 @@ async function* translateSdkStream(sdkMessages, ctx) {
135
138
  }
136
139
  partialReasoningId = null;
137
140
  }
141
+ function* closePartialToolUse(index, interrupted = false) {
142
+ const toolCall = partialToolCalls.get(index);
143
+ if (toolCall) {
144
+ let input = {};
145
+ try {
146
+ const parsed = toolCall.args ? JSON.parse(toolCall.args) : {};
147
+ input = parsed && typeof parsed === "object" ? parsed : {};
148
+ } catch (error) {
149
+ if (!interrupted) throw error;
150
+ }
151
+ partialToolCalls.delete(index);
152
+ yield {
153
+ type: EventType.TOOL_CALL_END,
154
+ toolCallId: toolCall.id,
155
+ toolCallName: toolCall.name,
156
+ toolName: toolCall.name,
157
+ model,
158
+ timestamp: now(),
159
+ input
160
+ };
161
+ unresolvedToolCalls.add(toolCall.id);
162
+ }
163
+ }
164
+ function* closePartialToolUses(interrupted = false) {
165
+ for (const index of [...partialToolCalls.keys()]) yield* closePartialToolUse(index, interrupted);
166
+ }
138
167
  function* emitStructuredOutput(object) {
139
168
  const raw = JSON.stringify(object);
140
169
  const messageId = genId();
@@ -252,7 +281,11 @@ async function* translateSdkStream(sdkMessages, ctx) {
252
281
  model,
253
282
  timestamp: now()
254
283
  };
255
- } else if (block.type === "tool_use") yield* emitToolUse(block);
284
+ } else if (block.type === "tool_use") {
285
+ const toolBlock = block;
286
+ if (streamedToolCallIds.has(toolBlock.id)) continue;
287
+ yield* emitToolUse(toolBlock);
288
+ }
256
289
  }
257
290
  function* handleUser(message) {
258
291
  const content = message.message.content;
@@ -279,6 +312,7 @@ async function* translateSdkStream(sdkMessages, ctx) {
279
312
  function* handleResult(message) {
280
313
  yield* closePartialText();
281
314
  yield* closePartialReasoning();
315
+ yield* closePartialToolUses();
282
316
  yield* synthesizeUnresolvedResults();
283
317
  const usage = buildUsage(message.usage, message.total_cost_usd);
284
318
  if (ctx.expectStructuredOutput === true) {
@@ -335,13 +369,14 @@ async function* translateSdkStream(sdkMessages, ctx) {
335
369
  partialMessageId = event.message.id ?? genId();
336
370
  streamedMessageIds.add(partialMessageId);
337
371
  } else if (event.type === "content_block_start") {
338
- partialBlockType = event.content_block.type;
339
- const startedBlock = event.content_block;
340
- partialIsStructuredOutput = ctx.expectStructuredOutput === true && startedBlock.type === "tool_use" && "name" in startedBlock && startedBlock.name === "StructuredOutput";
372
+ const block = event.content_block;
373
+ const partialBlockType = block.type;
374
+ partialBlockTypes.set(event.index, partialBlockType);
375
+ partialIsStructuredOutput = ctx.expectStructuredOutput === true && block.type === "tool_use" && block.name === "StructuredOutput";
341
376
  if (partialIsStructuredOutput) {
342
377
  partialStructuredJson = "";
343
- if ("id" in startedBlock && typeof startedBlock.id === "string") syntheticOutputToolIds.add(startedBlock.id);
344
- if ("input" in startedBlock) capturedStructuredOutput = rememberStructuredOutput(capturedStructuredOutput, startedBlock.input);
378
+ if (typeof block.id === "string") syntheticOutputToolIds.add(block.id);
379
+ if ("input" in block) capturedStructuredOutput = rememberStructuredOutput(capturedStructuredOutput, block.input);
345
380
  }
346
381
  if (partialBlockType === "text") {
347
382
  partialTextMessageId = partialMessageId ?? genId();
@@ -371,6 +406,23 @@ async function* translateSdkStream(sdkMessages, ctx) {
371
406
  model,
372
407
  timestamp: now()
373
408
  };
409
+ } else if (partialBlockType === "tool_use" && !partialIsStructuredOutput && typeof block.id === "string" && typeof block.name === "string") {
410
+ const name = stripMcpPrefix(block.name);
411
+ partialToolCalls.set(event.index, {
412
+ id: block.id,
413
+ name,
414
+ args: ""
415
+ });
416
+ streamedToolCallIds.add(block.id);
417
+ yield {
418
+ type: EventType.TOOL_CALL_START,
419
+ toolCallId: block.id,
420
+ toolCallName: name,
421
+ toolName: name,
422
+ model,
423
+ timestamp: now(),
424
+ ...partialMessageId !== null && { parentMessageId: partialMessageId }
425
+ };
374
426
  }
375
427
  } else if (event.type === "content_block_delta") {
376
428
  if (event.delta.type === "text_delta" && partialTextStarted && partialTextMessageId && typeof event.delta.text === "string") {
@@ -392,13 +444,28 @@ async function* translateSdkStream(sdkMessages, ctx) {
392
444
  model,
393
445
  timestamp: now()
394
446
  };
447
+ else if (event.delta.type === "input_json_delta" && typeof event.delta.partial_json === "string") {
448
+ const toolCall = partialToolCalls.get(event.index);
449
+ if (toolCall) {
450
+ toolCall.args += event.delta.partial_json;
451
+ yield {
452
+ type: EventType.TOOL_CALL_ARGS,
453
+ toolCallId: toolCall.id,
454
+ model,
455
+ timestamp: now(),
456
+ delta: event.delta.partial_json
457
+ };
458
+ }
459
+ }
395
460
  } else if (event.type === "content_block_stop") {
461
+ const partialBlockType = partialBlockTypes.get(event.index);
462
+ partialBlockTypes.delete(event.index);
396
463
  if (partialIsStructuredOutput && partialStructuredJson !== "") try {
397
464
  capturedStructuredOutput = rememberStructuredOutput(capturedStructuredOutput, JSON.parse(partialStructuredJson));
398
465
  } catch {}
399
466
  if (partialBlockType === "text") yield* closePartialText();
400
467
  else if (partialBlockType === "thinking") yield* closePartialReasoning();
401
- partialBlockType = null;
468
+ else if (partialBlockType === "tool_use") yield* closePartialToolUse(event.index);
402
469
  partialIsStructuredOutput = false;
403
470
  partialStructuredJson = "";
404
471
  }
@@ -436,6 +503,7 @@ async function* translateSdkStream(sdkMessages, ctx) {
436
503
  } else if (sdkMessage.type === "result") yield* handleResult(sdkMessage);
437
504
  }
438
505
  } catch (error) {
506
+ yield* closePartialToolUses(true);
439
507
  yield* synthesizeUnresolvedResults();
440
508
  throw error;
441
509
  }
@@ -1 +1 @@
1
- {"version":3,"file":"translate.js","names":[],"sources":["../../../src/stream/translate.ts"],"sourcesContent":["import { EventType, buildBaseUsage } from '@tanstack/ai'\nimport {\n parseJsonFromAssistantText,\n structuredOutputCompleteChunk,\n structuredOutputStartChunk,\n} from '@tanstack/ai/adapter-internals'\nimport type { AdapterYieldChunk, TokenUsage } from '@tanstack/ai'\nimport type {\n AgentSdkMessage,\n SdkAssistantMessage,\n SdkPartialAssistantMessage,\n SdkResultMessage,\n SdkToolResultContent,\n SdkUsage,\n SdkUserMessage,\n} from './sdk-types'\n\n/** Name of the CUSTOM event carrying the Claude Code session id. */\nexport const SESSION_ID_EVENT = 'claude-code.session-id'\n\n/** Server name used for bridged TanStack tools (model sees `mcp__tanstack__<name>`). */\nexport const BRIDGED_MCP_SERVER_NAME = 'tanstack'\n\nconst BRIDGED_MCP_PREFIX = `mcp__${BRIDGED_MCP_SERVER_NAME}__`\n\n/**\n * Claude Code `--json-schema` injects a fake tool named StructuredOutput.\n * The model \"calls\" it with the schema JSON. The result message may omit\n * `structured_output`; harvest the tool input in that case.\n */\nexport const SYNTHETIC_STRUCTURED_OUTPUT_TOOL = 'StructuredOutput'\n\n/** Claude Code-specific usage details attached to RUN_FINISHED usage. */\nexport type ClaudeCodeProviderUsageDetails = {\n /** Total cost of the harness run in USD, as reported by Claude Code. */\n totalCostUsd?: number\n}\n\nexport interface TranslateContext {\n model: string\n runId: string\n threadId: string\n parentRunId?: string\n genId: () => string\n /** Called as soon as the harness reports its session id. */\n onSessionId?: (sessionId: string) => void\n /** Called for each raw SDK message, for logging. */\n onSdkMessage?: (message: AgentSdkMessage) => void\n /** Emit structured-output events from `result.structured_output`. */\n expectStructuredOutput?: boolean\n}\n\n/**\n * Strip the bridged MCP server prefix so tool-call events match the TanStack\n * tool names the application registered. Built-in harness tools (Bash, Read,\n * Edit, ...) and foreign MCP tools pass through verbatim.\n */\nexport function stripMcpPrefix(name: string): string {\n return name.startsWith(BRIDGED_MCP_PREFIX)\n ? name.slice(BRIDGED_MCP_PREFIX.length)\n : name\n}\n\nfunction isUsefulStructuredObject(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length > 0\n )\n}\n\nfunction rememberStructuredOutput(current: unknown, next: unknown): unknown {\n return isUsefulStructuredObject(next) ? next : current\n}\n\nfunction stringifyToolResultContent(\n content: SdkToolResultContent | undefined,\n): string {\n if (content === undefined) return ''\n if (typeof content === 'string') return content\n return content\n .map((block) => (typeof block.text === 'string' ? block.text : ''))\n .join('')\n}\n\nfunction buildUsage(\n usage: SdkUsage | undefined,\n totalCostUsd: number | undefined,\n): TokenUsage<ClaudeCodeProviderUsageDetails> | undefined {\n if (!usage) return undefined\n const promptTokens = usage.input_tokens ?? 0\n const completionTokens = usage.output_tokens ?? 0\n const result = buildBaseUsage<ClaudeCodeProviderUsageDetails>({\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n })\n const cacheWrite = usage.cache_creation_input_tokens\n const cacheRead = usage.cache_read_input_tokens\n const promptTokensDetails = {\n ...(cacheWrite ? { cacheWriteTokens: cacheWrite } : {}),\n ...(cacheRead ? { cachedTokens: cacheRead } : {}),\n }\n if (Object.keys(promptTokensDetails).length > 0) {\n result.promptTokensDetails = promptTokensDetails\n }\n if (totalCostUsd !== undefined) {\n result.providerUsageDetails = { totalCostUsd }\n }\n return result\n}\n\n/**\n * Translate a Claude Code Agent SDK message stream into AG-UI StreamChunk\n * events.\n *\n * The harness runs its own agent loop and executes its own tools, so the\n * translation always ends with `finishReason: 'stop'` (or `'length'` /\n * RUN_ERROR) — never `'tool_calls'`. Harness tool activity is emitted as\n * already-resolved TOOL_CALL_START/ARGS/END + TOOL_CALL_RESULT sequences so\n * UIs can render it, while the TanStack engine never tries to execute them.\n *\n * Invariant: every TOOL_CALL_START is eventually paired with a\n * TOOL_CALL_RESULT (synthesized as `{\"status\":\"interrupted\"}` when the run\n * ends or aborts before the harness reported one) so the engine's\n * pending-tool-call scan on the next request never force-executes them.\n */\nexport async function* translateSdkStream(\n sdkMessages: AsyncIterable<AgentSdkMessage>,\n ctx: TranslateContext,\n): AsyncIterable<AdapterYieldChunk> {\n const { model, runId, threadId, genId } = ctx\n const now = () => Date.now()\n\n let runStarted = false\n /** Tool calls started but with no result yet. */\n const unresolvedToolCalls = new Set<string>()\n const syntheticOutputToolIds = new Set<string>()\n let capturedStructuredOutput: unknown\n let assistantTextForHarvest = ''\n let partialStructuredJson = ''\n let partialIsStructuredOutput = false\n /** Anthropic message ids whose text/thinking already streamed via partials. */\n const streamedMessageIds = new Set<string>()\n\n // Partial-stream state\n let partialMessageId: string | null = null\n let partialBlockType: string | null = null\n let partialTextMessageId: string | null = null\n let partialTextContent = ''\n let partialTextStarted = false\n let partialReasoningId: string | null = null\n\n function* startRun(): Generator<AdapterYieldChunk> {\n if (runStarted) return\n runStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model,\n timestamp: now(),\n ...(ctx.parentRunId !== undefined && { parentRunId: ctx.parentRunId }),\n }\n }\n\n function* synthesizeUnresolvedResults(): Generator<AdapterYieldChunk> {\n for (const toolCallId of unresolvedToolCalls) {\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId,\n messageId: genId(),\n model,\n timestamp: now(),\n content: JSON.stringify({ status: 'interrupted' }),\n }\n }\n unresolvedToolCalls.clear()\n }\n\n function* closePartialText(): Generator<AdapterYieldChunk> {\n if (partialTextStarted && partialTextMessageId) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: partialTextMessageId,\n model,\n timestamp: now(),\n }\n }\n partialTextStarted = false\n partialTextMessageId = null\n partialTextContent = ''\n }\n\n function* closePartialReasoning(): Generator<AdapterYieldChunk> {\n if (partialReasoningId) {\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: partialReasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: partialReasoningId,\n model,\n timestamp: now(),\n }\n }\n partialReasoningId = null\n }\n\n function* emitStructuredOutput(\n object: unknown,\n ): Generator<AdapterYieldChunk> {\n const raw = JSON.stringify(object)\n const messageId = genId()\n yield structuredOutputStartChunk({\n messageId,\n model,\n threadId,\n runId,\n })\n yield structuredOutputCompleteChunk({\n messageId,\n model,\n threadId,\n runId,\n object,\n raw,\n })\n }\n\n function* emitToolUse(block: {\n id: string\n name: string\n input: unknown\n }): Generator<AdapterYieldChunk> {\n const toolCallName = stripMcpPrefix(block.name)\n if (\n ctx.expectStructuredOutput === true &&\n toolCallName === SYNTHETIC_STRUCTURED_OUTPUT_TOOL\n ) {\n capturedStructuredOutput = rememberStructuredOutput(\n capturedStructuredOutput,\n block.input,\n )\n syntheticOutputToolIds.add(block.id)\n return\n }\n const args = JSON.stringify(block.input ?? {})\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: block.id,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: block.id,\n model,\n timestamp: now(),\n delta: args,\n args,\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: block.id,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n input: block.input ?? {},\n }\n unresolvedToolCalls.add(block.id)\n }\n\n function* handleAssistant(\n message: SdkAssistantMessage,\n ): Generator<AdapterYieldChunk> {\n const alreadyStreamed =\n message.message.id !== undefined &&\n streamedMessageIds.has(message.message.id)\n\n for (const block of message.message.content) {\n if (block.type === 'text') {\n if (alreadyStreamed) continue\n const messageId = message.message.id ?? genId()\n const text = (block as { text: string }).text\n if (!alreadyStreamed) assistantTextForHarvest += text\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp: now(),\n role: 'assistant',\n }\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp: now(),\n delta: text,\n content: text,\n }\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n model,\n timestamp: now(),\n }\n } else if (block.type === 'thinking') {\n if (alreadyStreamed) continue\n const reasoningId = genId()\n const thinking = (block as { thinking: string }).thinking\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningId,\n role: 'reasoning' as const,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningId,\n delta: thinking,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n } else if (block.type === 'tool_use') {\n yield* emitToolUse(\n block as { id: string; name: string; input: unknown },\n )\n }\n }\n }\n\n function* handleUser(message: SdkUserMessage): Generator<AdapterYieldChunk> {\n const content = message.message.content\n if (typeof content === 'string') return\n for (const block of content) {\n if (block.type !== 'tool_result') continue\n const toolResult = block as {\n tool_use_id: string\n content?: SdkToolResultContent\n is_error?: boolean\n }\n if (syntheticOutputToolIds.has(toolResult.tool_use_id)) {\n syntheticOutputToolIds.delete(toolResult.tool_use_id)\n continue\n }\n unresolvedToolCalls.delete(toolResult.tool_use_id)\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: toolResult.tool_use_id,\n messageId: genId(),\n model,\n timestamp: now(),\n content: stringifyToolResultContent(toolResult.content),\n ...(toolResult.is_error === true && { state: 'output-error' as const }),\n }\n }\n }\n\n function* handleResult(\n message: SdkResultMessage,\n ): Generator<AdapterYieldChunk> {\n yield* closePartialText()\n yield* closePartialReasoning()\n yield* synthesizeUnresolvedResults()\n\n const usage = buildUsage(message.usage, message.total_cost_usd)\n if (ctx.expectStructuredOutput === true) {\n const fromResult = isUsefulStructuredObject(message.structured_output)\n ? message.structured_output\n : undefined\n const fromTool = isUsefulStructuredObject(capturedStructuredOutput)\n ? capturedStructuredOutput\n : undefined\n let fromText: unknown\n if (fromResult === undefined && fromTool === undefined) {\n const raw = assistantTextForHarvest || message.result || ''\n if (raw.trim() !== '') {\n try {\n fromText = parseJsonFromAssistantText(raw)\n } catch {\n fromText = undefined\n }\n }\n }\n const object = fromResult ?? fromTool ?? fromText\n if (isUsefulStructuredObject(object)) {\n yield* emitStructuredOutput(object)\n }\n }\n if (message.subtype === 'success') {\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: now(),\n finishReason: 'stop',\n ...(usage !== undefined && { usage }),\n }\n } else if (message.subtype === 'error_max_turns') {\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: now(),\n finishReason: 'length',\n ...(usage !== undefined && { usage }),\n }\n } else {\n const errorMessage =\n message.errors && message.errors.length > 0\n ? message.errors.join('; ')\n : `Claude Code run failed: ${message.subtype}`\n yield {\n type: EventType.RUN_ERROR,\n model,\n timestamp: now(),\n message: errorMessage,\n code: message.subtype,\n error: { message: errorMessage, code: message.subtype },\n }\n }\n }\n\n function* handleStreamEvent(\n message: SdkPartialAssistantMessage,\n ): Generator<AdapterYieldChunk> {\n const event = message.event\n if (event.type === 'message_start') {\n partialMessageId = event.message.id ?? genId()\n streamedMessageIds.add(partialMessageId)\n } else if (event.type === 'content_block_start') {\n partialBlockType = event.content_block.type\n const startedBlock = event.content_block\n partialIsStructuredOutput =\n ctx.expectStructuredOutput === true &&\n startedBlock.type === 'tool_use' &&\n 'name' in startedBlock &&\n startedBlock.name === SYNTHETIC_STRUCTURED_OUTPUT_TOOL\n if (partialIsStructuredOutput) {\n partialStructuredJson = ''\n if ('id' in startedBlock && typeof startedBlock.id === 'string') {\n syntheticOutputToolIds.add(startedBlock.id)\n }\n if ('input' in startedBlock) {\n capturedStructuredOutput = rememberStructuredOutput(\n capturedStructuredOutput,\n startedBlock.input,\n )\n }\n }\n if (partialBlockType === 'text') {\n partialTextMessageId = partialMessageId ?? genId()\n partialTextContent = ''\n if (!partialTextStarted) {\n partialTextStarted = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: partialTextMessageId,\n model,\n timestamp: now(),\n role: 'assistant',\n }\n }\n } else if (partialBlockType === 'thinking') {\n partialReasoningId = genId()\n yield {\n type: EventType.REASONING_START,\n messageId: partialReasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: partialReasoningId,\n role: 'reasoning' as const,\n model,\n timestamp: now(),\n }\n }\n } else if (event.type === 'content_block_delta') {\n if (\n event.delta.type === 'text_delta' &&\n partialTextStarted &&\n partialTextMessageId &&\n typeof event.delta.text === 'string'\n ) {\n partialTextContent += event.delta.text\n assistantTextForHarvest += event.delta.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: partialTextMessageId,\n model,\n timestamp: now(),\n delta: event.delta.text,\n content: partialTextContent,\n }\n } else if (\n event.delta.type === 'input_json_delta' &&\n partialIsStructuredOutput &&\n typeof event.delta.partial_json === 'string'\n ) {\n partialStructuredJson += event.delta.partial_json\n } else if (\n event.delta.type === 'thinking_delta' &&\n partialReasoningId &&\n typeof event.delta.thinking === 'string'\n ) {\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: partialReasoningId,\n delta: event.delta.thinking,\n model,\n timestamp: now(),\n }\n }\n } else if (event.type === 'content_block_stop') {\n if (partialIsStructuredOutput && partialStructuredJson !== '') {\n try {\n capturedStructuredOutput = rememberStructuredOutput(\n capturedStructuredOutput,\n JSON.parse(partialStructuredJson),\n )\n } catch {\n // Incomplete JSON; the complete assistant tool_use may still arrive.\n }\n }\n if (partialBlockType === 'text') {\n yield* closePartialText()\n } else if (partialBlockType === 'thinking') {\n yield* closePartialReasoning()\n }\n partialBlockType = null\n partialIsStructuredOutput = false\n partialStructuredJson = ''\n }\n }\n\n try {\n for await (const sdkMessage of sdkMessages) {\n ctx.onSdkMessage?.(sdkMessage)\n\n if (sdkMessage.type === 'system' && sdkMessage.subtype === 'init') {\n yield* startRun()\n ctx.onSessionId?.(sdkMessage.session_id)\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: SESSION_ID_EVENT,\n value: {\n sessionId: sdkMessage.session_id,\n model: sdkMessage.model,\n tools: sdkMessage.tools,\n skills: sdkMessage.skills ?? [],\n },\n }\n continue\n }\n\n // Anything before init still needs RUN_STARTED first.\n yield* startRun()\n\n if (sdkMessage.type === 'stream_event') {\n if (sdkMessage.parent_tool_use_id !== null) continue\n yield* handleStreamEvent(sdkMessage)\n } else if (sdkMessage.type === 'assistant') {\n if (sdkMessage.parent_tool_use_id !== null) continue\n yield* handleAssistant(sdkMessage)\n } else if (sdkMessage.type === 'user') {\n if (sdkMessage.parent_tool_use_id !== null) continue\n yield* handleUser(sdkMessage)\n } else if (sdkMessage.type === 'result') {\n yield* handleResult(sdkMessage)\n }\n // All other SDK message types (status, hooks, notifications, ...) are\n // harness-internal and intentionally ignored.\n }\n } catch (error) {\n // The run is dying (abort or SDK failure). Pair any started tool calls\n // with a synthetic result first so the next request's pending-tool-call\n // scan doesn't try to execute them, then let the adapter surface the\n // error as RUN_ERROR.\n yield* synthesizeUnresolvedResults()\n throw error\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"translate.js","names":[],"sources":["../../../src/stream/translate.ts"],"sourcesContent":["import { EventType, buildBaseUsage } from '@tanstack/ai'\nimport {\n parseJsonFromAssistantText,\n structuredOutputCompleteChunk,\n structuredOutputStartChunk,\n} from '@tanstack/ai/adapter-internals'\nimport type { AdapterYieldChunk, TokenUsage } from '@tanstack/ai'\nimport type {\n AgentSdkMessage,\n SdkAssistantMessage,\n SdkPartialAssistantMessage,\n SdkResultMessage,\n SdkToolResultContent,\n SdkUsage,\n SdkUserMessage,\n} from './sdk-types'\n\n/** Name of the CUSTOM event carrying the Claude Code session id. */\nexport const SESSION_ID_EVENT = 'claude-code.session-id'\n\n/** Server name used for bridged TanStack tools (model sees `mcp__tanstack__<name>`). */\nexport const BRIDGED_MCP_SERVER_NAME = 'tanstack'\n\nconst BRIDGED_MCP_PREFIX = `mcp__${BRIDGED_MCP_SERVER_NAME}__`\n\n/**\n * Claude Code `--json-schema` injects a fake tool named StructuredOutput.\n * The model \"calls\" it with the schema JSON. The result message may omit\n * `structured_output`; harvest the tool input in that case.\n */\nexport const SYNTHETIC_STRUCTURED_OUTPUT_TOOL = 'StructuredOutput'\n\n/** Claude Code-specific usage details attached to RUN_FINISHED usage. */\nexport type ClaudeCodeProviderUsageDetails = {\n /** Total cost of the harness run in USD, as reported by Claude Code. */\n totalCostUsd?: number\n}\n\nexport interface TranslateContext {\n model: string\n runId: string\n threadId: string\n parentRunId?: string\n genId: () => string\n /** Called as soon as the harness reports its session id. */\n onSessionId?: (sessionId: string) => void\n /** Called for each raw SDK message, for logging. */\n onSdkMessage?: (message: AgentSdkMessage) => void\n /** Emit structured-output events from `result.structured_output`. */\n expectStructuredOutput?: boolean\n}\n\n/**\n * Strip the bridged MCP server prefix so tool-call events match the TanStack\n * tool names the application registered. Built-in harness tools (Bash, Read,\n * Edit, ...) and foreign MCP tools pass through verbatim.\n */\nexport function stripMcpPrefix(name: string): string {\n return name.startsWith(BRIDGED_MCP_PREFIX)\n ? name.slice(BRIDGED_MCP_PREFIX.length)\n : name\n}\n\nfunction isUsefulStructuredObject(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length > 0\n )\n}\n\nfunction rememberStructuredOutput(current: unknown, next: unknown): unknown {\n return isUsefulStructuredObject(next) ? next : current\n}\n\nfunction stringifyToolResultContent(\n content: SdkToolResultContent | undefined,\n): string {\n if (content === undefined) return ''\n if (typeof content === 'string') return content\n return content\n .map((block) => (typeof block.text === 'string' ? block.text : ''))\n .join('')\n}\n\nfunction buildUsage(\n usage: SdkUsage | undefined,\n totalCostUsd: number | undefined,\n): TokenUsage<ClaudeCodeProviderUsageDetails> | undefined {\n if (!usage) return undefined\n const promptTokens = usage.input_tokens ?? 0\n const completionTokens = usage.output_tokens ?? 0\n const result = buildBaseUsage<ClaudeCodeProviderUsageDetails>({\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n })\n const cacheWrite = usage.cache_creation_input_tokens\n const cacheRead = usage.cache_read_input_tokens\n const promptTokensDetails = {\n ...(cacheWrite ? { cacheWriteTokens: cacheWrite } : {}),\n ...(cacheRead ? { cachedTokens: cacheRead } : {}),\n }\n if (Object.keys(promptTokensDetails).length > 0) {\n result.promptTokensDetails = promptTokensDetails\n }\n if (totalCostUsd !== undefined) {\n result.providerUsageDetails = { totalCostUsd }\n }\n return result\n}\n\n/**\n * Translate a Claude Code Agent SDK message stream into AG-UI StreamChunk\n * events.\n *\n * The harness runs its own agent loop and executes its own tools, so the\n * translation always ends with `finishReason: 'stop'` (or `'length'` /\n * RUN_ERROR) — never `'tool_calls'`. Harness tool activity is emitted as\n * already-resolved TOOL_CALL_START/ARGS/END + TOOL_CALL_RESULT sequences so\n * UIs can render it, while the TanStack engine never tries to execute them.\n *\n * Invariant: every TOOL_CALL_START is eventually paired with a\n * TOOL_CALL_RESULT (synthesized as `{\"status\":\"interrupted\"}` when the run\n * ends or aborts before the harness reported one) so the engine's\n * pending-tool-call scan on the next request never force-executes them.\n */\nexport async function* translateSdkStream(\n sdkMessages: AsyncIterable<AgentSdkMessage>,\n ctx: TranslateContext,\n): AsyncIterable<AdapterYieldChunk> {\n const { model, runId, threadId, genId } = ctx\n const now = () => Date.now()\n\n let runStarted = false\n /** Tool calls started but with no result yet. */\n const unresolvedToolCalls = new Set<string>()\n const syntheticOutputToolIds = new Set<string>()\n let capturedStructuredOutput: unknown\n let assistantTextForHarvest = ''\n let partialStructuredJson = ''\n let partialIsStructuredOutput = false\n /** Anthropic message ids whose text/thinking already streamed via partials. */\n const streamedMessageIds = new Set<string>()\n /** Tool-call ids already streamed via partials, so the complete message dedupes them. */\n const streamedToolCallIds = new Set<string>()\n\n // Partial-stream state\n let partialMessageId: string | null = null\n const partialBlockTypes = new Map<number, string>()\n let partialTextMessageId: string | null = null\n let partialTextContent = ''\n let partialTextStarted = false\n let partialReasoningId: string | null = null\n const partialToolCalls = new Map<\n number,\n { id: string; name: string; args: string }\n >()\n\n function* startRun(): Generator<AdapterYieldChunk> {\n if (runStarted) return\n runStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model,\n timestamp: now(),\n ...(ctx.parentRunId !== undefined && { parentRunId: ctx.parentRunId }),\n }\n }\n\n function* synthesizeUnresolvedResults(): Generator<AdapterYieldChunk> {\n for (const toolCallId of unresolvedToolCalls) {\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId,\n messageId: genId(),\n model,\n timestamp: now(),\n content: JSON.stringify({ status: 'interrupted' }),\n }\n }\n unresolvedToolCalls.clear()\n }\n\n function* closePartialText(): Generator<AdapterYieldChunk> {\n if (partialTextStarted && partialTextMessageId) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: partialTextMessageId,\n model,\n timestamp: now(),\n }\n }\n partialTextStarted = false\n partialTextMessageId = null\n partialTextContent = ''\n }\n\n function* closePartialReasoning(): Generator<AdapterYieldChunk> {\n if (partialReasoningId) {\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: partialReasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: partialReasoningId,\n model,\n timestamp: now(),\n }\n }\n partialReasoningId = null\n }\n\n function* closePartialToolUse(\n index: number,\n interrupted = false,\n ): Generator<AdapterYieldChunk> {\n const toolCall = partialToolCalls.get(index)\n if (toolCall) {\n let input: unknown = {}\n try {\n const parsed = toolCall.args ? JSON.parse(toolCall.args) : {}\n input = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (error) {\n if (!interrupted) throw error\n }\n partialToolCalls.delete(index)\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model,\n timestamp: now(),\n input,\n }\n unresolvedToolCalls.add(toolCall.id)\n }\n }\n\n function* closePartialToolUses(\n interrupted = false,\n ): Generator<AdapterYieldChunk> {\n for (const index of [...partialToolCalls.keys()]) {\n yield* closePartialToolUse(index, interrupted)\n }\n }\n\n function* emitStructuredOutput(\n object: unknown,\n ): Generator<AdapterYieldChunk> {\n const raw = JSON.stringify(object)\n const messageId = genId()\n yield structuredOutputStartChunk({\n messageId,\n model,\n threadId,\n runId,\n })\n yield structuredOutputCompleteChunk({\n messageId,\n model,\n threadId,\n runId,\n object,\n raw,\n })\n }\n\n function* emitToolUse(block: {\n id: string\n name: string\n input: unknown\n }): Generator<AdapterYieldChunk> {\n const toolCallName = stripMcpPrefix(block.name)\n if (\n ctx.expectStructuredOutput === true &&\n toolCallName === SYNTHETIC_STRUCTURED_OUTPUT_TOOL\n ) {\n capturedStructuredOutput = rememberStructuredOutput(\n capturedStructuredOutput,\n block.input,\n )\n syntheticOutputToolIds.add(block.id)\n return\n }\n const args = JSON.stringify(block.input ?? {})\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: block.id,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: block.id,\n model,\n timestamp: now(),\n delta: args,\n args,\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: block.id,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n input: block.input ?? {},\n }\n unresolvedToolCalls.add(block.id)\n }\n\n function* handleAssistant(\n message: SdkAssistantMessage,\n ): Generator<AdapterYieldChunk> {\n const alreadyStreamed =\n message.message.id !== undefined &&\n streamedMessageIds.has(message.message.id)\n\n for (const block of message.message.content) {\n if (block.type === 'text') {\n if (alreadyStreamed) continue\n const messageId = message.message.id ?? genId()\n const text = (block as { text: string }).text\n if (!alreadyStreamed) assistantTextForHarvest += text\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp: now(),\n role: 'assistant',\n }\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp: now(),\n delta: text,\n content: text,\n }\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n model,\n timestamp: now(),\n }\n } else if (block.type === 'thinking') {\n if (alreadyStreamed) continue\n const reasoningId = genId()\n const thinking = (block as { thinking: string }).thinking\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningId,\n role: 'reasoning' as const,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningId,\n delta: thinking,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n } else if (block.type === 'tool_use') {\n const toolBlock = block as { id: string; name: string; input: unknown }\n // Skip the tool call if the partial stream already emitted it.\n if (streamedToolCallIds.has(toolBlock.id)) continue\n yield* emitToolUse(toolBlock)\n }\n }\n }\n\n function* handleUser(message: SdkUserMessage): Generator<AdapterYieldChunk> {\n const content = message.message.content\n if (typeof content === 'string') return\n for (const block of content) {\n if (block.type !== 'tool_result') continue\n const toolResult = block as {\n tool_use_id: string\n content?: SdkToolResultContent\n is_error?: boolean\n }\n if (syntheticOutputToolIds.has(toolResult.tool_use_id)) {\n syntheticOutputToolIds.delete(toolResult.tool_use_id)\n continue\n }\n unresolvedToolCalls.delete(toolResult.tool_use_id)\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: toolResult.tool_use_id,\n messageId: genId(),\n model,\n timestamp: now(),\n content: stringifyToolResultContent(toolResult.content),\n ...(toolResult.is_error === true && { state: 'output-error' as const }),\n }\n }\n }\n\n function* handleResult(\n message: SdkResultMessage,\n ): Generator<AdapterYieldChunk> {\n yield* closePartialText()\n yield* closePartialReasoning()\n yield* closePartialToolUses()\n yield* synthesizeUnresolvedResults()\n\n const usage = buildUsage(message.usage, message.total_cost_usd)\n if (ctx.expectStructuredOutput === true) {\n const fromResult = isUsefulStructuredObject(message.structured_output)\n ? message.structured_output\n : undefined\n const fromTool = isUsefulStructuredObject(capturedStructuredOutput)\n ? capturedStructuredOutput\n : undefined\n let fromText: unknown\n if (fromResult === undefined && fromTool === undefined) {\n const raw = assistantTextForHarvest || message.result || ''\n if (raw.trim() !== '') {\n try {\n fromText = parseJsonFromAssistantText(raw)\n } catch {\n fromText = undefined\n }\n }\n }\n const object = fromResult ?? fromTool ?? fromText\n if (isUsefulStructuredObject(object)) {\n yield* emitStructuredOutput(object)\n }\n }\n if (message.subtype === 'success') {\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: now(),\n finishReason: 'stop',\n ...(usage !== undefined && { usage }),\n }\n } else if (message.subtype === 'error_max_turns') {\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: now(),\n finishReason: 'length',\n ...(usage !== undefined && { usage }),\n }\n } else {\n const errorMessage =\n message.errors && message.errors.length > 0\n ? message.errors.join('; ')\n : `Claude Code run failed: ${message.subtype}`\n yield {\n type: EventType.RUN_ERROR,\n model,\n timestamp: now(),\n message: errorMessage,\n code: message.subtype,\n error: { message: errorMessage, code: message.subtype },\n }\n }\n }\n\n function* handleStreamEvent(\n message: SdkPartialAssistantMessage,\n ): Generator<AdapterYieldChunk> {\n const event = message.event\n if (event.type === 'message_start') {\n partialMessageId = event.message.id ?? genId()\n streamedMessageIds.add(partialMessageId)\n } else if (event.type === 'content_block_start') {\n const block = event.content_block\n const partialBlockType = block.type\n partialBlockTypes.set(event.index, partialBlockType)\n partialIsStructuredOutput =\n ctx.expectStructuredOutput === true &&\n block.type === 'tool_use' &&\n block.name === SYNTHETIC_STRUCTURED_OUTPUT_TOOL\n if (partialIsStructuredOutput) {\n partialStructuredJson = ''\n if (typeof block.id === 'string') {\n syntheticOutputToolIds.add(block.id)\n }\n if ('input' in block) {\n capturedStructuredOutput = rememberStructuredOutput(\n capturedStructuredOutput,\n block.input,\n )\n }\n }\n if (partialBlockType === 'text') {\n partialTextMessageId = partialMessageId ?? genId()\n partialTextContent = ''\n if (!partialTextStarted) {\n partialTextStarted = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: partialTextMessageId,\n model,\n timestamp: now(),\n role: 'assistant',\n }\n }\n } else if (partialBlockType === 'thinking') {\n partialReasoningId = genId()\n yield {\n type: EventType.REASONING_START,\n messageId: partialReasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: partialReasoningId,\n role: 'reasoning' as const,\n model,\n timestamp: now(),\n }\n } else if (\n partialBlockType === 'tool_use' &&\n !partialIsStructuredOutput &&\n typeof block.id === 'string' &&\n typeof block.name === 'string'\n ) {\n const name = stripMcpPrefix(block.name)\n partialToolCalls.set(event.index, { id: block.id, name, args: '' })\n streamedToolCallIds.add(block.id)\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: block.id,\n toolCallName: name,\n toolName: name,\n model,\n timestamp: now(),\n ...(partialMessageId !== null && {\n parentMessageId: partialMessageId,\n }),\n }\n }\n } else if (event.type === 'content_block_delta') {\n if (\n event.delta.type === 'text_delta' &&\n partialTextStarted &&\n partialTextMessageId &&\n typeof event.delta.text === 'string'\n ) {\n partialTextContent += event.delta.text\n assistantTextForHarvest += event.delta.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: partialTextMessageId,\n model,\n timestamp: now(),\n delta: event.delta.text,\n content: partialTextContent,\n }\n } else if (\n event.delta.type === 'input_json_delta' &&\n partialIsStructuredOutput &&\n typeof event.delta.partial_json === 'string'\n ) {\n partialStructuredJson += event.delta.partial_json\n } else if (\n event.delta.type === 'thinking_delta' &&\n partialReasoningId &&\n typeof event.delta.thinking === 'string'\n ) {\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: partialReasoningId,\n delta: event.delta.thinking,\n model,\n timestamp: now(),\n }\n } else if (\n event.delta.type === 'input_json_delta' &&\n typeof event.delta.partial_json === 'string'\n ) {\n const toolCall = partialToolCalls.get(event.index)\n if (toolCall) {\n toolCall.args += event.delta.partial_json\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: toolCall.id,\n model,\n timestamp: now(),\n delta: event.delta.partial_json,\n }\n }\n }\n } else if (event.type === 'content_block_stop') {\n const partialBlockType = partialBlockTypes.get(event.index)\n partialBlockTypes.delete(event.index)\n if (partialIsStructuredOutput && partialStructuredJson !== '') {\n try {\n capturedStructuredOutput = rememberStructuredOutput(\n capturedStructuredOutput,\n JSON.parse(partialStructuredJson),\n )\n } catch {\n // Incomplete JSON; the complete assistant tool_use may still arrive.\n }\n }\n if (partialBlockType === 'text') {\n yield* closePartialText()\n } else if (partialBlockType === 'thinking') {\n yield* closePartialReasoning()\n } else if (partialBlockType === 'tool_use') {\n yield* closePartialToolUse(event.index)\n }\n partialIsStructuredOutput = false\n partialStructuredJson = ''\n }\n }\n\n try {\n for await (const sdkMessage of sdkMessages) {\n ctx.onSdkMessage?.(sdkMessage)\n\n if (sdkMessage.type === 'system' && sdkMessage.subtype === 'init') {\n yield* startRun()\n ctx.onSessionId?.(sdkMessage.session_id)\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: SESSION_ID_EVENT,\n value: {\n sessionId: sdkMessage.session_id,\n model: sdkMessage.model,\n tools: sdkMessage.tools,\n skills: sdkMessage.skills ?? [],\n },\n }\n continue\n }\n\n // Anything before init still needs RUN_STARTED first.\n yield* startRun()\n\n if (sdkMessage.type === 'stream_event') {\n if (sdkMessage.parent_tool_use_id !== null) continue\n yield* handleStreamEvent(sdkMessage)\n } else if (sdkMessage.type === 'assistant') {\n if (sdkMessage.parent_tool_use_id !== null) continue\n yield* handleAssistant(sdkMessage)\n } else if (sdkMessage.type === 'user') {\n if (sdkMessage.parent_tool_use_id !== null) continue\n yield* handleUser(sdkMessage)\n } else if (sdkMessage.type === 'result') {\n yield* handleResult(sdkMessage)\n }\n // All other SDK message types (status, hooks, notifications, ...) are\n // harness-internal and intentionally ignored.\n }\n } catch (error) {\n // The run is dying (abort or SDK failure). Close any half-streamed tool\n // calls, then pair every started tool call with a synthetic result so the\n // next request's pending-tool-call scan doesn't try to execute them. Then\n // let the adapter surface the error as RUN_ERROR.\n yield* closePartialToolUses(true)\n yield* synthesizeUnresolvedResults()\n throw error\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-claude-code",
3
- "version": "0.6.12",
3
+ "version": "0.7.2",
4
4
  "description": "Claude Code harness adapter for TanStack AI — run Claude Code as a chat backend with local tool execution and stateful sessions.",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -41,14 +41,14 @@
41
41
  "src"
42
42
  ],
43
43
  "peerDependencies": {
44
- "@tanstack/ai": "^0.61.0",
45
- "@tanstack/ai-sandbox": "^0.5.15"
44
+ "@tanstack/ai": "^0.64.0",
45
+ "@tanstack/ai-sandbox": "^0.5.18"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@vitest/coverage-v8": "4.1.10",
49
- "@tanstack/ai": "0.61.0",
50
- "@tanstack/ai-sandbox": "0.5.15",
51
- "@tanstack/ai-sandbox-local-process": "0.2.5"
49
+ "@tanstack/ai": "0.64.0",
50
+ "@tanstack/ai-sandbox": "0.5.18",
51
+ "@tanstack/ai-sandbox-local-process": "0.2.6"
52
52
  },
53
53
  "scripts": {
54
54
  "build": "vite build",
@@ -56,6 +56,7 @@
56
56
  "lint:fix": "oxlint src --type-aware --fix",
57
57
  "test:build": "publint --strict",
58
58
  "test:oxlint": "oxlint src --type-aware",
59
+ "test:coverage": "vitest run --coverage --coverage.include='src/**' --coverage.reporter=text-summary --coverage.reporter=json-summary",
59
60
  "test:lib": "vitest",
60
61
  "test:lib:dev": "pnpm test:lib --watch",
61
62
  "test:types": "tsc"
@@ -143,14 +143,20 @@ export async function* translateSdkStream(
143
143
  let partialIsStructuredOutput = false
144
144
  /** Anthropic message ids whose text/thinking already streamed via partials. */
145
145
  const streamedMessageIds = new Set<string>()
146
+ /** Tool-call ids already streamed via partials, so the complete message dedupes them. */
147
+ const streamedToolCallIds = new Set<string>()
146
148
 
147
149
  // Partial-stream state
148
150
  let partialMessageId: string | null = null
149
- let partialBlockType: string | null = null
151
+ const partialBlockTypes = new Map<number, string>()
150
152
  let partialTextMessageId: string | null = null
151
153
  let partialTextContent = ''
152
154
  let partialTextStarted = false
153
155
  let partialReasoningId: string | null = null
156
+ const partialToolCalls = new Map<
157
+ number,
158
+ { id: string; name: string; args: string }
159
+ >()
154
160
 
155
161
  function* startRun(): Generator<AdapterYieldChunk> {
156
162
  if (runStarted) return
@@ -211,6 +217,41 @@ export async function* translateSdkStream(
211
217
  partialReasoningId = null
212
218
  }
213
219
 
220
+ function* closePartialToolUse(
221
+ index: number,
222
+ interrupted = false,
223
+ ): Generator<AdapterYieldChunk> {
224
+ const toolCall = partialToolCalls.get(index)
225
+ if (toolCall) {
226
+ let input: unknown = {}
227
+ try {
228
+ const parsed = toolCall.args ? JSON.parse(toolCall.args) : {}
229
+ input = parsed && typeof parsed === 'object' ? parsed : {}
230
+ } catch (error) {
231
+ if (!interrupted) throw error
232
+ }
233
+ partialToolCalls.delete(index)
234
+ yield {
235
+ type: EventType.TOOL_CALL_END,
236
+ toolCallId: toolCall.id,
237
+ toolCallName: toolCall.name,
238
+ toolName: toolCall.name,
239
+ model,
240
+ timestamp: now(),
241
+ input,
242
+ }
243
+ unresolvedToolCalls.add(toolCall.id)
244
+ }
245
+ }
246
+
247
+ function* closePartialToolUses(
248
+ interrupted = false,
249
+ ): Generator<AdapterYieldChunk> {
250
+ for (const index of [...partialToolCalls.keys()]) {
251
+ yield* closePartialToolUse(index, interrupted)
252
+ }
253
+ }
254
+
214
255
  function* emitStructuredOutput(
215
256
  object: unknown,
216
257
  ): Generator<AdapterYieldChunk> {
@@ -349,9 +390,10 @@ export async function* translateSdkStream(
349
390
  timestamp: now(),
350
391
  }
351
392
  } else if (block.type === 'tool_use') {
352
- yield* emitToolUse(
353
- block as { id: string; name: string; input: unknown },
354
- )
393
+ const toolBlock = block as { id: string; name: string; input: unknown }
394
+ // Skip the tool call if the partial stream already emitted it.
395
+ if (streamedToolCallIds.has(toolBlock.id)) continue
396
+ yield* emitToolUse(toolBlock)
355
397
  }
356
398
  }
357
399
  }
@@ -388,6 +430,7 @@ export async function* translateSdkStream(
388
430
  ): Generator<AdapterYieldChunk> {
389
431
  yield* closePartialText()
390
432
  yield* closePartialReasoning()
433
+ yield* closePartialToolUses()
391
434
  yield* synthesizeUnresolvedResults()
392
435
 
393
436
  const usage = buildUsage(message.usage, message.total_cost_usd)
@@ -458,22 +501,22 @@ export async function* translateSdkStream(
458
501
  partialMessageId = event.message.id ?? genId()
459
502
  streamedMessageIds.add(partialMessageId)
460
503
  } else if (event.type === 'content_block_start') {
461
- partialBlockType = event.content_block.type
462
- const startedBlock = event.content_block
504
+ const block = event.content_block
505
+ const partialBlockType = block.type
506
+ partialBlockTypes.set(event.index, partialBlockType)
463
507
  partialIsStructuredOutput =
464
508
  ctx.expectStructuredOutput === true &&
465
- startedBlock.type === 'tool_use' &&
466
- 'name' in startedBlock &&
467
- startedBlock.name === SYNTHETIC_STRUCTURED_OUTPUT_TOOL
509
+ block.type === 'tool_use' &&
510
+ block.name === SYNTHETIC_STRUCTURED_OUTPUT_TOOL
468
511
  if (partialIsStructuredOutput) {
469
512
  partialStructuredJson = ''
470
- if ('id' in startedBlock && typeof startedBlock.id === 'string') {
471
- syntheticOutputToolIds.add(startedBlock.id)
513
+ if (typeof block.id === 'string') {
514
+ syntheticOutputToolIds.add(block.id)
472
515
  }
473
- if ('input' in startedBlock) {
516
+ if ('input' in block) {
474
517
  capturedStructuredOutput = rememberStructuredOutput(
475
518
  capturedStructuredOutput,
476
- startedBlock.input,
519
+ block.input,
477
520
  )
478
521
  }
479
522
  }
@@ -505,6 +548,26 @@ export async function* translateSdkStream(
505
548
  model,
506
549
  timestamp: now(),
507
550
  }
551
+ } else if (
552
+ partialBlockType === 'tool_use' &&
553
+ !partialIsStructuredOutput &&
554
+ typeof block.id === 'string' &&
555
+ typeof block.name === 'string'
556
+ ) {
557
+ const name = stripMcpPrefix(block.name)
558
+ partialToolCalls.set(event.index, { id: block.id, name, args: '' })
559
+ streamedToolCallIds.add(block.id)
560
+ yield {
561
+ type: EventType.TOOL_CALL_START,
562
+ toolCallId: block.id,
563
+ toolCallName: name,
564
+ toolName: name,
565
+ model,
566
+ timestamp: now(),
567
+ ...(partialMessageId !== null && {
568
+ parentMessageId: partialMessageId,
569
+ }),
570
+ }
508
571
  }
509
572
  } else if (event.type === 'content_block_delta') {
510
573
  if (
@@ -541,8 +604,25 @@ export async function* translateSdkStream(
541
604
  model,
542
605
  timestamp: now(),
543
606
  }
607
+ } else if (
608
+ event.delta.type === 'input_json_delta' &&
609
+ typeof event.delta.partial_json === 'string'
610
+ ) {
611
+ const toolCall = partialToolCalls.get(event.index)
612
+ if (toolCall) {
613
+ toolCall.args += event.delta.partial_json
614
+ yield {
615
+ type: EventType.TOOL_CALL_ARGS,
616
+ toolCallId: toolCall.id,
617
+ model,
618
+ timestamp: now(),
619
+ delta: event.delta.partial_json,
620
+ }
621
+ }
544
622
  }
545
623
  } else if (event.type === 'content_block_stop') {
624
+ const partialBlockType = partialBlockTypes.get(event.index)
625
+ partialBlockTypes.delete(event.index)
546
626
  if (partialIsStructuredOutput && partialStructuredJson !== '') {
547
627
  try {
548
628
  capturedStructuredOutput = rememberStructuredOutput(
@@ -557,8 +637,9 @@ export async function* translateSdkStream(
557
637
  yield* closePartialText()
558
638
  } else if (partialBlockType === 'thinking') {
559
639
  yield* closePartialReasoning()
640
+ } else if (partialBlockType === 'tool_use') {
641
+ yield* closePartialToolUse(event.index)
560
642
  }
561
- partialBlockType = null
562
643
  partialIsStructuredOutput = false
563
644
  partialStructuredJson = ''
564
645
  }
@@ -605,10 +686,11 @@ export async function* translateSdkStream(
605
686
  // harness-internal and intentionally ignored.
606
687
  }
607
688
  } catch (error) {
608
- // The run is dying (abort or SDK failure). Pair any started tool calls
609
- // with a synthetic result first so the next request's pending-tool-call
610
- // scan doesn't try to execute them, then let the adapter surface the
611
- // error as RUN_ERROR.
689
+ // The run is dying (abort or SDK failure). Close any half-streamed tool
690
+ // calls, then pair every started tool call with a synthetic result so the
691
+ // next request's pending-tool-call scan doesn't try to execute them. Then
692
+ // let the adapter surface the error as RUN_ERROR.
693
+ yield* closePartialToolUses(true)
612
694
  yield* synthesizeUnresolvedResults()
613
695
  throw error
614
696
  }