Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/ai-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@tanstack/ai': minor
---

New optional cancel options. Old calls work the same.

- `embed()` takes `abortSignal` and `timeout`. The effective signal reaches the adapter as `EmbeddingOptions.abortSignal`.
- `getVideoJobStatus()` takes `abortSignal` and `timeout`. The video adapter contract adds an optional `{ abortSignal }` to `getVideoStatus(jobId, options?)` and `getVideo(jobId, options?)`. The streaming `generateVideo()` poll passes its signal to both.
- `uploadFile()`, `getFile()`, and `deleteFile()` take `abortSignal`. `FilesAdapter.upload/get/delete` take an optional `{ abortSignal }`.
- `realtimeToken()` takes `abortSignal`. `RealtimeTokenAdapter.generateToken(options?)` and `RealtimeAdapter.connect(token, clientTools?, options?)` take an optional `{ abortSignal }`.
- `decide()` and `rerank()` take `timeout`, and race the adapter call against the signal like the other activities.
- `GenerationMiddlewareContext` has an optional `signal`: the effective activity signal.
5 changes: 5 additions & 0 deletions .changeset/ai-anthropic-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-anthropic': minor
---

Forward the new optional `abortSignal` of the files adapter to `upload`, `get`, and `delete`.
5 changes: 5 additions & 0 deletions .changeset/ai-bedrock-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-bedrock': minor
---

Forward the new optional embedding `abortSignal` to each `InvokeModel` call.
5 changes: 5 additions & 0 deletions .changeset/ai-byteplus-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-byteplus': minor
---

Forward the new optional `abortSignal` of `getVideoStatus` and `getVideo` to the task lookup. A caller signal now works together with the config `timeout`.
5 changes: 5 additions & 0 deletions .changeset/ai-byteplus-cancel-video-job.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-byteplus': minor
---

`byteplusVideo` implements the optional `cancelVideoJob(jobId, options?)` with `DELETE /contents/generations/tasks/{id}`. Ark cancels only a queued task. A streaming `generateVideo()` calls it when the caller aborts.
8 changes: 8 additions & 0 deletions .changeset/ai-cancel-video-job-mcp-signal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@tanstack/ai': minor
---

New optional cancel hooks. Old code works the same.

- `MCPToolSource.tools()` takes an optional `signal`. `chat()` passes the signal of its `abortController` to each MCP source during tool discovery.
- `VideoAdapter` has an optional `cancelVideoJob(jobId, options?)` method. A streaming `generateVideo()` calls it (best effort) when its `abortSignal` or `timeout` aborts after the provider accepted the job. A failed cancel is logged and does not change the abort result.
5 changes: 5 additions & 0 deletions .changeset/ai-client-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-client': minor
---

A client tool now gets `abortSignal` in its execute context. It aborts when you call `stop()` on the `ChatClient`.
9 changes: 9 additions & 0 deletions .changeset/ai-client-cancel-url-connect-signal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@tanstack/ai-client': minor
---

New optional cancel features. Old code works the same.

- `fetchServerSentEvents` and `fetchHttpStream` take an optional `cancelUrl`. With it, `ChatClient.stop()` sends `POST cancelUrl` with `{ runId, threadId }`, so the server can cancel a durable run. Only `stop()` sends it, not an unmount or a reload.
- Connection adapters can have an optional `cancelRun({ runId, threadId })` method. `stop()` calls it.
- `RealtimeClient` passes an `abortSignal` to `adapter.connect()`. A `disconnect()` during `connect()` aborts the setup, and the status goes to `idle`, not `error`.
5 changes: 5 additions & 0 deletions .changeset/ai-cloudflare-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-cloudflare': minor
---

Forward the new optional embedding `abortSignal` to the Workers AI REST request.
5 changes: 5 additions & 0 deletions .changeset/ai-cohere-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-cohere': minor
---

Forward the new optional embedding `abortSignal` to the embed request and to an image URL download.
5 changes: 5 additions & 0 deletions .changeset/ai-elevenlabs-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-elevenlabs': minor
---

Forward the new optional `abortSignal` of `generateToken()` to the signed URL request.
5 changes: 5 additions & 0 deletions .changeset/ai-fal-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-fal': minor
---

Forward the new optional `abortSignal` of `getVideoStatus` and `getVideo` to `fal.queue.status` and `fal.queue.result`.
5 changes: 5 additions & 0 deletions .changeset/ai-fal-cancel-video-job.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-fal': minor
---

`falVideo` implements the optional `cancelVideoJob(jobId, options?)` with `fal.queue.cancel`. A streaming `generateVideo()` calls it when the caller aborts.
5 changes: 5 additions & 0 deletions .changeset/ai-gemini-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-gemini': minor
---

Forward the new optional `abortSignal` in embeddings, Veo and Omni video status and download, files `get` and `delete`, and the realtime token request. `uploadGeminiFile()` takes `abortSignal`: it cancels the status requests and the wait between polls.
5 changes: 5 additions & 0 deletions .changeset/ai-grok-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-grok': minor
---

