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

  1. Verify the adapter emits text_start/text_delta/text_end for the same contentIndex
  2. Log the partial.content array when this fires to see which type actually occupies the index
  3. Ensure no other block type is assigned to the same contentIndex during the text block's lifetime
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/4e0c88dd3c87af35. Report an issue: GitHub.