can1357/oh-my-pi · error
Received toolcall_delta for non-toolCall content
Error message
Received toolcall_delta for non-toolCall content
What it means
toolcall_delta accumulates streamed JSON argument fragments for a toolCall block; the slot at contentIndex must be a toolCall block created by toolcall_start. If the slot is absent or another type, argument chunks cannot be attributed, so the library throws instead of merging JSON into the wrong block.
Source
Thrown at packages/agent/src/proxy.ts:356
} as ToolCall & StreamingPartialJsonCarrier;
partialJsonByIndex.set(proxyEvent.contentIndex, "");
return { type: "toolcall_start", contentIndex: proxyEvent.contentIndex, partial };
case "toolcall_delta": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "toolCall") {
const acc = (partialJsonByIndex.get(proxyEvent.contentIndex) ?? "") + proxyEvent.delta;
partialJsonByIndex.set(proxyEvent.contentIndex, acc);
content.arguments = parseStreamingJson(acc) || {};
setStreamingPartialJson(content, acc);
partial.content[proxyEvent.contentIndex] = { ...content }; // Trigger reactivity
return {
type: "toolcall_delta",
contentIndex: proxyEvent.contentIndex,
delta: proxyEvent.delta,
partial,
};
}
throw new Error("Received toolcall_delta for non-toolCall content");
}
case "toolcall_end": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "toolCall") {
partialJsonByIndex.delete(proxyEvent.contentIndex);
clearStreamingPartialJson(content);
return {
type: "toolcall_end",
contentIndex: proxyEvent.contentIndex,
toolCall: content,
partial,
};
}
return undefined;
}
case "done":View on GitHub (pinned to 9690622007)
Solutions
- Ensure toolcall_start precedes every toolcall_delta with the identical contentIndex and that partialJsonByIndex is populated
- Fix the adapter's tool-call index mapping (provider tool index -> proxy contentIndex)
- Don't overwrite a toolCall slot with another block type while deltas are still streaming
- Reset the stream state (including partialJsonByIndex) on retry instead of resuming mid-call
Example fix
// before: delta keyed by provider tool index
yield { type: "toolcall_delta", contentIndex: toolChunk.index, delta: toolChunk.args }
// after: map to the contentIndex used at toolcall_start
yield { type: "toolcall_delta", contentIndex: indexMap.get(toolChunk.index), delta: toolChunk.args } Defensive patterns
Strategy: validation
Validate before calling
const slot = partial.content[ev.contentIndex];
if (slot?.type !== "toolCall" || !partialJsonByIndex.has(ev.contentIndex)) {
throw new Error(`toolcall_delta at ${ev.contentIndex} without an open toolCall`);
} Type guard
function isOpenToolCall(partial: AssistantMessage, i: number, acc: Map<number, string>): boolean {
return partial.content[i]?.type === "toolCall" && acc.has(i);
} Try / catch
try {
processProxyEvent(model, ev, partial, partialJsonByIndex);
} catch (err) {
if (err instanceof Error && err.message.includes("toolcall_delta for non-toolCall")) {
logger.warn("Dropping orphan toolcall_delta", { contentIndex: ev.contentIndex });
return;
}
throw err;
} Prevention
- Map provider tool indexes to proxy contentIndex consistently in the adapter
- Start each tool call with toolcall_start and its partialJsonByIndex entry
- Never overwrite a toolCall slot before toolcall_end
- Reset stream state (content array + partialJsonByIndex) on reconnect/retry
When it happens
Trigger: toolcall_delta without a preceding toolcall_start at the same index; the slot was overwritten by text/thinking; adapter indexes tool-call argument chunks differently from the start event; duplicate toolcall_start cleared by a later toolcall_end then deltas keep arriving.
Common situations: OpenAI-style providers that stream tool-call argument deltas with a per-tool index the adapter mis-maps; parallel tool calls where indexes collide; replaying partial streams after reconnect.
Related errors
- Received text_delta for non-text content
- Received text_end for non-text content
- Received thinking_delta for non-thinking content
- Received thinking_end for non-thinking content
- Devin Connect frame length ${len} exceeds ${MAX_CONNECT_FRAM
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a56a8f39c1d2fa0b.
Report an issue: GitHub.