fix(gateway): surface in-stream errors from Zoo and Vercel AI gateways#569
Conversation
📝 WalkthroughWalkthroughZoo and Vercel AI gateway handlers now detect in-band error chunks mid-stream and surface the upstream error message. Zoo adds ChangesStream Error Propagation for AI Gateway Providers
Sequence DiagramsequenceDiagram
participant Client
participant GatewayHandler
participant StreamLoop
participant toGatewayStreamError
participant UIErrorHandler
Client->>GatewayHandler: createMessage()
GatewayHandler->>StreamLoop: iterate stream chunks
StreamLoop->>StreamLoop: receive chunk
alt chunk.error present
StreamLoop->>toGatewayStreamError: convert raw error
toGatewayStreamError->>StreamLoop: Error(message,status,code)
StreamLoop->>UIErrorHandler: throw Error
UIErrorHandler->>Client: surface upstream message / mapped UX
else normal chunk
StreamLoop->>GatewayHandler: emit content chunk
GatewayHandler->>Client: stream content
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
ffe3bf5 to
100eb44
Compare
Once a gateway response starts streaming the HTTP status is already 200, so upstream failures (e.g. provider rate limits) arrive as an in-band error chunk rather than a thrown HTTP error. Both handlers ignored these chunks, so the extension showed a generic "no response" instead of the real reason. - zoo-gateway: detect the error chunk and rebuild it into an Error carrying status/code so the existing classify/surface logic handles it (sign-in, add-credits, budget, etc.), and the upstream message reaches the user. - vercel-ai-gateway: detect the error chunk and throw the upstream message. - Add unit tests covering both handlers and the toGatewayStreamError mapping. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a case where the in-band error chunk has no message, exercising the default "Vercel AI Gateway stream error" fallback branch flagged as uncovered. Co-authored-by: Cursor <cursoragent@cursor.com>
100eb44 to
55bbcfd
Compare
navedmerchant
left a comment
There was a problem hiding this comment.
Please link an issue number and remove changeset, otherwise looks good to merge
| @@ -0,0 +1,5 @@ | |||
| --- | |||
| "zoo-code": patch | |||
There was a problem hiding this comment.
We do not need a changeset file
Per maintainer review — this change does not require a changeset. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Both points addressed:
|
Related GitHub Issue
Closes: #587
Description
When a gateway response starts streaming, the HTTP status is already committed as
200. Errors raised by the upstream model provider mid-stream are therefore delivered as an in-band error chunk on the SSE stream rather than as a thrown HTTP error.Both the Zoo Gateway and the Vercel AI Gateway handlers iterated chunks and only read
chunk.choices[0]?.delta, silently ignoring any chunk shaped like{ error: ... }. The stream then ended with no content, so the extension surfaced a generic "the model did not provide a response" message even though the real provider error was visible in the gateway logs. This affects any mid-stream provider error (server errors, content filtering, upstream timeouts, rate limits, etc.) — a429 Too Many Requestsis just the example that surfaced it.openrouter.tsalready guards against this with anif ("error" in chunk)check — these two handlers did not.How this is fixed:
zoo-gateway.ts— detect the in-stream error chunk and rebuild it via a newtoGatewayStreamErrorhelper into anErrorcarryingstatus/code. Because it is thrown from inside the existingtryblock, the currentsurfaceGatewayApiError/classifyGatewayApiErrorpath handles it unchanged (sign-in on 401, add-credits on 402, budget prompt on budget 429, contact-support on 403), and the upstream message reaches the user for everything else.vercel-ai-gateway.ts— detect the in-stream error chunk and throw the upstream message, or a default fallback when the chunk carries no message (this handler has no Zoo-specific auth/credit UX).Note: plain rate-limit
429s deliberately do not raise a toast (classifyGatewayApiErrorreturnsnone); the real message now surfaces inline in the chat, which matches the existing tested behavior for non-budget 429s.Test Procedure
Unit tests, scoped:
Added coverage:
errorchunk is received.toGatewayStreamErrormapsmessage/status/codeand falls back to a default message.errorchunk, and a default message when the chunk has no message.All specs pass;
check-typesandlintare green across the workspace.Pre-Submission Checklist
Documentation Updates
Additional Notes
This PR pairs with a server-side change in the website repo that emits the in-band
errorpart on the SSE stream (the AI SDK reports upstream failures as anerrorpart onfullStreaminstead of throwing). This extension change makes the client honor those chunks.