@tanstack/ai-svelte 0.15.1 → 0.16.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.
@@ -6,6 +6,7 @@ import type {
6
6
  ConnectConnectionAdapter,
7
7
  GenerationClientState,
8
8
  GenerationFetcher,
9
+ GenerationPersistenceOptions,
9
10
  InferGenerationOutputFromReturn,
10
11
  VideoGenerateInput,
11
12
  VideoGenerateResult,
@@ -22,12 +23,51 @@ export interface CreateGenerateVideoOptions<TOutput = VideoGenerateResult> {
22
23
  connection?: ConnectConnectionAdapter
23
24
  /** Direct async function that returns a completed video result */
24
25
  fetcher?: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>
25
- /** Unique identifier for this generation instance */
26
+ /**
27
+ * @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
28
+ */
26
29
  id?: string
27
30
  /** Additional body parameters to send with connect-based adapter requests */
28
31
  body?: Record<string, any>
29
32
  /** Display options for TanStack AI Devtools. */
30
33
  devtools?: AIDevtoolsDisplayOptions
34
+ /**
35
+ * How this generation persists across reloads.
36
+ * - Omit / `false`: ephemeral, in-memory only.
37
+ * - `true`: server-driven — on mount the client hydrates the last generation
38
+ * for its `threadId` from the server (needs a connection with a
39
+ * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.
40
+ */
41
+ persistence?: boolean
42
+ /**
43
+ * The **scope** this generation belongs to: a stable, app-chosen name for the
44
+ * slot successive runs fill — not a link to a chat conversation.
45
+ *
46
+ * The hook starts empty and produces many runs over its life; each gets its
47
+ * own `runId`, but all belong to one scope. Persistence keys on this, so
48
+ * derive it from your own domain and keep it identical across reloads (e.g.
49
+ * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread
50
+ * id on the wire, which the protocol requires.
51
+ *
52
+ * **Required whenever `persistence` is set** — an app that cannot name the
53
+ * scope has nothing to restore to. Optional for ephemeral generations, where
54
+ * it falls back to `id` purely to satisfy the wire.
55
+ */
56
+ threadId?: string
57
+ /**
58
+ * Server-driven hydration handler for `persistence: true` when the
59
+ * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /
60
+ * `rpcStream()` adapter built without handlers) — typically a one-line
61
+ * server-function call. The connection's own handler takes precedence.
62
+ */
63
+ hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']
64
+ /**
65
+ * Re-attach handler that replays a run still generating to completion on
66
+ * mount, when the connection doesn't carry one. Without it, a restored
67
+ * `running` snapshot surfaces as an (interrupted) error. The connection's
68
+ * own handler takes precedence.
69
+ */
70
+ joinRun?: ConnectConnectionAdapter['joinRun']
31
71
  /**
32
72
  * Callback when video generation completes. Can optionally return a transformed value.
33
73
  *
@@ -76,6 +116,13 @@ export interface CreateGenerateVideoReturn<TOutput = VideoGenerateResult> {
76
116
  dispose: () => void
77
117
  /** Update additional body parameters */
78
118
  updateBody: (body: Record<string, any>) => void
119
+ /**
120
+ * The id of the generation job currently running, or `null` when nothing is in
121
+ * flight. Each call to `generate` is one job with its own id. Pass it to your
122
+ * own endpoint to cancel or poll the provider job — `stop()` only aborts the
123
+ * local stream, it does not stop work already running on the provider.
124
+ */
125
+ readonly runId: string | null
79
126
  }
80
127
 
81
128
  /**
@@ -112,9 +159,12 @@ export interface CreateGenerateVideoReturn<TOutput = VideoGenerateResult> {
112
159
  // parameter is typed as `VideoGenerateResult` and `result` narrows to the
113
160
  // transform's return. See issue #848.
114
161
  export function createGenerateVideo<TTransformed = void>(
115
- options: Omit<CreateGenerateVideoOptions, 'onResult'> & {
162
+ options: Omit<
163
+ CreateGenerateVideoOptions,
164
+ 'onResult' | 'persistence' | 'threadId' | 'id'
165
+ > & {
116
166
  onResult?: (result: VideoGenerateResult) => TTransformed
117
- },
167
+ } & GenerationPersistenceOptions,
118
168
  ): CreateGenerateVideoReturn<
119
169
  InferGenerationOutputFromReturn<VideoGenerateResult, TTransformed>
120
170
  > {
@@ -122,9 +172,7 @@ export function createGenerateVideo<TTransformed = void>(
122
172
  VideoGenerateResult,
123
173
  TTransformed
124
174
  >
125
- const clientId =
126
- options.id ||
127
- `video-${Date.now()}-${Math.random().toString(36).substring(7)}`
175
+ const fallbackId = `video-${Date.now()}-${Math.random().toString(36).substring(7)}`
128
176
 
129
177
  // Create reactive state using Svelte 5 runes
130
178
  let result = $state<TOutput | null>(null)
@@ -133,14 +181,26 @@ export function createGenerateVideo<TTransformed = void>(
133
181
  let isLoading = $state(false)
134
182
  let error = $state<Error | undefined>(undefined)
135
183
  let status = $state<GenerationClientState>('idle')
184
+ let runId = $state<string | null>(null)
185
+ let disposed = false
136
186
 
137
187
  // `body` uses a conditional spread because `VideoGenerationClientOptions.body`
138
188
  // is declared `body?: Record<string, any>` (absent vs. present) under
139
189
  // `exactOptionalPropertyTypes`. The optional caller `options.body` may be
140
190
  // undefined, in which case we want the key to be absent on the target.
191
+ // Identity: pass `threadId` alone when set (never also pass deprecated `id`).
141
192
  const baseOptions = {
142
- id: clientId,
143
193
  body: options.body,
194
+ ...(options.threadId !== undefined
195
+ ? { threadId: options.threadId }
196
+ : { id: options.id ?? fallbackId }),
197
+ ...(options.persistence !== undefined && {
198
+ persistence: options.persistence,
199
+ }),
200
+ ...(options.hydrateGeneration !== undefined && {
201
+ hydrateGeneration: options.hydrateGeneration,
202
+ }),
203
+ ...(options.joinRun !== undefined && { joinRun: options.joinRun }),
144
204
  devtoolsBridgeFactory: createVideoDevtoolsBridge,
145
205
  devtools: {
146
206
  ...options.devtools,
@@ -154,29 +214,49 @@ export function createGenerateVideo<TTransformed = void>(
154
214
  onResult: ((r: VideoGenerateResult) => options.onResult?.(r)) as (
155
215
  result: VideoGenerateResult,
156
216
  ) => TOutput | null | void,
157
- onError: (e: Error) => options.onError?.(e),
158
- onProgress: (p: number, m?: string) => options.onProgress?.(p, m),
159
- onChunk: (c: StreamChunk) => options.onChunk?.(c),
160
- onJobCreated: (id: string) => options.onJobCreated?.(id),
161
- onStatusUpdate: (s: VideoStatusInfo) => options.onStatusUpdate?.(s),
217
+ onError: (e: Error) => {
218
+ if (!disposed) options.onError?.(e)
219
+ },
220
+ onProgress: (p: number, m?: string) => {
221
+ if (!disposed) options.onProgress?.(p, m)
222
+ },
223
+ onChunk: (c: StreamChunk) => {
224
+ if (!disposed) options.onChunk?.(c)
225
+ },
226
+ onJobCreated: (id: string) => {
227
+ if (!disposed) options.onJobCreated?.(id)
228
+ },
229
+ onStatusUpdate: (s: VideoStatusInfo) => {
230
+ if (!disposed) options.onStatusUpdate?.(s)
231
+ },
162
232
  onResultChange: (r: TOutput | null) => {
233
+ if (disposed) return
163
234
  result = r
164
235
  },
165
236
  onLoadingChange: (l: boolean) => {
237
+ if (disposed) return
166
238
  isLoading = l
167
239
  },
168
240
  onErrorChange: (e: Error | undefined) => {
241
+ if (disposed) return
169
242
  error = e
170
243
  },
171
244
  onStatusChange: (s: GenerationClientState) => {
245
+ if (disposed) return
172
246
  status = s
173
247
  },
174
248
  onJobIdChange: (id: string | null) => {
249
+ if (disposed) return
175
250
  jobId = id
176
251
  },
177
252
  onVideoStatusChange: (s: VideoStatusInfo | null) => {
253
+ if (disposed) return
178
254
  videoStatus = s
179
255
  },
256
+ onResumeStateChange: (rs: { runId: string } | null) => {
257
+ if (disposed) return
258
+ runId = rs?.runId ?? null
259
+ },
180
260
  }
181
261
 
182
262
  let client: VideoGenerationClient<TOutput>
@@ -197,6 +277,8 @@ export function createGenerateVideo<TTransformed = void>(
197
277
  )
198
278
  }
199
279
 
280
+ // Mount devtools only. Generation runs are never auto-started on setup —
281
+ // persisted state is read-only for display.
200
282
  client.mountDevtools()
201
283
 
202
284
  // Note: Cleanup is handled by calling dispose() directly when needed.
@@ -205,6 +287,12 @@ export function createGenerateVideo<TTransformed = void>(
205
287
  // Users should call video.dispose() in their component's cleanup if needed.
206
288
 
207
289
  const generate = async (input: VideoGenerateInput) => {
290
+ // Svelte has no remount effect to revive a disposed client (the other
291
+ // frameworks revive via mountDevtools() in their mount effects), so an
292
+ // explicit generate() after dispose() is the Svelte revive path: bring
293
+ // the client and the reactive bindings back together.
294
+ disposed = false
295
+ client.mountDevtools()
208
296
  await client.generate(input)
209
297
  }
210
298
 
@@ -217,6 +305,7 @@ export function createGenerateVideo<TTransformed = void>(
217
305
  }
218
306
 
219
307
  const dispose = () => {
308
+ disposed = true
220
309
  client.dispose()
221
310
  }
222
311
 
@@ -248,5 +337,8 @@ export function createGenerateVideo<TTransformed = void>(
248
337
  reset,
249
338
  dispose,
250
339
  updateBody,
340
+ get runId() {
341
+ return runId
342
+ },
251
343
  }
252
344
  }
@@ -7,6 +7,8 @@ import type {
7
7
  GenerationClientOptions,
8
8
  GenerationClientState,
9
9
  GenerationFetcher,
10
+ GenerationPersistenceOptions,
11
+ GenerationRestoredResult,
10
12
  InferGenerationOutputFromReturn,
11
13
  } from '@tanstack/ai-client'
12
14
 
@@ -24,12 +26,51 @@ export interface CreateGenerationOptions<TInput, TResult, TOutput = TResult> {
24
26
  connection?: ConnectConnectionAdapter
25
27
  /** Direct async function for one-shot generation (no streaming protocol needed) */
26
28
  fetcher?: GenerationFetcher<TInput, TResult>
27
- /** Unique identifier for this generation instance */
29
+ /**
30
+ * @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
31
+ */
28
32
  id?: string
29
33
  /** Additional body parameters to send with connect-based adapter requests */
30
34
  body?: Record<string, any>
31
35
  /** Display options for TanStack AI Devtools. */
32
36
  devtools?: AIDevtoolsDisplayOptions
37
+ /**
38
+ * How this generation persists across reloads.
39
+ * - Omit / `false`: ephemeral, in-memory only.
40
+ * - `true`: server-driven — on mount the client hydrates the last generation
41
+ * for its `threadId` from the server (needs a connection with a
42
+ * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.
43
+ */
44
+ persistence?: boolean
45
+ /**
46
+ * The **scope** this generation belongs to: a stable, app-chosen name for the
47
+ * slot successive runs fill — not a link to a chat conversation.
48
+ *
49
+ * The hook starts empty and produces many runs over its life; each gets its
50
+ * own `runId`, but all belong to one scope. Persistence keys on this, so
51
+ * derive it from your own domain and keep it identical across reloads (e.g.
52
+ * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread
53
+ * id on the wire, which the protocol requires.
54
+ *
55
+ * **Required whenever `persistence` is set** — an app that cannot name the
56
+ * scope has nothing to restore to. Optional for ephemeral generations, where
57
+ * it falls back to `id` purely to satisfy the wire.
58
+ */
59
+ threadId?: string
60
+ /**
61
+ * Server-driven hydration handler for `persistence: true` when the
62
+ * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /
63
+ * `rpcStream()` adapter built without handlers) — typically a one-line
64
+ * server-function call. The connection's own handler takes precedence.
65
+ */
66
+ hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration']
67
+ /**
68
+ * Re-attach handler that replays a run still generating to completion on
69
+ * mount, when the connection doesn't carry one. Without it, a restored
70
+ * `running` snapshot surfaces as an (interrupted) error. The connection's
71
+ * own handler takes precedence.
72
+ */
73
+ joinRun?: ConnectConnectionAdapter['joinRun']
33
74
  /**
34
75
  * Callback when a result is received. Can optionally return a transformed value.
35
76
  *
@@ -44,14 +85,24 @@ export interface CreateGenerationOptions<TInput, TResult, TOutput = TResult> {
44
85
  onProgress?: (progress: number, message?: string) => void
45
86
  /** Callback for each stream chunk (connect-based adapter mode only) */
46
87
  onChunk?: (chunk: StreamChunk) => void
88
+ /**
89
+ * @internal Rebuild a typed result from a restored snapshot, injected by each
90
+ * specialized function (image / speech / audio / transcription / summarize).
91
+ * Forwarded to the client so a server-hydrate restore repaints `result`.
92
+ */
93
+ reconstructResult?: (restored: GenerationRestoredResult) => TResult | null
47
94
  }
