can1357/oh-my-pi · error · AIError.ValidationError

Cursor ${targetModelId} cannot continue history from a diffe

Error message

Cursor ${targetModelId} cannot continue history from a different model (${msg.provider}/${msg.model}); start a new session.

What it means

Cursor K3 models reconstruct prior assistant turns' signed reasoning from the conversation history. If any prior assistant message was produced by a different model (different provider/model id), there is no K3-signed thinking to replay, so the library throws ValidationError telling you to start a new session instead of silently corrupting context.

Source

Thrown at packages/ai/src/providers/cursor.ts:4785

function assertCursorKimiK3HistoryReplayable(
	messages: Message[],
	activeUserMessageIndex: number,
	targetModelId: string | undefined,
): void {
	if (!targetModelId || classifyModel("cursor", targetModelId).family !== "k3") return;
	const historyEnd = activeUserMessageIndex >= 0 ? activeUserMessageIndex : messages.length;
	const missingThinkingTurns: number[] = [];
	const newlyWarnedKeys: string[] = [];
	let assistantTurn = 0;
	for (let i = 0; i < historyEnd; i++) {
		const msg = messages[i];
		if (msg.role !== "assistant") continue;
		assistantTurn++;
		const isSameCursorModel = msg.api === "cursor-agent" && msg.provider === "cursor" && msg.model === targetModelId;
		if (!isSameCursorModel) {
			// Foreign history genuinely cannot replay K3 thinking: another model's
			// turns carry no K3-signed reasoning to reconstruct.
			throw new AIError.ValidationError(
				`Cursor ${targetModelId} cannot continue history from a different model (${msg.provider}/${msg.model}); start a new session.`,
			);
		}
		const hasThinking = msg.content.some(item => item.type === "thinking" && item.thinking.length > 0);
		if (hasThinking) continue;
		const warningKey = `${msg.api}\0${msg.provider}\0${msg.model}\0${msg.timestamp}`;
		if (warnedCursorKimiK3ReplayMessages.has(warningKey)) continue;
		missingThinkingTurns.push(assistantTurn);
		newlyWarnedKeys.push(warningKey);
	}
	if (missingThinkingTurns.length === 0) return;
	for (const key of newlyWarnedKeys) warnedCursorKimiK3ReplayMessages.add(key);
	logger.warn(
		`Cursor kimi-k3 history contains same-model assistant turn(s) ${missingThinkingTurns.join(", ")} without thinking blocks; replaying those spans without reasoning may make generation less stable`,
		{ model: targetModelId, assistantTurns: missingThinkingTurns },
	);
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Start a new session/conversation when switching to a different Cursor K3 model, as the message says.
  2. Filter or truncate history to only assistant turns from the exact same cursor model before calling.
  3. Route the request back to the original model that produced the history if continuation is required.
  4. Adjust session bookkeeping so model changes reset the stored transcript instead of replaying foreign turns.

Example fix

// before
await streamCursor(newModel, { ...context, messages: oldMessages });
// after
const sameModelMessages = oldMessages.filter(
  m => m.role !== "assistant" || (m.provider === "cursor" && m.model === newModel.id),
);
await streamCursor(newModel, { ...context, messages: sameModelMessages });
Defensive patterns

Strategy: validation

Validate before calling

const foreign = messages.find(m => m.role === "assistant" &&
  !(m.api === "cursor-agent" && m.provider === "cursor" && m.model === targetModelId));
if (foreign) {
  // start a new session or filter history before calling Cursor
}

Type guard

null

Try / catch

try {
  await streamCursor(model, ctx, { messages, ...options });
} catch (err) {
  if (err instanceof AIError.ValidationError && err.message.includes("cannot continue history from a different model")) {
    // reset the session: clear transcript or spin up a new conversation
  } else throw err;
}

Prevention

When it happens

Trigger: Calling the Cursor streaming path with a conversation history that includes assistant messages where msg.api !== "cursor-agent" or msg.provider !== "cursor" or msg.model !== targetModelId — typically switching models mid-session (e.g. from claude to a cursor K3 model).

Common situations: User switches models in the middle of a chat and the client replays full history; a fallback route substituted another provider's assistant turns into the transcript; session persistence across a model upgrade changes the model id string.

Related errors


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