can1357/oh-my-pi · error

Received thinking_delta for non-thinking content

Error message

Received thinking_delta for non-thinking content

What it means

thinking_delta requires the content slot at contentIndex to be a thinking block created by a preceding thinking_start. If the slot is missing or holds text/toolCall/image, the stream violates the proxy event contract and the library throws to protect message integrity.

Source

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

			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");
		}

		case "thinking_end": {
			const content = partial.content[proxyEvent.contentIndex];
			if (content?.type === "thinking") {
				content.thinkingSignature = proxyEvent.contentSignature;
				return {
					type: "thinking_end",
					contentIndex: proxyEvent.contentIndex,
					content: content.thinking,
					partial,
				};
			}
			throw new Error("Received thinking_end for non-thinking content");
		}

		case "image_end":
			partial.content[proxyEvent.contentIndex] = proxyEvent.content;

View on GitHub (pinned to 9690622007)

Solutions

  1. Emit thinking_start at the exact contentIndex before any thinking_delta
  2. Fix the adapter's index mapping for reasoning blocks (each block type must own its own index)
  3. Do not drop thinking_start events when filtering the stream
  4. Update proxy/agent packages to compatible versions

Example fix

// before
for (const chunk of reasoningChunks) yield { type: "thinking_delta", contentIndex: i, delta: chunk }
// after
yield { type: "thinking_start", contentIndex: i }
for (const chunk of reasoningChunks) yield { type: "thinking_delta", contentIndex: i, delta: chunk }
Defensive patterns

Strategy: type-guard

Validate before calling

if (partial.content[ev.contentIndex]?.type !== "thinking") {
  throw new Error(`thinking_delta at ${ev.contentIndex} without a thinking block`);
}

Type guard

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

Try / catch

try {
  processProxyEvent(model, ev, partial, partialJsonByIndex);
} catch (err) {
  if (err instanceof Error && err.message.includes("thinking_delta for non-thinking")) {
    logger.warn("Dropping orphan thinking_delta", { contentIndex: ev.contentIndex });
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: thinking_delta arrives without a prior thinking_start at that contentIndex; thinking_start was overwritten by another block; provider adapter emits thinking deltas under the wrong index.

Common situations: Models with extended thinking via a custom/relay provider whose adapter misindexes reasoning chunks; dropping thinking_start when filtering reasoning-only events; a provider that interleaves reasoning and text on shared indexes.

Related errors


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