Forward the new optional `abortSignal` in video status and download, files upload/get/delete, the realtime token request, and the realtime WebRTC SDP request.
5 changes: 5 additions & 0 deletions .changeset/ai-lovable-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-lovable': minor
---

Forward the new optional `abortSignal` in embeddings and in video status and download.
5 changes: 5 additions & 0 deletions .changeset/ai-mistral-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-mistral': minor
---

Forward the new optional embedding `abortSignal` to the embeddings request.
5 changes: 5 additions & 0 deletions .changeset/ai-neon-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-neon': minor
---

Forward the new optional embedding `abortSignal` to the embeddings request.
5 changes: 5 additions & 0 deletions .changeset/ai-ollama-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-ollama': minor
---

Forward the new optional embedding `abortSignal` to the embed request. An injected `Ollama` client cannot take a per-call signal, so `embed()` only rejects on abort for it.
5 changes: 5 additions & 0 deletions .changeset/ai-openai-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-openai': minor
---

Forward the new optional `abortSignal` to the provider request in embeddings, video status and download, files upload/get/delete, the realtime token request, and the realtime WebRTC SDP request.
5 changes: 5 additions & 0 deletions .changeset/ai-openrouter-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-openrouter': minor
---

Forward the new optional `abortSignal` of `getVideoStatus` and `getVideo` to the generation lookup and the content download.
5 changes: 5 additions & 0 deletions .changeset/ai-vercel-gateway-abort-signal-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-vercel-gateway': minor
---

Forward the new optional embedding `abortSignal` to the embeddings request.
20 changes: 20 additions & 0 deletions docs/advanced/files-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,26 @@ await deleteFile({ adapter: files, id: handle });

fal storage is upload-only. `falFiles()` has no `get` or `delete`, so `getFile()` and `deleteFile()` throw with it.

### Cancel a file request

`uploadFile()`, `getFile()`, and `deleteFile()` take an `abortSignal`. When it aborts, the call rejects. The adapter passes the signal to the provider request when the provider SDK can cancel it:

```typescript
import { uploadFile } from "@tanstack/ai";
import { openaiFiles } from "@tanstack/ai-openai";

export async function POST(request: Request) {
const handle = await uploadFile({
adapter: openaiFiles(),
input: await request.blob(),
abortSignal: request.signal,
});
return Response.json(handle);
}
```

The Gemini and fal SDKs do not cancel a running upload. With those providers, the call still rejects at once, but the upload can finish on the provider.

### Grok (xAI): handles are public URLs

