@tanstack/ai-svelte 0.15.0 → 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.
package/README.md CHANGED
@@ -1,3 +1,20 @@
1
+ <div align="center">
2
+ <picture>
3
+ <source
4
+ media="(prefers-color-scheme: dark)"
5
+ srcset="https://tanstack.com/api/readme/ai.png?framework=svelte&theme=dark"
6
+ />
7
+ <source
8
+ media="(prefers-color-scheme: light)"
9
+ srcset="https://tanstack.com/api/readme/ai.png?framework=svelte"
10
+ />
11
+ <img
12
+ src="https://tanstack.com/api/readme/ai.png?framework=svelte"
13
+ alt="TanStack Svelte AI"
14
+ width="900"
15
+ />
16
+ </picture>
17
+ </div>
1
18
  # @tanstack/ai-svelte
2
19
 
3
20
  Svelte bindings for TanStack AI.
@@ -1,5 +1,8 @@
1
1
  import { ChatClient } from '@tanstack/ai-client';
2
2
  import { createChatDevtoolsBridge } from '@tanstack/ai-client/devtools';
3
+ import { onMount } from 'svelte';
4
+ const EMPTY_INTERRUPTS = Object.freeze([]);
5
+ const EMPTY_INTERRUPT_ERRORS = Object.freeze([]);
3
6
  /**
4
7
  * Creates a reactive chat instance for Svelte 5.
5
8
  *
@@ -31,9 +34,6 @@ import { createChatDevtoolsBridge } from '@tanstack/ai-client/devtools';
31
34
  * ```
32
35
  */
