can1357/oh-my-pi · error

Received text_delta for non-text content

Error message

Received text_delta for non-text content

What it means

processProxyEvent folds streamed proxy events into an in-progress AssistantMessage. A text_delta must land on a content slot already initialized as a text block by a preceding text_start at the same contentIndex. When the slot is missing, empty (hole), or holds a different block type (thinking/toolCall/image), the proxy protocol is out of order or inconsistent, so the library throws rather than corrupting the message.

Source

Thrown at packages/agent/src/proxy.ts:273

			(partial as { stopReason?: string }).stopReason = undefined;
			return { type: "start", partial };

		case "text_start":
			partial.content[proxyEvent.contentIndex] = { type: "text", text: "" };
			return { type: "text_start", contentIndex: proxyEvent.contentIndex, partial };

		case "text_delta": {
			const content = partial.content[proxyEvent.contentIndex];
			if (content?.type === "text") {
				content.text += proxyEvent.delta;
				return {
					type: "text_delta",
					contentIndex: proxyEvent.contentIndex,
					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: "" };

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure every text_delta is preceded by a text_start with the identical contentIndex
  2. Check the provider adapter/translator so native delta events map to the same contentIndex as their start event
  3. Stop filtering, deduplicating, or reordering proxy events before passing them to processProxyEvent
  4. Upgrade the proxy/agent packages to matching versions so event shapes agree

Example fix

// before: emitting deltas without a start
yield { type: "text_delta", contentIndex: 0, delta: chunk }
// after: always start the block first
yield { type: "text_start", contentIndex: 0 }
yield { type: "text_delta", contentIndex: 0, delta: chunk }
Defensive patterns

Strategy: type-guard

Validate before calling

const slot = partial.content[ev.contentIndex];
if (slot?.type !== "text") throw new Error(`text_delta at index ${ev.contentIndex} has no text block (got ${slot?.type ?? "empty"})`);

Type guard

function hasTextBlock(partial: AssistantMessage, i: number): boolean {
  return partial.content[i]?.type === "text";
}

Try / catch

try {
  const ev2 = processProxyEvent(model, ev, partial, partialJsonByIndex);
} catch (err) {
  if (err instanceof Error && err.message.includes("text_delta for non-text")) {
    logger.warn("Dropping misordered text_delta", { contentIndex: ev.contentIndex });
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: An upstream/provider event stream emits text_delta with a contentIndex that never got a text_start, or a text_start at index N was overwritten by another block type at index N, or events were dropped/reordered between the proxy and processProxyEvent.

Common situations: A custom or third-party API adapter mis-translating native chunks into proxy events; skipping text_start while replaying or filtering events; concurrent writes to the same contentIndex from interleaved streams; consuming events from a snapshot of the stream after a retry re-numbered content blocks.

Related errors


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