Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions apps/server/src/anthropicAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,6 @@ export function openAIChatStreamToAnthropicStream(

const reader = upstream.getReader();
let buffer = "";
let nextBlockIndex = 0;
let textBlockOpen = false;
let textBlockStarted = false;
let finalStopReason = "end_turn";
Expand Down Expand Up @@ -480,12 +479,16 @@ export function openAIChatStreamToAnthropicStream(
}
}

// Flush: close text block, then emit tool_use blocks.
// Flush: close text block, then emit tool_use blocks. Tool blocks
// follow the text block sequentially so their indices are contiguous
// (text=0, tool1=1, tool2=2, …) — Claude Code uses `index` to match
// content_block_start/delta/stop events, so collisions break tool calls.
closeTextBlock();
let toolBlockIndex = textBlockStarted ? 1 : 0;
for (const [idx, tc] of [...toolCallsByIndex.entries()].sort((a, b) => a[0] - b[0])) {
if (emittedToolStarts.has(idx)) continue;
emittedToolStarts.add(idx);
const blockIndex = textBlockStarted ? nextBlockIndex + 1 : nextBlockIndex;
const blockIndex = toolBlockIndex++;
// Anthropic streaming tool_use protocol:
// 1. content_block_start with EMPTY input ({}) — Claude Code expects
// input to arrive via input_json_delta, not in the start event.
Expand All @@ -505,7 +508,6 @@ export function openAIChatStreamToAnthropicStream(
type: "content_block_stop",
index: blockIndex,
});
nextBlockIndex = blockIndex;
}

enqueue("message_delta", {
Expand Down
7 changes: 6 additions & 1 deletion apps/server/src/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,12 @@ export function chatStreamToResponsesStream(
.map((line) => line.slice(5).trim())
.join("\n");
if (!data || data === "[DONE]") continue;
const chunk = JSON.parse(data) as unknown;
let chunk: unknown;
try {
chunk = JSON.parse(data);
} catch {
continue;
}
if (!isRecord(chunk) || !Array.isArray(chunk.choices)) continue;
for (const choice of chunk.choices) {
if (!isRecord(choice) || !isRecord(choice.delta)) continue;
Expand Down
Loading