can1357/oh-my-pi · error
Received text_end for non-text content
Error message
Received text_end for non-text content
What it means
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.
Source
Thrown at packages/agent/src/proxy.ts:287
delta: proxyEvent.delta,
partial,
};
}
throw new Error("Received text_delta for non-text content");
}
case "text_end": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "text") {
content.textSignature = proxyEvent.contentSignature;
return {
type: "text_end",
contentIndex: proxyEvent.contentIndex,
content: content.text,
partial,
};
}
throw new Error("Received text_end for non-text content");
}
case "thinking_start":
partial.content[proxyEvent.contentIndex] = { type: "thinking", thinking: "" };
return { type: "thinking_start", contentIndex: proxyEvent.contentIndex, partial };
case "thinking_delta": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "thinking") {
content.thinking += proxyEvent.delta;
return {
type: "thinking_delta",
contentIndex: proxyEvent.contentIndex,
delta: proxyEvent.delta,
partial,
};
}
throw new Error("Received thinking_delta for non-thinking content");View on GitHub (pinned to 9690622007)
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
Defensive patterns
Strategy: type-guard
Validate before calling
if (partial.content[ev.contentIndex]?.type !== "text") {
throw new Error(`text_end at ${ev.contentIndex} without a text block`);
} Type guard
function isTextSlot(partial: AssistantMessage, i: number): boolean {
return partial.content[i]?.type === "text";
} Try / catch
try {
processProxyEvent(model, ev, partial, partialJsonByIndex);
} catch (err) {
if (err instanceof Error && err.message.includes("text_end for non-text")) {
logger.warn("Ignoring orphan text_end", { contentIndex: ev.contentIndex });
return;
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Received text_delta for non-text content
- Received thinking_delta for non-thinking content
- Received thinking_end for non-thinking content
- Received toolcall_delta for non-toolCall 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/4e0c88dd3c87af35.
Report an issue: GitHub.