@tanstack/ai-client 0.23.3 → 0.25.1

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.
@@ -26,9 +26,11 @@ import type {
26
26
  GenerationClientOptions,
27
27
  GenerationClientState,
28
28
  GenerationFetcher,
29
+ GenerationPersistenceOptions,
29
30
  GenerationRestoredResult,
30
31
  GenerationResumeSnapshot,
31
32
  GenerationResumeState,
33
+ GenerationTransport,
32
34
  } from './generation-types'
33
35
 
34
36
  /**
@@ -108,10 +110,10 @@ export class GenerationClient<
108
110
  private readonly joinRunHandler:
109
111
  | ConnectConnectionAdapter['joinRun']
110
112
  | undefined
111
- private readonly uniqueId: string
113
+ private uniqueId: string
112
114
  private readonly devtoolsMetadata: AIDevtoolsClientMetadata
113
115
  private readonly devtoolsBridge: GenerationDevtoolsBridge<TOutput>
114
- private readonly threadId: string
116
+ private threadId: string
115
117
  private readonly persistenceScope: string | undefined
116
118
  // Server-driven mode (`persistence: true`): no local snapshot store; on mount
117
119
  // the client hydrates the last generation for `threadId` from the server.
@@ -133,26 +135,20 @@ export class GenerationClient<
133
135
  private serverHydrationStarted = false
134
136
 
135
137
  constructor(
136
- options: GenerationClientOptions<TInput, TResult, TOutput> &
137
- (
138
- | { connection: ConnectConnectionAdapter; fetcher?: never }
139
- | {
140
- fetcher: GenerationFetcher<TInput, TResult>
141
- connection?: never
142
- }
143
- ),
138
+ options: Omit<
139
+ GenerationClientOptions<TInput, TResult, TOutput>,
140
+ 'persistence' | 'threadId'
141
+ > &
142
+ GenerationPersistenceOptions &
143
+ GenerationTransport<TInput, TResult>,
144
144
  ) {
145
- // `threadId` is the single identity. Deprecated `id` is only a fallback
146
- // when no threadId is given (ephemeral runs / legacy call sites).
147
- this.uniqueId =
148
- options.threadId ?? options.id ?? this.generateUniqueId('generation')
149
- // AG-UI requires a thread id on every run, so fall back to uniqueId. The
150
- // generated fallback is for the WIRE ONLY: it is not stable across reloads,
151
- // so persistence must never key on it — see `persistenceScope` below.
152
- this.threadId = options.threadId ?? this.uniqueId
153
- // The persistence scope: the explicit `threadId` and nothing else. The
154
- // types require it whenever `persistence` is set; this field keeps the
155
- // fallback from silently becoming a storage key for JS callers.
145
+ // `threadId` is the only identity. Do not mint a random id during
146
+ // construct: hooks build this client during render.
147
+ this.threadId = options.threadId ?? ''
148
+ this.uniqueId = this.threadId
149
+ // The persistence scope is the explicit `threadId` and nothing else.
150
+ // The types require it whenever `persistence` is set. This field keeps a
151
+ // generated wire id from becoming a storage key for JS callers.
156
152
  this.persistenceScope = options.threadId
157
153
  this.connection = options.connection
158
154
  this.fetcher = options.fetcher
@@ -199,10 +195,17 @@ export class GenerationClient<
199
195
  }
200
196
 
201
197
  private buildDevtoolsBridgeOptions(): GenerationDevtoolsBridgeOptions<TOutput> {
198
+ const client = this
202
199
  return {
203
- hookId: this.uniqueId,
204
- clientId: this.uniqueId,
205
- threadId: this.threadId,
200
+ get hookId() {
201
+ return client.uniqueId
202
+ },
203
+ get clientId() {
204
+ return client.uniqueId
205
+ },
206
+ get threadId() {
207
+ return client.threadId
208
+ },
206
209
  metadata: this.devtoolsMetadata,
207
210
  getCoreState: () => ({
208
211
  input: this.input,
@@ -216,6 +219,7 @@ export class GenerationClient<
216
219
  }
217
220
 
218
221
  mountDevtools(): void {
222
+ this.ensureThreadId()
219
223
  // Mounting revives a disposed client. Framework hooks call this from
220
224
  // their mount effect, so a dispose → remount cycle (e.g. React
221
225
  // StrictMode's mount → cleanup → mount replay against the same memoized
@@ -629,6 +633,14 @@ export class GenerationClient<
629
633
  }
630
634
  }
631
635
 
636
+ private ensureThreadId(): string {
637
+ if (!this.threadId) {
638
+ this.threadId = this.generateUniqueId('generation')
639
+ }
640
+ this.uniqueId = this.threadId
641
+ return this.threadId
642
+ }
643
+
632
644
  private generateUniqueId(prefix: string): string {
633
645
  return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`
634
646
  }
@@ -201,27 +201,23 @@ export interface GenerationResumeSnapshot {
201
201
  }
202
202
 
203
203
  /**
204
- * The `persistence` / `threadId` / `id` identity shared by every generation hook.
204
+ * The `persistence` / `threadId` identity shared by every generation hook.
205
205
  *
206
206
  * Turning persistence on **requires** a `threadId`, the stable scope runs are
207
207
  * filed under. Without one the client would hydrate by a generated id that
208
208
  * changes every reload, so nothing would ever restore; making it a type error
209
209
  * means the compiler asks for the scope instead of the runtime inventing one.
210
210
  *
211
- * `threadId` is the single identity for the hook, the AG-UI wire thread, and
212
- * persistence. Legacy `id` is deprecated and typed `never` whenever
213
- * `threadId` is supplied — pass one scope, not two.
211
+ * `threadId` is the only identity for the hook, the AG-UI wire thread, and
212
+ * persistence. There is no separate instance `id`.
214
213
  *
215
- * Ephemeral generations (no `persistence`, or `persistence: false`) may still
216
- * pass a deprecated `id` when they have no `threadId`, as a wire/devtools
217
- * fallback. Prefer giving them a `threadId` instead.
218
- *
219
- * USAGE: intersect this onto a hook's parameter and subtract the three keys
220
- * from the options interface, leaving that interface a plain (non-union)
214
+ * USAGE: intersect this onto a hook's parameter and subtract `persistence`
215
+ * and `threadId` from the options interface (plus any other keys the hook
216
+ * redeclares, such as `onResult`), leaving that interface a plain (non-union)
221
217
  * object so `Pick` / `Omit` composition elsewhere keeps working:
222
218
  *
223
219
  * ```ts
224
- * options: Omit<UseGenerateImageOptions, 'onResult' | 'persistence' | 'threadId' | 'id'> & {
220
+ * options: Omit<UseGenerateImageOptions, 'onResult' | 'persistence' | 'threadId'> & {
225
221
  * onResult?: (result: ImageGenerationResult) => TTransformed
226
222
  * } & GenerationPersistenceOptions
227
223
  * ```
@@ -233,31 +229,13 @@ export interface GenerationResumeSnapshot {
233
229
  export type GenerationPersistenceOptions =
234
230
  | {
235
231
  persistence: true
236
- /** Required by `persistence` — the stable scope runs are filed under. */
237
- threadId: string
238
- /**
239
- * @deprecated Prefer `threadId`. Not allowed when `threadId` is set —
240
- * `threadId` is the single identity for the hook, the wire, and persistence.
241
- */
242
- id?: never
243
- }
244
- | {
245
- persistence?: false | undefined
246
- /** Stable scope for the generation slot (also the wire / devtools identity). */
232
+ /** Required by `persistence`. The stable scope runs are filed under. */
247
233
  threadId: string
248
- /**
249
- * @deprecated Prefer `threadId`. Not allowed when `threadId` is set.
250
- */
251
- id?: never
252
234
  }
