Repository navigation
feat(api): abort signal support for unbound, vercel-ai-gateway, zoo-gateway - #1653
easonLiangWorldedtech wants to merge 8 commits into
Conversation
…utils Extend src/api/providers/utils/abort-signal.ts with the abort-signal series helpers used by the gateway providers: - isRequestAborted(error, signal): wider abort detection - an aborted signal, a DOM AbortError, the OpenAI/Anthropic SDK APIUserAbortError (name check), or the exact SDK abort message "Request was aborted." - trusting name/message only on real Error instances so a plain object that merely looks like an abort propagates unchanged - createAbortError(providerName): fresh error satisfying the Task.ts abort contract (name "AbortError", message ending in "aborted") - rejectOnAbort(pending, signal, providerName): settle a signal-less async phase (model discovery) on the provider AbortError when the signal fires first; the abort listener detaches when pending settles - resolveModelWithAbort(fetchModel, signal, providerName): run model resolution inside a cancellation scope - entry fast-fail for a pre-aborted signal, the rejectOnAbort race while the lookup is pending, and normalization of abort-flavored lookup failures; any other resolution failure propagates unchanged Includes direct unit tests for the resolveModelWithAbort cancellation scope (pre-aborted fast-fail, no-signal pass-through, mid-resolution race, abort normalization, non-abort propagation), the isRequestAborted instanceof tightening tests, and the settle-guard test utility. Unit 1/3 of the Zoo-Code-Org#1295 split (content source: 62f596c). Part of the abort-signal series (round 1). Builds on Zoo-Code-Org#674, Zoo-Code-Org#901, Zoo-Code-Org#1008. Addresses Zoo-Code-Org#404.
- createMessage: bridge metadata.abortSignal to a per-request AbortController (Bedrock pattern: pre-aborted guard, once-listener, detached on completion so a task-scoped signal does not accumulate listeners); model resolution runs inside the shared resolveModelWithAbort cancellation scope (pre-aborted fast-fail, mid-resolution race) - aborted/timeout requests normalize to the provider AbortError on all three wire formats (anthropic /v1/messages, responses /v1/responses, openai chat completions), both pre-stream and mid-stream; non-abort failures keep the wrapped "Opencode Go completion error:" identity - completePrompt: forwards abortSignal/timeoutMs to all three SDK paths (timeoutMs <= 0 omits the SDK timeout option, since the SDK treats timeout: 0 as an immediate abort); aborted completions and APIConnectionTimeoutError/APITimeoutError normalize to the provider AbortError (series standard) The two inner pre-stream guard mutants (the abort-normalization condition and its provider-name literal) are documented as provably equivalent with mutator-specific Stryker directives: createMessage's outer catch applies the identical isRequestAborted check to the same controller signal and re-standardizes, so the inner layer's only unique behavior is the non-abort completion-error wrap (stays kill-tested). Unit 2/3 of the Zoo-Code-Org#1295 split (content source: 62f596c); stacks on the shared-util unit. Part of the abort-signal series (round 1). Builds on Zoo-Code-Org#674, Zoo-Code-Org#901, Zoo-Code-Org#1008. Addresses Zoo-Code-Org#404.
…ateway
Each provider's createMessage bridges metadata.abortSignal to a
per-request AbortController (Bedrock pattern: pre-aborted guard,
{ once: true } listener, detached on completion so a task-scoped
signal does not accumulate listeners) and runs model resolution inside
the shared resolveModelWithAbort cancellation scope; aborted requests
(pre-aborted, mid-resolution, mid-stream) normalize to the provider
AbortError while non-abort failures propagate or wrap unchanged.
completePrompt forwards abortSignal/timeoutMs to the OpenAI SDK
(timeoutMs <= 0 omits the SDK timeout option); aborted completions and
APIConnectionTimeoutError normalize to the provider AbortError (series
standard).
Zoo Gateway places the entry fast-fail before ensureAuthenticated() so
a pre-aborted task surfaces the AbortError instead of an auth failure
(pinned by an unauthenticated pre-abort test).
Unit 3/3 of the Zoo-Code-Org#1295 split (content source: 62f596c); stacks on the
shared-util and opencode-go units. Three sibling OpenAI-SDK gateway
providers with an identical abort-wiring shape are kept as one unit to
avoid tripling the review surface for mechanically identical changes
(a+d soft-cap rationale per the split budget; the mutation gate is
measured on the unit delta and is green).
Part of the abort-signal series (round 1). Builds on Zoo-Code-Org#674, Zoo-Code-Org#901, Zoo-Code-Org#1008.
Addresses Zoo-Code-Org#404.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📜 Recent review details
📝 Summary
Merge Risk: ⚪ Minimal · up to
|
Review statusThanks for contributing. This comment tracks the review sequence and the next action. Current step: Resolve the merge conflicts. The review sequence resumes after the branch is mergeable. Review-state labels are managed by this workflow; do not edit them manually. |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Conflicts were only in src/api/providers/utils/abort-signal.ts and its spec. Resolved by keeping main's canonical helper: the branch carried a copy that lacked throwIfAborted (main's fetchers import it, so the merge broke tsc) and the pre-aborted branch's unhandled-rejection guard. The PR keeps only its own provider wiring. Verified on the merged tree: tsc clean, eslint clean, prettier clean, 176 tests green (abort-signal + unbound + vercel-ai-gateway + zoo-gateway specs). Co-Authored-By: Claude <noreply@anthropic.com>
|
@coderabbitai full review |
|
|
@coderabbitai full review |
|
|
@coderabbitai full review |
|
|
Pushed an empty commit (1b9814a) to re-trigger the required checks and a CodeRabbit review pass at this head; no content changed. The last CodeRabbit review was recorded at an earlier commit, so the review gate is still showing the old verdict. |
|
All required checks are green at @coderabbitai full review |
|
|
@coderabbitai full review |
✅ Action performedFull review finished. |
Conflicts resolved: - vercel-ai-gateway.ts / zoo-gateway.ts (imports only): main added the model-cache fetchModels (needs the fetcher imports); this branch added the abort-signal utility imports. Kept both sides' imports. - unbound.spec.ts: main added a "model catalog" describe block at the position where this branch appended its abort/cancellation test suites. Kept both (branch suites first, main's catalog block after). Verification at this head: 535/535 green across the touched provider specs (unbound, vercel-ai-gateway, zoo-gateway, opencode-go); tsc --noEmit 0 errors; eslint --max-warnings=0 clean on touched files; prettier clean.
|
@coderabbitai review |
✅ Action performedReview finished.
|
Wires the external abort signal and per-request timeout into the
completePrompt(non-streaming) andcreateMessage(streaming) paths of the Unbound, Vercel AI Gateway, and Zoo Gateway providers. Stacks on the shared-util and opencode-go units of this split (resolveModelWithAbortinutils/abort-signal.ts).Each provider:
createMessage: bridgesmetadata?.abortSignal(Bedrock pattern: pre-aborted guard,{ once: true }listener, detached on completion so a task-scoped signal does not accumulate listeners) into a per-requestAbortController; model resolution runs inside the sharedresolveModelWithAbortcancellation scope (pre-aborted fast-fail before any catalog/SDK work, mid-resolution race). Aborted requests (pre-aborted, mid-resolution, mid-stream) normalize to the provider AbortError; non-abort failures propagate or wrap unchanged.completePrompt: forwardsabortSignal/timeoutMsto the OpenAI SDK (timeoutMs <= 0omits the SDK timeout option, since the SDK treatstimeout: 0as an immediate abort); aborted completions andAPIConnectionTimeoutErrornormalize to the provider AbortError (series standard).Zoo Gateway places the entry fast-fail before
ensureAuthenticated()so a pre-aborted task surfaces the AbortError instead of an auth failure.Tests:
completePromptpass-through tests (signal, timeoutMs incl. 0, and no-options backward compatibility) for all three providers, plus normalization/identity tests for aborted and timed-out completions.createMessagebridging tests per provider: pre-aborted signal -> rejects with the standardized AbortError before any request work (catalog and SDK both uncalled; unbound additionally asserts the SDK-level rejection identity since its error wrapper preserves main's behavior); abort mid-resolution -> settles on the standardized AbortError before the lookup is released, no late request; detach tests use the reference-identity pattern (the resolution race registers its own "abort" listener, so the bridge is the last registration).Series and unit
Unit 3/3 of the #1295 split (content source:
62f596c5d); stacks on the shared-util and opencode-go units. Three sibling OpenAI-SDK gateway providers with an identical abort-wiring shape are kept as one unit to avoid tripling the review surface for mechanically identical changes (a+d soft-cap rationale per the split budget; the mutation gate is measured on the unit delta and is green).Part of the abort-signal series (round 1). Builds on #674, #901, #1008. Addresses #404.
Review response (maintainer review of #1295)
rejectOnAbortrace, and the abort normalization now live once in the shared util (resolveModelWithAbort, unit 1/3) instead of being inlined per provider; all three providers call it and each spec exercises it end-to-end (pre-aborted -> catalog and SDK both uncalled; mid-resolution -> standardized AbortError before the lookup is released, no late request).createMessagecatches use the widerisRequestAbortedcondition (aborted signal, DOM AbortError, SDK APIUserAbortError, exact "Request was aborted." message; name/message checks require a realErrorinstance).Evidence
Updated this round (2026-10-13)
Merged current upstream
maininto the branch to clear the dirty state. Conflicts:vercel-ai-gateway.ts/zoo-gateway.ts(imports only — main's model-cache fetcher imports plus this branch's abort-signal utility imports, both kept) andunbound.spec.ts(main's new "model catalog" describe block coexists with this branch's appended abort/cancellation suites — both kept).Verification at this head: 535/535 green across the touched provider specs (unbound, vercel-ai-gateway, zoo-gateway, opencode-go);
tsc --noEmit→ 0 errors; per-file ESLint (--max-warnings=0) clean;prettier --checkclean on touched files.