33
36
  export function createChat(options) {
34
- // Generate a unique ID for this chat instance
35
- const clientId = options.id ||
36
- `chat-${Date.now()}-${Math.random().toString(36).substring(7)}`;
37
37
  // Create reactive state using Svelte 5 runes
38
38
  let messages = $state(options.initialMessages || []);
39
39
  let isLoading = $state(false);
@@ -43,6 +43,13 @@ export function createChat(options) {
43
43
  let connectionStatus = $state('disconnected');
44
44
  let sessionGenerating = $state(false);
45
45
  let queue = $state([]);
46
+ let runId = $state(null);
47
+ let interruptState = $state.raw({
48
+ interrupts: EMPTY_INTERRUPTS,
49
+ pendingInterrupts: EMPTY_INTERRUPTS,
50
+ interruptErrors: EMPTY_INTERRUPT_ERRORS,
51
+ resuming: false,
52
+ });
46
53
  // Create ChatClient instance.
47
54
  // Note: Svelte's createChat runs once per instance and `options` is captured
48
55
  // by reference. Callbacks are therefore frozen to whatever the caller passed
@@ -56,16 +63,21 @@ export function createChat(options) {
56
63
  const transport = options.connection
57
64
  ? { connection: options.connection }
58
65
  : { fetcher: options.fetcher };
66
+ // The hook's identity is its `threadId`, which ChatClient also uses as the
67
+ // persistence key — no separate `id`. When no `threadId` is given the client
68
+ // generates one, so an ephemeral chat still works but is not restored on reload.
59
69
  const client = new ChatClient({
60
70
  devtoolsBridgeFactory: createChatDevtoolsBridge,
61
71
  ...transport,
62
- id: clientId,
63
72
  ...(options.initialMessages !== undefined && {
64
73
  initialMessages: options.initialMessages,
65
74
  }),
66
75
  ...(options.persistence !== undefined && {
67
76
  persistence: options.persistence,
68
77
  }),
78
+ ...(options.initialResumeSnapshot !== undefined && {
79
+ initialResumeSnapshot: options.initialResumeSnapshot,
80
+ }),
69
81
  ...(options.body !== undefined && { body: options.body }),
70
82
  ...(options.threadId !== undefined && { threadId: options.threadId }),
71
83
  ...(options.forwardedProps !== undefined && {
@@ -100,6 +112,7 @@ export function createChat(options) {
100
112
  },
101
113
  onLoadingChange: (newIsLoading) => {
102
114
  isLoading = newIsLoading;
115
+ syncResumeState();
103
116
  },
104
117
  onStatusChange: (newStatus) => {
105
118
  status = newStatus;
@@ -120,26 +133,78 @@ export function createChat(options) {
120
133
  onQueueChange: (nextQueue) => {
121
134
  queue = nextQueue;
122
135
  },
136
+ onRunIdChange: (nextRunId) => {
137
+ runId = nextRunId;
138
+ },
139
+ onInterruptStateChange: (nextInterruptState) => {
140
+ interruptState = nextInterruptState;
141
+ options.onInterruptStateChange?.(nextInterruptState);
142
+ },
123
143
  });
144
+ function syncResumeState() {
145
+ runId = client.getCurrentRunId();
146
+ interruptState = client.getInterruptState();
147
+ }
124
148
  messages = client.getMessages();
149
+ interruptState = client.getInterruptState();
125
150
  if (options.live) {
126
151
  client.subscribe();
127
152
  }
128
153
  client.mountDevtools();
129
- // Note: Cleanup is handled by calling stop() directly when needed.
130
- // Unlike React/Vue/Solid, Svelte 5 runes like $effect can only be used
131
- // during component initialization, so we don't add automatic cleanup here.
132
- // Users should call chat.stop() in their component's cleanup if needed.
154
+ if (typeof window !== 'undefined') {
155
+ try {
156
+ onMount(() => {
157
+ // Delivery-durability resume is transparent: the resumable SSE
158
+ // connection adapter reattaches via the browser's native
159
+ // Last-Event-ID on reconnect. We only seed interrupt (state) resume.
160
+ syncResumeState();
161
+ client.attach();
162
+ // ONLY THE VIEW ON SCREEN HOLDS A STREAM. `onMount`'s returned function
163
+ // runs when the component is destroyed, which is the one automatic
164
+ // teardown Svelte gives us here — and it is enough, because a connection
165
+ // is all that must go. A page can own many chats and a browser allows
166
+ // only ~6 connections per origin, so one long-lived stream per chat
167
+ // starves every other request once a few views have been open.
168
+ //
169
+ // `detach` keeps the transcript and the resume pointer, so re-entering
170
+ // the view picks the run back up from the durable log.
171
+ return () => {
172
+ client.detach();
173
+ };
174
+ });
175
+ }
176
+ catch {
177
+ // Svelte lifecycle hooks are only valid during component initialization.
178
+ }
179
+ }
180
+ // Note: `dispose()` remains manual — it releases devtools and marks the client
181
+ // dead, which only the owner can decide. The CONNECTION is released
182
+ // automatically by the `onMount` teardown above.
133
183
  // Define methods
134
184
  const sendMessage = async (content, sendOptions) => {
135
- await client.sendMessage(content, undefined, sendOptions);
185
+ try {
186
+ await client.sendMessage(content, undefined, sendOptions);
187
+ }
188
+ finally {
189
+ syncResumeState();
190
+ }
136
191
  };
137
192
  const cancelQueued = (id) => client.cancelQueued(id);
138
193
  const append = async (message) => {
139
- await client.append(message);
194
+ try {
195
+ await client.append(message);
196
+ }
197
+ finally {
198
+ syncResumeState();
199
+ }
140
200
  };
141
201
  const reload = async () => {
142
- await client.reload();
202
+ try {
203
+ await client.reload();
204
+ }
205
+ finally {
206
+ syncResumeState();
207
+ }
143
208
  };
144
209
  const stop = () => {
145
210
  client.stop();
@@ -149,6 +214,7 @@ export function createChat(options) {
149
214
  };
150
215
  const clear = () => {
151
216
  client.clear();
217
+ syncResumeState();
152
218
  };
153
219
  const setMessages = (newMessages) => {
154
220
  client.setMessagesManually(newMessages);
@@ -158,7 +224,28 @@ export function createChat(options) {
158
224
  };
159
225
  const addToolApprovalResponse = async (response) => {
160
226
  await client.addToolApprovalResponse(response);
227
+ syncResumeState();
228
+ };
229
+ const resumeInterrupts = async (resumeItems, state) => {
230
+ const result = await client.resumeInterrupts(resumeItems, state);
231
+ syncResumeState();
232
+ return result;
233
+ };
234
+ const resolveInterrupts = (resolution) => {
235
+ if (typeof resolution === 'boolean') {
236
+ client.resolveInterrupts(resolution);
237
+ }
238
+ else {
239
+ client.resolveInterrupts(resolution);
240
+ }
241
+ };
242
+ const cancelInterrupts = () => {
243
+ client.cancelInterrupts();
161
244
  };
245
+ const retryInterrupts = () => {
246
+ client.retryInterrupts();
247
+ };
248
+ const resumeInterruptsUnsafe = (resumeItems, state) => client.resumeInterruptsUnsafe(resumeItems, state);
162
249
  /**
163
250
  * @deprecated Use `updateForwardedProps` instead.
164
251
  * Both populate the same wire payload.
@@ -209,7 +296,7 @@ export function createChat(options) {
209
296
  : null);
210
297
  // Return the chat interface with reactive getters
211
298
  // Using getters allows Svelte to track reactivity without needing $ prefix
212
- // eslint-disable-next-line no-restricted-syntax -- rune return shape diverges from generic CreateChatReturn<TTools, TSchema, TContext> due to TSchema conditional partial/final fields; TS can't structurally narrow.
299
+ // oxlint-disable-next-line eslint-js/no-restricted-syntax -- rune return shape diverges from generic CreateChatReturn<TTools, TSchema, TContext> due to TSchema conditional partial/final fields; TS can't structurally narrow.
213
300
  return {
214
301
  get messages() {
215
302
  return messages;
@@ -235,6 +322,21 @@ export function createChat(options) {
235
322
  get queue() {
236
323
  return queue;
237
324
  },
325
+ get runId() {
326
+ return runId;
327
+ },
328
+ get interrupts() {
329
+ return interruptState.interrupts;
330
+ },
331
+ get pendingInterrupts() {
332
+ return interruptState.interrupts;
333
+ },
334
+ get interruptErrors() {
335
+ return interruptState.interruptErrors;
336
+ },
337
+ get resuming() {
338
+ return interruptState.resuming;
339
+ },
238
340
  get partial() {
239
341
  return partial;
240
342
  },
@@ -251,6 +353,11 @@ export function createChat(options) {
251
353
  clear,
252
354
  addToolResult,
253
355
  addToolApprovalResponse,
356
+ resolveInterrupts,
357
+ cancelInterrupts,
358
+ retryInterrupts,
359
+ resumeInterruptsUnsafe,
360
+ resumeInterrupts,
254
361
  updateBody,
255
362
  updateForwardedProps,
256
363
  updateContext,
@@ -1,16 +1,19 @@
1
+ import type { CreateGenerationOptions, CreateGenerationReturn } from './create-generation.svelte';
1
2
  import type { AudioGenerationResult, StreamChunk } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, AudioGenerateInput, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
3
+ import type { AIDevtoolsDisplayOptions, AudioGenerateInput, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
3
4
  /**
4
5
  * Options for the createGenerateAudio function.
5
6
  *
6
7
  * @template TOutput - The output type after optional transform (defaults to AudioGenerationResult)
7
8
  */
8
- export interface CreateGenerateAudioOptions<TOutput = AudioGenerationResult> {
9
+ export interface CreateGenerateAudioOptions<TOutput = AudioGenerationResult> extends Pick<CreateGenerationOptions<AudioGenerateInput, AudioGenerationResult, TOutput>, 'persistence' | 'threadId' | 'hydrateGeneration' | 'joinRun'> {
9
10
  /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
10
11
  connection?: ConnectConnectionAdapter;
11
12
  /** Direct async function for audio generation */
12
13
  fetcher?: GenerationFetcher<AudioGenerateInput, AudioGenerationResult>;
13
- /** Unique identifier for this generation instance */
14
+ /**
15
+ * @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
16
+ */
14
17
  id?: string;
15
18
  /** Additional body parameters to send with connect-based adapter requests */
16
19
  body?: Record<string, any>;
@@ -36,7 +39,7 @@ export interface CreateGenerateAudioOptions<TOutput = AudioGenerationResult> {
36
39
  *
37
40
  * @template TOutput - The output type (after optional transform)
38
41
  */
39
- export interface CreateGenerateAudioReturn<TOutput = AudioGenerationResult> {
42
+ export interface CreateGenerateAudioReturn<TOutput = AudioGenerationResult> extends Omit<CreateGenerationReturn<TOutput>, 'generate'> {
40
43
  /** The generation result containing audio, or null */
41
44
  readonly result: TOutput | null;
42
45
  /** Whether generation is in progress */
@@ -47,12 +50,6 @@ export interface CreateGenerateAudioReturn<TOutput = AudioGenerationResult> {
47
50
  readonly status: GenerationClientState;
48
51
  /** Trigger audio generation */
49
52
  generate: (input: AudioGenerateInput) => Promise<void>;
50
- /** Abort the current generation */
51
- stop: () => void;
52
- /** Clear result, error, and return to idle */
53
- reset: () => void;
54
- /** Update additional body parameters */
55
- updateBody: (body: Record<string, any>) => void;
56
53
  }
57
54
  /**
58
55
  * Creates a reactive audio generation instance for Svelte 5.
@@ -80,6 +77,6 @@ export interface CreateGenerateAudioReturn<TOutput = AudioGenerationResult> {
80
77
  * </div>
81
78
  * ```
82
79
  */
83
- export declare function createGenerateAudio<TTransformed = void>(options: Omit<CreateGenerateAudioOptions, 'onResult'> & {
80
+ export declare function createGenerateAudio<TTransformed = void>(options: Omit<CreateGenerateAudioOptions, 'onResult' | 'persistence' | 'threadId' | 'id'> & {
84
81
  onResult?: (result: AudioGenerationResult) => TTransformed;
85
- }): CreateGenerateAudioReturn<InferGenerationOutputFromReturn<AudioGenerationResult, TTransformed>>;
82
+ } & GenerationPersistenceOptions): CreateGenerateAudioReturn<InferGenerationOutputFromReturn<AudioGenerationResult, TTransformed>>;
@@ -1,4 +1,5 @@
1
1
  import { createGeneration } from './create-generation.svelte';
2
+ import { reconstructAudioResult } from '@tanstack/ai-client';
2
3
  /**
3
4
  * Creates a reactive audio generation instance for Svelte 5.
4
5
  *
@@ -35,6 +36,7 @@ export function createGenerateAudio(options) {
35
36
  const gen = createGeneration({
36
37
  ...options,
37
38
  devtools,
39
+ reconstructResult: reconstructAudioResult,
38
40
  });
39
41
  return {
40
42
  get result() {
@@ -53,5 +55,9 @@ export function createGenerateAudio(options) {
53
55
  stop: gen.stop,
54
56
  reset: gen.reset,
55
57
  updateBody: gen.updateBody,
58
+ dispose: gen.dispose,
59
+ get runId() {
60
+ return gen.runId;
61
+ },
56
62
  };
57
63
  }
@@ -1,16 +1,19 @@
1
+ import type { CreateGenerationOptions, CreateGenerationReturn } from './create-generation.svelte';
1
2
  import type { ImageGenerationResult, StreamChunk } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, ImageGenerateInput, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
3
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, ImageGenerateInput, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
3
4
  /**
4
5
  * Options for the createGenerateImage function.
5
6
  *
6
7
  * @template TOutput - The output type after optional transform (defaults to ImageGenerationResult)
7
8
  */
8
- export interface CreateGenerateImageOptions<TOutput = ImageGenerationResult> {
9
+ export interface CreateGenerateImageOptions<TOutput = ImageGenerationResult> extends Pick<CreateGenerationOptions<ImageGenerateInput, ImageGenerationResult, TOutput>, 'persistence' | 'threadId' | 'hydrateGeneration' | 'joinRun'> {
9
10
  /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
10
11
  connection?: ConnectConnectionAdapter;
11
12
  /** Direct async function for image generation */
12
13
  fetcher?: GenerationFetcher<ImageGenerateInput, ImageGenerationResult>;
13
- /** Unique identifier for this generation instance */
14
+ /**
15
+ * @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
16
+ */
14
17
  id?: string;
15
18
  /** Additional body parameters to send with connect-based adapter requests */
16
19
  body?: Record<string, any>;
@@ -36,7 +39,7 @@ export interface CreateGenerateImageOptions<TOutput = ImageGenerationResult> {
36
39
  *
37
40
  * @template TOutput - The output type (after optional transform)
38
41
  */
39
- export interface CreateGenerateImageReturn<TOutput = ImageGenerationResult> {
42
+ export interface CreateGenerateImageReturn<TOutput = ImageGenerationResult> extends Omit<CreateGenerationReturn<TOutput>, 'generate'> {
40
43
  /** The generation result containing images, or null */
41
44
  readonly result: TOutput | null;
42
45
  /** Whether generation is in progress */
@@ -47,12 +50,6 @@ export interface CreateGenerateImageReturn<TOutput = ImageGenerationResult> {
47
50
  readonly status: GenerationClientState;
48
51
  /** Trigger image generation */
49
52
  generate: (input: ImageGenerateInput) => Promise<void>;
50
- /** Abort the current generation */
51
- stop: () => void;
52
- /** Clear result, error, and return to idle */
53
- reset: () => void;
54
- /** Update additional body parameters */
55
- updateBody: (body: Record<string, any>) => void;
56
53
  }
57
54
  /**
58
55
  * Creates a reactive image generation instance for Svelte 5.
@@ -89,6 +86,6 @@ export interface CreateGenerateImageReturn<TOutput = ImageGenerationResult> {
89
86
  * </div>
90
87
  * ```
91
88
  */
92
- export declare function createGenerateImage<TTransformed = void>(options: Omit<CreateGenerateImageOptions, 'onResult'> & {
89
+ export declare function createGenerateImage<TTransformed = void>(options: Omit<CreateGenerateImageOptions, 'onResult' | 'persistence' | 'threadId' | 'id'> & {
93
90
  onResult?: (result: ImageGenerationResult) => TTransformed;
94
- }): CreateGenerateImageReturn<InferGenerationOutputFromReturn<ImageGenerationResult, TTransformed>>;
91
+ } & GenerationPersistenceOptions): CreateGenerateImageReturn<InferGenerationOutputFromReturn<ImageGenerationResult, TTransformed>>;
@@ -1,4 +1,5 @@
1
1
  import { createGeneration } from './create-generation.svelte';
2
+ import { reconstructImageResult } from '@tanstack/ai-client';
2
3
  /**
3
4
  * Creates a reactive image generation instance for Svelte 5.
4
5
  *
@@ -44,6 +45,7 @@ export function createGenerateImage(options) {
44
45
  const gen = createGeneration({
45
46
  ...options,
46
47
  devtools,
48
+ reconstructResult: reconstructImageResult,
47
49
  });
48
50
  return {
49
51
  get result() {
@@ -62,5 +64,9 @@ export function createGenerateImage(options) {
62
64
  stop: gen.stop,
63
65
  reset: gen.reset,
64
66
  updateBody: gen.updateBody,
67
+ dispose: gen.dispose,
68
+ get runId() {
69
+ return gen.runId;
70
+ },
65
71
  };
66
72
  }
@@ -1,16 +1,19 @@
1
+ import type { CreateGenerationOptions, CreateGenerationReturn } from './create-generation.svelte';
1
2
  import type { StreamChunk, TTSResult } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn, SpeechGenerateInput } from '@tanstack/ai-client';
3
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, InferGenerationOutputFromReturn, SpeechGenerateInput } from '@tanstack/ai-client';
3
4
  /**
4
5
  * Options for the createGenerateSpeech function.
5
6
  *
6
7
  * @template TOutput - The output type after optional transform (defaults to TTSResult)
7
8
  */
8
- export interface CreateGenerateSpeechOptions<TOutput = TTSResult> {
9
+ export interface CreateGenerateSpeechOptions<TOutput = TTSResult> extends Pick<CreateGenerationOptions<SpeechGenerateInput, TTSResult, TOutput>, 'persistence' | 'threadId' | 'hydrateGeneration' | 'joinRun'> {
9
10
  /** Connect-based adapter for streaming transport (SSE, HTTP stream, custom) */
10
11
  connection?: ConnectConnectionAdapter;
11
12
  /** Direct async function for speech generation */
12
13
  fetcher?: GenerationFetcher<SpeechGenerateInput, TTSResult>;
13
- /** Unique identifier for this generation instance */
14
+ /**
15
+ * @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
16
+ */
14
17
  id?: string;
15
18
  /** Additional body parameters to send with connect-based adapter requests */
16
19
  body?: Record<string, any>;
@@ -36,7 +39,7 @@ export interface CreateGenerateSpeechOptions<TOutput = TTSResult> {
36
39
  *
37
40
  * @template TOutput - The output type (after optional transform)
38
41
  */
39
- export interface CreateGenerateSpeechReturn<TOutput = TTSResult> {
42
+ export interface CreateGenerateSpeechReturn<TOutput = TTSResult> extends Omit<CreateGenerationReturn<TOutput>, 'generate'> {
40
43
  /** The TTS result containing audio data, or null */
41
44
  readonly result: TOutput | null;
42
45
  /** Whether generation is in progress */
@@ -47,12 +50,6 @@ export interface CreateGenerateSpeechReturn<TOutput = TTSResult> {
47
50
  readonly status: GenerationClientState;
48
51
  /** Trigger speech generation */
49
52
  generate: (input: SpeechGenerateInput) => Promise<void>;
50
- /** Abort the current generation */
51
- stop: () => void;
52
- /** Clear result, error, and return to idle */
53
- reset: () => void;
54
- /** Update additional body parameters */
55
- updateBody: (body: Record<string, any>) => void;
56
53
  }
57
54
  /**
58
55
  * Creates a reactive speech generation (text-to-speech) instance for Svelte 5.
@@ -80,6 +77,6 @@ export interface CreateGenerateSpeechReturn<TOutput = TTSResult> {
80
77
  * </div>
81
78
  * ```
82
79
  */
83
- export declare function createGenerateSpeech<TTransformed = void>(options: Omit<CreateGenerateSpeechOptions, 'onResult'> & {
80
+ export declare function createGenerateSpeech<TTransformed = void>(options: Omit<CreateGenerateSpeechOptions, 'onResult' | 'persistence' | 'threadId' | 'id'> & {
84
81
  onResult?: (result: TTSResult) => TTransformed;
85
- }): CreateGenerateSpeechReturn<InferGenerationOutputFromReturn<TTSResult, TTransformed>>;
82
+ } & GenerationPersistenceOptions): CreateGenerateSpeechReturn<InferGenerationOutputFromReturn<TTSResult, TTransformed>>;
@@ -1,4 +1,5 @@
1
1
  import { createGeneration } from './create-generation.svelte';
2
+ import { reconstructSpeechResult } from '@tanstack/ai-client';
2
3
  /**
3
4
  * Creates a reactive speech generation (text-to-speech) instance for Svelte 5.
4
5
  *
@@ -35,6 +36,7 @@ export function createGenerateSpeech(options) {
35
36
  const gen = createGeneration({
36
37
  ...options,
37
38
  devtools,
39
+ reconstructResult: reconstructSpeechResult,
38
40
  });
39
41
  return {
40
42
  get result() {
@@ -53,5 +55,9 @@ export function createGenerateSpeech(options) {
53
55
  stop: gen.stop,
54
56
  reset: gen.reset,
55
57
  updateBody: gen.updateBody,
58
+ dispose: gen.dispose,
59
+ get runId() {
60
+ return gen.runId;
61
+ },
56
62
  };
57
63
  }
@@ -1,5 +1,5 @@
1
1
  import type { StreamChunk } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn, VideoGenerateInput, VideoGenerateResult, VideoStatusInfo } from '@tanstack/ai-client';
2
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, InferGenerationOutputFromReturn, VideoGenerateInput, VideoGenerateResult, VideoStatusInfo } from '@tanstack/ai-client';
3
3
  /**
4
4
  * Options for the createGenerateVideo function.
5
5
  *
@@ -10,12 +10,51 @@ export interface CreateGenerateVideoOptions<TOutput = VideoGenerateResult> {
10
10
  connection?: ConnectConnectionAdapter;
11
11
  /** Direct async function that returns a completed video result */
12
12
  fetcher?: GenerationFetcher<VideoGenerateInput, VideoGenerateResult>;
13
- /** Unique identifier for this generation instance */
13
+ /**
14
+ * @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
15
+ */
14
16
  id?: string;
15
17
  /** Additional body parameters to send with connect-based adapter requests */
16
18
  body?: Record<string, any>;
17
19
  /** Display options for TanStack AI Devtools. */
18
20
  devtools?: AIDevtoolsDisplayOptions;
21
+ /**
22
+ * How this generation persists across reloads.
23
+ * - Omit / `false`: ephemeral, in-memory only.
24
+ * - `true`: server-driven — on mount the client hydrates the last generation
25
+ * for its `threadId` from the server (needs a connection with a
26
+ * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.
27
+ */
28
+ persistence?: boolean;
29
+ /**
30
+ * The **scope** this generation belongs to: a stable, app-chosen name for the
31
+ * slot successive runs fill — not a link to a chat conversation.
32
+ *
33
+ * The hook starts empty and produces many runs over its life; each gets its
34
+ * own `runId`, but all belong to one scope. Persistence keys on this, so
35
+ * derive it from your own domain and keep it identical across reloads (e.g.
36
+ * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread
37
+ * id on the wire, which the protocol requires.
38
+ *
39
+ * **Required whenever `persistence` is set** — an app that cannot name the
40
+ * scope has nothing to restore to. Optional for ephemeral generations, where
41
+ * it falls back to `id` purely to satisfy the wire.
42
+ */
43
+ threadId?: string;
44
+ /**
45
+ * Server-driven hydration handler for `persistence: true` when the
46
+ * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /
47
+ * `rpcStream()` adapter built without handlers) — typically a one-line
48
+ * server-function call. The connection's own handler takes precedence.
49
+ */
50
+ hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration'];
51
+ /**
52
+ * Re-attach handler that replays a run still generating to completion on
53
+ * mount, when the connection doesn't carry one. Without it, a restored
54
+ * `running` snapshot surfaces as an (interrupted) error. The connection's
55
+ * own handler takes precedence.
56
+ */
57
+ joinRun?: ConnectConnectionAdapter['joinRun'];
19
58
  /**
20
59
  * Callback when video generation completes. Can optionally return a transformed value.
21
60
  *
@@ -63,6 +102,13 @@ export interface CreateGenerateVideoReturn<TOutput = VideoGenerateResult> {
63
102
  dispose: () => void;
64
103
  /** Update additional body parameters */
65
104
  updateBody: (body: Record<string, any>) => void;
105
+ /**
106
+ * The id of the generation job currently running, or `null` when nothing is in
107
+ * flight. Each call to `generate` is one job with its own id. Pass it to your
108
+ * own endpoint to cancel or poll the provider job — `stop()` only aborts the
109
+ * local stream, it does not stop work already running on the provider.
110
+ */
111
+ readonly runId: string | null;
66
112
  }
67
113
  /**
68
114
  * Creates a reactive video generation instance for Svelte 5.
@@ -94,6 +140,6 @@ export interface CreateGenerateVideoReturn<TOutput = VideoGenerateResult> {
94
140
  * </div>
95
141
  * ```
96
142
  */
97
- export declare function createGenerateVideo<TTransformed = void>(options: Omit<CreateGenerateVideoOptions, 'onResult'> & {
143
+ export declare function createGenerateVideo<TTransformed = void>(options: Omit<CreateGenerateVideoOptions, 'onResult' | 'persistence' | 'threadId' | 'id'> & {
98
144
  onResult?: (result: VideoGenerateResult) => TTransformed;
99
- }): CreateGenerateVideoReturn<InferGenerationOutputFromReturn<VideoGenerateResult, TTransformed>>;
145
+ } & GenerationPersistenceOptions): CreateGenerateVideoReturn<InferGenerationOutputFromReturn<VideoGenerateResult, TTransformed>>;