Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
51 lines
2.1 KiB
TypeScript
51 lines
2.1 KiB
TypeScript
/**
|
|
* Helpers for detecting Vercel AI SDK internal stream-state errors.
|
|
*
|
|
* Background — issue #1101 follow-up:
|
|
*
|
|
* When a third-party Anthropic-compat backend (DeepSeek's
|
|
* `deepseek-v4-flash` is the canonical offender) streams thinking
|
|
* deltas without first emitting a `reasoning-start` content-block
|
|
* signal, the Vercel AI SDK's reasoning state machine has nothing
|
|
* registered for the incoming `part.id` and enqueues an
|
|
* `error` chunk on `stream` with the text
|
|
* `reasoning part <id> not found` — once per orphan delta. The
|
|
* analogous error exists for text parts.
|
|
*
|
|
* These chunks are *internal SDK bookkeeping noise*, not user-facing
|
|
* errors. Worse, treating them as real errors (adding a placeholder
|
|
* assistant message for each) breaks tool_use/tool_result contiguity
|
|
* on the next turn: the Anthropic message grouper splits the
|
|
* tool-result `role: 'tool'` messages from their parent tool_use
|
|
* `role: 'assistant'`, and the backend responds with
|
|
* `400 messages.N: tool_use ids were found without tool_result blocks
|
|
* immediately after`.
|
|
*
|
|
* Filtering these specific errors at the chunk-handler boundary
|
|
* stops the cascade: the orphan deltas are dropped silently (the SDK
|
|
* continues processing other chunks), no fake assistant messages
|
|
* land in history, and the next turn's request stays well-formed.
|
|
*/
|
|
|
|
const STATE_ERROR_PATTERN = /^(?:reasoning|text)\s+part\s+\S+\s+not\s+found$/i;
|
|
|
|
/**
|
|
* Return true if `error` is one of the SDK's internal stream-state
|
|
* tracking errors (e.g. an out-of-order reasoning delta). Accepts
|
|
* the loose `unknown` shape that comes off the chunk so callers
|
|
* don't need to narrow upstream.
|
|
*/
|
|
export function isSdkStreamStateError(error: unknown): boolean {
|
|
if (typeof error === 'string') {
|
|
return STATE_ERROR_PATTERN.test(error.trim());
|
|
}
|
|
if (error instanceof Error) {
|
|
return STATE_ERROR_PATTERN.test(error.message.trim());
|
|
}
|
|
if (error && typeof error === 'object' && 'message' in error) {
|
|
const msg = (error as { message?: unknown }).message;
|
|
if (typeof msg === 'string') return STATE_ERROR_PATTERN.test(msg.trim());
|
|
}
|
|
return false;
|
|
}
|