`grokFiles()` uploads to the xAI Files API. Then it mints a [public URL](https://docs.x.ai/developers/files/public-urls) for the file and uses that URL as the reference of the handle.
Expand Down
2 changes: 2 additions & 0 deletions docs/advanced/otel.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ The four extension points also apply to media spans. Media spans report `kind: '

The shared lifecycle hooks (`onStart`, `onUsage`, `onFinish`, `onAbort`, `onError`) use the `GenerationMiddlewareContext`, which is the same for each activity. That is why one instance works everywhere. For a custom backend, implement the base `GenerationMiddleware` contract. Its hooks get the `GenerationMiddlewareContext` and fire for each activity, chat included.

In a custom middleware, `ctx.signal` is the effective abort signal of the activity call: the caller `abortSignal` and the `timeout` together. It is `undefined` when the call has neither. Pass it to async work in a result transform, such as an upload, so that a cancelled call also stops that work.

The `GenerationMiddleware` types come from the package root. The `otelMiddleware` value lives on the `@tanstack/ai/middlewares/otel` subpath, so an import of `@tanstack/ai` never needs the optional `@opentelemetry/api` peer.

### Capturing media content
Expand Down
1 change: 1 addition & 0 deletions docs/api/ai.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ console.log(result.queue.value, result.urgency.value, result.refund.value);
| `state` | Required. The content that every question judges: a string, an object, or an array. An array is one state, not a batch. |
| `questions` | Required. A map of `choice`, `score`, and `boolean` questions. The key `meta` is reserved. |
| `abortSignal` | Cancels the request. |
| `timeout` | Stops the request after this many milliseconds. |
| `modelOptions` | Provider options. |
| `middleware` | Observe-only generation middleware. |
| `debug` | Debug logging. |
Expand Down
25 changes: 13 additions & 12 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@
"label": "OpenTelemetry",
"to": "advanced/otel",
"addedAt": "2026-05-08",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
},
{
"label": "Compaction",
Expand Down Expand Up @@ -626,7 +626,7 @@
"label": "Managed MCP with chat()",
"to": "tools/mcp-managed",
"addedAt": "2026-06-05",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
},
{
"label": "Manual MCP",
Expand Down Expand Up @@ -751,7 +751,7 @@
"label": "Advanced",
"to": "resumable-streams/advanced",
"addedAt": "2026-08-04",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
},
{
"label": "WebSockets",
Expand Down Expand Up @@ -884,7 +884,7 @@
"label": "Realtime Voice Chat",
"to": "media/realtime-chat",
"addedAt": "2026-04-15",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
},
{
"label": "Text-to-Speech",
Expand Down Expand Up @@ -936,12 +936,13 @@
"label": "Video Generation",
"to": "media/video-generation",
"addedAt": "2026-04-15",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
},
{
"label": "Video Generation: Advanced",
"to": "media/video-generation-advanced",
"addedAt": "2026-10-21"
"addedAt": "2026-10-21",
"updatedAt": "2026-11-02"
},
{
"label": "Live Generation",
Expand Down Expand Up @@ -971,7 +972,7 @@
"label": "Embeddings",
"to": "embeddings",
"addedAt": "2026-07-10",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
}
],
"tab": "guides"
Expand All @@ -983,7 +984,7 @@
"label": "Reranking",
"to": "rerank/rerank",
"addedAt": "2026-06-25",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
}
],
"tab": "guides"
Expand All @@ -995,7 +996,7 @@
"label": "Evaluate",
"to": "evaluate/evaluate",
"addedAt": "2026-09-18",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
}
],
"tab": "guides"
Expand Down Expand Up @@ -1493,7 +1494,7 @@
"label": "Takeover & Detached Runs",
"to": "sandbox/takeover",
"addedAt": "2026-08-04",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
},
{
"label": "Takeover Internals",
Expand Down Expand Up @@ -1609,7 +1610,7 @@
"label": "Files API",
"to": "advanced/files-api",
"addedAt": "2026-09-11",
"updatedAt": "2026-10-22"
"updatedAt": "2026-11-02"
},
{
"label": "Per-Model Type Safety",
Expand Down Expand Up @@ -2005,7 +2006,7 @@
"label": "@tanstack/ai",
"to": "api/ai",
"addedAt": "2026-04-15",
"updatedAt": "2026-10-21"
"updatedAt": "2026-11-02"
},
{
"label": "@tanstack/ai-client",
Expand Down
21 changes: 21 additions & 0 deletions docs/embeddings.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,27 @@ const result = await embed({
console.log(result.usage?.promptTokens);
```

### Cancel or time out

Pass `abortSignal` to cancel the call, or `timeout` in milliseconds to stop it after a fixed time. The adapter cancels the provider request, and `embed()` rejects. A timeout rejects with a `TimeoutError`.

```typescript
import { embed } from "@tanstack/ai";
import { openaiEmbedding } from "@tanstack/ai-openai";

export async function POST(request: Request) {
const result = await embed({
adapter: openaiEmbedding("text-embedding-3-small"),
input: "a red guitar",
abortSignal: request.signal,
timeout: 10_000,
});
return Response.json(result.embeddings[0]?.vector);
}
```

When you pass both, the first one to fire wins. Middleware gets `onAbort`, not `onError`.

### Errors

`embed()` rejects with the provider error. Middleware `onError` hooks run before the rejection reaches your code.
Expand Down
3 changes: 2 additions & 1 deletion docs/evaluate/evaluate.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ On the TypeSafe wire, the `boolean` question type is named `noul`.
| `state` | `string \| object \| array` | The data that every question judges. Required. |
| `questions` | `Record<string, Question>` | Map of `choice`, `score`, and `boolean` questions. Required. |
| `abortSignal` | `AbortSignal` | Cancel the request |
| `timeout` | `number` | Stop the request after this many milliseconds |
| `modelOptions` | provider options | Provider-specific options |
| `middleware` | `Array<GenerationMiddleware>` | Observe-only lifecycle hooks (usage, finish, error, abort) |
| `debug` | `DebugOption` | Debug logging |
Expand All @@ -325,7 +326,7 @@ On the TypeSafe wire, the `boolean` question type is named `noul`.

### Cancel a request

Pass an `abortSignal`:
Pass an `abortSignal`. To stop after a fixed time, pass `timeout` in milliseconds instead. A timeout rejects with a `TimeoutError`.

```typescript
import { decide, boolean } from '@tanstack/ai'
Expand Down
19 changes: 19 additions & 0 deletions docs/media/realtime-chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,25 @@ client.destroy()

The React hook disconnects by itself when the component unmounts. With `RealtimeClient`, call `disconnect()` and `destroy()` yourself.

A `disconnect()` while `connect()` still runs cancels the connection setup. `connect()` then rejects with an `AbortError`, and the status is `idle`, not `error`. The OpenAI and Grok adapters stop their setup requests.

### Cancel the token request

`realtimeToken()` takes an `abortSignal`. Pass the request signal, so the provider call stops when the HTTP request to your route is aborted:

```typescript
import { realtimeToken } from '@tanstack/ai'
import { openaiRealtimeToken } from '@tanstack/ai-openai'

export async function POST(request: Request) {
const token = await realtimeToken({
adapter: openaiRealtimeToken({ model: 'gpt-realtime-2.1' }),
abortSignal: request.signal,
})
return Response.json(token)
}
```

### Tips

- Mint tokens only on the server. Never send an API key to the browser.
Expand Down
Loading
Loading