v3.5.0
Released: October 11, 2026
Highlights
- A stop takes effect at once: an aborted
signalnow ends the request in the middle of a provider call, a rate limit wait, or a tool, not only between tool rounds stream()says when it was stopped: a stopped stream ends quietly with a last chunk whosefinish_reasonis'aborted'generate()rejects with anAbortError: the same convention asfetch- Tools respect the stop: tools that have not started are skipped, and tools receive the signal as
ctx.signal - Providers cancel the real HTTP call: Vertex AI, Gemini, Anthropic on Vertex and Ollama
generate()now pass the signal on - New exports:
AbortedError,raceAbort,untilAborted metadata.abortedon agent results: a stopped streaming agent run returns the text that had arrived and says it was stopped
@toolpack-sdk/knowledge has no changes in this release; it is bumped to 3.5.0 to keep the packages in step.
toolpack-sdk
New features
Stopping a request takes effect at once
Pass an AbortSignal as signal on a request, as before. What is new is when the stop is seen. Previously it was only checked between tool rounds, so a stop during a slow provider call waited for that call to end. Now the caller is released right away, also in the middle of a provider call, a rate limit wait, a tool, or a conversation summary.
const controller = new AbortController();
stopButton.onclick = () => controller.abort();
for await (const chunk of toolpack.stream({ model, messages, signal: controller.signal })) {
if (chunk.finish_reason === 'aborted') break; // stopped: what arrived before is kept
process.stdout.write(chunk.delta);
}
What each call does on a stop:
| Call | On a stop |
|---|---|
stream() | Ends without throwing. The last chunk is { delta: '', finish_reason: 'aborted' } |
generate() | Rejects with an AbortedError (name is 'AbortError', code is 'ABORTED') |
import { AbortedError } from 'toolpack-sdk';
try {
const response = await toolpack.generate({ model, messages, signal: controller.signal });
} catch (err) {
if (err instanceof AbortedError) return; // stopped by the caller, not a failure
throw err;
}
Tools and the stop
- A tool that has not started when the stop arrives is not run. This also covers tool calls that had already arrived in a stream
- The SDK stops waiting for a tool that is running, and for a pending human confirmation (
onToolConfirm) - Tools receive the signal as
ctx.signal, so a long-running tool can end its own work:
execute: async (args, ctx) => {
const res = await fetch(args.url, { signal: ctx.signal });
return await res.text();
}
A tool that does not read ctx.signal keeps running in the background after a stop; its result is ignored. Tools that run in the same parallel batch all start together, so a stop only skips the ones that have not started.
Providers cancel the HTTP call
The signal is now passed to the provider's own HTTP request, so the call is cancelled and not left running in the background:
| Provider | generate() | stream() |
|---|---|---|
| Anthropic, OpenAI, OpenRouter | already cancelled | already cancelled |
| Vertex AI | new | new |
| Gemini | new | new |
| Anthropic on Vertex | new | new |
| Ollama | new | already cancelled |
Custom providers that ignore request.signal still work: the caller is released at once, but their request runs to its end in the background.
New exports
| Export | What it is |
|---|---|
AbortedError | The error generate() rejects with on a stop. Extends SDKError |
raceAbort(promise, signal?) | Settles like promise, unless the signal is aborted first: then it rejects with an AbortedError |
untilAborted(source, signal?) | Yields what an async iterable yields and ends quietly as soon as the signal is aborted |
ToolContext.signal | AbortSignal | undefined, aborted when the run was stopped |
RetryOptions.signal | For withRetry: no further attempt is started, and a wait for one ends at once |
CompletionChunk.finish_reason | New value 'aborted' |
Behaviour changes
generate()with asignalnow rejects with anAbortedErroron a stop. Before, it waited for the provider call to end and returned the last response. Code that aborts agenerate()call needs atry/catch(checkerr.name === 'AbortError'orerr instanceof AbortedError)stream()with asignalnow ends with one extra chunk,{ delta: '', finish_reason: 'aborted' }. Code that handles everyfinish_reasonvalue should handle'aborted'- After a stop, pending tools are skipped and the result of a running tool is not sent to the model
- Requests without a
signalbehave exactly as before
Improvements and fixes
- A stop during the final text-only round at the tool-round cap is no longer swallowed:
generate()rejects andstream()ends as stopped, like any other stop - A stop during the conversation summary (context window strategy
summarize) is seen at once, and is no longer treated as a failed summary that falls back to pruning - A rate limit wait that is stopped clears its timer, so it no longer keeps the process alive for up to 60 seconds
- A stop is never reported as a provider error
@toolpack-sdk/agents
New features
A stopped run reports that it was stopped
Passing signal to invokeAgent() or run() works as before, and the stop now takes effect at once (see the toolpack-sdk notes). What the caller gets back depends on whether the run streams:
| Run | On a stop |
|---|---|
Streaming (onChunk set, or the mode has streaming) | Resolves with the text that had arrived, and result.metadata.aborted is true |
| Not streaming | Rejects with an error whose name is 'AbortError'. onError and agent:error fire as for any failed run |
const controller = new AbortController();
const result = await agent.invokeAgent({
message: 'Write a long report',
signal: controller.signal,
onChunk: (delta) => process.stdout.write(delta),
});
if (result.metadata?.aborted) {
// result.output is the part that arrived before the stop
}
Agent Mind on a stopped run
A stopped streaming run is saved like a failed one: beliefs and reflections from the run are kept, and its goal writes are dropped. A run that was cut off should not record goals as done.
Behaviour changes
- A run that does not stream now rejects with an
AbortErrorwhen its signal is aborted. Before, it returned the last response - A stop is seen in the middle of a model call or a tool, not only between tool rounds. Sub-agents reached through
delegate_to_agentreceive the same signal
Compatibility
- Node.js >= 20
- No new required dependencies
@toolpack-sdk/agents3.5.0 needstoolpack-sdk^3.5.0- Code that does not pass a
signalneeds no changes. Code that aborts agenerate()call, or an agent run that does not stream, should catch theAbortError(see Behaviour changes)
Install
npm install toolpack-sdk@3.5.0
npm install @toolpack-sdk/knowledge@3.5.0
npm install @toolpack-sdk/agents@3.5.0