48
95
 
49
96
  /**
50
97
  * Return type for the createGeneration function.
51
98
  *
52
99
  * @template TOutput - The output type (after optional transform)
100
+ * @template TInput - The input type accepted by `generate` (defaults to any object)
53
101
  */
54
- export interface CreateGenerationReturn<TOutput> {
102
+ export interface CreateGenerationReturn<
103
+ TOutput,
104
+ TInput extends Record<string, any> = Record<string, any>,
105
+ > {
55
106
  /** The generation result, or null if not yet generated */
56
107
  readonly result: TOutput | null
57
108
  /** Whether a generation is currently in progress */
@@ -61,7 +112,7 @@ export interface CreateGenerationReturn<TOutput> {
61
112
  /** Current state of the generation client */
62
113
  readonly status: GenerationClientState
63
114
  /** Trigger a generation request */
64
- generate: (input: Record<string, any>) => Promise<void>
115
+ generate: (input: TInput) => Promise<void>
65
116
  /** Abort the current generation */
66
117
  stop: () => void
67
118
  /** Clear result, error, and return to idle */
@@ -70,6 +121,13 @@ export interface CreateGenerationReturn<TOutput> {
70
121
  dispose: () => void
71
122
  /** Update additional body parameters */
72
123
  updateBody: (body: Record<string, any>) => void
124
+ /**
125
+ * The id of the generation job currently running, or `null` when nothing is in
126
+ * flight. Each call to `generate` is one job with its own id. Pass it to your
127
+ * own endpoint to cancel or poll the provider job — `stop()` only aborts the
128
+ * local stream, it does not stop work already running on the provider.
129
+ */
130
+ readonly runId: string | null
73
131
  }
74
132
 
75
133
  /**
@@ -114,30 +172,48 @@ export function createGeneration<
114
172
  TResult,
115
173
  TTransformed = void,
116
174
  >(
117
- options: Omit<CreateGenerationOptions<TInput, TResult>, 'onResult'> & {
175
+ options: Omit<
176
+ CreateGenerationOptions<TInput, TResult>,
177
+ 'onResult' | 'persistence' | 'threadId' | 'id'
178
+ > & {
118
179
  onResult?: (result: TResult) => TTransformed
119
- },
180
+ } & GenerationPersistenceOptions,
120
181
  ): CreateGenerationReturn<
121
- InferGenerationOutputFromReturn<TResult, TTransformed>
182
+ InferGenerationOutputFromReturn<TResult, TTransformed>,
183
+ TInput
122
184
  > {
123
185
  type TOutput = InferGenerationOutputFromReturn<TResult, TTransformed>
124
- const clientId =
125
- options.id || `gen-${Date.now()}-${Math.random().toString(36).substring(7)}`
186
+ const fallbackId = `gen-${Date.now()}-${Math.random().toString(36).substring(7)}`
126
187
 
127
188
  // Create reactive state using Svelte 5 runes
128
189
  let result = $state<TOutput | null>(null)
129
190
  let isLoading = $state(false)
130
191
  let error = $state<Error | undefined>(undefined)
131
192
  let status = $state<GenerationClientState>('idle')
193
+ let runId = $state<string | null>(null)
194
+ let disposed = false
132
195
 
133
196
  // `body` uses a conditional spread because `GenerationClientOptions.body`
134
197
  // is declared `body?: Record<string, any>` (absent vs. present) under
135
198
  // `exactOptionalPropertyTypes`. Assigning `undefined` directly would be
136
199
  // rejected — the optional caller `options.body` may be undefined, in which
137
200
  // case we want the key to be absent.
201
+ // Identity: pass `threadId` alone when set (never also pass deprecated `id`).
138
202
  const clientOptions: GenerationClientOptions<TInput, TResult, TOutput> = {
139
- id: clientId,
140
203
  body: options.body,
204
+ ...(options.threadId !== undefined
205
+ ? { threadId: options.threadId }
206
+ : { id: options.id ?? fallbackId }),
207
+ ...(options.persistence !== undefined && {
208
+ persistence: options.persistence,
209
+ }),
210
+ ...(options.hydrateGeneration !== undefined && {
211
+ hydrateGeneration: options.hydrateGeneration,
212
+ }),
213
+ ...(options.joinRun !== undefined && { joinRun: options.joinRun }),
214
+ ...(options.reconstructResult
215
+ ? { reconstructResult: options.reconstructResult }
216
+ : {}),
141
217
  devtoolsBridgeFactory: createGenerationDevtoolsBridge,
142
218
  devtools: {
143
219
  ...options.devtools,
@@ -150,21 +226,35 @@ export function createGeneration<
150
226
  onResult: ((r: TResult) => options.onResult?.(r)) as (
151
227
  result: TResult,
152
228
  ) => TOutput | null | void,
153
- onError: (e: Error) => options.onError?.(e),
154
- onProgress: (p: number, m?: string) => options.onProgress?.(p, m),
155
- onChunk: (c: StreamChunk) => options.onChunk?.(c),
229
+ onError: (e: Error) => {
230
+ if (!disposed) options.onError?.(e)
231
+ },
232
+ onProgress: (p: number, m?: string) => {
233
+ if (!disposed) options.onProgress?.(p, m)
234
+ },
235
+ onChunk: (c: StreamChunk) => {
236
+ if (!disposed) options.onChunk?.(c)
237
+ },
156
238
  onResultChange: (r: TOutput | null) => {
239
+ if (disposed) return
157
240
  result = r
158
241
  },
159
242
  onLoadingChange: (l: boolean) => {
243
+ if (disposed) return
160
244
  isLoading = l
161
245
  },
162
246
  onErrorChange: (e: Error | undefined) => {
247
+ if (disposed) return
163
248
  error = e
164
249
  },
165
250
  onStatusChange: (s: GenerationClientState) => {
251
+ if (disposed) return
166
252
  status = s
167
253
  },
254
+ onResumeStateChange: (rs) => {
255
+ if (disposed) return
256
+ runId = rs?.runId ?? null
257
+ },
168
258
  }
169
259
 
170
260
  let client: GenerationClient<TInput, TResult, TOutput>
@@ -185,6 +275,8 @@ export function createGeneration<
185
275
  )
186
276
  }
187
277
 
278
+ // Mount devtools only. Generation runs are never auto-started on setup —
279
+ // persisted state is read-only for display.
188
280
  client.mountDevtools()
189
281
 
190
282
  // Note: Cleanup is handled by calling dispose() directly when needed.
@@ -193,6 +285,12 @@ export function createGeneration<
193
285
  // Users should call gen.dispose() in their component's cleanup if needed.
194
286
 
195
287
  const generate = async (input: TInput) => {
288
+ // Svelte has no remount effect to revive a disposed client (the other
289
+ // frameworks revive via mountDevtools() in their mount effects), so an
290
+ // explicit generate() after dispose() is the Svelte revive path: bring
291
+ // the client and the reactive bindings back together.
292
+ disposed = false
293
+ client.mountDevtools()
196
294
  await client.generate(input)
197
295
  }
198
296
 
@@ -205,6 +303,7 @@ export function createGeneration<
205
303
  }
206
304
 
207
305
  const dispose = () => {
306
+ disposed = true
208
307
  client.dispose()
209
308
  }
210
309
 
@@ -225,10 +324,13 @@ export function createGeneration<
225
324
  get status() {
226
325
  return status
227
326
  },
228
- generate: generate as (input: Record<string, any>) => Promise<void>,
327
+ generate,
229
328
  stop,
230
329
  reset,
231
330
  dispose,
232
331
  updateBody,
332
+ get runId() {
333
+ return runId
334
+ },
233
335
  }
234
336
  }
@@ -1,10 +1,16 @@
1
1
  import { createGeneration } from './create-generation.svelte'
2
+ import { reconstructSummarizeResult } from '@tanstack/ai-client'
3
+ import type {
4
+ CreateGenerationOptions,
5
+ CreateGenerationReturn,
6
+ } from './create-generation.svelte'
2
7
  import type { StreamChunk, SummarizationResult } from '@tanstack/ai'
3
8
  import type {
4
9
  AIDevtoolsDisplayOptions,
5
10
  ConnectConnectionAdapter,
6
11
  GenerationClientState,
7
12
  GenerationFetcher,
13
+ GenerationPersistenceOptions,
8
14
  InferGenerationOutputFromReturn,
9
15
  SummarizeGenerateInput,
10
16
  } from '@tanstack/ai-client'
@@ -14,12 +20,19 @@ import type {
14
20
  *
15
21
  * @template TOutput - The output type after optional transform (defaults to SummarizationResult)
16
22
  */
17
- export interface CreateSummarizeOptions<TOutput = SummarizationResult> {
23
+ export interface CreateSummarizeOptions<
24
+ TOutput = SummarizationResult,
25
+ > extends Pick<
26
+ CreateGenerationOptions<SummarizeGenerateInput, SummarizationResult, TOutput>,
27
+ 'persistence' | 'threadId' | 'hydrateGeneration' | 'joinRun'
28
+ > {
18
29
  /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
19
30
  connection?: ConnectConnectionAdapter
20
31
  /** Direct async function for summarization */
21
32
  fetcher?: GenerationFetcher<SummarizeGenerateInput, SummarizationResult>
22
- /** Unique identifier for this generation instance */
33
+ /**
34
+ * @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
35
+ */
23
36
  id?: string
24
37
  /** Additional body parameters to send with connect-based adapter requests */
25
38
  body?: Record<string, any>
@@ -46,7 +59,9 @@ export interface CreateSummarizeOptions<TOutput = SummarizationResult> {
46
59
  *
47
60
  * @template TOutput - The output type (after optional transform)
48
61
  */
49
- export interface CreateSummarizeReturn<TOutput = SummarizationResult> {
62
+ export interface CreateSummarizeReturn<
63
+ TOutput = SummarizationResult,
64
+ > extends Omit<CreateGenerationReturn<TOutput>, 'generate'> {
50
65
  /** The summarization result, or null */
51
66
  readonly result: TOutput | null
52
67
  /** Whether summarization is in progress */
@@ -57,12 +72,6 @@ export interface CreateSummarizeReturn<TOutput = SummarizationResult> {
57
72
  readonly status: GenerationClientState
58
73
  /** Trigger summarization */
59
74
  generate: (input: SummarizeGenerateInput) => Promise<void>
60
- /** Abort the current summarization */
61
- stop: () => void
62
- /** Clear result, error, and return to idle */
63
- reset: () => void
64
- /** Update additional body parameters */
65
- updateBody: (body: Record<string, any>) => void
66
75
  }
67
76
 
68
77
  /**
@@ -96,9 +105,12 @@ export interface CreateSummarizeReturn<TOutput = SummarizationResult> {
96
105
  * ```
97
106
  */
98
107
  export function createSummarize<TTransformed = void>(
99
- options: Omit<CreateSummarizeOptions, 'onResult'> & {
108
+ options: Omit<
109
+ CreateSummarizeOptions,
110
+ 'onResult' | 'persistence' | 'threadId' | 'id'
111
+ > & {
100
112
  onResult?: (result: SummarizationResult) => TTransformed
101
- },
113
+ } & GenerationPersistenceOptions,
102
114
  ): CreateSummarizeReturn<
103
115
  InferGenerationOutputFromReturn<SummarizationResult, TTransformed>
104
116
  > {
@@ -115,6 +127,7 @@ export function createSummarize<TTransformed = void>(
115
127
  >({
116
128
  ...options,
117
129
  devtools,
130
+ reconstructResult: reconstructSummarizeResult,
118
131
  })
119
132
 
120
133
  return {
@@ -130,9 +143,13 @@ export function createSummarize<TTransformed = void>(
130
143
  get status() {
131
144
  return gen.status
132
145
  },
133
- generate: gen.generate as (input: SummarizeGenerateInput) => Promise<void>,
146
+ generate: gen.generate,
134
147
  stop: gen.stop,
135
148
  reset: gen.reset,
136
149
  updateBody: gen.updateBody,
150
+ dispose: gen.dispose,
151
+ get runId() {
152
+ return gen.runId
153
+ },
137
154
  }
138
155
  }
@@ -1,10 +1,16 @@
1
1
  import { createGeneration } from './create-generation.svelte'
2
+ import { reconstructTranscriptionResult } from '@tanstack/ai-client'
3
+ import type {
4
+ CreateGenerationOptions,
5
+ CreateGenerationReturn,
6
+ } from './create-generation.svelte'
2
7
  import type { StreamChunk, TranscriptionResult } from '@tanstack/ai'
3
8
  import type {
4
9
  AIDevtoolsDisplayOptions,
5
10
  ConnectConnectionAdapter,
6
11
  GenerationClientState,
7
12
  GenerationFetcher,
13
+ GenerationPersistenceOptions,
8
14
  InferGenerationOutputFromReturn,
9
15
  TranscriptionGenerateInput,
10
16
  } from '@tanstack/ai-client'
@@ -14,12 +20,23 @@ import type {
14
20
  *
15
21
  * @template TOutput - The output type after optional transform (defaults to TranscriptionResult)
16
22
  */
17
- export interface CreateTranscriptionOptions<TOutput = TranscriptionResult> {
23
+ export interface CreateTranscriptionOptions<
24
+ TOutput = TranscriptionResult,
25
+ > extends Pick<
26
+ CreateGenerationOptions<
27
+ TranscriptionGenerateInput,
28
+ TranscriptionResult,
29
+ TOutput
30
+ >,
31
+ 'persistence' | 'threadId' | 'hydrateGeneration' | 'joinRun'
32
+ > {
18
33
  /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
19
34
  connection?: ConnectConnectionAdapter
20
35
  /** Direct async function for transcription */
21
36
  fetcher?: GenerationFetcher<TranscriptionGenerateInput, TranscriptionResult>
22
- /** Unique identifier for this generation instance */
37
+ /**
38
+ * @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
39
+ */
23
40
  id?: string
24
41
  /** Additional body parameters to send with connect-based adapter requests */
25
42
  body?: Record<string, any>
@@ -46,7 +63,9 @@ export interface CreateTranscriptionOptions<TOutput = TranscriptionResult> {
46
63
  *
47
64
  * @template TOutput - The output type (after optional transform)
48
65
  */
49
- export interface CreateTranscriptionReturn<TOutput = TranscriptionResult> {
66
+ export interface CreateTranscriptionReturn<
67
+ TOutput = TranscriptionResult,
68
+ > extends Omit<CreateGenerationReturn<TOutput>, 'generate'> {
50
69
  /** The transcription result, or null */
51
70
  readonly result: TOutput | null
52
71
  /** Whether transcription is in progress */
@@ -57,12 +76,6 @@ export interface CreateTranscriptionReturn<TOutput = TranscriptionResult> {
57
76
  readonly status: GenerationClientState
58
77
  /** Trigger transcription */
59
78
  generate: (input: TranscriptionGenerateInput) => Promise<void>
60
- /** Abort the current transcription */
61
- stop: () => void
62
- /** Clear result, error, and return to idle */
63
- reset: () => void
64
- /** Update additional body parameters */
65
- updateBody: (body: Record<string, any>) => void
66
79
  }
67
80
 
68
81
  /**
@@ -101,9 +114,12 @@ export interface CreateTranscriptionReturn<TOutput = TranscriptionResult> {
101
114
  * ```
102
115
  */
103
116
  export function createTranscription<TTransformed = void>(
104
- options: Omit<CreateTranscriptionOptions, 'onResult'> & {
117
+ options: Omit<
118
+ CreateTranscriptionOptions,
119
+ 'onResult' | 'persistence' | 'threadId' | 'id'
120
+ > & {
105
121
  onResult?: (result: TranscriptionResult) => TTransformed
106
- },
122
+ } & GenerationPersistenceOptions,
107
123
  ): CreateTranscriptionReturn<
108
124
  InferGenerationOutputFromReturn<TranscriptionResult, TTransformed>
109
125
  > {
@@ -120,6 +136,7 @@ export function createTranscription<TTransformed = void>(
120
136
  >({
121
137
  ...options,
122
138
  devtools,
139
+ reconstructResult: reconstructTranscriptionResult,
123
140
  })
124
141
 
125
142
  return {
@@ -135,11 +152,13 @@ export function createTranscription<TTransformed = void>(
135
152
  get status() {
136
153
  return gen.status
137
154
  },
138
- generate: gen.generate as (
139
- input: TranscriptionGenerateInput,
140
- ) => Promise<void>,
155
+ generate: gen.generate,
141
156
  stop: gen.stop,
142
157
  reset: gen.reset,
143
158
  updateBody: gen.updateBody,
159
+ dispose: gen.dispose,
160
+ get runId() {
161
+ return gen.runId
162
+ },
144
163
  }
145
164
  }
package/src/index.ts CHANGED
@@ -65,6 +65,16 @@ export type {
65
65
  // Re-export from ai-client for convenience
66
66
  export {
67
67
  fetchServerSentEvents,
68
+ localStoragePersistence,
69
+ sessionStoragePersistence,
70
+ indexedDBPersistence,
71
+ StorageUnavailableError,
72
+ type ChatClientPersistence,
73
+ type ChatPersistedState,
74
+ type ChatPersistenceOption,
75
+ type ChatStorageAdapter,
76
+ type WebStoragePersistenceOptions,
77
+ type IndexedDBPersistenceOptions,
68
78
  fetchHttpStream,
69
79
  xhrServerSentEvents,
70
80
  xhrHttpStream,