can1357/oh-my-pi · error

Received thinking_end for non-thinking content

Error message

Received thinking_end for non-thinking content

What it means

thinking_end attaches the content signature to the thinking block at contentIndex; the slot must already be a thinking block. A missing or mismatched slot means the event stream is inconsistent with the partially built AssistantMessage, so the library throws rather than writing a signature onto the wrong block.

Source

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

					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;
			return {
				type: "image_end",
				contentIndex: proxyEvent.contentIndex,
				content: proxyEvent.content,
				partial,
			};

		case "toolcall_start":
			partial.content[proxyEvent.contentIndex] = {
				type: "toolCall",
				id: proxyEvent.id,
				name: proxyEvent.toolName,
				arguments: {},
				[kStreamingPartialJson]: "",

View on GitHub (pinned to 9690622007)

Solutions

  1. Confirm the adapter closes reasoning on the same contentIndex it opened
  2. Inspect partial.content at failure time to find the actual block type occupying the index
  3. Avoid sharing one contentIndex between thinking and text blocks
  4. Align package versions between proxy producer and agent consumer
Defensive patterns

Strategy: type-guard

Validate before calling

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

Type guard

function canCloseThinking(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_end for non-thinking")) {
    logger.warn("Ignoring orphan thinking_end", { contentIndex: ev.contentIndex });
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: thinking_end for an index that never received thinking_start; the block at the index was replaced by text/toolCall/image; duplicate or out-of-order thinking_end events.

Common situations: Custom providers whose reasoning stream ends on a shifted index; event replay where the start was pruned; providers that reuse indexes across block types in one turn.

Related errors


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