@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.
@@ -34,8 +34,7 @@ import { createVideoDevtoolsBridge } from '@tanstack/ai-client/devtools';
34
34
  // parameter is typed as `VideoGenerateResult` and `result` narrows to the
35
35
  // transform's return. See issue #848.
36
36
  export function createGenerateVideo(options) {
37
- const clientId = options.id ||
38
- `video-${Date.now()}-${Math.random().toString(36).substring(7)}`;
37
+ const fallbackId = `video-${Date.now()}-${Math.random().toString(36).substring(7)}`;
39
38
  // Create reactive state using Svelte 5 runes
40
39
  let result = $state(null);
41
40
  let jobId = $state(null);
@@ -43,13 +42,25 @@ export function createGenerateVideo(options) {
43
42
  let isLoading = $state(false);
44
43
  let error = $state(undefined);
45
44
  let status = $state('idle');
45
+ let runId = $state(null);
46
+ let disposed = false;
46
47
  // `body` uses a conditional spread because `VideoGenerationClientOptions.body`
47
48
  // is declared `body?: Record<string, any>` (absent vs. present) under
48
49
  // `exactOptionalPropertyTypes`. The optional caller `options.body` may be
49
50
  // undefined, in which case we want the key to be absent on the target.
51
+ // Identity: pass `threadId` alone when set (never also pass deprecated `id`).
50
52
  const baseOptions = {
51
- id: clientId,
52
53
  body: options.body,
54
+ ...(options.threadId !== undefined
55
+ ? { threadId: options.threadId }
56
+ : { id: options.id ?? fallbackId }),
57
+ ...(options.persistence !== undefined && {
58
+ persistence: options.persistence,
59
+ }),
60
+ ...(options.hydrateGeneration !== undefined && {
61
+ hydrateGeneration: options.hydrateGeneration,
62
+ }),
63
+ ...(options.joinRun !== undefined && { joinRun: options.joinRun }),
53
64
  devtoolsBridgeFactory: createVideoDevtoolsBridge,
54
65
  devtools: {
55
66
  ...options.devtools,
@@ -61,29 +72,61 @@ export function createGenerateVideo(options) {
61
72
  // (`TOutput`, with null/void/undefined stripped) are identical at runtime;
62
73
  // the cast bridges the relationship that the conditional type hides.
63
74
  onResult: ((r) => options.onResult?.(r)),
64
- onError: (e) => options.onError?.(e),
65
- onProgress: (p, m) => options.onProgress?.(p, m),
66
- onChunk: (c) => options.onChunk?.(c),
67
- onJobCreated: (id) => options.onJobCreated?.(id),
68
- onStatusUpdate: (s) => options.onStatusUpdate?.(s),
75
+ onError: (e) => {
76
+ if (!disposed)
77
+ options.onError?.(e);
78
+ },
79
+ onProgress: (p, m) => {
80
+ if (!disposed)
81
+ options.onProgress?.(p, m);
82
+ },
83
+ onChunk: (c) => {
84
+ if (!disposed)
85
+ options.onChunk?.(c);
86
+ },
87
+ onJobCreated: (id) => {
88
+ if (!disposed)
89
+ options.onJobCreated?.(id);
90
+ },
91
+ onStatusUpdate: (s) => {
92
+ if (!disposed)
93
+ options.onStatusUpdate?.(s);
94
+ },
69
95
  onResultChange: (r) => {
96
+ if (disposed)
97
+ return;
70
98
  result = r;
71
99
  },
72
100
  onLoadingChange: (l) => {
101
+ if (disposed)
102
+ return;
73
103
  isLoading = l;
74
104
  },
75
105
  onErrorChange: (e) => {
106
+ if (disposed)
107
+ return;
76
108
  error = e;
77
109
  },
78
110
  onStatusChange: (s) => {
111
+ if (disposed)
112
+ return;
79
113
  status = s;
80
114
  },
81
115
  onJobIdChange: (id) => {
116
+ if (disposed)
117
+ return;
82
118
  jobId = id;
83
119
  },
84
120
  onVideoStatusChange: (s) => {
121
+ if (disposed)
122
+ return;
85
123
  videoStatus = s;
86
124
  },
125
+ onResumeStateChange: (rs) => {
126
+ if (disposed)
127
+ return;
128
+ runId = rs?.runId ?? null;
129
+ },
87
130
  };
88
131
  let client;
89
132
  if (options.connection) {
@@ -101,12 +144,20 @@ export function createGenerateVideo(options) {
101
144
  else {
102
145
  throw new Error('createGenerateVideo requires either a connection or fetcher option');
103
146
  }
147
+ // Mount devtools only. Generation runs are never auto-started on setup —
148
+ // persisted state is read-only for display.
104
149
  client.mountDevtools();
105
150
  // Note: Cleanup is handled by calling dispose() directly when needed.
106
151
  // Unlike React/Vue/Solid, Svelte 5 runes like $effect can only be used
107
152
  // during component initialization, so we don't add automatic cleanup here.
108
153
  // Users should call video.dispose() in their component's cleanup if needed.
109
154
  const generate = async (input) => {
155
+ // Svelte has no remount effect to revive a disposed client (the other
156
+ // frameworks revive via mountDevtools() in their mount effects), so an
157
+ // explicit generate() after dispose() is the Svelte revive path: bring
158
+ // the client and the reactive bindings back together.
159
+ disposed = false;
160
+ client.mountDevtools();
110
161
  await client.generate(input);
111
162
  };
112
163
  const stop = () => {
@@ -116,6 +167,7 @@ export function createGenerateVideo(options) {
116
167
  client.reset();
117
168
  };
118
169
  const dispose = () => {
170
+ disposed = true;
119
171
  client.dispose();
120
172
  };
121
173
  const updateBody = (newBody) => {
@@ -145,5 +197,8 @@ export function createGenerateVideo(options) {
145
197
  reset,
146
198
  dispose,
147
199
  updateBody,
200
+ get runId() {
201
+ return runId;
202
+ },
148
203
  };
149
204
  }
@@ -1,5 +1,5 @@
1
1
  import type { StreamChunk } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
2
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, GenerationRestoredResult, InferGenerationOutputFromReturn } from '@tanstack/ai-client';
3
3
  /**
4
4
  * Options for the createGeneration function.
5
5
  *
@@ -14,12 +14,51 @@ export interface CreateGenerationOptions<TInput, TResult, TOutput = TResult> {
14
14
  connection?: ConnectConnectionAdapter;
15
15
  /** Direct async function for one-shot generation (no streaming protocol needed) */
16
16
  fetcher?: GenerationFetcher<TInput, TResult>;
17
- /** Unique identifier for this generation instance */
17
+ /**
18
+ * @deprecated Prefer `threadId`. Only allowed when `threadId` is omitted (see `GenerationPersistenceOptions`).
19
+ */
18
20
  id?: string;
19
21
  /** Additional body parameters to send with connect-based adapter requests */
20
22
  body?: Record<string, any>;
21
23
  /** Display options for TanStack AI Devtools. */
22
24
  devtools?: AIDevtoolsDisplayOptions;
25
+ /**
26
+ * How this generation persists across reloads.
27
+ * - Omit / `false`: ephemeral, in-memory only.
28
+ * - `true`: server-driven — on mount the client hydrates the last generation
29
+ * for its `threadId` from the server (needs a connection with a
30
+ * `hydrateGeneration` handler) and repaints it; it never auto-starts a run.
31
+ */
32
+ persistence?: boolean;
33
+ /**
34
+ * The **scope** this generation belongs to: a stable, app-chosen name for the
35
+ * slot successive runs fill — not a link to a chat conversation.
36
+ *
37
+ * The hook starts empty and produces many runs over its life; each gets its
38
+ * own `runId`, but all belong to one scope. Persistence keys on this, so
39
+ * derive it from your own domain and keep it identical across reloads (e.g.
40
+ * `` `video-${videoId}-start-frame` ``). It is also sent as the AG-UI thread
41
+ * id on the wire, which the protocol requires.
42
+ *
43
+ * **Required whenever `persistence` is set** — an app that cannot name the
44
+ * scope has nothing to restore to. Optional for ephemeral generations, where
45
+ * it falls back to `id` purely to satisfy the wire.
46
+ */
47
+ threadId?: string;
48
+ /**
49
+ * Server-driven hydration handler for `persistence: true` when the
50
+ * connection doesn't carry one (e.g. alongside `fetcher`, or a `stream()` /
51
+ * `rpcStream()` adapter built without handlers) — typically a one-line
52
+ * server-function call. The connection's own handler takes precedence.
53
+ */
54
+ hydrateGeneration?: ConnectConnectionAdapter['hydrateGeneration'];
55
+ /**
56
+ * Re-attach handler that replays a run still generating to completion on
57
+ * mount, when the connection doesn't carry one. Without it, a restored
58
+ * `running` snapshot surfaces as an (interrupted) error. The connection's
59
+ * own handler takes precedence.
60
+ */
61
+ joinRun?: ConnectConnectionAdapter['joinRun'];
23
62
  /**
24
63
  * Callback when a result is received. Can optionally return a transformed value.
25
64
  *
@@ -34,13 +73,20 @@ export interface CreateGenerationOptions<TInput, TResult, TOutput = TResult> {
34
73
  onProgress?: (progress: number, message?: string) => void;
35
74
  /** Callback for each stream chunk (connect-based adapter mode only) */
36
75
  onChunk?: (chunk: StreamChunk) => void;
76
+ /**
77
+ * @internal Rebuild a typed result from a restored snapshot, injected by each
78
+ * specialized function (image / speech / audio / transcription / summarize).
79
+ * Forwarded to the client so a server-hydrate restore repaints `result`.
80
+ */
81
+ reconstructResult?: (restored: GenerationRestoredResult) => TResult | null;
37
82
  }
38
83
  /**
39
84
  * Return type for the createGeneration function.
40
85
  *
41
86
  * @template TOutput - The output type (after optional transform)
87
+ * @template TInput - The input type accepted by `generate` (defaults to any object)
42
88
  */
43
- export interface CreateGenerationReturn<TOutput> {
89
+ export interface CreateGenerationReturn<TOutput, TInput extends Record<string, any> = Record<string, any>> {
44
90
  /** The generation result, or null if not yet generated */
45
91
  readonly result: TOutput | null;
46
92
  /** Whether a generation is currently in progress */
@@ -50,7 +96,7 @@ export interface CreateGenerationReturn<TOutput> {
50
96
  /** Current state of the generation client */
51
97
  readonly status: GenerationClientState;
52
98
  /** Trigger a generation request */
53
- generate: (input: Record<string, any>) => Promise<void>;
99
+ generate: (input: TInput) => Promise<void>;
54
100
  /** Abort the current generation */
55
101
  stop: () => void;
56
102
  /** Clear result, error, and return to idle */
@@ -59,6 +105,13 @@ export interface CreateGenerationReturn<TOutput> {
59
105
  dispose: () => void;
60
106
  /** Update additional body parameters */
61
107
  updateBody: (body: Record<string, any>) => void;
108
+ /**
109
+ * The id of the generation job currently running, or `null` when nothing is in
110
+ * flight. Each call to `generate` is one job with its own id. Pass it to your
111
+ * own endpoint to cancel or poll the provider job — `stop()` only aborts the
112
+ * local stream, it does not stop work already running on the provider.
113
+ */
114
+ readonly runId: string | null;
62
115
  }
63
116
  /**
64
117
  * Creates a reactive generation instance for Svelte 5.
@@ -91,6 +144,6 @@ export interface CreateGenerationReturn<TOutput> {
91
144
  * </div>
92
145
  * ```
93
146
  */
94
- export declare function createGeneration<TInput extends Record<string, any>, TResult, TTransformed = void>(options: Omit<CreateGenerationOptions<TInput, TResult>, 'onResult'> & {
147
+ export declare function createGeneration<TInput extends Record<string, any>, TResult, TTransformed = void>(options: Omit<CreateGenerationOptions<TInput, TResult>, 'onResult' | 'persistence' | 'threadId' | 'id'> & {
95
148
  onResult?: (result: TResult) => TTransformed;
96
- }): CreateGenerationReturn<InferGenerationOutputFromReturn<TResult, TTransformed>>;
149
+ } & GenerationPersistenceOptions): CreateGenerationReturn<InferGenerationOutputFromReturn<TResult, TTransformed>, TInput>;
@@ -38,20 +38,35 @@ import { createGenerationDevtoolsBridge } from '@tanstack/ai-client/devtools';
38
38
  // default, leaving the parameter `any` — a hard error under `strict`. See
39
39
  // issue #848.
40
40
  export function createGeneration(options) {
41
- const clientId = options.id || `gen-${Date.now()}-${Math.random().toString(36).substring(7)}`;
41
+ const fallbackId = `gen-${Date.now()}-${Math.random().toString(36).substring(7)}`;
42
42
  // Create reactive state using Svelte 5 runes
43
43
  let result = $state(null);
44
44
  let isLoading = $state(false);
45
45
  let error = $state(undefined);
46
46
  let status = $state('idle');
47
+ let runId = $state(null);
48
+ let disposed = false;
47
49
  // `body` uses a conditional spread because `GenerationClientOptions.body`
48
50
  // is declared `body?: Record<string, any>` (absent vs. present) under
49
51
  // `exactOptionalPropertyTypes`. Assigning `undefined` directly would be
50
52
  // rejected — the optional caller `options.body` may be undefined, in which
51
53
  // case we want the key to be absent.
54
+ // Identity: pass `threadId` alone when set (never also pass deprecated `id`).
52
55
  const clientOptions = {
53
- id: clientId,
54
56
  body: options.body,
57
+ ...(options.threadId !== undefined
58
+ ? { threadId: options.threadId }
59
+ : { id: options.id ?? fallbackId }),
60
+ ...(options.persistence !== undefined && {
61
+ persistence: options.persistence,
62
+ }),
63
+ ...(options.hydrateGeneration !== undefined && {
64
+ hydrateGeneration: options.hydrateGeneration,
65
+ }),
66
+ ...(options.joinRun !== undefined && { joinRun: options.joinRun }),
67
+ ...(options.reconstructResult
68
+ ? { reconstructResult: options.reconstructResult }
69
+ : {}),
55
70
  devtoolsBridgeFactory: createGenerationDevtoolsBridge,
56
71
  devtools: {
57
72
  ...options.devtools,
@@ -62,21 +77,43 @@ export function createGeneration(options) {
62
77
  // (`TOutput`, with null/void/undefined stripped) are identical at runtime;
63
78
  // the cast bridges the relationship that the conditional type hides.
64
79
  onResult: ((r) => options.onResult?.(r)),
65
- onError: (e) => options.onError?.(e),
66
- onProgress: (p, m) => options.onProgress?.(p, m),
67
- onChunk: (c) => options.onChunk?.(c),
80
+ onError: (e) => {
81
+ if (!disposed)
82
+ options.onError?.(e);
83
+ },
84
+ onProgress: (p, m) => {
85
+ if (!disposed)
86
+ options.onProgress?.(p, m);
87
+ },
88
+ onChunk: (c) => {
89
+ if (!disposed)
90
+ options.onChunk?.(c);
91
+ },
68
92
  onResultChange: (r) => {
93
+ if (disposed)
94
+ return;
69
95
  result = r;
70
96
  },
71
97
  onLoadingChange: (l) => {
98
+ if (disposed)
99
+ return;
72
100
  isLoading = l;
73
101
  },
74
102
  onErrorChange: (e) => {
103
+ if (disposed)
104
+ return;
75
105
  error = e;
76
106
  },
77
107
  onStatusChange: (s) => {
108
+ if (disposed)
109
+ return;
78
110
  status = s;
79
111
  },
112
+ onResumeStateChange: (rs) => {
113
+ if (disposed)
114
+ return;
115
+ runId = rs?.runId ?? null;
116
+ },
80
117
  };
81
118
  let client;
82
119
  if (options.connection) {
@@ -94,12 +131,20 @@ export function createGeneration(options) {
94
131
  else {
95
132
  throw new Error('createGeneration requires either a connection or fetcher option');
96
133
  }
134
+ // Mount devtools only. Generation runs are never auto-started on setup —
135
+ // persisted state is read-only for display.
97
136
  client.mountDevtools();
98
137
  // Note: Cleanup is handled by calling dispose() directly when needed.
99
138
  // Unlike React/Vue/Solid, Svelte 5 runes like $effect can only be used
100
139
  // during component initialization, so we don't add automatic cleanup here.
101
140
  // Users should call gen.dispose() in their component's cleanup if needed.
102
141
  const generate = async (input) => {
142
+ // Svelte has no remount effect to revive a disposed client (the other
143
+ // frameworks revive via mountDevtools() in their mount effects), so an
144
+ // explicit generate() after dispose() is the Svelte revive path: bring
145
+ // the client and the reactive bindings back together.
146
+ disposed = false;
147
+ client.mountDevtools();
103
148
  await client.generate(input);
104
149
  };
105
150
  const stop = () => {
@@ -109,6 +154,7 @@ export function createGeneration(options) {
109
154
  client.reset();
110
155
  };
111
156
  const dispose = () => {
157
+ disposed = true;
112
158
  client.dispose();
113
159
  };
114
160
  const updateBody = (newBody) => {
@@ -127,10 +173,13 @@ export function createGeneration(options) {
127
173
  get status() {
128
174
  return status;
129
175
  },
130
- generate: generate,
176
+ generate,
131
177
  stop,
132
178
  reset,
133
179
  dispose,
134
180
  updateBody,
181
+ get runId() {
182
+ return runId;
183
+ },
135
184
  };
136
185
  }
@@ -1,16 +1,19 @@
1
+ import type { CreateGenerationOptions, CreateGenerationReturn } from './create-generation.svelte';
1
2
  import type { StreamChunk, SummarizationResult } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn, SummarizeGenerateInput } from '@tanstack/ai-client';
3
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, InferGenerationOutputFromReturn, SummarizeGenerateInput } from '@tanstack/ai-client';
3
4
  /**
4
5
  * Options for the createSummarize function.
5
6
  *
6
7
  * @template TOutput - The output type after optional transform (defaults to SummarizationResult)
7
8
  */
8
- export interface CreateSummarizeOptions<TOutput = SummarizationResult> {
9
+ export interface CreateSummarizeOptions<TOutput = SummarizationResult> extends Pick<CreateGenerationOptions<SummarizeGenerateInput, SummarizationResult, 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 summarization */
12
13
  fetcher?: GenerationFetcher<SummarizeGenerateInput, SummarizationResult>;
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 CreateSummarizeOptions<TOutput = SummarizationResult> {
36
39
  *
37
40
  * @template TOutput - The output type (after optional transform)
38
41
  */
39
- export interface CreateSummarizeReturn<TOutput = SummarizationResult> {
42
+ export interface CreateSummarizeReturn<TOutput = SummarizationResult> extends Omit<CreateGenerationReturn<TOutput>, 'generate'> {
40
43
  /** The summarization result, or null */
41
44
  readonly result: TOutput | null;
42
45
  /** Whether summarization is in progress */
@@ -47,12 +50,6 @@ export interface CreateSummarizeReturn<TOutput = SummarizationResult> {
47
50
  readonly status: GenerationClientState;
48
51
  /** Trigger summarization */
49
52
  generate: (input: SummarizeGenerateInput) => Promise<void>;
50
- /** Abort the current summarization */
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 text summarization instance for Svelte 5.
@@ -84,6 +81,6 @@ export interface CreateSummarizeReturn<TOutput = SummarizationResult> {
84
81
  * </div>
85
82
  * ```
86
83
  */
87
- export declare function createSummarize<TTransformed = void>(options: Omit<CreateSummarizeOptions, 'onResult'> & {
84
+ export declare function createSummarize<TTransformed = void>(options: Omit<CreateSummarizeOptions, 'onResult' | 'persistence' | 'threadId' | 'id'> & {
88
85
  onResult?: (result: SummarizationResult) => TTransformed;
89
- }): CreateSummarizeReturn<InferGenerationOutputFromReturn<SummarizationResult, TTransformed>>;
86
+ } & GenerationPersistenceOptions): CreateSummarizeReturn<InferGenerationOutputFromReturn<SummarizationResult, TTransformed>>;
@@ -1,4 +1,5 @@
1
1
  import { createGeneration } from './create-generation.svelte';
2
+ import { reconstructSummarizeResult } from '@tanstack/ai-client';
2
3
  /**
3
4
  * Creates a reactive text summarization instance for Svelte 5.
4
5
  *
@@ -39,6 +40,7 @@ export function createSummarize(options) {
39
40
  const gen = createGeneration({
40
41
  ...options,
41
42
  devtools,
43
+ reconstructResult: reconstructSummarizeResult,
42
44
  });
43
45
  return {
44
46
  get result() {
@@ -57,5 +59,9 @@ export function createSummarize(options) {
57
59
  stop: gen.stop,
58
60
  reset: gen.reset,
59
61
  updateBody: gen.updateBody,
62
+ dispose: gen.dispose,
63
+ get runId() {
64
+ return gen.runId;
65
+ },
60
66
  };
61
67
  }
@@ -1,16 +1,19 @@
1
+ import type { CreateGenerationOptions, CreateGenerationReturn } from './create-generation.svelte';
1
2
  import type { StreamChunk, TranscriptionResult } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, InferGenerationOutputFromReturn, TranscriptionGenerateInput } from '@tanstack/ai-client';
3
+ import type { AIDevtoolsDisplayOptions, ConnectConnectionAdapter, GenerationClientState, GenerationFetcher, GenerationPersistenceOptions, InferGenerationOutputFromReturn, TranscriptionGenerateInput } from '@tanstack/ai-client';
3
4
  /**
4
5
  * Options for the createTranscription function.
5
6
  *
6
7
  * @template TOutput - The output type after optional transform (defaults to TranscriptionResult)
7
8
  */
8
- export interface CreateTranscriptionOptions<TOutput = TranscriptionResult> {
9
+ export interface CreateTranscriptionOptions<TOutput = TranscriptionResult> extends Pick<CreateGenerationOptions<TranscriptionGenerateInput, TranscriptionResult, 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 transcription */
12
13
  fetcher?: GenerationFetcher<TranscriptionGenerateInput, TranscriptionResult>;
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 CreateTranscriptionOptions<TOutput = TranscriptionResult> {
36
39
  *
37
40
  * @template TOutput - The output type (after optional transform)
38
41
  */
39
- export interface CreateTranscriptionReturn<TOutput = TranscriptionResult> {
42
+ export interface CreateTranscriptionReturn<TOutput = TranscriptionResult> extends Omit<CreateGenerationReturn<TOutput>, 'generate'> {
40
43
  /** The transcription result, or null */
41
44
  readonly result: TOutput | null;
42
45
  /** Whether transcription is in progress */
@@ -47,12 +50,6 @@ export interface CreateTranscriptionReturn<TOutput = TranscriptionResult> {
47
50
  readonly status: GenerationClientState;
48
51
  /** Trigger transcription */
49
52
  generate: (input: TranscriptionGenerateInput) => Promise<void>;
50
- /** Abort the current transcription */
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 transcription instance for Svelte 5.
@@ -89,6 +86,6 @@ export interface CreateTranscriptionReturn<TOutput = TranscriptionResult> {
89
86
  * </div>
90
87
  * ```
91
88
  */
92
- export declare function createTranscription<TTransformed = void>(options: Omit<CreateTranscriptionOptions, 'onResult'> & {
89
+ export declare function createTranscription<TTransformed = void>(options: Omit<CreateTranscriptionOptions, 'onResult' | 'persistence' | 'threadId' | 'id'> & {
93
90
  onResult?: (result: TranscriptionResult) => TTransformed;
94
- }): CreateTranscriptionReturn<InferGenerationOutputFromReturn<TranscriptionResult, TTransformed>>;
91
+ } & GenerationPersistenceOptions): CreateTranscriptionReturn<InferGenerationOutputFromReturn<TranscriptionResult, TTransformed>>;
@@ -1,4 +1,5 @@
1
1
  import { createGeneration } from './create-generation.svelte';
2
+ import { reconstructTranscriptionResult } from '@tanstack/ai-client';
2
3
  /**
3
4
  * Creates a reactive audio transcription instance for Svelte 5.
4
5
  *
@@ -44,6 +45,7 @@ export function createTranscription(options) {
44
45
  const gen = createGeneration({
45
46
  ...options,
46
47
  devtools,
48
+ reconstructResult: reconstructTranscriptionResult,
47
49
  });
48
50
  return {
49
51
  get result() {
@@ -62,5 +64,9 @@ export function createTranscription(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
  }
package/dist/index.d.ts CHANGED
@@ -16,4 +16,4 @@ export { createGenerateVideo } from './create-generate-video.svelte';
16
16
  export type { CreateGenerateVideoOptions, CreateGenerateVideoReturn, } from './create-generate-video.svelte';
17
17
  export { createAudioRecorder } from './create-audio-recorder.svelte';
18
18
  export type { CreateAudioRecorderOptions, CreateAudioRecorderReturn, } from './create-audio-recorder.svelte';
19
- export { fetchServerSentEvents, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, clientTools, type ConnectionAdapter, type ConnectConnectionAdapter, type SubscribeConnectionAdapter, type RunAgentInputContext, type FetchConnectionOptions, type XhrConnectionOptions, type InferChatMessages, type GenerationClientState, type ImageGenerateInput, type AudioGenerateInput, type SpeechGenerateInput, type TranscriptionGenerateInput, type SummarizeGenerateInput, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, } from '@tanstack/ai-client';
19
+ export { fetchServerSentEvents, localStoragePersistence, sessionStoragePersistence, indexedDBPersistence, StorageUnavailableError, type ChatClientPersistence, type ChatPersistedState, type ChatPersistenceOption, type ChatStorageAdapter, type WebStoragePersistenceOptions, type IndexedDBPersistenceOptions, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, clientTools, type ConnectionAdapter, type ConnectConnectionAdapter, type SubscribeConnectionAdapter, type RunAgentInputContext, type FetchConnectionOptions, type XhrConnectionOptions, type InferChatMessages, type GenerationClientState, type ImageGenerateInput, type AudioGenerateInput, type SpeechGenerateInput, type TranscriptionGenerateInput, type SummarizeGenerateInput, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, } from '@tanstack/ai-client';
package/dist/index.js CHANGED
@@ -9,4 +9,4 @@ export { createSummarize } from './create-summarize.svelte';
9
9
  export { createGenerateVideo } from './create-generate-video.svelte';
10
10
  export { createAudioRecorder } from './create-audio-recorder.svelte';
11
11
  // Re-export from ai-client for convenience
12
- export { fetchServerSentEvents, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, clientTools, } from '@tanstack/ai-client';
12
+ export { fetchServerSentEvents, localStoragePersistence, sessionStoragePersistence, indexedDBPersistence, StorageUnavailableError, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, clientTools, } from '@tanstack/ai-client';
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { AnyClientTool, InferSchemaType, ModelMessage, SchemaInput } from '@tanstack/ai';
2
- import type { AIDevtoolsDisplayOptions, ChatClientOptions, ChatClientState, ChatRequestBody, ClientContextOptionFromTools, ConnectionStatus, DistributedOmit, InferredClientContext, MultimodalContent, QueueConfig, QueueOption, QueueStrategy, QueuedMessage, SendMessageOptions, UIMessage, WhenBusy } from '@tanstack/ai-client';
1
+ import type { AnyClientTool, InferSchemaType, ModelMessage, RunAgentResumeItem, SchemaInput } from '@tanstack/ai';
2
+ import type { AIDevtoolsDisplayOptions, BoundInterrupts, ChatClientOptions, ChatClientState, ChatInterrupt, ChatInterruptState, ChatRequestBody, ChatResumeState, ClientContextOptionFromTools, ConnectionStatus, DistributedOmit, InferredClientContext, MultimodalContent, QueueConfig, QueueOption, QueueStrategy, QueuedMessage, SendMessageOptions, UIMessage, WhenBusy } from '@tanstack/ai-client';
3
3
  export type { ChatRequestBody, MultimodalContent, QueueConfig, QueuedMessage, QueueOption, QueueStrategy, SendMessageOptions, UIMessage, WhenBusy, };
4
4
  /**
5
5
  * Recursive partial — every property and every nested array element is
@@ -30,7 +30,7 @@ export type DeepPartial<T> = T extends ReadonlyArray<infer U> ? Array<DeepPartia
30
30
  * Note: Connection and body changes will recreate the ChatClient instance.
31
31
  * To update these options, remount the component or use a key prop.
32
32
  */
33
- export type CreateChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>> = DistributedOmit<ChatClientOptions<TTools, TContext>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange' | 'onQueueChange' | 'context' | 'devtools'> & {
33
+ export type CreateChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>> = DistributedOmit<ChatClientOptions<TTools, TContext>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange' | 'onQueueChange' | 'onResumeStateChange' | 'onRunIdChange' | 'context' | 'devtools' | 'id'> & {
34
34
  live?: boolean;
35
35
  /** Display options for TanStack AI Devtools. */
36
36
  devtools?: AIDevtoolsDisplayOptions;
@@ -100,6 +100,32 @@ interface BaseCreateChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any
100
100
  id: string;
101
101
  approved: boolean;
102
102
  }) => Promise<void>;
103
+ /**
104
+ * The id of the run this client has in flight — one it started or rejoined —
105
+ * or `null` when there is none (including while a run sits paused on an
106
+ * interrupt, waiting on approval).
107
+ *
108
+ * A run is one turn of the conversation, so this changes from turn to turn. A
109
+ * whole tool loop stays inside one run, while resuming after an interrupt
110
+ * continues the turn under a new id — so one user message can produce several
111
+ * run ids. Use it to talk to your own server about that run (cancel it, poll
112
+ * it, correlate a log line).
113
+ */
114
+ readonly runId: string | null;
115
+ readonly interrupts: BoundInterrupts<TTools>;
116
+ /** @deprecated Use `interrupts`. */
117
+ readonly pendingInterrupts: BoundInterrupts<TTools>;
118
+ readonly interruptErrors: ChatInterruptState<TTools>['interruptErrors'];
119
+ readonly resuming: boolean;
120
+ resolveInterrupts: {
121
+ (approved: boolean): void;
122
+ (resolver: (interrupt: ChatInterrupt<TTools>) => undefined): void;
123
+ };
124
+ cancelInterrupts: () => void;
125
+ retryInterrupts: () => void;
126
+ resumeInterruptsUnsafe: (resume: Array<RunAgentResumeItem>, state?: ChatResumeState) => Promise<boolean>;
127
+ /** @deprecated Use bound interrupt methods or `resumeInterruptsUnsafe`. */
128
+ resumeInterrupts: (resume: Array<RunAgentResumeItem>, state?: ChatResumeState) => Promise<boolean>;
103
129
  /**
104
130
  * Reload the last assistant message
105
131
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-svelte",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Svelte 5 bindings for TanStack AI streaming chat, structured outputs, and media generation.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -46,11 +46,11 @@
46
46
  "media-generation"
47
47
  ],
48
48
  "dependencies": {
49
- "@tanstack/ai-client": "0.22.1"
49
+ "@tanstack/ai-client": "0.23.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "svelte": "^5.0.0",
53
- "@tanstack/ai": "^0.42.0"
53
+ "@tanstack/ai": "^0.43.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@standard-schema/spec": "^1.1.0",
@@ -62,13 +62,13 @@
62
62
  "svelte": "^5.20.0",
63
63
  "svelte-check": "^4.2.0",
64
64
  "typescript": "5.9.3",
65
- "vite": "^7.3.3",
66
- "@tanstack/ai": "0.42.0"
65
+ "vite": "^8.1.4",
66
+ "@tanstack/ai": "0.43.0"
67
67
  },
68
68
  "scripts": {
69
69
  "clean": "rm -rf ./dist",
70
- "lint:fix": "eslint ./src --fix",
71
- "test:eslint": "eslint ./src",
70
+ "lint:fix": "oxlint src --type-aware --fix",
71
+ "test:oxlint": "oxlint src --type-aware",
72
72
  "test:lib": "vitest run",
73
73
  "test:lib:dev": "pnpm test:lib --watch",
74
74
  "test:types": "svelte-check",