{"record":{"id":"a56a8f39c1d2fa0b","repo":"can1357/oh-my-pi","slug":"received-toolcall-delta-for-non-toolcall-content","errorCode":null,"errorMessage":"Received toolcall_delta for non-toolCall content","messagePattern":"Received toolcall_delta for non-toolCall content","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/proxy.ts","lineNumber":356,"sourceCode":"\t\t\t} as ToolCall & StreamingPartialJsonCarrier;\n\t\t\tpartialJsonByIndex.set(proxyEvent.contentIndex, \"\");\n\t\t\treturn { type: \"toolcall_start\", contentIndex: proxyEvent.contentIndex, partial };\n\t\tcase \"toolcall_delta\": {\n\t\t\tconst content = partial.content[proxyEvent.contentIndex];\n\t\t\tif (content?.type === \"toolCall\") {\n\t\t\t\tconst acc = (partialJsonByIndex.get(proxyEvent.contentIndex) ?? \"\") + proxyEvent.delta;\n\t\t\t\tpartialJsonByIndex.set(proxyEvent.contentIndex, acc);\n\t\t\t\tcontent.arguments = parseStreamingJson(acc) || {};\n\t\t\t\tsetStreamingPartialJson(content, acc);\n\t\t\t\tpartial.content[proxyEvent.contentIndex] = { ...content }; // Trigger reactivity\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"toolcall_delta\",\n\t\t\t\t\tcontentIndex: proxyEvent.contentIndex,\n\t\t\t\t\tdelta: proxyEvent.delta,\n\t\t\t\t\tpartial,\n\t\t\t\t};\n\t\t\t}\n\t\t\tthrow new Error(\"Received toolcall_delta for non-toolCall content\");\n\t\t}\n\n\t\tcase \"toolcall_end\": {\n\t\t\tconst content = partial.content[proxyEvent.contentIndex];\n\t\t\tif (content?.type === \"toolCall\") {\n\t\t\t\tpartialJsonByIndex.delete(proxyEvent.contentIndex);\n\t\t\t\tclearStreamingPartialJson(content);\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"toolcall_end\",\n\t\t\t\t\tcontentIndex: proxyEvent.contentIndex,\n\t\t\t\t\ttoolCall: content,\n\t\t\t\t\tpartial,\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\n\t\tcase \"done\":","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/agent/src/proxy.ts#L338-L374","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: delta keyed by provider tool index\nyield { type: \"toolcall_delta\", contentIndex: toolChunk.index, delta: toolChunk.args }\n// after: map to the contentIndex used at toolcall_start\nyield { type: \"toolcall_delta\", contentIndex: indexMap.get(toolChunk.index), delta: toolChunk.args }","handlingStrategy":"validation","validationCode":"const slot = partial.content[ev.contentIndex];\nif (slot?.type !== \"toolCall\" || !partialJsonByIndex.has(ev.contentIndex)) {\n  throw new Error(`toolcall_delta at ${ev.contentIndex} without an open toolCall`);\n}","typeGuard":"function isOpenToolCall(partial: AssistantMessage, i: number, acc: Map<number, string>): boolean {\n  return partial.content[i]?.type === \"toolCall\" && acc.has(i);\n}","tryCatchPattern":"try {\n  processProxyEvent(model, ev, partial, partialJsonByIndex);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"toolcall_delta for non-toolCall\")) {\n    logger.warn(\"Dropping orphan toolcall_delta\", { contentIndex: ev.contentIndex });\n    return;\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["streaming","protocol","tool-call"],"backgroundTag":"stream-out-of-order","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}