Skip to content

port(omniroute): carry upstream status and retry hint in post-keepalive error frames - #1000

Merged
bloodf merged 2 commits into
mainfrom
port/omniroute-keepalive-error-frames
Sep 25, 2026
Merged

bloodf merged 2 commits into
mainfrom
port/omniroute-keepalive-error-frames

Conversation

@bloodf

@bloodf bloodf commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Scope

Ports OmniRoute #14632, the keepalive-frame part of #14572, and the finish-reason part of #14247.

  • Late failures keep their status. Once earlyStreamKeepalive has committed a 200, a later failure used to go out as a generic event: error with the raw body, and the HTTP status and Retry-After were lost. The final frame now matches the client's wire format:

    • Responses: a flat error event with sequence_number. It has no top-level error key, because openai-node would throw on one.
    • Chat Completions: a data: {"error":…} line.
    • Claude Messages: an Anthropic event: error.

    Each carries status_code and retry_after_seconds (seconds or an HTTP date, capped at 0..3600). /v1/responses passes errorFormat: "responses" and /v1/messages passes errorFormat: "claude".

  • Finish reasons on OpenAI passthrough. Streaming and non-streaming OpenAI passthrough now maps Claude stop_reason literals in finish_reason (end_turn, tool_use, …) to OpenAI values through the existing toOpenAIFinish(…, "claude"). Claude clients are unaffected.

  • Not ported:

    • DurinDoor has no synthesized Responses keepalive frames and no Codex WebSocket failure frames. formatTranslatedStreamError already sends sequence_number: 0.
    • The threshold re-tuning and the combo 403 message from #14247.

Review notes:

  • pause_turn maps to tool_calls, following the fork's existing Claude translator. OmniRoute maps it to stop.
  • The Responses error keeps its event: error line, as OpenAI's stream and our formatTranslatedStreamError do.

Tests

  • keepalive-error-frame-status.test.js (8 tests): each wire format, the HTTP-date cap, a missing header, a thrown handler, the fast path, and each route.
  • openai-finish-reason-claude-literals.test.js (16 tests).
  • Local test:ci: 0 assertion failures out of 12,131. The run was made on a heavily loaded machine, where one suite (db-concurrent) timed out in beforeAll. That suite passes with a longer hook timeout and is unrelated to this change. GitHub CI is the authoritative run.

Docs

docs/troubleshooting.mdx: new section "Streaming request fails after keepalive frames".

Baseline impact

None.

Wire format and migration

The frame fields are additive. The fast path (the handler answers inside the threshold) is unchanged.

…ve error frames

Ports OmniRoute #14632, the keepalive-frame part of #14572, and the finish-reason part of #14247. Late failures after the early keepalive keep their status and Retry-After in a wire-correct frame (Responses error event with sequence_number, chat data error line, Anthropic event: error). OpenAI passthrough maps Claude stop_reason literals in finish_reason.
@vercel

vercel Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
durindoor Ready Ready Preview Sep 25, 2026 7:35pm UTC

@bloodf
bloodf merged commit 1c3d05d into main Sep 25, 2026
7 checks passed

This branch was successfully deployed

1 active deployment
Preview — eb3caa5a Deployed Sep 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant