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
- Emit thinking_start at the exact contentIndex before any thinking_delta
- Fix the adapter's index mapping for reasoning blocks (each block type must own its own index)
- Do not drop thinking_start events when filtering the stream
- 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
- Emit thinking_start before any thinking_delta at the same index
- Give thinking blocks their own contentIndex separate from text/toolCall
- Don't strip thinking_start when hiding reasoning from the UI — filter downstream instead
- Verify adapters against extended-thinking models
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
- Received thinking_end for non-thinking content
- Received text_delta for non-text content
- Received text_end for non-text 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/a6bed8229248bcd8.
Report an issue: GitHub.