Skip to content

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

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

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

Conversation

@easonLiangWorldedtech
Copy link
Copy Markdown

@easonLiangWorldedtech easonLiangWorldedtech commented Jun 1, 2026

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

Added { signal: metadata?.abortSignal } to streaming .chat.completions.create() calls in 13 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

Also added abortSignal?: AbortSignal to ApiHandlerCreateMessageMetadata interface and wired it through Task.ts's AbortController.

Test Coverage

  • task-abort-signal-passing.spec.ts — Comprehensive tests for abort signal flow
  • openai-compatible-abort-signal.spec.ts — Provider-level abort signal verification

p.s All code generated by AI, this is the testing video(I only tested LM Studio)

2026-06-01.230625.mp4

Summary by CodeRabbit

  • New Features

    • Enabled request cancellation for in-flight AI streaming across providers; task cancellation now aborts the active API request.
  • Bug Fixes

    • File-reading flow now marks turns as failed when files are blocked, are directories, or when read errors occur.
  • Tests

    • Added tests for abort-signal propagation and expanded legacy/path file-read coverage.
  • Style/Types

    • Improved detection for legacy file-read parameters to better recognize older formats.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

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: 19a6a60c-f7c5-4aa0-929b-60565871d5d9

📥 Commits

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

📒 Files selected for processing (1)
  • src/api/providers/__tests__/openai-compatible-abort-signal.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/api/providers/tests/openai-compatible-abort-signal.spec.ts

📝 Walkthrough

Walkthrough

Adds optional abortSignal to ApiHandler metadata, attaches per-request AbortSignal in Task, forwards it into provider streaming/create calls across providers, and adds/updates tests verifying forwarding and lifecycle.

Changes

Abort Signal Support for Request Cancellation

Layer / File(s) Summary
Contract and task integration wiring
src/api/index.ts, src/core/task/Task.ts
ApiHandlerCreateMessageMetadata adds optional abortSignal?: AbortSignal. Task.attemptApiRequest attaches the per-request AbortSignal to metadata passed to api.createMessage().
Provider abort signal implementations
src/api/providers/base-openai-compatible-provider.ts, src/api/providers/openai-compatible.ts, src/api/providers/openai.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/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
Provider handlers now forward metadata?.abortSignal into their SDK streaming/create calls (e.g., chat.completions.create(..., { signal }) or streamText(..., { abortSignal })) enabling cancellation of in-flight requests.
Tests, types, and ReadFileTool adjustments
src/api/providers/__tests__/openai-compatible-abort-signal.spec.ts, src/core/task/__tests__/task-abort-signal-passing.spec.ts, provider test updates (fireworks.spec.ts, sambanova.spec.ts, zai.spec.ts), packages/types/src/tool-params.ts, src/core/tools/ReadFileTool.ts, src/core/tools/__tests__/readFileTool.spec.ts
Adds Vitest suite validating abort-signal forwarding to streamText() and Task metadata wiring; updates provider tests to accept a second options object; broadens legacy read-file param detection; removes a temporary legacy log and marks certain legacy read errors as tool failures; expands legacy-format read tests and edge cases.

Sequence Diagram

sequenceDiagram
  participant User
  participant Task
  participant ApiHandler
  participant Provider
  participant HTTP
  User->>Task: start request / later click stop
  Task->>Task: create AbortController -> signal
  Task->>ApiHandler: createMessage(metadata includes abortSignal)
  ApiHandler->>Provider: start streaming call with options including signal
  Provider->>HTTP: open streaming HTTP request using provided signal
  User->>Task: stop -> controller.abort()
  HTTP-->>Provider: connection aborted (by signal)
  Provider-->>ApiHandler: stream terminated / error propagated
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • taltas
  • navedmerchant
  • hannesrudolph

"I hop through the code with a tiny drum,
I add a soft signal and whisper 'stop' — come!
Streams now obey when the controller says 'no',
CPUs rest, and the logs breathe slow. 🐇"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes one minor out-of-scope change: updates to legacy file reading logic in ReadFileTool.ts and related test coverage that are unrelated to the abort signal implementation for issue #404. Consider separating the ReadFileTool.ts changes (marking tool failures for blocked/directory/exception cases) into a separate PR, as they address a different concern than abort signal propagation.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding abortSignal passing to streaming API calls across all providers to fix the Stop functionality.
Description check ✅ Passed The description covers the key aspects: issue reference, problem statement, implementation approach, and test coverage. All major template sections are adequately addressed.
Linked Issues check ✅ Passed The PR directly addresses issue #404 by passing abortSignal through all provider streaming calls, enabling proper request cancellation when users click Stop, thus solving the reported problem of continued API token consumption.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/api/providers/__tests__/openai-compatible-abort-signal.spec.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@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
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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

…oo-Code-Org#404)

When user clicks stop during streaming, the HTTP request continues running
because Vercel AI SDK's streamText() doesn't receive an abort signal. This
wastes API tokens/compute on the provider side.

Changes:
- Add abortSignal?: AbortSignal to ApiHandlerCreateMessageMetadata interface
- Pass Task.ts's AbortController.signal through metadata to createMessage()
- Use signal in all providers' streamText() options
@easonLiangWorldedtech easonLiangWorldedtech force-pushed the fix/abort-signal-openai-compatible branch from ef0a6de to ee47fbb Compare June 3, 2026 08:45
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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

…tests (Zoo-Code-Org#404)

CodeRabbit nitpick: three test cases had duplicate unused helper functions
(createMockStream + mockUsage) that were never called. Removed dead code and
inlined the usage Promise directly in each mockReturnValue call.
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

2 participants