Repository navigation
feat(api): add CompletePromptOptions parameter to completePrompt method - #901
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough
ChangesComplete prompt options
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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! |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/api/providers/base-openai-compatible-provider.ts (1)
215-229: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftThread
CompletePromptOptionsinto these completion requests.
abortSignalandtimeoutMsare defined onCompletePromptOptions, but everycompletePrompthere dropsoptions, so caller cancellation and per-call timeouts never reach the SDK request.
src/api/providers/base-openai-compatible-provider.ts#L215-L229src/api/providers/opencode-go.ts#L488-L537src/api/providers/openrouter.ts#L577-L606src/api/providers/poe.ts#L141-L147src/api/providers/qwen-code.ts#L330-L341src/api/providers/requesty.ts#L207-L221src/api/providers/unbound.ts#L195-L209src/api/providers/vercel-ai-gateway.ts#L120-L136🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/api/providers/base-openai-compatible-provider.ts` around lines 215 - 229, Thread CompletePromptOptions through each completePrompt implementation so abortSignal and timeoutMs reach the underlying SDK completion request. Update the request construction or invocation in src/api/providers/base-openai-compatible-provider.ts lines 215-229, src/api/providers/opencode-go.ts lines 488-537, src/api/providers/openrouter.ts lines 577-606, src/api/providers/poe.ts lines 141-147, src/api/providers/qwen-code.ts lines 330-341, src/api/providers/requesty.ts lines 207-221, src/api/providers/unbound.ts lines 195-209, and src/api/providers/vercel-ai-gateway.ts lines 120-136, reusing each provider’s existing request options and preserving current completion behavior.
🧹 Nitpick comments (1)
src/core/task/__tests__/Task.spec.ts (1)
2235-2252: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSimplify the mock stream factory.
The custom mock implementation manually overrides the async iterator protocol, but
for awaitloops rely exclusively on the[Symbol.asyncIterator]()method. Since that method returns a native generator, your customnext(),return(), andthrow()methods are bypassed as dead code, andcallCountis never actually incremented here.You can safely simplify this to a standard async generator function, which naturally fulfills the
AsyncGeneratorcontract and correctly increments your counter as intended:♻️ Proposed refactor
- const mockStreamFactory = () => { - return { - async *[Symbol.asyncIterator]() { - yield { type: "text", text: `response ${callCount}` } - }, - async next() { - callCount++ - return { done: true, value: { type: "text", text: `response ${callCount - 1}` } } - }, - async return() { - return { done: true, value: undefined } - }, - async throw(e: any) { - throw e - }, - [Symbol.asyncDispose]: async () => {}, - } as AsyncGenerator<ApiStreamChunk> - } + const mockStreamFactory = async function* (): AsyncGenerator<ApiStreamChunk> { + yield { type: "text", text: `response ${callCount}` } + callCount++ + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/task/__tests__/Task.spec.ts` around lines 2235 - 2252, Replace the custom object returned by mockStreamFactory with a standard async generator function. Move the callCount increment into the generator execution before yielding the response, remove the manually defined next, return, throw, and async iterator members, and preserve the existing response text and AsyncGenerator<ApiStreamChunk> contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/api/providers/gemini.ts`:
- Line 579: All completePrompt implementations accept CompletePromptOptions but
do not propagate cancellation or timeout settings to their requests. Update
src/api/providers/gemini.ts:579 in completePrompt to pass abortSignal to
generateContent; src/api/providers/minimax.ts:292-295 in completePrompt to pass
the signal to client.messages.create; src/api/providers/mistral.ts:195 in
completePrompt to pass abortSignal and timeoutMs to client.chat.complete;
src/api/providers/native-ollama.ts:553-579 in completePrompt to pass abortSignal
to client.chat; src/api/providers/openai-codex.ts:1157 in completePrompt to use
options?.abortSignal for fetch, preserving the existing controller as fallback;
src/api/providers/openai-compatible.ts:200 in completePrompt to pass abortSignal
to generateText; and src/api/providers/openai-native.ts:1487-1585 in
completePrompt to use options?.abortSignal for responses.create instead of
relying solely on the shared controller.
---
Outside diff comments:
In `@src/api/providers/base-openai-compatible-provider.ts`:
- Around line 215-229: Thread CompletePromptOptions through each completePrompt
implementation so abortSignal and timeoutMs reach the underlying SDK completion
request. Update the request construction or invocation in
src/api/providers/base-openai-compatible-provider.ts lines 215-229,
src/api/providers/opencode-go.ts lines 488-537, src/api/providers/openrouter.ts
lines 577-606, src/api/providers/poe.ts lines 141-147,
src/api/providers/qwen-code.ts lines 330-341, src/api/providers/requesty.ts
lines 207-221, src/api/providers/unbound.ts lines 195-209, and
src/api/providers/vercel-ai-gateway.ts lines 120-136, reusing each provider’s
existing request options and preserving current completion behavior.
---
Nitpick comments:
In `@src/core/task/__tests__/Task.spec.ts`:
- Around line 2235-2252: Replace the custom object returned by mockStreamFactory
with a standard async generator function. Move the callCount increment into the
generator execution before yielding the response, remove the manually defined
next, return, throw, and async iterator members, and preserve the existing
response text and AsyncGenerator<ApiStreamChunk> contract.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 098310bc-c298-461e-b612-f08806baff20
📒 Files selected for processing (30)
src/api/index.tssrc/api/providers/anthropic-vertex.tssrc/api/providers/anthropic.tssrc/api/providers/base-openai-compatible-provider.tssrc/api/providers/bedrock.tssrc/api/providers/fake-ai.tssrc/api/providers/gemini.tssrc/api/providers/kenari.tssrc/api/providers/lite-llm.tssrc/api/providers/lm-studio.tssrc/api/providers/minimax.tssrc/api/providers/mistral.tssrc/api/providers/native-ollama.tssrc/api/providers/openai-codex.tssrc/api/providers/openai-compatible.tssrc/api/providers/openai-native.tssrc/api/providers/openai.tssrc/api/providers/opencode-go.tssrc/api/providers/openrouter.tssrc/api/providers/poe.tssrc/api/providers/qwen-code.tssrc/api/providers/requesty.tssrc/api/providers/unbound.tssrc/api/providers/vercel-ai-gateway.tssrc/api/providers/vscode-lm.tssrc/api/providers/xai.tssrc/api/providers/zoo-gateway.tssrc/core/task/__tests__/Task.spec.tssrc/utils/__tests__/enhance-prompt.spec.tssrc/utils/single-completion-handler.ts
37133de to
62ed671
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/api/providers/base-openai-compatible-provider.ts (1)
215-243: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUnused
optionsparameter incompletePromptimplementations.Although the
optionsparameter (containingabortSignalandtimeoutMs) is now included in thecompletePromptsignature across all providers, it is never forwarded to the underlying SDK clients (e.g., OpenAI or Anthropic). As a result, network requests will not actually be aborted when the cancellation signal is triggered, defeating the objective of stopping requests after cancellation.
src/api/providers/base-openai-compatible-provider.ts#L215-L243: Pass the options to the OpenAI client call:await this.client.chat.completions.create(params, { signal: options?.abortSignal, timeout: options?.timeoutMs }).src/api/providers/opencode-go.ts#L488-L545: Pass the options to both the Anthropic and OpenAI client calls.src/api/providers/requesty.ts#L207-L226: Pass the options to the OpenAI client call.src/api/providers/vercel-ai-gateway.ts#L120-L144: Pass the options to the OpenAI client call.src/api/providers/xai.ts#L145-L163: Pass the options to the Responses API client call.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/api/providers/base-openai-compatible-provider.ts` around lines 215 - 243, Forward CompletePromptOptions.abortSignal and timeoutMs to every underlying SDK request: update completePrompt in src/api/providers/base-openai-compatible-provider.ts (215-243), both Anthropic and OpenAI calls in src/api/providers/opencode-go.ts (488-545), the OpenAI call in src/api/providers/requesty.ts (207-226), the OpenAI call in src/api/providers/vercel-ai-gateway.ts (120-144), and the Responses API call in src/api/providers/xai.ts (145-163). Use each client’s request options so cancellation and timeouts are applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/api/providers/base-openai-compatible-provider.ts`:
- Around line 215-243: Forward CompletePromptOptions.abortSignal and timeoutMs
to every underlying SDK request: update completePrompt in
src/api/providers/base-openai-compatible-provider.ts (215-243), both Anthropic
and OpenAI calls in src/api/providers/opencode-go.ts (488-545), the OpenAI call
in src/api/providers/requesty.ts (207-226), the OpenAI call in
src/api/providers/vercel-ai-gateway.ts (120-144), and the Responses API call in
src/api/providers/xai.ts (145-163). Use each client’s request options so
cancellation and timeouts are applied.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9e539678-aff9-4a10-a391-e6ceb6abf6a6
📒 Files selected for processing (31)
src/api/index.tssrc/api/providers/__tests__/fake-ai.spec.tssrc/api/providers/anthropic-vertex.tssrc/api/providers/anthropic.tssrc/api/providers/base-openai-compatible-provider.tssrc/api/providers/bedrock.tssrc/api/providers/fake-ai.tssrc/api/providers/gemini.tssrc/api/providers/kenari.tssrc/api/providers/lite-llm.tssrc/api/providers/lm-studio.tssrc/api/providers/minimax.tssrc/api/providers/mistral.tssrc/api/providers/native-ollama.tssrc/api/providers/openai-codex.tssrc/api/providers/openai-compatible.tssrc/api/providers/openai-native.tssrc/api/providers/openai.tssrc/api/providers/opencode-go.tssrc/api/providers/openrouter.tssrc/api/providers/poe.tssrc/api/providers/qwen-code.tssrc/api/providers/requesty.tssrc/api/providers/unbound.tssrc/api/providers/vercel-ai-gateway.tssrc/api/providers/vscode-lm.tssrc/api/providers/xai.tssrc/api/providers/zoo-gateway.tssrc/core/task/__tests__/Task.spec.tssrc/utils/__tests__/enhance-prompt.spec.tssrc/utils/single-completion-handler.ts
🚧 Files skipped from review as they are similar to previous changes (17)
- src/utils/single-completion-handler.ts
- src/api/providers/bedrock.ts
- src/api/providers/qwen-code.ts
- src/api/providers/vscode-lm.ts
- src/api/providers/openai-codex.ts
- src/api/providers/mistral.ts
- src/api/providers/zoo-gateway.ts
- src/api/providers/openai.ts
- src/api/providers/openrouter.ts
- src/api/providers/minimax.ts
- src/api/providers/anthropic-vertex.ts
- src/api/providers/kenari.ts
- src/api/providers/unbound.ts
- src/api/providers/openai-compatible.ts
- src/api/providers/native-ollama.ts
- src/core/task/tests/Task.spec.ts
- src/api/providers/openai-native.ts
edelauna
left a comment
There was a problem hiding this comment.
to minor comments about consistency, but otherwise looks good.
Add optional second parameter to all provider completePrompt methods for future abort signal and timeout support. - Add CompletePromptOptions interface with abortSignal and timeoutMs fields - Update 25+ provider implementations to accept options?: CompletePromptOptions parameter - Simplify providers to use basic request calls without signal/timeout logic
Co-authored-by: edelauna <[email protected]>
6d67b5c to
98201f4
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/api/providers/base-openai-compatible-provider.ts`:
- Line 215: Forward the received CompletePromptOptions from completePrompt
through each provider’s SDK request so abortSignal and timeoutMs are applied.
Update the implementations in
src/api/providers/base-openai-compatible-provider.ts:215-215,
src/api/providers/lm-studio.ts:190-190, src/api/providers/minimax.ts:292-292,
and src/api/providers/requesty.ts:207-207; preserve existing request behavior
while passing the options to the underlying completion call.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c572da25-8110-4ac2-a5a7-23dba23165c3
📒 Files selected for processing (31)
src/api/index.tssrc/api/providers/__tests__/fake-ai.spec.tssrc/api/providers/anthropic-vertex.tssrc/api/providers/anthropic.tssrc/api/providers/base-openai-compatible-provider.tssrc/api/providers/bedrock.tssrc/api/providers/fake-ai.tssrc/api/providers/gemini.tssrc/api/providers/kenari.tssrc/api/providers/lite-llm.tssrc/api/providers/lm-studio.tssrc/api/providers/minimax.tssrc/api/providers/mistral.tssrc/api/providers/native-ollama.tssrc/api/providers/openai-codex.tssrc/api/providers/openai-compatible.tssrc/api/providers/openai-native.tssrc/api/providers/openai.tssrc/api/providers/opencode-go.tssrc/api/providers/openrouter.tssrc/api/providers/poe.tssrc/api/providers/qwen-code.tssrc/api/providers/requesty.tssrc/api/providers/unbound.tssrc/api/providers/vercel-ai-gateway.tssrc/api/providers/vscode-lm.tssrc/api/providers/xai.tssrc/api/providers/zoo-gateway.tssrc/core/task/__tests__/Task.spec.tssrc/utils/__tests__/enhance-prompt.spec.tssrc/utils/single-completion-handler.ts
🚧 Files skipped from review as they are similar to previous changes (27)
- src/utils/tests/enhance-prompt.spec.ts
- src/api/providers/kenari.ts
- src/api/providers/xai.ts
- src/api/index.ts
- src/api/providers/openai-compatible.ts
- src/api/providers/openrouter.ts
- src/api/providers/opencode-go.ts
- src/api/providers/anthropic-vertex.ts
- src/api/providers/poe.ts
- src/api/providers/gemini.ts
- src/api/providers/lite-llm.ts
- src/api/providers/qwen-code.ts
- src/api/providers/tests/fake-ai.spec.ts
- src/api/providers/openai-codex.ts
- src/api/providers/anthropic.ts
- src/api/providers/openai-native.ts
- src/api/providers/vercel-ai-gateway.ts
- src/api/providers/unbound.ts
- src/api/providers/native-ollama.ts
- src/api/providers/zoo-gateway.ts
- src/api/providers/mistral.ts
- src/api/providers/vscode-lm.ts
- src/api/providers/openai.ts
- src/utils/single-completion-handler.ts
- src/core/task/tests/Task.spec.ts
- src/api/providers/fake-ai.ts
- src/api/providers/bedrock.ts
Co-authored-by: edelauna <[email protected]>
824a196 to
73054a4
Compare
edelauna
left a comment
There was a problem hiding this comment.
Thanks for your contribution and all the reviews you went through.
- completePrompt: bridge CompletePromptOptions.abortSignal and timeoutMs into a
request-local vscode.CancellationTokenSource (the VS Code LanguageModelChat API
only accepts a CancellationToken, not an AbortSignal); apply timeoutMs only when
it is a positive value
- completePrompt: report aborted requests (external signal, timeout, or host
cancellation) as errors with name = "AbortError" on both the error and success
paths; remove the abort listener and dispose the token source in finally
- createMessage: bridge metadata.abortSignal into the internal request
CancellationTokenSource (Bedrock pattern: pre-aborted guard + { once: true }
listener stored in a named const), fail fast with an AbortError when the signal
is already aborted, surface host CancellationError with name = "AbortError", and
detach the listener / dispose the source in finally
- vscode-lm.spec.ts: add pre-aborted and mid-flight abort, timeout, listener
attach/detach, and backward-compatibility tests
- fake-ai.spec.ts: option pass-through tests already merged on main via Zoo-Code-Org#901;
verified green without changes
…ssion tests Add a fast-fail throwIfAborted guard to the shared abort-signal utilities and regression tests for the CompletePromptOptions interface (added by Zoo-Code-Org#901).
…ssion tests (Zoo-Code-Org#1288) * feat(api): add throwIfAborted helper and completePrompt options regression tests Add a fast-fail throwIfAborted guard to the shared abort-signal utilities and regression tests for the CompletePromptOptions interface (added by Zoo-Code-Org#901). * feat(api): add shared isRequestAborted and createAbortError helpers to abort-signal utils The OpenAI-family provider PRs (Zoo-Code-Org#1309, Zoo-Code-Org#1311) carry per-provider copies of the same abort-detection helper (isRequestAborted) and the same abort-error constructor (createAbortError); only the provider name in the message differs. Per the CodeRabbit maintainability finding on Zoo-Code-Org#1309 (extract the shared abort helpers into utils/abort-signal.ts), these are now shared in the foundation utility: - isRequestAborted(error, signal?) - true when the caller signal fired, a native AbortError / OpenAI SDK APIUserAbortError was raised, or the message is exactly "Request was aborted." (exact match; a substring match would misclassify unrelated errors that merely mention aborting) - createAbortError(providerName) - fresh error with name === "AbortError" and message "The <providerName> request was aborted", satisfying the Task.ts abort contract - exported OpenAiRequestOptions type 7 new tests (isRequestAborted 4, createAbortError 3). * test(abort-signal): assert throwIfAborted error message The mutation-diff gate reports a surviving StringLiteral mutant on the throwIfAborted message literal. Assert the exact message so a blanked literal fails the suite. --------- Co-authored-by: Eason Liang <[email protected]> Co-authored-by: easonLiangWorldedtech <[email protected]> Co-authored-by: edelauna <[email protected]>
- 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 - the OpenAI branch runs message conversion and request construction inside the same cleanup scope as the Anthropic and Responses branches, so a conversion failure (e.g. an unstringifiable tool input) still detaches the bridged abort listener (CodeRabbit fix on this PR) - 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). The cleanup-scope re-indentation made the gate instrument pre-existing request-construction lines for the first time; their previously unobservable mutants are kill-tested: R1 tool-result merge shape for preserveReasoning models and the plain shape for non-preserveReasoning models, the includeMaxTokens on/off branches with a ceiling-bounded model, and the parallel_tool_calls default/explicit-false. 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.
…ssion tests (Zoo-Code-Org#1288) * feat(api): add throwIfAborted helper and completePrompt options regression tests Add a fast-fail throwIfAborted guard to the shared abort-signal utilities and regression tests for the CompletePromptOptions interface (added by Zoo-Code-Org#901). * feat(api): add shared isRequestAborted and createAbortError helpers to abort-signal utils The OpenAI-family provider PRs (Zoo-Code-Org#1309, Zoo-Code-Org#1311) carry per-provider copies of the same abort-detection helper (isRequestAborted) and the same abort-error constructor (createAbortError); only the provider name in the message differs. Per the CodeRabbit maintainability finding on Zoo-Code-Org#1309 (extract the shared abort helpers into utils/abort-signal.ts), these are now shared in the foundation utility: - isRequestAborted(error, signal?) - true when the caller signal fired, a native AbortError / OpenAI SDK APIUserAbortError was raised, or the message is exactly "Request was aborted." (exact match; a substring match would misclassify unrelated errors that merely mention aborting) - createAbortError(providerName) - fresh error with name === "AbortError" and message "The <providerName> request was aborted", satisfying the Task.ts abort contract - exported OpenAiRequestOptions type 7 new tests (isRequestAborted 4, createAbortError 3). * test(abort-signal): assert throwIfAborted error message The mutation-diff gate reports a surviving StringLiteral mutant on the throwIfAborted message literal. Assert the exact message so a blanked literal fails the suite. --------- Co-authored-by: Eason Liang <[email protected]> Co-authored-by: easonLiangWorldedtech <[email protected]> Co-authored-by: edelauna <[email protected]>
…rg#1651) * feat(api): add abort-signal cancellation-scope helpers to the shared 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. * fix: restore throwIfAborted export dropped by the util rewrite (+ tests) * fix(abort-r1): handle a rejecting pending promise in rejectOnAbort's pre-aborted branch * test(abort-r1): remove the process listener on every exit path * chore: trigger CodeRabbit re-review (findings already addressed) --------- Co-authored-by: Eason Liang <[email protected]> Co-authored-by: easonLiangWorldedtech <[email protected]>
…eMessage) (Zoo-Code-Org#1300) * feat(api): abort signal support for vscode-lm + fake-ai completion tests - completePrompt: bridge CompletePromptOptions.abortSignal and timeoutMs into a request-local vscode.CancellationTokenSource (the VS Code LanguageModelChat API only accepts a CancellationToken, not an AbortSignal); apply timeoutMs only when it is a positive value - completePrompt: report aborted requests (external signal, timeout, or host cancellation) as errors with name = "AbortError" on both the error and success paths; remove the abort listener and dispose the token source in finally - createMessage: bridge metadata.abortSignal into the internal request CancellationTokenSource (Bedrock pattern: pre-aborted guard + { once: true } listener stored in a named const), fail fast with an AbortError when the signal is already aborted, surface host CancellationError with name = "AbortError", and detach the listener / dispose the source in finally - vscode-lm.spec.ts: add pre-aborted and mid-flight abort, timeout, listener attach/detach, and backward-compatibility tests - fake-ai.spec.ts: option pass-through tests already merged on main via Zoo-Code-Org#901; verified green without changes * fix(api): address CodeRabbit review on vscode-lm abort handling - createMessage: sendRequest now uses the request-local cancellation source, the finally block disposes that local source and clears the shared field only when it still points at this request, and the error path no longer calls ensureCleanState (prevents an older finishing request from cancelling/disposing a newer request's token) - completePrompt: a pre-aborted signal now fails fast before getClient() and again before sendRequest(), so a cancelled request never initializes or invokes the host - completePrompt: a host vscode.CancellationError is normalized to an AbortError alongside isAborted() - spec: the timeout test now expects an AbortError (the cancelled token aborts the completion); the pre-abort test asserts sendRequest is never called; added a CancellationError -> AbortError rejection test; the mock CancellationTokenSource cancel() now flips isCancellationRequested to match the real API * fix(api): handle vscode-lm createMessage aborts during client init and streaming - createMessage re-checks the external abort signal after client initialization (and before sendRequest), cancelling the local token source and throwing an AbortError when the signal aborted while getClient() was pending - createMessage re-checks the external abort signal at the top of the stream consumption loop so a late abort stops the stream instead of yielding stale chunks (the bridged listener still covers the normal mid-flight case) - spec: the mid-flight abort test now expects the stream to stop with an AbortError; added a regression test where client initialization is gated on a release promise and the signal aborts in that window - the generator rejects with AbortError and sendRequest is never called * fix(api): start vscode-lm request cancellation before client initialization * fix(api): cancel vscode-lm request on premature generator closure + fix pre-abort test comment * test(vscode-lm): assert abort/timeout edge paths for the mutation-diff gate Adds focused assertions for the remaining mutation-diff mutants on the abort-signal and timeoutMs paths: external-abort pre-checks and the in-stream abort re-throw, cancellation listener cleanup, timeout timer scheduling and clearing, and host CancellationError wrapping. * test(vscode-lm): assert exact abort error messages and listener cleanup Close the 18 surviving mutants of the PR Zoo-Code-Org#1300 mutation-diff gate on vscode-lm.ts: - assert the exact canonical abort error message at every abort path in both createMessage and completePrompt - rewrite the finally listener-detach guards to if (externalAbortSignal && handler) so the guard only runs when the signal exists, and direct the correlated LogicalOperator mutant - direct the StringLiteral mutants on the guard/bridge abort errors: the catch re-throws its own canonical abort error, so those literals are unobservable - add a test where the external signal aborts while the host cancellation token was not cancelled, exercising the right-hand operand of isAborted() - strengthen the listener-spy tests to assert removeEventListener is called with the same listener reference, and clear the timeout timer unconditionally * fix(vscode-lm): check request-local cancellation in createMessage and cover negative timeout * fix(vscode-lm): re-check request-local token after client init Address CodeRabbit findings on the abort-signal series: - Re-check the request-local token immediately after getClient() so a request superseded while client initialization is pending aborts before token counting or host invocation. - Drive the counting-abort regression through the public path by mocking countTokens on the host chat mock, removing the @ts-ignore private-method suppression. - Add a gated-initialization supersession regression covering the new post-init guard. * fix: normalize superseded createMessage client-init failures to AbortError * fix(vscode-lm): reject quiet stream end after abort in createMessage The in-loop cancellation check only fires when the host delivers another chunk: if the external signal aborts (or a newer request supersedes this one) while the host stream is parked and the stream then ends normally, createMessage counted output tokens and yielded a usage chunk for the partial text instead of rejecting. completePrompt already guards this with its post-loop isAborted() check; createMessage now mirrors it with the same token/signal check after the stream loop and before output-token counting. Regression tests cover both cancellation sources (external signal abort and supersession via ensureCleanState) with a gated stream that ends quietly, asserting the canonical AbortError and no usage chunk. * test(vscode-lm): pin budget-gate edges and disable unobservable guard strings Preflight on the unit delta flagged the context-window budget gate and the refusal message: the gate condition (non-positive window disables trimming), the raw-budget arithmetic, the strict > admission boundary, and the refusal message's budget figure and pairing warning were all covered only by looser regex assertions. Add three tests that pin each edge (zero window sends an oversized request untouched; the refusal message is asserted in full, including the raw budget figure; a conversation exactly at the raw budget is sent, which the >= mutant refuses). The post-loop quiet-end guard's own error strings are never observed: the catch's abort branch intercepts the throw and re-throws its own canonical abort error with identical message and name, so those StringLiteral mutants are disabled with a concrete reason, matching the in-loop guard's existing directives. * test(vscode-lm): pin the raw-budget tool-schema subtraction With no tools in the request the -toolSchemaChars operator in the raw-budget arithmetic is equivalent to +toolSchemaChars (0 both ways), so the mutant was unobservable. Add a refusal test with a non-empty tool schema that asserts the exact budget figure: the mutant that flips the operator inflates the reported budget by twice the schema size and fails the assertion. * fix(api): reject non-finite and cap overflowing completePrompt timeouts CodeRabbit: the timeoutMs guard let Infinity and values above 2^31-1 through to setTimeout, which Node coerces to a 1ms delay - completePrompt could then cancel almost immediately with an AbortError. Non-finite values now mean "no timeout" and finite delays are capped at 2^31-1 ms. Regression tests: Infinity schedules no timer and completes; 2^31 ms schedules a capped timer that does not fire within the first second. Negative controls: reverting the guard fails each test in isolation (1 red per control). * fix(api): harden mergeAbortSignalAndTimeout against invalid delays Port of the timeout guard finding (CodeRabbit, Zoo-Code-Org#1300 thread wave): Node's AbortSignal.timeout validates its delay as uint32 - Infinity and finite fractions (1500.5, 0.5) throw ERR_OUT_OF_RANGE, and delays above 2^31-1 ms clamp to 1 ms (immediate abort). The helper now treats non-finite values as "no timeout", rounds fractional delays up (never down to 0), and caps the rest at 2^31-1 ms. vscode-lm's own setTimeout guard is unaffected: setTimeout accepts fractional delays (verified). Regression tests for non-finite, overflowing, and fractional values; each revert fails exactly one test. * test(api): assert the rounded delay reaching AbortSignal.timeout CodeRabbit: the fractional-timeout test only proved the call did not throw and eventually aborted - a round-down mutation (1.5 -> 1) would still pass. The test now spies AbortSignal.timeout and pins the received delay: 2 for 1.5 and 1 for 0.5 (never 0). Negative control: mutating Math.ceil to Math.floor fails exactly this test. * fix(api): round fractional completion timeouts up before scheduling CodeRabbit: the raw setTimeout path passed 1500.5 through and Node truncates non-integer delays, cancelling at 1500 ms - inconsistent with the PR objective and mergeAbortSignalAndTimeout's 1501. The guard now applies Math.ceil before the cap. New test pins the delay handed to setTimeout (1501 for 1500.5); negative control (reverting the ceil) fails exactly that test. * fix(api): reconcile Copilot cancellation tests with the merged abort contract The merge brought in main's GitHubCopilotHandler cancellation tests, which were written against main's vscode-lm internals. Two reconciliations: - createMessage now also records the caller's abort in an abortedByCaller flag set synchronously by the bridge listener, and the post-getClient / post-count re-checks consult it in addition to the token flag. The token flag alone cannot be the sole signal: cleanup cancels the token too, and a host token's flag is not observable before its cancel settles (main's own implementation used the same flag for this reason). - github-copilot.spec.ts: cancellation assertions now expect this unit's canonical abort error (createAbortError -> "The VS Code LM request was aborted", name AbortError) instead of main's interim strings, and the mid-stream completePrompt test asserts the contract that content racing a cancellation is not surfaced (rejects instead of resolving with late text), matching the pinned vscode-lm behavior. Full api suite green (3220 passed / 2 skipped); tsc, eslint, prettier clean. * test(vscode-lm): pin the bridged token in the completePrompt abort test createMessage already asserts sendRequest received the bridged token; completePrompt only checked that the token source was disposed, so a fresh token passed to sendRequest would have gone unnoticed. * style: apply repo Prettier to the merge resolution * test(api): pin the abort-bridge listener identity in the cleanup assertion The initialization-abort test only counted removeEventListener calls. Spy on addEventListener too and assert the removed listener is the exact callback the bridge registered, so a wrong-listener cleanup cannot pass. * fix(vscode-lm): cancel in-flight completions on handler disposal Review follow-ups: completePrompt token sources are tracked on the handler and cancelled in dispose() (removed from the set in finally), so tearing the handler down stops completion requests as well as streaming ones. completePrompt also adopts the shared hasUsableTimeout/toSdkTimeoutMs helpers instead of inlining ceil/cap. Adds two focused tests: an external abort landing while getClient() is pending rejects with the contract AbortError without a host request, and disposal during client initialization cancels the active completion token. vscode-lm.spec.ts 382 passed; tsc clean for the file. * fix(vscode-lm): reject a cancellation that lands inside the final token count Review follow-ups: internalCountTokens returns 0 on cancellation, so the generator re-checks the request cancellation before yielding the usage chunk instead of completing normally with a zero-output chunk. The boundary test now pins a window whose 0.8*3 character budget is an exact integer (2.4 is exact for a 128k-token window), so the equality case is representable and the >= mutant is detectable; a focused test aborts during the output count and asserts the contract AbortError. vscode-lm.spec.ts 382 passed (new count-abort test included). * test(vscode-lm): abort from the host count instead of spying a private method The platform-unit coverage run reported 19 downstream failures: spying on the private internalCountTokens made the abort land differently under coverage instrumentation. The test now aborts from the host countTokens mock (the output count is the call carrying the streamed text) and restores the original implementation, so the suite passes with and without coverage (383 passed in both). * fix(vscode-lm): surface the shared canonical abort for host cancellation Human review follow-up: replace the remaining hand-built AbortError for a host CancellationError with createAbortError(this.providerName); update the two assertions that pinned the old branded message to the canonical text. vscode-lm.spec.ts 383 passed; tsc clean. * test(vscode-lm): pin signal-only aborts and completion token disposal Review follow-ups: a createMessage test neutralises the bridged token cancel so only the external signal aborts (canonical AbortError); dispose tests cover a streaming completion, two concurrent completions, and a finished completion (which must not be cancelled again). vscode-lm.spec 387 passed; tsc clean. * test(vscode-lm): yield-empty generators in the signal-only abort test Lint follow-up: the quiet stream stubs must be generators, so they yield an empty iterable instead of only awaiting the gate. vscode-lm.spec 387 passed; eslint 0. --------- Co-authored-by: Eason Liang <[email protected]> Co-authored-by: edelauna <[email protected]> Co-authored-by: easonLiangWorldedtech <[email protected]> Co-authored-by: dsh-agent <dsh@local>
…oo-Code-Org#1309) * feat(api): add throwIfAborted helper and completePrompt options regression tests Add a fast-fail throwIfAborted guard to the shared abort-signal utilities and regression tests for the CompletePromptOptions interface (added by Zoo-Code-Org#901). * feat(api): abort-signal wiring for lm-studio and qwen-code (round 2) * fix(api): align lm-studio specs with abort-signal call shape and address review CI: platform-unit-test (ubuntu-latest) was failing on four LM Studio spec assertions that still expected the pre-PR single-argument SDK call shape. createMessage now calls chat.completions.create(params, { signal }) with the request-local AbortSignal, and completePrompt passes no options when no signal/timeout is configured. Update the two existing specs to the same two-argument assertions used by the new qwen-code-native-tools.spec.ts (expect.any(AbortSignal) for createMessage, undefined second argument for the no-signal completePrompt case). Review: (1) narrow isRequestAborted to an exact match on the OpenAI SDK abort error text ("Request was aborted.") instead of a substring scan, so unrelated errors that merely mention "abort" are no longer normalized as user cancellations; (2) abort the request-local controller in the createMessage finally block so early stream termination (break/return) cancels the in-flight SDK request. * test(api): cover LM Studio reasoning_content delta in abort-signal spec Changed-line coverage verification against the full PR diff (git diff origin/main...HEAD) found two executable added lines in lm-studio.ts uncovered by the PR's spec files: the reasoning_content/reasoning delta branch of createMessage (upstream main had since re-based that block into the PR diff). Add a focused streaming regression test to lm-studio-timeout.spec.ts that exercises the branch, restoring 100% changed-line coverage on both provider files (lm-studio.ts 35/35, qwen-code.ts 65/65 executable added lines). * test(api): cover qwen-code degenerate stream shapes for full branch coverage Codecov's patch report on the previous head flagged 5 partial-coverage lines in qwen-code.ts (lines 328, 338, 344, 398, 399): the defensive branches of the createMessage stream loop — a chunk with no choice (choices[0] ?? fallback), a delta that repeats the previous full content (empty after trimming), a think block that starts the text (empty leading split segment), and a zeroed usage payload (prompt_tokens || 0, completion_tokens || 0). Add one focused degenerate-stream-shapes test to qwen-code-native-tools spec covering all five branches in a single stream. Branch-level lcov cross-reference on the PR's added lines now reports zero partial-coverage added lines in both provider files, and line-level changed-line coverage stays 100% (lm-studio.ts 35/35, qwen-code.ts 65/65 executable added lines). * feat(api): add shared isRequestAborted and createAbortError helpers to abort-signal utils The OpenAI-family provider PRs (Zoo-Code-Org#1309, Zoo-Code-Org#1311) carry per-provider copies of the same abort-detection helper (isRequestAborted) and the same abort-error constructor (createAbortError); only the provider name in the message differs. Per the CodeRabbit maintainability finding on Zoo-Code-Org#1309 (extract the shared abort helpers into utils/abort-signal.ts), these are now shared in the foundation utility: - isRequestAborted(error, signal?) - true when the caller signal fired, a native AbortError / OpenAI SDK APIUserAbortError was raised, or the message is exactly "Request was aborted." (exact match; a substring match would misclassify unrelated errors that merely mention aborting) - createAbortError(providerName) - fresh error with name === "AbortError" and message "The <providerName> request was aborted", satisfying the Task.ts abort contract - exported OpenAiRequestOptions type 7 new tests (isRequestAborted 4, createAbortError 3). * refactor(api): use shared abort helpers from foundation utils in lm-studio and qwen-code The per-provider copies of isRequestAborted / createAbortError / OpenAiRequestOptions were extracted into src/api/providers/utils/abort-signal.ts by foundation PR Zoo-Code-Org#1288 (commit a0117fb) following the CodeRabbit maintainability finding on this PR; the providers now import the shared helpers. createAbortError takes the provider name as a parameter; provider behavior and abort messages are unchanged. * fix(api): normalize aborts during qwen-code 401 retry and lm-studio token counting CodeRabbit follow-ups on the abort-signal series: an abort landing while the qwen-code 401 retry request is in flight escaped callApiWithRetry as the raw SDK abort error instead of the normalized 'The Qwen Code request was aborted'; the retried apiCall now goes through the same isRequestAborted normalization. In lm-studio createMessage, an abort landing while input token counting is pending was silently dropped (a listener added to an already-aborted signal never fires), so the aborted state is bridged into the request-local controller and throwIfAborted fast-fails before the request is issued. Regression tests cover both paths. * test(api): cover the non-abort path of the qwen-code 401 retry Closes the remaining changed-line gap in qwen-code.ts: a 401 whose retry fails with a non-abort error must be rethrown unchanged (the abort check's false branch). * fix(ci): declare vitest as a root devDependency so the mutation gate resolves its bin * test(api): strengthen focused tests for the changed-code mutation gate * test(api): harden abort-wiring tests against hanging mutants * test(api): exclude proven-equivalent Stryker mutants from the changed-code gate * test(api): exclude unobservable inner-abort provider-name literals from the gate The two inner throw createAbortError("LM Studio") sites in createMessage and completePrompt always produce an error whose name is AbortError. isRequestAborted treats name AbortError as an aborted request, so the outer catch re-normalizes those failures into the same contract error. The provider-name StringLiteral at the inner sites is therefore unobservable and the StringLiteral mutants are provably equivalent. * fix(api): settle lm-studio and qwen-code wait points against the abort signal - add settleOnAbort helper: await pending work but reject with the request abort error the moment the signal aborts; the underlying work keeps running (a shared deduped token refresh is never cancelled by one request's signal) and the listener is removed on either outcome - lm-studio: race the input and output token counts against the request-local signal so a Stop during a pending count settles the generator promptly; skip usage reporting if a stop lands in the microtask gap before the usage yield - qwen-code: race ensureAuthenticated (credential load and token refresh) against the request signal in createMessage and completePrompt; read the OpenAI SDK status through a narrow shape instead of catch (error: any) - specs: pending-count and pending-auth abort contract tests plus settleOnAbort unit tests - eslint-suppressions: drop the qwen-code no-explicit-any entry consumed by the catch-clause fix * test(api): add abort kill-tests for lm-studio and qwen-code * Adapt qwen-code spec to per-scope NativeToolCallParser API * fix(deps): repair pnpm-lock.yaml broken by merge conflict resolution The merge of main into this branch resolved the lockfile conflict by taking main's lockfile plus only the PR-side importers hunk, leaving the root importer's vitest 4.1.9 reference without a matching packages/snapshots entry. pnpm install --frozen-lockfile failed with ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY in every CI job. Regenerated the lockfile with pnpm install --no-frozen-lockfile at the merge head: adds the [email protected] package/snapshot entries and the new peer resolutions. Verified: pnpm install --frozen-lockfile passes, lint 11/11, check-types 11/11, code-qa workflow, lifecycle model-check 7/7, mcp integration check, translations, knip, and the 7 PR-affected vitest suites (143 tests). * fix(deps): align root vitest with the 4.1.11 security bump GitHub dependency review flagged vitest 4.1.9 (the root devDependency this branch introduced) as vulnerable; the repo already moved the per-package vitest pins to 4.1.11 in Zoo-Code-Org#1582 (security). Set the root pin to 4.1.11 and regenerated pnpm-lock.yaml, which drops the now-unused [email protected] package/snapshot entries. Verified: pnpm install --frozen-lockfile passes, knip, and the 7 PR-affected vitest suites (143 tests). * fix(api): race the Qwen 401 refresh wait against the abort signal and tighten listener-identity assertions Address the CodeRabbit findings on the 401 refresh path: - callApiWithRetry now races the refresh wait against the caller's signal (settleOnAbort), so a stop settles promptly instead of blocking on the token endpoint; the shared refresh keeps running and only this wait is cut. - The abort-during-401-refresh spec now keeps the token endpoint pending forever, proving the wait is cut by the abort (a post-hoc aborted check alone would hang). - settleOnAbort listener-cleanup specs assert the exact registered listener reference is removed, not just that some function was removed. * fix(qwen-code): keep in-memory credentials current after a cancelled 401 wait (CodeRabbit, R2, Zoo-Code-Org#1309) doRefreshAccessToken persisted the refreshed token but left the in-memory credentials to the waiting caller's assignment. When settleOnAbort rejected on abort before the shared refresh settled, that assignment was skipped: the background refresh had already saved the new token, yet the handler kept the stale one in memory, so the next request reused it and triggered a second 401 refresh. Assign this.credentials in the shared refresh itself, and restructure the refresh-wait test so the abort lands after settleOnAbort has registered its listener (the old mock aborted synchronously inside the fetch, only exercising the already-aborted fast path). New regression test: abort cuts the established wait, the refresh settles in the background, and the in-memory credentials carry the refreshed token. 28/28 specs pass; eslint clean (suppressions unchanged). * chore: trigger label reconcile (CI green + CR approved, cron stalled) * Assert the abort bridge while the request is in flight Both tests asserted after the call had already finished, so they passed even with a broken bridge: the provider's finally block aborts the request signal on its own, and releasing the count gate lets counting finish normally. - the signal test now keeps the mocked stream open, aborts the caller signal and checks the captured request signal before the stream is released; - the cancellation test now asserts the pending count rejects while the gate is still closed, and releases the gate in a finally block. 54 tests green across the two suites; prettier, eslint and tsc clean. * Re-trigger review at the current head Every review thread on this PR is resolved and CI is green at this head; the review decision still points at an older commit. This empty commit re-runs the review so the decision and the label reflect the current head. * Address pre-merge checks: detach settleOnAbort's listener on abort; pin completePrompt fast-fail settleOnAbort registered an abort listener that its onAbort callback never removed: when the pending promise never settled (e.g. a wedged shared token refresh), the listener stayed attached to the caller's signal for the signal's whole life. A single cleanup function now detaches the exact registered listener at most once, called from the abort callback and from the pending promise's fulfillment/rejection handlers. New test aborts with a never-settling pending promise and asserts the listener is removed by the abort callback itself (negative control: removing the cleanup call from onAbort fails exactly that test). Also adds the missing focused test for LmStudioHandler.completePrompt's pre-aborted-signal fast-fail: an already-aborted signal rejects with the contract AbortError and chat.completions.create is never called (negative control: removing the throwIfAborted guard fails exactly that test). Verification at this head: 67/67 green across abort-signal.spec.ts and lm-studio.spec.ts; tsc --noEmit 0 errors; eslint --max-warnings=0 clean on touched files with suppression counts unchanged; prettier clean. * Address review: pin listener removal by function reference Both listener-removal tests accepted any function in the removeSpy assertion, so removing a different function than the one registered would still pass. Per the path instruction (and CR suggestions): the lm-studio createMessage test now captures the registered listener from addSpy and asserts removeSpy was called with that exact reference; the settleOnAbort rejection-propagation test now uses the same addSpy.mock.calls[0]?.[1] pattern as the other cleanup tests in the file. Verification at this head: 67/67 green across abort-signal.spec.ts and lm-studio.spec.ts; tsc --noEmit 0 errors; eslint --max-warnings=0 clean on touched files; prettier clean. * Address pre-merge warnings: guard settleOnAbort's early return; share Qwen credential loads settleOnAbort's already-aborted early return handed the caller the abort error without attaching a rejection handler to the pending promise, exposing a rejecting pending promise as an unhandled rejection. A no-op rejection guard now runs before the early return (same shape as rejectOnAbort's existing guard). New test aborts with a rejecting pending promise and asserts no unhandledRejection fires while the abort error surfaces (negative control: removing the guard fails exactly that test). QwenCodeHandler duplicated credential loading after cancellation: createMessage/completePrompt race ensureAuthenticated() with settleOnAbort, so a cancelled first request left its load running and a second request started a fresh credential read. ensureAuthenticated now shares the in-flight load promise the same way refreshAccessToken shares its refresh, cleared in a finally once the load settles, so a failed load is retried by the next request. New tests: concurrent cancel+retry reuses one credential read; success and failure both clear the shared promise (negative control: breaking the reuse guard fails exactly the reuse test; the test releases the hung read in a finally so a failing run cannot leak the pending load into later tests). Review test-fidelity threads: the two qwen-code-native-tools 401 refresh tests now abort only after the refresh wait is established (fetch never settles; drain until the fetch starts) instead of aborting synchronously inside the fetch mock, which exercised only the already-aborted fast path; the two qwen-code listener tests now assert the exact registered listener reference is removed instead of expect.any(Function). Verification at this head: 128/128 green across abort-signal, qwen-code, qwen-code-native-tools and lm-studio specs; tsc --noEmit 0 errors; eslint --max-warnings=0 clean with suppression counts unchanged; prettier clean. * Address review: cover LM Studio early consumer termination createMessage's finally block cancels the request-local SDK signal and detaches the external abort listener, but no test pinned the early consumer exit (break/return while the SDK stream is still pending): the existing coverage only exercised normal completion and external-signal aborts. New test consumes one chunk from a stream that then stays pending, calls stream.return(), and asserts the request-local SDK signal becomes aborted and the exact registered external listener is removed (by reference, per the established pattern). Negative control: removing the finally-block abort fails exactly this test (isolated run). Verification at this head: 27/27 green in lm-studio.spec.ts; tsc --noEmit 0 errors; eslint --max-warnings=0 clean with suppression counts unchanged; prettier clean. * Address review: pin no premature abort before early termination The early-termination test only observed the request-local signal after stream.return(), so a premature abort (one landing while the consumer is still iterating) would also pass. The test now captures the SDK request options after the first chunk and asserts the request-local signal is still live (aborted === false) before the early return, retaining the post-return aborted assertion. Negative control: aborting the request-local controller right after the SDK create call fails exactly this test (isolated run: the new false assertion reddens). Verification at this head: 27/27 green in lm-studio.spec.ts; tsc --noEmit 0 errors; eslint --max-warnings=0 clean with suppression counts unchanged; prettier clean. * chore(api): remove Stryker suppression comments from touched files Per repo policy (AGENTS.md: Stryker suppression comments are banned; the mutation gate rejects them in touched files). Equivalence arguments for the resulting mutants are recorded in the PR body. * fix(api): reject non-finite and cap overflowing mergeAbortSignalAndTimeout delays Port of the Zoo-Code-Org#1300 timeout guard finding (CodeRabbit): Node's AbortSignal.timeout throws ERR_OUT_OF_RANGE for non-integer delays (Infinity) and clamps delays above 2^31-1 ms to 1 ms, so a non-finite timeoutMs threw a RangeError instead of running the request, and an overflowing one aborted almost immediately. The helper now treats non-finite values as "no timeout" and caps finite delays at 2^31-1 ms. Regression tests added; negative controls (reverting the guard) fail exactly the two new tests. * fix(api): round fractional timeouts before AbortSignal.timeout CodeRabbit follow-up on the timeout guard: Node validates AbortSignal.timeout delays as uint32, so finite fractions (1500.5, 0.5) threw ERR_OUT_OF_RANGE just like Infinity did. The helper now rounds delays up (a positive fraction never becomes 0 ms) before capping at 2^31-1 ms. Regression test mergeAbortSignalAndTimeout (undefined, 1.5); negative control (reverting the rounding) fails exactly that test. * test(api): assert the rounded delay reaching AbortSignal.timeout CodeRabbit: the fractional-timeout test only proved the call did not throw and eventually aborted - a round-down mutation (1.5 -> 1) would still pass. The test now spies AbortSignal.timeout and pins the received delay: 2 for 1.5 and 1 for 0.5 (never 0). Negative control: mutating Math.ceil to Math.floor fails exactly this test. * fix(api): restore the OpenAiRequestOptions type the abort wiring imports upstream/main removed the type in its knip dead-code pass; lm-studio.ts and qwen-code.ts still import it, so the merge has to re-add it. * fix(api): report an elapsed per-request timeout as TimeoutError completePrompt merged the caller signal with the timeout and mapped every abort-shaped failure to AbortError, so a deadline expiry looked like a user stop. Distinguish them via the merged signal reason: TimeoutError when the timer fired while the caller signal stayed live, AbortError for a real cancellation. Applied to both lm-studio and qwen-code (same PR, same contract) with regression tests whose negative controls fail with expected AbortError to be TimeoutError. * refactor(providers): share the timeout-vs-abort helpers and dedupe the LM Studio catch selection Review follow-ups: createTimeoutError/isTimeoutAbort move from qwen-code.ts and lm-studio.ts into utils/abort-signal.ts (providerName stays a createTimeoutError parameter; behavior unchanged) and both providers import them. LM Studio completePrompt selects TimeoutError vs the contract AbortError through one private helper used by the inner create catch and the outer completion catch. The qwen elapsed-timeout test now passes a live caller signal alongside timeoutMs and asserts TimeoutError, and uses mockImplementationOnce so the signal-waiting mock cannot leak into later tests. Contract tests for the shared helpers live in abort-signal.spec.ts. qwen-code + lm-studio + abort-signal suites 111 passed; abort-signal 58 passed; tsc clean. * fix(abort-signal): classify timeout aborts by the merged signal reason Review follow-ups: isTimeoutAbort takes only the merged requestSignal and keys on its retained abort reason, so a timeout that fires before a later caller abort stays a TimeoutError (and a caller-first abort stays a cancellation); both providers updated. Tests cover both abort orders. The elapsed-timeout completePrompt test moved into the completePrompt suite. abort-signal + qwen-code + lm-studio specs 116 passed; tsc clean. * chore(lm-studio): drop the unused caller signal parameter Follow-up to the isTimeoutAbort signature change: toCompletionAbortError only needs the merged request signal. tsc clean; eslint on the touched providers exits 0. * test(lmstudio): wait for the request instead of a fixed sleep * fix(api): settle qwen-code streams that end quietly on abort Review threads on Zoo-Code-Org#1309: - qwen-code.ts: the OpenAI SDK iterator returns without throwing when its controller aborts (openai/core/streaming.js: isAbortError -> return), so a stop mid-stream left the generator looking like a completed turn with no usage chunk. Re-check the request-local signal after the stream loop, as lm-studio.ts already does. Regression test included; negative control: the test fails with the guard removed. - lm-studio.ts: drop the unreachable already-aborted branch - nothing is awaited between throwIfAborted and the listener registration - and fix the comment that justified it. - abort-signal.ts: settleOnAbort delegates to rejectOnAbort/raceAbort instead of maintaining a second copy of the race. raceAbort now detaches its listener exactly once from whichever side settles first (the abort callback included), so both helper suites keep their leak guards; three assertions that pinned the addEventListener argument count now assert only that the registration happened once. - abort-signal.ts: comments describe what the helpers do instead of referring to a PR series. - specs: replace fixed 10 ms sleeps and fixed-depth promise ticks with vi.waitFor on the observable condition, and add concurrent-request tests for both providers (aborting one request must not cancel the other's signal). Local verification: api/providers + api/transform 3716 tests green, tsc --noEmit clean, eslint --max-warnings=0 clean with no suppression changes, prettier clean, knip strict clean. * test(api): poll instead of assuming await depth, and exercise the merged signal for real,,Three review findings on the abort-signal tests:,,- qwen-code-native-tools.spec.ts: the two 401-refresh tests drained up to 50 microtasks with, 'await Promise.resolve()' before asserting the refresh fetch had started, and waitForCreateCall, re-implemented vi.waitFor with a setTimeout loop. Both replaced by vi.waitFor polls, matching, qwen-code.spec.ts. Control: a 2-deep microtask drain fails with 'expected "vi.fn()" to be called, 1 times, but got 0 times' at spec:796 while the poll passes. Honest note: on the SDK-call wait a, 2-deep drain still happened to pass today, so that half is a latent trap rather than a present, failure - the helper is gone either way.,- abort-signal.spec.ts: the isTimeoutAbort order tests built two unlinked controllers (and copied, the reason by hand), so they passed regardless of what the merge actually preserves. Both now, build the signal with the real mergeAbortSignals. Control: swapping the two abort calls in the, timeout-first test now fails with "AssertionError: expected false to be true" - the test tracks, the merge semantics it describes.,- abort-signal.spec.ts: added the null abort-reason case. Control: removing the optional chain on, requestSignal.reason at abort-signal.ts:320 fails the new test with 'TypeError: Cannot read, properties of null (reading name)' at abort-signal.ts:320:85, which is the surviving, OptionalChaining mutant mutation-diff reported on that line.,,Verification: api/providers/__tests__/qwen-code-native-tools.spec.ts +,api/providers/utils/__tests__/abort-signal.spec.ts -> 2 files, 99 tests passed. tsc --noEmit: 0,errors. eslint --max-warnings=0 exit 0 on both files with src/eslint-suppressions.json untouched.,prettier --check clean. * test(api): ban setTimeout in provider specs, use waitFor and shared helpers --------- Co-authored-by: Eason Liang <[email protected]> Co-authored-by: easonLiangWorldedtech <[email protected]> Co-authored-by: easonLiangWorldedtech <[email protected]> Co-authored-by: Elliott de Launay <[email protected]>
…ssion tests (Zoo-Code-Org#1288) * feat(api): add throwIfAborted helper and completePrompt options regression tests Add a fast-fail throwIfAborted guard to the shared abort-signal utilities and regression tests for the CompletePromptOptions interface (added by Zoo-Code-Org#901). * feat(api): add shared isRequestAborted and createAbortError helpers to abort-signal utils The OpenAI-family provider PRs (Zoo-Code-Org#1309, Zoo-Code-Org#1311) carry per-provider copies of the same abort-detection helper (isRequestAborted) and the same abort-error constructor (createAbortError); only the provider name in the message differs. Per the CodeRabbit maintainability finding on Zoo-Code-Org#1309 (extract the shared abort helpers into utils/abort-signal.ts), these are now shared in the foundation utility: - isRequestAborted(error, signal?) - true when the caller signal fired, a native AbortError / OpenAI SDK APIUserAbortError was raised, or the message is exactly "Request was aborted." (exact match; a substring match would misclassify unrelated errors that merely mention aborting) - createAbortError(providerName) - fresh error with name === "AbortError" and message "The <providerName> request was aborted", satisfying the Task.ts abort contract - exported OpenAiRequestOptions type 7 new tests (isRequestAborted 4, createAbortError 3). * test(abort-signal): assert throwIfAborted error message The mutation-diff gate reports a surviving StringLiteral mutant on the throwIfAborted message literal. Assert the exact message so a blanked literal fails the suite. --------- Co-authored-by: Eason Liang <[email protected]> Co-authored-by: easonLiangWorldedtech <[email protected]> Co-authored-by: edelauna <[email protected]> (cherry picked from commit e12a42e)
Summary
Fixes #615 — Core plumbing for abort signal in
completePromptpath.This PR adds an optional second parameter (
CompletePromptOptions) to all providercompletePromptmethods, enabling future abort signal and timeout support without breaking existing callers.Changes
New:
CompletePromptOptionsinterfaceDesign note: Rather than passing the full
ApiHandlerCreateMessageMetadataas issue #615 described, this PR introduces a minimal interface that only exposesabortSignalandtimeoutMs. This keeps the API surface clean while still enabling abort signal pass-through. Themetadatapattern used in the stream path (viaApiHandlerCreateMessageMetadata) is already handled separately.Updated files
src/api/index.ts— AddedCompletePromptOptions, updatedSingleCompletionHandler.completePromptsignatureoptions?: CompletePromptOptions(backward compatible, optional)src/utils/single-completion-handler.ts— Forwards options to the handlerTests (
Task.spec.ts)toBeInstanceOf(AbortSignal)→toBe(task.currentRequestAbortController!.signal)for signal identity verificationAcceptance criteria (from #615)
SingleCompletionHandler.completePromptinterface accepts optional metadata/optionssingle-completion-handler.tsforwards options to the providerTask.tsattaches abortSignal to metadata (already wired in feat(api): pass abortSignal to streaming API calls for all providers (#404) #434, this PR completes the interface layer)Related
Summary by CodeRabbit
New Features
optionsparameter, includingabortSignaland optionaltimeoutMs.completePromptacross providers and the single-completion flow to accept and forward these options.Tests
abortSignalinstance and verified retries use a fresh, initially un-aborted signal.completePromptto be called with an explicitundefinedoptions argument when omitted.completePromptwith the correct prompt/options.