@tanstack/ai 0.53.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -13
- package/dist/esm/activities/chat/index.js +5 -3
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/generateLiveVideo/adapter.d.ts +69 -0
- package/dist/esm/activities/generateLiveVideo/adapter.js +23 -0
- package/dist/esm/activities/generateLiveVideo/adapter.js.map +1 -0
- package/dist/esm/activities/generateLiveVideo/index.d.ts +99 -0
- package/dist/esm/activities/generateLiveVideo/index.js +162 -0
- package/dist/esm/activities/generateLiveVideo/index.js.map +1 -0
- package/dist/esm/activities/generateVideo/index.js +3 -1
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateWorld/adapter.d.ts +69 -0
- package/dist/esm/activities/generateWorld/adapter.js +23 -0
- package/dist/esm/activities/generateWorld/adapter.js.map +1 -0
- package/dist/esm/activities/generateWorld/index.d.ts +99 -0
- package/dist/esm/activities/generateWorld/index.js +162 -0
- package/dist/esm/activities/generateWorld/index.js.map +1 -0
- package/dist/esm/activities/index.d.ts +8 -2
- package/dist/esm/activities/index.js +11 -7
- package/dist/esm/activities/middleware/types.d.ts +1 -1
- package/dist/esm/client.d.ts +4 -2
- package/dist/esm/client.js +3 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +4 -2
- package/dist/esm/index.js +3 -1
- package/dist/esm/middlewares/otel.js +3 -1
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/types.d.ts +112 -0
- package/package.json +2 -2
- package/skills/ai-core/adapter-configuration/SKILL.md +90 -42
- package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +39 -21
- package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +5 -0
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +14 -6
- package/skills/ai-core/adapter-configuration/references/grok-adapter.md +33 -25
- package/skills/ai-core/adapter-configuration/references/groq-adapter.md +7 -2
- package/skills/ai-core/adapter-configuration/references/ollama-adapter.md +25 -12
- package/skills/ai-core/adapter-configuration/references/openai-adapter.md +19 -9
- package/skills/ai-core/adapter-configuration/references/openrouter-adapter.md +34 -21
- package/skills/ai-core/ag-ui-protocol/SKILL.md +16 -10
- package/skills/ai-core/chat-experience/SKILL.md +228 -108
- package/skills/ai-core/client-persistence/SKILL.md +21 -9
- package/skills/ai-core/custom-backend-integration/SKILL.md +86 -52
- package/skills/ai-core/debug-logging/SKILL.md +100 -18
- package/skills/ai-core/locks/SKILL.md +35 -7
- package/skills/ai-core/media-generation/SKILL.md +114 -49
- package/skills/ai-core/middleware/SKILL.md +174 -69
- package/skills/ai-core/structured-outputs/SKILL.md +98 -49
- package/skills/ai-core/tool-calling/SKILL.md +245 -158
- package/src/activities/chat/index.ts +6 -7
- package/src/activities/generateLiveVideo/adapter.ts +99 -0
- package/src/activities/generateLiveVideo/index.ts +339 -0
- package/src/activities/generateVideo/index.ts +3 -4
- package/src/activities/generateWorld/adapter.ts +96 -0
- package/src/activities/generateWorld/index.ts +339 -0
- package/src/activities/index.ts +44 -0
- package/src/activities/middleware/types.ts +2 -0
- package/src/client.ts +8 -0
- package/src/index.ts +8 -0
- package/src/middlewares/otel.ts +2 -0
- package/src/types.ts +128 -0
|
@@ -22,8 +22,9 @@ This skill builds on ai-core and ai-core/chat-experience. Read them first.
|
|
|
22
22
|
|
|
23
23
|
Connect `useChat` to a custom SSE backend with auth headers:
|
|
24
24
|
|
|
25
|
-
```
|
|
25
|
+
```tsx
|
|
26
26
|
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
|
|
27
|
+
import { token } from './auth'
|
|
27
28
|
|
|
28
29
|
function Chat() {
|
|
29
30
|
const { messages, sendMessage, isLoading } = useChat({
|
|
@@ -69,6 +70,7 @@ framing. This is the recommended default.
|
|
|
69
70
|
|
|
70
71
|
```typescript
|
|
71
72
|
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
|
|
73
|
+
import { token, tenantId } from './auth'
|
|
72
74
|
|
|
73
75
|
const { messages, sendMessage } = useChat({
|
|
74
76
|
connection: fetchServerSentEvents('https://my-api.com/chat', {
|
|
@@ -85,6 +87,7 @@ const { messages, sendMessage } = useChat({
|
|
|
85
87
|
|
|
86
88
|
```typescript
|
|
87
89
|
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
|
|
90
|
+
import { sessionId, getAccessToken } from './auth'
|
|
88
91
|
|
|
89
92
|
const { messages, sendMessage } = useChat({
|
|
90
93
|
connection: fetchServerSentEvents(
|
|
@@ -95,7 +98,7 @@ const { messages, sendMessage } = useChat({
|
|
|
95
98
|
},
|
|
96
99
|
body: {
|
|
97
100
|
provider: 'openai',
|
|
98
|
-
model: 'gpt-
|
|
101
|
+
model: 'gpt-5.5',
|
|
99
102
|
},
|
|
100
103
|
}),
|
|
101
104
|
),
|
|
@@ -110,6 +113,10 @@ The `body` field in options is merged into the POST request body alongside
|
|
|
110
113
|
```typescript
|
|
111
114
|
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'
|
|
112
115
|
|
|
116
|
+
// Same signature as globalThis.fetch — wrap it however you need.
|
|
117
|
+
const myCustomFetch: typeof fetch = (input, init) =>
|
|
118
|
+
fetch(input, { ...init, credentials: 'include' })
|
|
119
|
+
|
|
113
120
|
const { messages, sendMessage } = useChat({
|
|
114
121
|
connection: fetchServerSentEvents('/api/chat', {
|
|
115
122
|
fetchClient: myCustomFetch,
|
|
@@ -124,6 +131,7 @@ instead of SSE. Each line is one JSON-encoded `StreamChunk` followed by `\n`.
|
|
|
124
131
|
|
|
125
132
|
```typescript
|
|
126
133
|
import { useChat, fetchHttpStream } from '@tanstack/ai-react'
|
|
134
|
+
import { token } from './auth'
|
|
127
135
|
|
|
128
136
|
const { messages, sendMessage } = useChat({
|
|
129
137
|
connection: fetchHttpStream('https://my-api.com/chat', {
|
|
@@ -143,6 +151,7 @@ JSON object per line.
|
|
|
143
151
|
|
|
144
152
|
```typescript
|
|
145
153
|
import { useChat, fetchHttpStream } from '@tanstack/ai-react'
|
|
154
|
+
import { region, refreshToken } from './auth'
|
|
146
155
|
|
|
147
156
|
const { messages, sendMessage } = useChat({
|
|
148
157
|
connection: fetchHttpStream(
|
|
@@ -168,15 +177,11 @@ This is the simpler model and covers most HTTP-based protocols.
|
|
|
168
177
|
|
|
169
178
|
```typescript
|
|
170
179
|
import { useChat } from '@tanstack/ai-react'
|
|
171
|
-
import type {
|
|
172
|
-
import type { StreamChunk
|
|
173
|
-
|
|
174
|
-
const websocketAdapter:
|
|
175
|
-
async *connect(
|
|
176
|
-
messages: Array<UIMessage>,
|
|
177
|
-
data?: Record<string, any>,
|
|
178
|
-
abortSignal?: AbortSignal,
|
|
179
|
-
): AsyncGenerator<StreamChunk> {
|
|
180
|
+
import type { ConnectConnectionAdapter } from '@tanstack/ai-react'
|
|
181
|
+
import type { StreamChunk } from '@tanstack/ai'
|
|
182
|
+
|
|
183
|
+
const websocketAdapter: ConnectConnectionAdapter = {
|
|
184
|
+
async *connect(messages, data, abortSignal) {
|
|
180
185
|
const ws = new WebSocket('wss://my-api.com/chat')
|
|
181
186
|
|
|
182
187
|
// Wait for connection
|
|
@@ -243,25 +248,51 @@ returns an `AsyncIterable<StreamChunk>` that stays open, and `send` dispatches
|
|
|
243
248
|
messages through it.
|
|
244
249
|
|
|
245
250
|
```typescript
|
|
246
|
-
import
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
//
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
import { useChat } from '@tanstack/ai-react'
|
|
252
|
+
import type { SubscribeConnectionAdapter } from '@tanstack/ai-react'
|
|
253
|
+
import type { StreamChunk } from '@tanstack/ai'
|
|
254
|
+
|
|
255
|
+
// One socket for the lifetime of the client; every run's chunks arrive on it.
|
|
256
|
+
const ws = new WebSocket('wss://my-api.com/chat')
|
|
257
|
+
const ready = new Promise<void>((resolve) => {
|
|
258
|
+
ws.addEventListener('open', () => resolve(), { once: true })
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const pushAdapter: SubscribeConnectionAdapter = {
|
|
262
|
+
async *subscribe(abortSignal) {
|
|
263
|
+
// Long-lived async iterable: yields chunks whenever the server pushes
|
|
264
|
+
// them, until the socket closes or the signal aborts
|
|
265
|
+
const queue: Array<StreamChunk> = []
|
|
266
|
+
let wake: (() => void) | null = null
|
|
267
|
+
let closed = false
|
|
268
|
+
|
|
269
|
+
ws.addEventListener('message', (event) => {
|
|
270
|
+
const chunk: StreamChunk = JSON.parse(event.data)
|
|
271
|
+
queue.push(chunk)
|
|
272
|
+
wake?.()
|
|
273
|
+
})
|
|
274
|
+
ws.addEventListener('close', () => {
|
|
275
|
+
closed = true
|
|
276
|
+
wake?.()
|
|
277
|
+
})
|
|
278
|
+
abortSignal?.addEventListener('abort', () => ws.close())
|
|
279
|
+
|
|
280
|
+
while (!closed || queue.length > 0) {
|
|
281
|
+
const next = queue.shift()
|
|
282
|
+
if (next !== undefined) {
|
|
283
|
+
yield next
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
await new Promise<void>((r) => {
|
|
287
|
+
wake = r
|
|
288
|
+
})
|
|
289
|
+
}
|
|
256
290
|
},
|
|
257
291
|
|
|
258
|
-
async send(
|
|
259
|
-
messages: Array<UIMessage>,
|
|
260
|
-
data?: Record<string, any>,
|
|
261
|
-
abortSignal?: AbortSignal,
|
|
262
|
-
): Promise<void> {
|
|
292
|
+
async send(messages, data) {
|
|
263
293
|
// Dispatch messages; chunks arrive through subscribe()
|
|
264
|
-
await
|
|
294
|
+
await ready
|
|
295
|
+
ws.send(JSON.stringify({ messages, ...data }))
|
|
265
296
|
},
|
|
266
297
|
}
|
|
267
298
|
|
|
@@ -279,12 +310,9 @@ a shorthand for creating a `ConnectConnectionAdapter` from an async generator:
|
|
|
279
310
|
|
|
280
311
|
```typescript
|
|
281
312
|
import { useChat, stream } from '@tanstack/ai-react'
|
|
282
|
-
import type { StreamChunk
|
|
313
|
+
import type { StreamChunk } from '@tanstack/ai'
|
|
283
314
|
|
|
284
|
-
const directAdapter = stream(async function* (
|
|
285
|
-
messages: Array<UIMessage>,
|
|
286
|
-
data?: Record<string, any>,
|
|
287
|
-
): AsyncGenerator<StreamChunk> {
|
|
315
|
+
const directAdapter = stream(async function* (messages, data) {
|
|
288
316
|
const response = await fetch('https://my-api.com/chat', {
|
|
289
317
|
method: 'POST',
|
|
290
318
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -305,7 +333,8 @@ const directAdapter = stream(async function* (
|
|
|
305
333
|
|
|
306
334
|
for (const line of lines) {
|
|
307
335
|
if (line.trim()) {
|
|
308
|
-
|
|
336
|
+
const chunk: StreamChunk = JSON.parse(line)
|
|
337
|
+
yield chunk
|
|
309
338
|
}
|
|
310
339
|
}
|
|
311
340
|
}
|
|
@@ -324,35 +353,43 @@ The `ConnectionAdapter` interface has two mutually exclusive modes. Providing
|
|
|
324
353
|
both throws at runtime.
|
|
325
354
|
|
|
326
355
|
```typescript
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
356
|
+
import type {
|
|
357
|
+
ConnectConnectionAdapter,
|
|
358
|
+
ConnectionAdapter,
|
|
359
|
+
SubscribeConnectionAdapter,
|
|
360
|
+
} from '@tanstack/ai-react'
|
|
361
|
+
import { channel } from './channel'
|
|
362
|
+
|
|
363
|
+
// WRONG -- type-checks (ConnectionAdapter is a union) but throws at runtime:
|
|
364
|
+
// "Connection adapter must provide either connect or both subscribe and
|
|
365
|
+
// send, not both modes"
|
|
366
|
+
const adapter: ConnectionAdapter = {
|
|
330
367
|
async *connect(messages) {
|
|
331
368
|
/* ... */
|
|
332
369
|
},
|
|
333
370
|
subscribe(signal) {
|
|
334
|
-
|
|
371
|
+
return channel.chunks(signal)
|
|
335
372
|
},
|
|
336
373
|
async send(messages) {
|
|
337
|
-
|
|
374
|
+
await channel.send(messages)
|
|
338
375
|
},
|
|
339
376
|
}
|
|
340
377
|
|
|
341
378
|
// CORRECT -- pick one mode
|
|
342
379
|
// Option A: ConnectConnectionAdapter (pull-based)
|
|
343
|
-
const pullAdapter = {
|
|
380
|
+
const pullAdapter: ConnectConnectionAdapter = {
|
|
344
381
|
async *connect(messages, data, abortSignal) {
|
|
345
382
|
// ... yield StreamChunks
|
|
346
383
|
},
|
|
347
384
|
}
|
|
348
385
|
|
|
349
386
|
// Option B: SubscribeConnectionAdapter (push-based)
|
|
350
|
-
const pushAdapter = {
|
|
387
|
+
const pushAdapter: SubscribeConnectionAdapter = {
|
|
351
388
|
subscribe(abortSignal) {
|
|
352
|
-
return
|
|
389
|
+
return channel.chunks(abortSignal)
|
|
353
390
|
},
|
|
354
391
|
async send(messages, data, abortSignal) {
|
|
355
|
-
await
|
|
392
|
+
await channel.send({ messages, ...data }, abortSignal)
|
|
356
393
|
},
|
|
357
394
|
}
|
|
358
395
|
```
|
|
@@ -388,15 +425,11 @@ streaming, implement retry logic in your connection adapter:
|
|
|
388
425
|
|
|
389
426
|
```typescript
|
|
390
427
|
import { useChat } from '@tanstack/ai-react'
|
|
391
|
-
import type {
|
|
392
|
-
import type { StreamChunk
|
|
393
|
-
|
|
394
|
-
const resilientAdapter:
|
|
395
|
-
async *connect(
|
|
396
|
-
messages: Array<UIMessage>,
|
|
397
|
-
data?: Record<string, any>,
|
|
398
|
-
abortSignal?: AbortSignal,
|
|
399
|
-
): AsyncGenerator<StreamChunk> {
|
|
428
|
+
import type { ConnectConnectionAdapter } from '@tanstack/ai-react'
|
|
429
|
+
import type { StreamChunk } from '@tanstack/ai'
|
|
430
|
+
|
|
431
|
+
const resilientAdapter: ConnectConnectionAdapter = {
|
|
432
|
+
async *connect(messages, data, abortSignal) {
|
|
400
433
|
const maxRetries = 3
|
|
401
434
|
let attempt = 0
|
|
402
435
|
|
|
@@ -427,7 +460,8 @@ const resilientAdapter: ConnectionAdapter = {
|
|
|
427
460
|
|
|
428
461
|
for (const line of lines) {
|
|
429
462
|
if (line.trim()) {
|
|
430
|
-
|
|
463
|
+
const chunk: StreamChunk = JSON.parse(line)
|
|
464
|
+
yield chunk
|
|
431
465
|
}
|
|
432
466
|
}
|
|
433
467
|
}
|
|
@@ -30,8 +30,10 @@ printed, or pipe logs into a custom logger (pino, winston, etc.). The same
|
|
|
30
30
|
import { chat } from '@tanstack/ai'
|
|
31
31
|
import { openaiText } from '@tanstack/ai-openai'
|
|
32
32
|
|
|
33
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
34
|
+
|
|
33
35
|
const stream = chat({
|
|
34
|
-
adapter: openaiText('gpt-5.
|
|
36
|
+
adapter: openaiText('gpt-5.5'),
|
|
35
37
|
messages,
|
|
36
38
|
debug: true, // all categories on, prints to console
|
|
37
39
|
})
|
|
@@ -49,8 +51,13 @@ Each log line is prefixed with an emoji and `[tanstack-ai:<category>]`:
|
|
|
49
51
|
## Turn it off
|
|
50
52
|
|
|
51
53
|
```typescript
|
|
54
|
+
import { chat } from '@tanstack/ai'
|
|
55
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
56
|
+
|
|
57
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
58
|
+
|
|
52
59
|
chat({
|
|
53
|
-
adapter: openaiText('gpt-5.
|
|
60
|
+
adapter: openaiText('gpt-5.5'),
|
|
54
61
|
messages,
|
|
55
62
|
debug: false, // silence everything, including errors
|
|
56
63
|
})
|
|
@@ -63,6 +70,9 @@ Omitting `debug` is **not** the same as `debug: false`. When omitted, the
|
|
|
63
70
|
## `DebugOption` — the accepted shapes
|
|
64
71
|
|
|
65
72
|
```typescript
|
|
73
|
+
import type { Logger } from '@tanstack/ai'
|
|
74
|
+
|
|
75
|
+
// As exported by '@tanstack/ai'
|
|
66
76
|
type DebugOption = boolean | DebugConfig
|
|
67
77
|
|
|
68
78
|
interface DebugConfig {
|
|
@@ -95,8 +105,13 @@ Pass a `DebugConfig` object. Unspecified categories default to `true`, so it's
|
|
|
95
105
|
easiest to toggle by setting specific flags to `false`:
|
|
96
106
|
|
|
97
107
|
```typescript
|
|
108
|
+
import { chat } from '@tanstack/ai'
|
|
109
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
110
|
+
|
|
111
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
112
|
+
|
|
98
113
|
chat({
|
|
99
|
-
adapter: openaiText('gpt-5.
|
|
114
|
+
adapter: openaiText('gpt-5.5'),
|
|
100
115
|
messages,
|
|
101
116
|
debug: { middleware: false }, // everything except middleware
|
|
102
117
|
})
|
|
@@ -105,8 +120,13 @@ chat({
|
|
|
105
120
|
To print only a specific set, set the rest to `false` explicitly:
|
|
106
121
|
|
|
107
122
|
```typescript
|
|
123
|
+
import { chat } from '@tanstack/ai'
|
|
124
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
125
|
+
|
|
126
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
127
|
+
|
|
108
128
|
chat({
|
|
109
|
-
adapter: openaiText('gpt-5.
|
|
129
|
+
adapter: openaiText('gpt-5.5'),
|
|
110
130
|
messages,
|
|
111
131
|
debug: {
|
|
112
132
|
provider: true,
|
|
@@ -124,7 +144,8 @@ chat({
|
|
|
124
144
|
## Pipe into your own logger
|
|
125
145
|
|
|
126
146
|
```typescript
|
|
127
|
-
import type
|
|
147
|
+
import { chat, type Logger } from '@tanstack/ai'
|
|
148
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
128
149
|
import pino from 'pino'
|
|
129
150
|
|
|
130
151
|
const pinoLogger = pino()
|
|
@@ -135,8 +156,10 @@ const logger: Logger = {
|
|
|
135
156
|
error: (msg, meta) => pinoLogger.error(meta, msg),
|
|
136
157
|
}
|
|
137
158
|
|
|
159
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
160
|
+
|
|
138
161
|
chat({
|
|
139
|
-
adapter: openaiText('gpt-5.
|
|
162
|
+
adapter: openaiText('gpt-5.5'),
|
|
140
163
|
messages,
|
|
141
164
|
debug: { logger }, // all categories on, piped to pino
|
|
142
165
|
})
|
|
@@ -170,11 +193,48 @@ concepts don't exist in their pipelines.
|
|
|
170
193
|
Same `debug` option everywhere:
|
|
171
194
|
|
|
172
195
|
```typescript
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
196
|
+
import {
|
|
197
|
+
summarize,
|
|
198
|
+
generateImage,
|
|
199
|
+
generateSpeech,
|
|
200
|
+
generateTranscription,
|
|
201
|
+
generateVideo,
|
|
202
|
+
} from '@tanstack/ai'
|
|
203
|
+
import {
|
|
204
|
+
openaiSummarize,
|
|
205
|
+
openaiImage,
|
|
206
|
+
openaiSpeech,
|
|
207
|
+
openaiTranscription,
|
|
208
|
+
openaiVideo,
|
|
209
|
+
} from '@tanstack/ai-openai'
|
|
210
|
+
import { logger } from './logger'
|
|
211
|
+
import { audio } from './recording'
|
|
212
|
+
|
|
213
|
+
summarize({
|
|
214
|
+
adapter: openaiSummarize('gpt-5.5'),
|
|
215
|
+
text: 'Long article…',
|
|
216
|
+
debug: true,
|
|
217
|
+
})
|
|
218
|
+
generateImage({
|
|
219
|
+
adapter: openaiImage('gpt-image-2'),
|
|
220
|
+
prompt: 'a cat',
|
|
221
|
+
debug: { logger },
|
|
222
|
+
})
|
|
223
|
+
generateSpeech({
|
|
224
|
+
adapter: openaiSpeech('tts-1-hd'),
|
|
225
|
+
text: 'Hello',
|
|
226
|
+
debug: { request: true },
|
|
227
|
+
})
|
|
228
|
+
generateTranscription({
|
|
229
|
+
adapter: openaiTranscription('gpt-4o-transcribe'),
|
|
230
|
+
audio,
|
|
231
|
+
debug: false,
|
|
232
|
+
})
|
|
233
|
+
generateVideo({
|
|
234
|
+
adapter: openaiVideo('sora-2'),
|
|
235
|
+
prompt: 'a wave',
|
|
236
|
+
debug: { output: true },
|
|
237
|
+
})
|
|
178
238
|
```
|
|
179
239
|
|
|
180
240
|
Realtime session adapters in provider packages (e.g. `openaiRealtime`,
|
|
@@ -187,6 +247,12 @@ categories don't apply.
|
|
|
187
247
|
### a. HIGH: Treating omitted `debug` as silent
|
|
188
248
|
|
|
189
249
|
```typescript
|
|
250
|
+
import { chat } from '@tanstack/ai'
|
|
251
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
252
|
+
|
|
253
|
+
const adapter = openaiText('gpt-5.5')
|
|
254
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
255
|
+
|
|
190
256
|
// WRONG — expecting this to be completely silent
|
|
191
257
|
chat({ adapter, messages })
|
|
192
258
|
// Errors still print via [tanstack-ai:errors] ... on failure.
|
|
@@ -203,6 +269,12 @@ Source: docs/advanced/debug-logging.md
|
|
|
203
269
|
### b. MEDIUM: Reaching for middleware when `debug` would do
|
|
204
270
|
|
|
205
271
|
```typescript
|
|
272
|
+
import { chat, type ChatMiddleware } from '@tanstack/ai'
|
|
273
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
274
|
+
|
|
275
|
+
const adapter = openaiText('gpt-5.5')
|
|
276
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
277
|
+
|
|
206
278
|
// WRONG — writing logging middleware to see chunks flow
|
|
207
279
|
const chunkLogger: ChatMiddleware = {
|
|
208
280
|
name: 'chunk-logger',
|
|
@@ -234,22 +306,32 @@ prefer implementations that don't throw — silenced exceptions are harder to
|
|
|
234
306
|
debug than loud ones.
|
|
235
307
|
|
|
236
308
|
```typescript
|
|
309
|
+
import type { Logger } from '@tanstack/ai'
|
|
310
|
+
|
|
237
311
|
// WRONG — a logger that can throw on serialization
|
|
238
312
|
const fragile: Logger = {
|
|
239
313
|
debug: (msg, meta) => console.debug(msg, JSON.stringify(meta)), // cyclic meta → throws
|
|
240
|
-
|
|
314
|
+
info: (msg, meta) => console.info(msg, JSON.stringify(meta)),
|
|
315
|
+
warn: (msg, meta) => console.warn(msg, JSON.stringify(meta)),
|
|
316
|
+
error: (msg, meta) => console.error(msg, JSON.stringify(meta)),
|
|
241
317
|
}
|
|
242
318
|
|
|
243
319
|
// CORRECT — guard serialization in the logger itself
|
|
244
|
-
const
|
|
245
|
-
|
|
320
|
+
const guarded =
|
|
321
|
+
(log: (...args: Array<unknown>) => void): Logger['debug'] =>
|
|
322
|
+
(msg, meta) => {
|
|
246
323
|
try {
|
|
247
|
-
|
|
324
|
+
log(msg, JSON.stringify(meta))
|
|
248
325
|
} catch {
|
|
249
|
-
|
|
326
|
+
log(msg) // fall back to the bare message rather than throw
|
|
250
327
|
}
|
|
251
|
-
}
|
|
252
|
-
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const safe: Logger = {
|
|
331
|
+
debug: guarded(console.debug),
|
|
332
|
+
info: guarded(console.info),
|
|
333
|
+
warn: guarded(console.warn),
|
|
334
|
+
error: guarded(console.error),
|
|
253
335
|
}
|
|
254
336
|
```
|
|
255
337
|
|
|
@@ -35,20 +35,40 @@ per-thread (or other) lock yourself when multi-writer races matter.
|
|
|
35
35
|
## Wire locks
|
|
36
36
|
|
|
37
37
|
```ts
|
|
38
|
+
import { chat } from '@tanstack/ai'
|
|
38
39
|
import { withLocks, InMemoryLockStore } from '@tanstack/ai/locks'
|
|
40
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
43
|
+
|
|
44
|
+
chat({
|
|
45
|
+
adapter: openaiText('gpt-5.6'),
|
|
46
|
+
messages,
|
|
47
|
+
middleware: [
|
|
48
|
+
withLocks(new InMemoryLockStore()), // single process
|
|
49
|
+
],
|
|
50
|
+
})
|
|
43
51
|
```
|
|
44
52
|
|
|
45
53
|
Alongside persistence — optional, locks do not require it:
|
|
46
54
|
|
|
47
55
|
```ts
|
|
56
|
+
import { chat } from '@tanstack/ai'
|
|
48
57
|
import { withLocks, InMemoryLockStore } from '@tanstack/ai/locks'
|
|
49
|
-
import {
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
59
|
+
import { memoryPersistence, withPersistence } from '@tanstack/ai-persistence'
|
|
60
|
+
|
|
61
|
+
const persistence = memoryPersistence()
|
|
62
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
63
|
+
|
|
64
|
+
chat({
|
|
65
|
+
adapter: openaiText('gpt-5.6'),
|
|
66
|
+
messages,
|
|
67
|
+
middleware: [
|
|
68
|
+
withPersistence(persistence),
|
|
69
|
+
withLocks(new InMemoryLockStore()),
|
|
70
|
+
],
|
|
71
|
+
})
|
|
52
72
|
```
|
|
53
73
|
|
|
54
74
|
`withLocks` provides `LocksCapability` for downstream middleware (e.g.
|
|
@@ -73,7 +93,9 @@ annotation), then hand it to `withLocks`. Acquire the key, run `fn`, release whe
|
|
|
73
93
|
`fn` settles:
|
|
74
94
|
|
|
75
95
|
```ts
|
|
96
|
+
import { chat } from '@tanstack/ai'
|
|
76
97
|
import { defineLock, withLocks } from '@tanstack/ai/locks'
|
|
98
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
77
99
|
import { acquire } from './my-lock-backend'
|
|
78
100
|
|
|
79
101
|
const locks = defineLock({
|
|
@@ -87,7 +109,13 @@ const locks = defineLock({
|
|
|
87
109
|
},
|
|
88
110
|
})
|
|
89
111
|
|
|
90
|
-
|
|
112
|
+
const messages = [{ role: 'user' as const, content: 'Hello' }]
|
|
113
|
+
|
|
114
|
+
chat({
|
|
115
|
+
adapter: openaiText('gpt-5.6'),
|
|
116
|
+
messages,
|
|
117
|
+
middleware: [withLocks(locks)],
|
|
118
|
+
})
|
|
91
119
|
```
|
|
92
120
|
|
|
93
121
|
## Lease semantics
|