253
235
  | {
254
236
  persistence?: false | undefined
255
- threadId?: undefined
256
- /**
257
- * @deprecated Prefer `threadId` as the single identity. Only allowed when
258
- * `threadId` is omitted — legacy wire/devtools fallback for ephemeral runs.
259
- */
260
- id?: string
237
+ /** Stable scope for the generation slot (also the wire / DevTools identity). */
238
+ threadId?: string
261
239
  }
262
240
 
263
241
  // ===========================
@@ -330,28 +308,19 @@ export type GenerationTransport<TInput, TResult> =
330
308
  */
331
309
  // eslint-disable-next-line @typescript-eslint/naming-convention -- _TInput is unused in the interface body but part of the public positional generic API (callers supply it for inference)
332
310
  export interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {
333
- /**
334
- * @deprecated Prefer {@link GenerationClientOptions.threadId}. Legacy instance
335
- * id used only as a wire/devtools fallback when `threadId` is omitted. When
336
- * both are passed, `threadId` wins and `id` is ignored. Framework hooks type
337
- * `id` as `never` whenever `threadId` is set — see
338
- * {@link GenerationPersistenceOptions}.
339
- */
340
- id?: string
341
-
342
311
  /**
343
312
  * The **scope** this generation belongs to: a stable, app-chosen name for the
344
313
  * slot successive runs fill, not a link to a chat conversation. This is the
345
- * single identity for the client — wire thread id, devtools hook id, and
314
+ * only identity for the client: wire thread id, DevTools hook id, and
346
315
  * persistence key.
347
316
  *
348
- * A generation hook starts empty and produces many runs over its life — each
317
+ * A generation hook starts empty and produces many runs over its life. Each
349
318
  * run gets its own `runId`, but they all belong to one scope. Persistence
350
319
  * keys on this: server-driven hydrates the last run for it on mount. It is
351
320
  * also sent as the AG-UI thread id on the wire, since the protocol requires
352
321
  * one.
353
322
  *
354
- * Derive it from your own domain — it must be meaningful before any media
323
+ * Derive it from your own domain. It must be meaningful before any media
355
324
  * exists and identical after a reload:
356
325
  *
357
326
  * ```ts
@@ -360,9 +329,8 @@ export interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {
360
329
  *
361
330
  * **Required whenever `persistence` is set.** An app that cannot name the
362
331
  * scope has nothing to restore *to*, and a generated fallback would key each
363
- * reload differently — silently restoring nothing. Optional only for
364
- * ephemeral runs, where it falls back to deprecated `id` (or a generated id)
365
- * purely to satisfy the wire and nothing is written.
332
+ * reload differently, silently restoring nothing. Optional only for
333
+ * ephemeral runs. If omitted, the client mints a wire id after mount.
366
334
  */
367
335
  threadId?: string
368
336
 
package/src/index.ts CHANGED
@@ -30,6 +30,7 @@ export type {
30
30
  ChatClientPersistence,
31
31
  ChatPersistedState,
32
32
  ChatPersistenceOption,
33
+ ChatPersistenceOptions,
33
34
  ChatStorageAdapter,
34
35
  ChatClientOptions,
35
36
  ChatPendingInterrupt,
@@ -38,6 +39,9 @@ export type {
38
39
  ChatInterrupt,
39
40
  ChatInterruptState,
40
41
  GenericAGUIInterrupt,
42
+ GenericInterrupt,
43
+ RegisteredGenericInterrupt,
44
+ ResolvableChatInterrupt,
41
45
  UnboundInterrupt,
42
46
  InterruptItemStatus,
43
47
  ToolApprovalInterrupt,
@@ -143,6 +147,7 @@ export {
143
147
  xhrHttpStream,
144
148
  stream,
145
149
  rpcStream,
150
+ webSocket,
146
151
  StreamTruncatedError,
147
152
  DurableStreamIncompleteError,
148
153
  StreamReconnectLimitError,
@@ -155,6 +160,7 @@ export {
155
160
  type RunAgentInputContext,
156
161
  type StreamConnectionHandlers,
157
162
  type SubscribeConnectionAdapter,
163
+ type WebSocketConnectionOptions,
158
164
  type XhrConnectionOptions,
159
165
  } from './connection-adapters'
160
166