Skip to content

Support AbortSignal in AI binding.#6379

Merged
kentonv merged 2 commits intomainfrom
kenton/ai-binding-abort-signal
Mar 30, 2026
Merged

Support AbortSignal in AI binding.#6379
kentonv merged 2 commits intomainfrom
kenton/ai-binding-abort-signal

Conversation

@kentonv
Copy link
Copy Markdown
Member

@kentonv kentonv commented Mar 22, 2026

Adds another option signal: AbortSignal, which is passed to the underlying fetch().

This allows an application to cancel an AI request.

@kentonv kentonv requested a review from G4brym March 22, 2026 14:54
@kentonv kentonv requested review from a team as code owners March 22, 2026 14:54
Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

The signal option leaks into the serialized JSON body in the websocket path, unlike the other two paths which correctly strip it. Minor bug — JSON.stringify(signal) produces {}, so the server receives a spurious "signal": {} field.

Comment on lines 264 to 265
options,
});
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.

Bug: this JSON body serializes the full options object which still contains signal. Unlike #generateFetch (which receives cleanedOptions) and #generateStreamFetch (which destructures signal out), the websocket path sends "signal": {} to the server.

The fix has two parts:

  1. In run(), pass cleanedOptions instead of options:
res = await this.#generateWebsocketFetch(inputs, cleanedOptions, model);
  1. Here, read signal from this.#options (same pattern as #generateFetch) instead of options:
Suggested change
options,
});
const fetchOptions: RequestInit = {

And below, change options.signal to this.#options.signal.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Mar 22, 2026

I'm Bonk, and I've done a quick review of your PR.

Summary: Adds AbortSignal support to the AI binding by threading a signal option through to the underlying fetch() calls, with good test coverage for the happy path, pre-aborted signal, and in-flight abort.

Issues found (1):

  1. Signal leaks into serialized body in the websocket path (medium severity) — #generateWebsocketFetch receives the raw options object and serializes it into the JSON body (JSON.stringify({ inputs, options })). Unlike #generateFetch (which receives cleanedOptions) and #generateStreamFetch (which destructures signal out), the websocket path sends a spurious "signal": {} to the server. Posted inline with fix suggestion.

Everything else looks clean — the type definitions are consistent across all snapshot files, the cleanedOptions destructuring correctly strips signal alongside the other non-body fields, tests cover the three key abort scenarios, and the mock hangingModel approach with scheduler.wait(1e8) is a solid workaround.

github run

@kentonv kentonv force-pushed the kenton/ai-binding-abort-signal branch from cf6ddb5 to 2dbda91 Compare March 22, 2026 15:24
prefix,
extraHeaders,
sessionOptions,
signal,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@G4brym there's a weird inconsistency where cleanedOptions is only passed to #generateFetch(), not to #generateWebsocketFetch nor #generateStreamFetch.

My coding agent seems to have solved this by adding filtering code directly inside #generateWebsocketFetch and #generateStreamFetch. But would it be more appropriate to pass the cleanedOptions to these functions instead? Or is it intentional that they want prefix / extraHeaders / sessionOptions to be embedded in their request query params / body?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the missing cleanedOptions in these 2 functions should be a mistake, because both generatestream and generatewebsocket use this.#options
yes, we should probably move to pass the cleanedOptions and remove the filtering code from the individual functions

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.

Yes, I can second that, we can safely pass cleanedOptions to both #generateStreamFetch() and #generateWebsocketFetch() for clarity

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

OK, pushed 48dafb7 to clean this up.

returnRawResponse?: boolean;
prefix?: string;
extraHeaders?: object;
signal?: AbortSignal;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How would this actually work in practice with a JSRPC binding?
Considering the AI binding is being actively migrated to JSRPC I'm concerned that this exposes an implementation detail that will force us to stay with fetch invocations for a very long time even when no longer necessary.
Especially since we so far could not make the AbortSignal pass over JSRPC work in production even after you did your best to help @kentonv

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if signal were to work over jsrpc, i like the idea
the plan we have in mind for the .run() method, is to move to jsrpc all invocations that are websocket !== true
only websocket requests will stay over fetch (mostly text to audio or audio to text)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I honestly didn't know that AbortSignal over RPC still wasn't working. We'll have to fix that.

But we cannot block landing this PR on that. The inability to cancel requests is a huge pain point in gadgets and presumably for many other Workers AI users.

Copy link
Copy Markdown
Member

@G4brym G4brym left a comment

Choose a reason for hiding this comment

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

lgtm, this will also require upstream work, so that if a request is canceled by the user's signal, the AI inference is also canceled

Adds another option `signal: AbortSignal`, which is passed to the underlying `fetch()`.

This allows an application to cancel an AI request.
@kentonv kentonv force-pushed the kenton/ai-binding-abort-signal branch from 2dbda91 to 7bf8379 Compare March 27, 2026 15:02
@kentonv
Copy link
Copy Markdown
Member Author

kentonv commented Mar 27, 2026

(just a rebase)

`cleanedOptions` was being created but passed only into `#generateFetch`, not `#generateWebsocketFetch` nor `#generateStreamFetch`. They should all be using the same pattern.

(Written by ChatGPT 5.4.)
@kentonv kentonv merged commit 4b4efe5 into main Mar 30, 2026
24 checks passed
@kentonv kentonv deleted the kenton/ai-binding-abort-signal branch March 30, 2026 19:16
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.

5 participants