Skip to content

feat(api): pass abortSignal to streaming API calls for all providers (#404)#434

Closed
easonLiangWorldedtech wants to merge 3 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:fix/abort-signal-openai-compatible
Closed

feat(api): pass abortSignal to streaming API calls for all providers (#404)#434
easonLiangWorldedtech wants to merge 3 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:fix/abort-signal-openai-compatible

Conversation

@easonLiangWorldedtech

@easonLiangWorldedtech easonLiangWorldedtech commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #404 — When the user clicks Stop during streaming, the HTTP request to the provider continues running because the abort signal wasn't being passed through. This wastes API tokens/compute on the provider side.

Changes

Commit 1: feat(api): add abort signal support for API providers and task

  • Added { signal: metadata?.abortSignal } to streaming .chat.completions.create() calls in 16 providers:
    • lm-studio.ts, openai.ts, deepseek.ts
    • base-openai-compatible-provider.ts (merged into requestOptions)
    • lite-llm.ts, mimo.ts, opencode-go.ts, openrouter.ts, qwen-code.ts, requesty.ts, unbound.ts, vercel-ai-gateway.ts, zai.ts
    • anthropic.ts, azure-openai.ts, bedrock.ts
  • Added abortSignal?: AbortSignal to ApiHandlerCreateMessageMetadata interface
  • Wired abort signal through Task.ts's AbortController

Commit 2: feat(api): integrate external abort signal with internal controllers

  • Previously, Anthropic, Bedrock, and OpenAI Native used their own internal AbortControllers but did not listen to the external abort signal from the Task execution chain. Clicking stop would not immediately cancel requests to these providers. Now all providers consistently respect the user's stop action.
  • Added optional metadata parameter with abortSignal to completePrompt methods across 22 provider implementations:
    • Anthropic, Anthropic Vertex, Base OpenAI Compatible
    • Gemini/Vertex, LiteLLM, LM Studio, MiniMax, Mistral
    • Native Ollama, OpenAI, OpenAI Compatible, OpenRouter
    • Qwen Code, Requesty, Unbound, Vercel AI Gateway
    • Opencode Go, xAI, Zoo Gateway, VSCode LM, Poe, Bedrock, OpenAI Codex, OpenAI Native
  • Added comment explaining Mistral completePrompt does not support non-streaming abort (Mistral SDK limitation)

Test Coverage

  • task-abort-signal-passing.spec.ts — Comprehensive tests for abort signal flow through Task execution chain
  • openai-compatible-abort-signal.spec.ts — Provider-level abort signal verification for OpenAI Compatible handler
  • Added abort signal tests for all modified providers (20+ test files updated)

Architecture

User clicks Stop
  → Task.abort() called
  → currentRequestAbortController.abort() triggered
  → abortSignal.aborted === true
  → All provider SDK calls receive { signal: abortSignal }
  → HTTP requests cancelled immediately
  → API tokens/compute saved on provider side

test video ref(not latest)

2026-06-01.230625.mp4

Summary by CodeRabbit

  • New Features

    • Global request cancellation: an optional abort signal can now cancel in-flight streaming and non-streaming AI requests across providers.
  • Tests

    • Expanded unit and integration coverage verifying abort-signal propagation and cancellation behavior across streaming, completions, and task request lifecycles.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 02350b68-ee54-4bbe-9b3a-73a5625410bf

📥 Commits

Reviewing files that changed from the base of the PR and between 963e058 and ad82a44.

📒 Files selected for processing (20)
  • src/api/providers/__tests__/bedrock.spec.ts
  • src/api/providers/__tests__/deepseek.spec.ts
  • src/api/providers/__tests__/gemini.spec.ts
  • src/api/providers/__tests__/native-ollama.spec.ts
  • src/api/providers/__tests__/openai-native.spec.ts
  • src/api/providers/__tests__/vercel-ai-gateway.spec.ts
  • src/api/providers/__tests__/vscode-lm.spec.ts
  • src/api/providers/__tests__/zoo-gateway.spec.ts
  • src/api/providers/bedrock.ts
  • src/api/providers/gemini.ts
  • src/api/providers/native-ollama.ts
  • src/api/providers/openai-codex.ts
  • src/api/providers/openai-native.ts
  • src/api/providers/openai.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/unbound.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/vscode-lm.ts
  • src/api/providers/xai.ts
  • src/api/providers/zoo-gateway.ts
✅ Files skipped from review due to trivial changes (1)
  • src/api/providers/openai-native.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • src/api/providers/openai-codex.ts
  • src/api/providers/tests/deepseek.spec.ts
  • src/api/providers/tests/zoo-gateway.spec.ts
  • src/api/providers/xai.ts
  • src/api/providers/unbound.ts
  • src/api/providers/tests/openai-native.spec.ts
  • src/api/providers/openai.ts
  • src/api/providers/zoo-gateway.ts
  • src/api/providers/tests/gemini.spec.ts
  • src/api/providers/tests/bedrock.spec.ts
  • src/api/providers/vscode-lm.ts
  • src/api/providers/gemini.ts
  • src/api/providers/tests/vscode-lm.spec.ts
  • src/api/providers/native-ollama.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/bedrock.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/tests/vercel-ai-gateway.spec.ts
  • src/api/providers/tests/native-ollama.spec.ts

📝 Walkthrough

Walkthrough

Adds optional AbortSignal to ApiHandler metadata; Task attaches per-request signal; providers forward metadata.abortSignal into streaming and non-streaming SDK request options; many tests updated/added to assert option shapes and runtime cancellation behavior.

Changes

Abort Signal Propagation

Layer / File(s) Summary
Contract & Task wiring
src/api/index.ts, src/core/task/Task.ts
Adds abortSignal?: AbortSignal to ApiHandlerCreateMessageMetadata and stores the per-request AbortSignal into metadata before provider calls.
Provider handlers forward abortSignal
src/api/providers/...
Many providers now pass metadata?.abortSignal (as signal or abortSignal per SDK) into streaming and non-streaming SDK calls; several completePrompt signatures accept optional metadata to forward the signal.
Adapter-specific bridges
src/api/providers/bedrock.ts, src/api/providers/native-ollama.ts, src/api/providers/openai-native.ts, src/api/providers/vscode-lm.ts
Providers that require internal controllers/token sources bridge external abortSignal into their internal cancellation mechanisms (attach listeners, forward to internal controllers, or map to token sources).
Tests: shape updates and abort behavior
src/api/providers/__tests__/*, src/core/task/__tests__/*
Updates many tests to expect a second options argument (using expect.any(Object) or explicit { signal: ... }) and adds test suites that assert abortSignal forwarding and runtime cancellation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Zoo-Code-Org/Zoo-Code#344: Related earlier work on the Zoo Gateway provider that this PR extends by wiring abortSignal into ZooGatewayHandler calls.
  • Zoo-Code-Org/Zoo-Code#210: Also modifies Mimo/OpenAI-compatible provider interactions; related area of provider request handling.

Suggested reviewers

  • taltas
  • navedmerchant
  • hannesrudolph
  • JamesRobert20

"🐰 I threaded a signal through tasks and streams,
When users stop, the network halts its schemes.
From Task to provider, the abort trips the wire,
Streams end neatly, no runaway fire.
Hooray — now stop truly stops, carrot-powered dreams!"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@easonLiangWorldedtech easonLiangWorldedtech changed the title fix(api): pass abortSignal to streamText() for OpenAI Compatible prov… fix(api): pass abortSignal to streaming API calls for all providers (#404) Jun 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/api/providers/openai.ts (1)

247-250: ⚡ Quick win

Main non-streaming path omits abortSignal.

The O3 non-streaming flow (Line 408) and both streaming flows forward metadata?.abortSignal, but this main non-streaming request still passes only path. For consistency and to cancel non-streaming requests too, forward the signal here as well.

♻️ Proposed change
-				response = await this.client.chat.completions.create(
-					requestOptions,
-					this._isAzureAiInference(modelUrl) ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {},
-				)
+				response = await this.client.chat.completions.create(requestOptions, {
+					...(this._isAzureAiInference(modelUrl) ? { path: OPENAI_AZURE_AI_INFERENCE_PATH } : {}),
+					signal: metadata?.abortSignal,
+				})
🤖 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/openai.ts` around lines 247 - 250, The non-streaming call
to this.client.chat.completions.create omits the abort signal; update the call
site that currently passes only { path: OPENAI_AZURE_AI_INFERENCE_PATH } so it
also forwards metadata?.abortSignal (or include abortSignal when not undefined)
alongside path when _isAzureAiInference(modelUrl) is true, and ensure the same
abortSignal is passed in the alternate (non-Azure) branch as well so
requestOptions and the signal are both honored by
this.client.chat.completions.create.
🤖 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.

Nitpick comments:
In `@src/api/providers/openai.ts`:
- Around line 247-250: The non-streaming call to
this.client.chat.completions.create omits the abort signal; update the call site
that currently passes only { path: OPENAI_AZURE_AI_INFERENCE_PATH } so it also
forwards metadata?.abortSignal (or include abortSignal when not undefined)
alongside path when _isAzureAiInference(modelUrl) is true, and ensure the same
abortSignal is passed in the alternate (non-Azure) branch as well so
requestOptions and the signal are both honored by
this.client.chat.completions.create.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a5124a34-2d10-49c1-a063-7e83f8ab99f7

📥 Commits

Reviewing files that changed from the base of the PR and between f1f7cb4 and 299c474.

📒 Files selected for processing (18)
  • src/api/index.ts
  • src/api/providers/__tests__/openai-compatible-abort-signal.spec.ts
  • src/api/providers/base-openai-compatible-provider.ts
  • src/api/providers/deepseek.ts
  • src/api/providers/lite-llm.ts
  • src/api/providers/lm-studio.ts
  • src/api/providers/mimo.ts
  • src/api/providers/openai-compatible.ts
  • src/api/providers/openai.ts
  • src/api/providers/opencode-go.ts
  • src/api/providers/openrouter.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/requesty.ts
  • src/api/providers/unbound.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/zai.ts
  • src/core/task/Task.ts
  • src/core/task/__tests__/task-abort-signal-passing.spec.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/api/providers/__tests__/openai-compatible-abort-signal.spec.ts (3)

73-82: ⚡ Quick win

Remove unused helper function and variable.

The createMockStream function (lines 73-80) and mockUsage variable (line 82) are defined but never used in this test. The mock is set up directly on line 84-87 instead.

♻️ Suggested cleanup
-		function createMockStream(yieldValue: any) {
-			return {
-				fullStream: (async function* () {
-					yield yieldValue
-				})(),
-				usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
-			}
-		}
-
-		const mockUsage = Promise.resolve({ inputTokens: 10, outputTokens: 5 })
-
 		mockStreamText.mockReturnValue({
 			fullStream: mockFullStream(),
-			usage: mockUsage,
+			usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
 		})
🤖 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/__tests__/openai-compatible-abort-signal.spec.ts` around
lines 73 - 82, Remove the unused helper and variable: delete the
createMockStream function and the mockUsage constant since they are never
referenced in the test; ensure any tests still rely on the explicit mock set up
later (the inline mock at lines that replace createMockStream) and run tests to
confirm nothing else depends on createMockStream or mockUsage.

108-117: ⚡ Quick win

Remove duplicate unused helper function.

The createMockStream function (lines 108-115) is defined but never used. This is a duplicate of the unused helper from the previous test.

♻️ Suggested cleanup
-		function createMockStream(yieldValue: any) {
-			return {
-				fullStream: (async function* () {
-					yield yieldValue
-				})(),
-				usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
-			}
-		}
-
-		const mockUsage = Promise.resolve({ inputTokens: 10, outputTokens: 5 })
-
 		mockStreamText.mockReturnValue({
 			fullStream: mockFullStream(),
-			usage: mockUsage,
+			usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
 		})
🤖 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/__tests__/openai-compatible-abort-signal.spec.ts` around
lines 108 - 117, The test contains a duplicate, unused helper function
createMockStream (and its adjacent mockUsage Promise) that duplicates a helper
from the previous test; remove the createMockStream declaration (and the unused
mockUsage Promise if it is also unused) so the test file only keeps one shared
helper and avoids dead code; search for createMockStream and mockUsage to ensure
no usages remain before deleting.

142-151: ⚡ Quick win

Remove third duplicate unused helper function.

Yet another createMockStream function (lines 142-149) that is defined but never called.

♻️ Suggested cleanup
-		function createMockStream(yieldValue: any) {
-			return {
-				fullStream: (async function* () {
-					yield yieldValue
-				})(),
-				usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
-			}
-		}
-
-		const mockUsage = Promise.resolve({ inputTokens: 10, outputTokens: 5 })
-
 		mockStreamText.mockReturnValue({
 			fullStream: mockFullStream(),
-			usage: mockUsage,
+			usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
 		})
🤖 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/__tests__/openai-compatible-abort-signal.spec.ts` around
lines 142 - 151, Remove the duplicate, unused helper function createMockStream
(the async generator returning fullStream and usage) from the test; leave the
existing mockUsage constant in place if tests use it. Locate the duplicate
createMockStream definition in the file (the one that returns { fullStream:
(async function*(){ yield yieldValue })(), usage: Promise.resolve(...) }) and
delete it so there is only the intended helper(s) remaining and no unused symbol
shadowing.
🤖 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.

Nitpick comments:
In `@src/api/providers/__tests__/openai-compatible-abort-signal.spec.ts`:
- Around line 73-82: Remove the unused helper and variable: delete the
createMockStream function and the mockUsage constant since they are never
referenced in the test; ensure any tests still rely on the explicit mock set up
later (the inline mock at lines that replace createMockStream) and run tests to
confirm nothing else depends on createMockStream or mockUsage.
- Around line 108-117: The test contains a duplicate, unused helper function
createMockStream (and its adjacent mockUsage Promise) that duplicates a helper
from the previous test; remove the createMockStream declaration (and the unused
mockUsage Promise if it is also unused) so the test file only keeps one shared
helper and avoids dead code; search for createMockStream and mockUsage to ensure
no usages remain before deleting.
- Around line 142-151: Remove the duplicate, unused helper function
createMockStream (the async generator returning fullStream and usage) from the
test; leave the existing mockUsage constant in place if tests use it. Locate the
duplicate createMockStream definition in the file (the one that returns {
fullStream: (async function*(){ yield yieldValue })(), usage:
Promise.resolve(...) }) and delete it so there is only the intended helper(s)
remaining and no unused symbol shadowing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: efeee00b-056f-4dc5-aadf-f93a7464e61f

📥 Commits

Reviewing files that changed from the base of the PR and between ef0a6de and ee47fbb.

📒 Files selected for processing (24)
  • packages/types/src/tool-params.ts
  • src/api/index.ts
  • src/api/providers/__tests__/fireworks.spec.ts
  • src/api/providers/__tests__/openai-compatible-abort-signal.spec.ts
  • src/api/providers/__tests__/sambanova.spec.ts
  • src/api/providers/__tests__/zai.spec.ts
  • src/api/providers/base-openai-compatible-provider.ts
  • src/api/providers/deepseek.ts
  • src/api/providers/lite-llm.ts
  • src/api/providers/lm-studio.ts
  • src/api/providers/mimo.ts
  • src/api/providers/openai-compatible.ts
  • src/api/providers/openai.ts
  • src/api/providers/opencode-go.ts
  • src/api/providers/openrouter.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/requesty.ts
  • src/api/providers/unbound.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/zai.ts
  • src/core/task/Task.ts
  • src/core/task/__tests__/task-abort-signal-passing.spec.ts
  • src/core/tools/ReadFileTool.ts
  • src/core/tools/__tests__/readFileTool.spec.ts
🚧 Files skipped from review as they are similar to previous changes (17)
  • src/api/index.ts
  • src/api/providers/zai.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/tests/sambanova.spec.ts
  • src/api/providers/opencode-go.ts
  • src/api/providers/unbound.ts
  • src/api/providers/lite-llm.ts
  • src/api/providers/lm-studio.ts
  • src/api/providers/mimo.ts
  • src/api/providers/tests/fireworks.spec.ts
  • src/api/providers/tests/zai.spec.ts
  • src/api/providers/deepseek.ts
  • src/core/task/Task.ts
  • src/api/providers/openrouter.ts
  • src/api/providers/qwen-code.ts
  • src/core/task/tests/task-abort-signal-passing.spec.ts
  • src/api/providers/openai.ts

@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch from 4e0b0e7 to 71da495 Compare June 9, 2026 16:35
easonLiangWorldedtech pushed a commit to easonLiangWorldedtech/Zoo-Code that referenced this pull request Jun 9, 2026
…oo-Code-Org#434)

- Add { signal } parameter to chat.completions.create() in all provider handlers
- Update test assertions to expect.any(Object) for the second parameter
- Add new tests for abort signal passing (abort-signal.spec.ts, task-abort-signal-passing.spec.ts)
- Fix fireworks.spec.ts streaming test assertions

27 files changed, 122 insertions(+), 54 deletions(-)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/api/providers/__tests__/unbound.spec.ts (1)

91-91: 💤 Low value

Consider a more specific assertion for request options.

The test now uses expect.any(Object) to match the second argument (request options), which is loose. While dedicated abort signal tests exist elsewhere, this test could be more precise about the expected shape.

✨ Optional: More specific assertion
-		expect.any(Object),
+		expect.objectContaining({ signal: expect.anything() }),

This verifies the options object has a signal property without coupling the test to specific abort signal values.

🤖 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/__tests__/unbound.spec.ts` at line 91, Replace the loose
expect.any(Object) used for the request options in the test with a more specific
matcher that asserts the presence of an abort signal; for example change the
second-argument matcher to expect.objectContaining({ signal: expect.any(Object)
}) (or expect.anything() if preferred) so the test verifies the options object
includes a signal property while leaving other fields flexible — update the
assertion that currently uses expect.any(Object) in the unbound.spec.ts test to
use expect.objectContaining({ signal: expect.any(Object) }).

Source: Coding guidelines

src/api/providers/__tests__/vercel-ai-gateway.spec.ts (1)

209-209: 💤 Low value

Consider more specific assertions for request options.

Multiple test assertions now use expect.any(Object) to match the second argument (request options containing the abort signal). While dedicated abort signal tests exist elsewhere, these assertions could be more precise about the expected shape. As per coding guidelines, unit tests should verify request construction details.

✨ Optional: More specific assertion pattern

Replace instances of:

-		expect.any(Object),
+		expect.objectContaining({ signal: expect.anything() }),

This verifies the options object has a signal property without coupling tests to specific abort signal values, improving request construction verification while keeping tests focused on their primary assertions (temperature, tools, etc.).

Also applies to: 225-225, 256-256, 335-335, 353-353, 371-371, 389-389, 489-489

🤖 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/__tests__/vercel-ai-gateway.spec.ts` at line 209, Tests use
broad expect.any(Object) for the request options; update each assertion (the
mocked fetch/HTTP request assertions in vercelAiGateway tests such as those
around temperature/tools checks) to assert the options contain an abort signal
instead of matching any object — replace expect.any(Object) with a more specific
matcher like expect.objectContaining({ signal: expect.any(Object) }) (or
expect.objectContaining({ signal: expect.any(AbortSignal) }) if the runtime
provides AbortSignal) so the tests verify the presence/shape of the signal
without coupling to its concrete value; apply this change to the multiple
occurrences noted in the spec.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@src/api/providers/__tests__/unbound.spec.ts`:
- Line 91: Replace the loose expect.any(Object) used for the request options in
the test with a more specific matcher that asserts the presence of an abort
signal; for example change the second-argument matcher to
expect.objectContaining({ signal: expect.any(Object) }) (or expect.anything() if
preferred) so the test verifies the options object includes a signal property
while leaving other fields flexible — update the assertion that currently uses
expect.any(Object) in the unbound.spec.ts test to use expect.objectContaining({
signal: expect.any(Object) }).

In `@src/api/providers/__tests__/vercel-ai-gateway.spec.ts`:
- Line 209: Tests use broad expect.any(Object) for the request options; update
each assertion (the mocked fetch/HTTP request assertions in vercelAiGateway
tests such as those around temperature/tools checks) to assert the options
contain an abort signal instead of matching any object — replace
expect.any(Object) with a more specific matcher like expect.objectContaining({
signal: expect.any(Object) }) (or expect.objectContaining({ signal:
expect.any(AbortSignal) }) if the runtime provides AbortSignal) so the tests
verify the presence/shape of the signal without coupling to its concrete value;
apply this change to the multiple occurrences noted in the spec.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 19e09453-6e0e-433d-bec0-4ce04430def6

📥 Commits

Reviewing files that changed from the base of the PR and between 4e0b0e7 and 71da495.

📒 Files selected for processing (29)
  • src/api/index.ts
  • src/api/providers/__tests__/base-openai-compatible-provider.spec.ts
  • src/api/providers/__tests__/fireworks.spec.ts
  • src/api/providers/__tests__/lmstudio-native-tools.spec.ts
  • src/api/providers/__tests__/mimo.spec.ts
  • src/api/providers/__tests__/openai-compatible-abort-signal.spec.ts
  • src/api/providers/__tests__/opencode-go.spec.ts
  • src/api/providers/__tests__/qwen-code-native-tools.spec.ts
  • src/api/providers/__tests__/requesty.spec.ts
  • src/api/providers/__tests__/sambanova.spec.ts
  • src/api/providers/__tests__/unbound.spec.ts
  • src/api/providers/__tests__/vercel-ai-gateway.spec.ts
  • src/api/providers/__tests__/zai.spec.ts
  • src/api/providers/base-openai-compatible-provider.ts
  • src/api/providers/deepseek.ts
  • src/api/providers/lite-llm.ts
  • src/api/providers/lm-studio.ts
  • src/api/providers/mimo.ts
  • src/api/providers/openai-compatible.ts
  • src/api/providers/openai.ts
  • src/api/providers/opencode-go.ts
  • src/api/providers/openrouter.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/requesty.ts
  • src/api/providers/unbound.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/zai.ts
  • src/core/task/Task.ts
  • src/core/task/__tests__/task-abort-signal-passing.spec.ts
✅ Files skipped from review due to trivial changes (4)
  • src/api/providers/tests/lmstudio-native-tools.spec.ts
  • src/api/providers/tests/base-openai-compatible-provider.spec.ts
  • src/api/providers/tests/qwen-code-native-tools.spec.ts
  • src/api/providers/tests/requesty.spec.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • src/api/providers/tests/sambanova.spec.ts
  • src/api/providers/openrouter.ts
  • src/api/providers/mimo.ts
  • src/api/providers/lm-studio.ts
  • src/api/providers/opencode-go.ts
  • src/core/task/Task.ts
  • src/api/index.ts
  • src/api/providers/openai-compatible.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/tests/openai-compatible-abort-signal.spec.ts
  • src/api/providers/lite-llm.ts
  • src/core/task/tests/task-abort-signal-passing.spec.ts
  • src/api/providers/tests/fireworks.spec.ts
  • src/api/providers/openai.ts

@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch from 71da495 to c52904d Compare June 9, 2026 18:45
@easonLiangWorldedtech easonLiangWorldedtech changed the title fix(api): pass abortSignal to streaming API calls for all providers (#404) feat(api): pass abortSignal to streaming API calls for all providers (#404) Jun 9, 2026
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

easonLiangWorldedtech pushed a commit to easonLiangWorldedtech/Zoo-Code that referenced this pull request Jun 9, 2026
@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch 2 times, most recently from 086f8e1 to 0a86942 Compare June 10, 2026 18:27

@edelauna edelauna left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like as part of a merge conflict/resolution you're removing some code from main, can you re-review to ensure the diff is minimized to only the files related to your change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets not remove the diagnostic changes please.

@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch 4 times, most recently from 79dd3fb to c5a6f53 Compare June 13, 2026 13:09
easonLiangWorldedtech pushed a commit to easonLiangWorldedtech/Zoo-Code that referenced this pull request Jun 13, 2026
…ode-Org#434)

- Add AbortController support to openai-compatible provider
- Pass abort signal through Task execution chain
- Add comprehensive tests for abort signal behavior across all providers
@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch from c5a6f53 to b5b554d Compare June 13, 2026 13:36
easonLiangWorldedtech pushed a commit to easonLiangWorldedtech/Zoo-Code that referenced this pull request Jun 13, 2026
…ode-Org#434)

- Add AbortController support to openai-compatible provider
- Pass abort signal through Task execution chain
- Add comprehensive tests for abort signal behavior across all providers
@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch from b5b554d to fab4533 Compare June 13, 2026 13:43
easonLiangWorldedtech pushed a commit to easonLiangWorldedtech/Zoo-Code that referenced this pull request Jun 13, 2026
…ode-Org#434)

- Add AbortController support to openai-compatible provider
- Pass abort signal through Task execution chain
- Add comprehensive tests for abort signal behavior across all providers
@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch from 67b226c to 9e3130f Compare June 13, 2026 19:10
@easonLiangWorldedtech

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/gemini.ts`:
- Around line 346-350: The non-stream Gemini call is passing
metadata?.abortSignal as a top-level signal which `@google/genai` ignores; update
the non-stream call (completePrompt / models.generateContent usage) to pass the
abort signal inside config by using config: { ...config, abortSignal:
metadata?.abortSignal } instead of signal: metadata?.abortSignal so cancellation
works the same as the streaming path (models.generateContentStream).
🪄 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: b666a8fe-43e0-4dfd-9e2d-6ae6fcb29a8a

📥 Commits

Reviewing files that changed from the base of the PR and between de408ee and 9e3130f.

📒 Files selected for processing (14)
  • src/api/providers/__tests__/deepseek.spec.ts
  • src/api/providers/__tests__/vercel-ai-gateway.spec.ts
  • src/api/providers/bedrock.ts
  • src/api/providers/gemini.ts
  • src/api/providers/native-ollama.ts
  • src/api/providers/openai-codex.ts
  • src/api/providers/openai-native.ts
  • src/api/providers/openai.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/unbound.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/vscode-lm.ts
  • src/api/providers/xai.ts
  • src/api/providers/zoo-gateway.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/api/providers/tests/deepseek.spec.ts
  • src/api/providers/openai-native.ts
  • src/api/providers/openai-codex.ts
  • src/api/providers/xai.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/zoo-gateway.ts
  • src/api/providers/bedrock.ts
  • src/api/providers/openai.ts
  • src/api/providers/native-ollama.ts
  • src/api/providers/tests/vercel-ai-gateway.spec.ts
  • src/api/providers/vscode-lm.ts

Comment thread src/api/providers/gemini.ts
@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch 2 times, most recently from a140cd5 to b3e4008 Compare June 13, 2026 19:42
@easonLiangWorldedtech

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/__tests__/native-ollama.spec.ts (1)

642-672: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Abort-bridge behavior is not actually verified in these abortSignal tests.

Both tests only assert that chat is called without a signal option. That checks call-shape, but not the contract implied by Line 642 title: controller.abort() should invoke the per-request client's abort() method. This leaves the key cancellation path untested.

Suggested test hardening
 const mockedData = vi.hoisted(() => ({
 	mockChat: vi.fn(),
 	mockGetOllamaModels: vi.fn(),
+	mockAbort: vi.fn(),
 }))

 vi.mock("ollama", () => {
 	return {
 		Ollama: vi.fn().mockImplementation(() => ({
 			chat: mockedData.mockChat,
+			abort: mockedData.mockAbort,
 		})),
 		Message: vi.fn(),
 	}
 })
 		it("should wire abortSignal to per-request client's abort() method", async () => {
+			mockedData.mockAbort.mockClear()
 			...
 			const controller = new AbortController()

-			for await (const _chunk of handlerWithSignal.createMessage(
+			const stream = handlerWithSignal.createMessage(
 				"system",
 				[{ role: "user", content: "Hello!" }],
 				{ taskId: "test", abortSignal: controller.signal },
-			)) {
+			)
+			controller.abort()
+			for await (const _chunk of stream) {
 				break
 			}
-
-			expect(mockedData.mockChat).toHaveBeenCalled()
-			const callArgs = mockedData.mockChat.mock.calls[0][0]
-			expect(callArgs.signal).toBeUndefined()
+			expect(mockedData.mockAbort).toHaveBeenCalled()
 		})

Also applies to: 674-697

🤖 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/__tests__/native-ollama.spec.ts` around lines 642 - 672,
The test only checks that chat was called without a signal but doesn't verify
the abort bridge; update the "should wire abortSignal to per-request client's
abort() method" test (and the similar test at the other site) so
mockedData.mockChat's implementation returns/uses a per-request client object
with an abort spy (or capture the client created inside the mock), start the
async iteration from NativeOllamaHandler.createMessage, then call
controller.abort(), await any iteration cleanup, and assert that the captured
per-request client's abort spy was invoked (i.e., controller.abort() triggers
the client's abort()); keep the existing assertion that callArgs.signal is
undefined.
🤖 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/__tests__/native-ollama.spec.ts`:
- Around line 642-672: The test only checks that chat was called without a
signal but doesn't verify the abort bridge; update the "should wire abortSignal
to per-request client's abort() method" test (and the similar test at the other
site) so mockedData.mockChat's implementation returns/uses a per-request client
object with an abort spy (or capture the client created inside the mock), start
the async iteration from NativeOllamaHandler.createMessage, then call
controller.abort(), await any iteration cleanup, and assert that the captured
per-request client's abort spy was invoked (i.e., controller.abort() triggers
the client's abort()); keep the existing assertion that callArgs.signal is
undefined.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 60472a9b-aea0-449a-8bf4-fe633c3d43ce

📥 Commits

Reviewing files that changed from the base of the PR and between 9e3130f and b3e4008.

📒 Files selected for processing (17)
  • src/api/providers/__tests__/deepseek.spec.ts
  • src/api/providers/__tests__/gemini.spec.ts
  • src/api/providers/__tests__/native-ollama.spec.ts
  • src/api/providers/__tests__/vercel-ai-gateway.spec.ts
  • src/api/providers/__tests__/zoo-gateway.spec.ts
  • src/api/providers/bedrock.ts
  • src/api/providers/gemini.ts
  • src/api/providers/native-ollama.ts
  • src/api/providers/openai-codex.ts
  • src/api/providers/openai-native.ts
  • src/api/providers/openai.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/unbound.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/vscode-lm.ts
  • src/api/providers/xai.ts
  • src/api/providers/zoo-gateway.ts
✅ Files skipped from review due to trivial changes (1)
  • src/api/providers/tests/zoo-gateway.spec.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • src/api/providers/unbound.ts
  • src/api/providers/tests/deepseek.spec.ts
  • src/api/providers/openai-native.ts
  • src/api/providers/tests/gemini.spec.ts
  • src/api/providers/openai-codex.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/gemini.ts
  • src/api/providers/bedrock.ts
  • src/api/providers/zoo-gateway.ts
  • src/api/providers/xai.ts
  • src/api/providers/openai.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/vscode-lm.ts
  • src/api/providers/native-ollama.ts
  • src/api/providers/tests/vercel-ai-gateway.spec.ts

@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch 3 times, most recently from e8aab5f to 963e058 Compare June 13, 2026 20:23
@easonLiangWorldedtech

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

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/__tests__/native-ollama.spec.ts (1)

241-254: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

completePrompt abort test does not verify abort() bridging despite the test intent.

At Line 249, you pass abortSignal, but the test never triggers abort or checks capturedAbortSpies; it only validates callArgs.signal is undefined. A broken requestClient.abort() path would still pass.

Suggested fix
 it("should wire abortSignal to per-request client's abort() method", async () => {
+  mockedData.capturedAbortSpies.length = 0
   const controller = new AbortController()
@@
   await handler.completePrompt("Test prompt", { taskId: "test", abortSignal: mockAbortSignal })
@@
   const callArgs = mockedData.mockChat.mock.calls[0][0]
   expect(callArgs.signal).toBeUndefined()
+
+  const abortSpy = mockedData.capturedAbortSpies[mockedData.capturedAbortSpies.length - 1]
+  expect(abortSpy).toBeDefined()
+  expect(abortSpy!).toHaveBeenCalledTimes(0)
+
+  controller.abort()
+  await new Promise((r) => setTimeout(r, 0))
+  expect(abortSpy!).toHaveBeenCalledTimes(1)
 })
🤖 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/__tests__/native-ollama.spec.ts` around lines 241 - 254,
The test for completePrompt should actually trigger the abort and assert that
the per-request client's abort() bridging is invoked: create an AbortController
(controller), spy or capture the per-request client's abort method via
capturedAbortSpies (or a jest.spyOn on requestClient.abort) before calling
handler.completePrompt("Test prompt", { taskId: "test", abortSignal:
controller.signal }), then call controller.abort() (or controller.abort(new
Error(...))) and await any promise resolution, and finally expect the captured
abort spy to have been called (e.g.,
expect(capturedAbortSpies[0]).toHaveBeenCalled()) in addition to the existing
expectation that callArgs.signal is undefined; ensure the abort is triggered
after invoking completePrompt so the bridge path is exercised.
🤖 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/__tests__/bedrock.spec.ts`:
- Around line 1585-1606: The tests for AwsBedrockHandler.createMessage currently
only iterate the generator and never assert cancellation propagation; update
both tests (the pre-aborted case and the mid-stream abort case) to explicitly
assert that the request/abort signal was triggered: for the pre-aborted test,
assert controller.signal.aborted is true immediately after consuming the
generator or assert that the generator iteration throws an
AbortError/DOMException; for the mid-stream-abort test, set up a mock that
records whether the internal request was aborted (or assert the thrown
AbortError) and assert that the internal controller.signal (or the passed
controller.signal) becomes aborted after you call controller.abort(); reference
AwsBedrockHandler.createMessage and the test generator variable to locate where
to add these assertions.

In `@src/api/providers/__tests__/openai-native.spec.ts`:
- Around line 1850-1891: The tests for OpenAiNativeHandler fallback currently
only assert fetchOptions.signal is defined; update both affected test cases to
assert signal propagation and abortion semantics: in the "should reuse
existingSignal when SDK fails and fallback to fetch" test (and the similar
SDK→fallback test), check that fetchOptions.signal === controller.signal (i.e.,
the same AbortSignal instance is passed) and add an assertion that after
controller.abort() the signal's aborted property is true (or await a
fetch/stream result that observes the abort) to prove the fallback signal flips
to aborted; locate the tests by the test name strings and update the assertions
accordingly.

---

Outside diff comments:
In `@src/api/providers/__tests__/native-ollama.spec.ts`:
- Around line 241-254: The test for completePrompt should actually trigger the
abort and assert that the per-request client's abort() bridging is invoked:
create an AbortController (controller), spy or capture the per-request client's
abort method via capturedAbortSpies (or a jest.spyOn on requestClient.abort)
before calling handler.completePrompt("Test prompt", { taskId: "test",
abortSignal: controller.signal }), then call controller.abort() (or
controller.abort(new Error(...))) and await any promise resolution, and finally
expect the captured abort spy to have been called (e.g.,
expect(capturedAbortSpies[0]).toHaveBeenCalled()) in addition to the existing
expectation that callArgs.signal is undefined; ensure the abort is triggered
after invoking completePrompt so the bridge path is exercised.
🪄 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: 8248e33f-6c3c-4e16-b131-5b114f1f9a72

📥 Commits

Reviewing files that changed from the base of the PR and between b3e4008 and 963e058.

📒 Files selected for processing (20)
  • src/api/providers/__tests__/bedrock.spec.ts
  • src/api/providers/__tests__/deepseek.spec.ts
  • src/api/providers/__tests__/gemini.spec.ts
  • src/api/providers/__tests__/native-ollama.spec.ts
  • src/api/providers/__tests__/openai-native.spec.ts
  • src/api/providers/__tests__/vercel-ai-gateway.spec.ts
  • src/api/providers/__tests__/vscode-lm.spec.ts
  • src/api/providers/__tests__/zoo-gateway.spec.ts
  • src/api/providers/bedrock.ts
  • src/api/providers/gemini.ts
  • src/api/providers/native-ollama.ts
  • src/api/providers/openai-codex.ts
  • src/api/providers/openai-native.ts
  • src/api/providers/openai.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/unbound.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/vscode-lm.ts
  • src/api/providers/xai.ts
  • src/api/providers/zoo-gateway.ts
🚧 Files skipped from review as they are similar to previous changes (16)
  • src/api/providers/unbound.ts
  • src/api/providers/openai-native.ts
  • src/api/providers/openai-codex.ts
  • src/api/providers/tests/deepseek.spec.ts
  • src/api/providers/tests/zoo-gateway.spec.ts
  • src/api/providers/tests/gemini.spec.ts
  • src/api/providers/gemini.ts
  • src/api/providers/qwen-code.ts
  • src/api/providers/vercel-ai-gateway.ts
  • src/api/providers/xai.ts
  • src/api/providers/bedrock.ts
  • src/api/providers/vscode-lm.ts
  • src/api/providers/openai.ts
  • src/api/providers/tests/vercel-ai-gateway.spec.ts
  • src/api/providers/zoo-gateway.ts
  • src/api/providers/native-ollama.ts

Comment on lines +1585 to +1606
it("should handle pre-aborted signals by calling controller.abort() immediately", async () => {
const handler = new AwsBedrockHandler({
apiModelId: "anthropic.claude-3-5-sonnet-20241022-v2:0",
awsAccessKey: "test-access-key",
awsSecretKey: "test-secret-key",
awsRegion: "us-east-1",
})

const controller = new AbortController()
controller.abort() // Pre-abort the signal

const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
const generator = handler.createMessage("System prompt", messages, {
taskId: "test-task",
abortSignal: controller.signal,
})

// Consume the stream - pre-aborted signal should trigger internal abort
for await (const _ of generator) {
// consume
}
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Abort tests currently pass without proving cancellation propagation.

At Line 1603 and Line 1627, both tests only consume/suppress iteration; they never assert that the request signal became aborted. With the current mock stream shape, these can pass even if abort wiring breaks.

Suggested assertion tightening
 it("should handle pre-aborted signals by calling controller.abort() immediately", async () => {
@@
-  // Consume the stream - pre-aborted signal should trigger internal abort
-  for await (const _ of generator) {
-    // consume
-  }
+  await generator.next()
+
+  const clientInstance =
+    vi.mocked(BedrockRuntimeClient).mock.results[vi.mocked(BedrockRuntimeClient).mock.results.length - 1]?.value
+  const mockSendFn = clientInstance?.send as ReturnType<typeof vi.fn>
+  const sendOptions = mockSendFn.mock.calls[0][1]
+  expect(sendOptions.abortSignal).toBeDefined()
+  expect(sendOptions.abortSignal.aborted).toBe(true)
 })
@@
 it("should use { once: true } listener for external abort signal", async () => {
@@
-  controller.abort()
+  const clientInstance =
+    vi.mocked(BedrockRuntimeClient).mock.results[vi.mocked(BedrockRuntimeClient).mock.results.length - 1]?.value
+  const mockSendFn = clientInstance?.send as ReturnType<typeof vi.fn>
+  const sendOptions = mockSendFn.mock.calls[0][1]
+  expect(sendOptions.abortSignal.aborted).toBe(false)
+
+  controller.abort()
+  expect(sendOptions.abortSignal.aborted).toBe(true)
@@
-  await consumePromise.catch(() => {})
+  await consumePromise.catch(() => {})
 })

Also applies to: 1608-1639

🤖 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/__tests__/bedrock.spec.ts` around lines 1585 - 1606, The
tests for AwsBedrockHandler.createMessage currently only iterate the generator
and never assert cancellation propagation; update both tests (the pre-aborted
case and the mid-stream abort case) to explicitly assert that the request/abort
signal was triggered: for the pre-aborted test, assert controller.signal.aborted
is true immediately after consuming the generator or assert that the generator
iteration throws an AbortError/DOMException; for the mid-stream-abort test, set
up a mock that records whether the internal request was aborted (or assert the
thrown AbortError) and assert that the internal controller.signal (or the passed
controller.signal) becomes aborted after you call controller.abort(); reference
AwsBedrockHandler.createMessage and the test generator variable to locate where
to add these assertions.

Comment thread src/api/providers/__tests__/openai-native.spec.ts
Fix 6 major issues and 6 nitpick inconsistencies from CodeRabbit review:

Major fixes (Stop functionality broken for some providers):
- openai-codex.ts: Pass full metadata in retry loop, not just taskId
- openai-native.ts: Reuse existing controller signal in fallback path
- vscode-lm.ts: Bridge external abortSignal to VSCode CancellationToken
- bedrock.ts: Handle pre-aborted signals + use {once: true} listener
- gemini.ts: Move abortSignal to config.abortSignal for both streaming and non-streaming calls
- native-ollama.ts: Use per-request client with abort() method

Nitpick fixes (consistent signal forwarding pattern):
- openai.ts, unbound.ts, qwen-code.ts, xai.ts, vercel-ai-gateway.ts, zoo-gateway.ts:
  Normalize all completePrompt methods to use {signal: metadata?.abortSignal}

Test fixes (improve coverage for abort signal paths):
- bedrock.spec.ts: Assert controller.signal.aborted state for pre-aborted and mid-stream tests
- openai-native.spec.ts: Capture fetchOptions before abort, verify external signal becomes aborted
- native-ollama.spec.ts: Use spyCountBefore to track per-request client abort spy correctly
@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch from 963e058 to ad82a44 Compare June 13, 2026 20:45
@easonLiangWorldedtech

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@edelauna

Copy link
Copy Markdown
Contributor

Closing in favor of smaller, focused PRs

Thanks for the work here — the intent is right and most of the provider-level wiring is solid. However, at 59 files this is too large to review effectively, and the review surfaced several bugs that would have been obvious in smaller PRs (missing { once: true }, SingleCompletionHandler interface not updated making all completePrompt abort work dead code, etc.).

We've broken this into 4 sub-issues on #404, ordered by dependency:

Order Issue Scope Risk
1st #615 Core plumbing — interface, Task.ts, single-completion-handler Low
2nd #616 ~18 pass-through providers (one-liner signal: additions) Low
2nd #617 4 custom bridging providers (openai-native, openai-codex, bedrock, native-ollama) High
2nd #618 3 SDK-quirk providers (vscode-lm, gemini, mistral) Medium

#615 must land first. #616, #617, #618 can go in parallel after that.

Each issue includes the specific review findings and acceptance criteria from this PR's review. The code from this branch can be cherry-picked into each — most of the work is already done, it just needs to be split and the bugs fixed.

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.

[BUG] Stop does not work on OpenAI Compatible API Provider

3 participants