{"record":{"id":"4e0c88dd3c87af35","repo":"can1357/oh-my-pi","slug":"received-text-end-for-non-text-content","errorCode":null,"errorMessage":"Received text_end for non-text content","messagePattern":"Received text_end for non-text content","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/proxy.ts","lineNumber":287,"sourceCode":"\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 text_delta for non-text content\");\n\t\t}\n\n\t\tcase \"text_end\": {\n\t\t\tconst content = partial.content[proxyEvent.contentIndex];\n\t\t\tif (content?.type === \"text\") {\n\t\t\t\tcontent.textSignature = proxyEvent.contentSignature;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\tcontentIndex: proxyEvent.contentIndex,\n\t\t\t\t\tcontent: content.text,\n\t\t\t\t\tpartial,\n\t\t\t\t};\n\t\t\t}\n\t\t\tthrow new Error(\"Received text_end for non-text content\");\n\t\t}\n\n\t\tcase \"thinking_start\":\n\t\t\tpartial.content[proxyEvent.contentIndex] = { type: \"thinking\", thinking: \"\" };\n\t\t\treturn { type: \"thinking_start\", contentIndex: proxyEvent.contentIndex, partial };\n\n\t\tcase \"thinking_delta\": {\n\t\t\tconst content = partial.content[proxyEvent.contentIndex];\n\t\t\tif (content?.type === \"thinking\") {\n\t\t\t\tcontent.thinking += proxyEvent.delta;\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"thinking_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 thinking_delta for non-thinking content\");","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/agent/src/proxy.ts#L269-L305","documentation":"Same contract as text_delta: text_end finalizes the text block at proxyEvent.contentIndex, so the slot must already contain a block of type \"text\". If the slot is absent or holds another content type, the event stream is inconsistent with what processProxyEvent built, and it throws instead of finalizing the wrong block.","triggerScenarios":"text_end arrives for a contentIndex with no prior text_start, or the block at that index was replaced by a thinking/toolCall/image block, or text_end is delivered twice / after the block was overwritten.","commonSituations":"Provider adapters that emit end events on a different index than the deltas; replaying a captured event log where a start was dropped; mixed-version proxy/agent where the content array layout changed.","solutions":["Verify the adapter emits text_start/text_delta/text_end for the same contentIndex","Log the partial.content array when this fires to see which type actually occupies the index","Ensure no other block type is assigned to the same contentIndex during the text block's lifetime","Align agent/proxy package versions"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"if (partial.content[ev.contentIndex]?.type !== \"text\") {\n  throw new Error(`text_end at ${ev.contentIndex} without a text block`);\n}","typeGuard":"function isTextSlot(partial: AssistantMessage, i: number): boolean {\n  return partial.content[i]?.type === \"text\";\n}","tryCatchPattern":"try {\n  processProxyEvent(model, ev, partial, partialJsonByIndex);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"text_end for non-text\")) {\n    logger.warn(\"Ignoring orphan text_end\", { contentIndex: ev.contentIndex });\n    return;\n  }\n  throw err;\n}","preventionTips":["Pair every text_start with exactly one text_end at the same index","Don't overwrite a content slot while its block is open","Replay event logs verbatim, without dropping events","Test adapters against streamed models that interleave content types"],"tags":["streaming","protocol","proxy"],"backgroundTag":"stream-out-of-order","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}