@tanstack/ai-client 0.22.0 → 0.23.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 +15 -1
- package/dist/esm/audio-recorder.js +190 -213
- package/dist/esm/audio-recorder.js.map +1 -1
- package/dist/esm/chat-client.d.ts +172 -3
- package/dist/esm/chat-client.js +1656 -1386
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/cleared-stream-tracker.d.ts +23 -0
- package/dist/esm/cleared-stream-tracker.js +97 -0
- package/dist/esm/cleared-stream-tracker.js.map +1 -0
- package/dist/esm/client-persistor.d.ts +25 -12
- package/dist/esm/client-persistor.js +260 -235
- package/dist/esm/client-persistor.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +231 -10
- package/dist/esm/connection-adapters.js +989 -574
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/devtools-noop.d.ts +1 -0
- package/dist/esm/devtools-noop.js +79 -139
- package/dist/esm/devtools-noop.js.map +1 -1
- package/dist/esm/devtools.d.ts +31 -1
- package/dist/esm/devtools.js +977 -1127
- package/dist/esm/devtools.js.map +1 -1
- package/dist/esm/events.js +224 -226
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/generation-client.d.ts +145 -2
- package/dist/esm/generation-client.js +659 -321
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-reconstruct.d.ts +21 -0
- package/dist/esm/generation-reconstruct.js +85 -0
- package/dist/esm/generation-reconstruct.js.map +1 -0
- package/dist/esm/generation-types.d.ts +289 -3
- package/dist/esm/generation-types.js +356 -13
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/index.d.ts +9 -4
- package/dist/esm/index.js +7 -39
- package/dist/esm/interrupt-manager.d.ts +77 -0
- package/dist/esm/interrupt-manager.js +787 -0
- package/dist/esm/interrupt-manager.js.map +1 -0
- package/dist/esm/mcp-app-bridge.js +56 -64
- package/dist/esm/mcp-app-bridge.js.map +1 -1
- package/dist/esm/realtime-client.js +366 -440
- package/dist/esm/realtime-client.js.map +1 -1
- package/dist/esm/response-stream.js +19 -26
- package/dist/esm/response-stream.js.map +1 -1
- package/dist/esm/sse-parser.js +44 -47
- package/dist/esm/sse-parser.js.map +1 -1
- package/dist/esm/sse-utils.js +8 -9
- package/dist/esm/sse-utils.js.map +1 -1
- package/dist/esm/storage-adapters.d.ts +62 -0
- package/dist/esm/storage-adapters.js +174 -0
- package/dist/esm/storage-adapters.js.map +1 -0
- package/dist/esm/types.d.ts +212 -10
- package/dist/esm/types.js +38 -7
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +113 -2
- package/dist/esm/video-generation-client.js +665 -379
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +7 -7
- package/src/chat-client.ts +1079 -61
- package/src/cleared-stream-tracker.ts +151 -0
- package/src/client-persistor.ts +102 -33
- package/src/connection-adapters.ts +1185 -142
- package/src/devtools-noop.ts +4 -3
- package/src/devtools.ts +121 -3
- package/src/generation-client.ts +563 -13
- package/src/generation-reconstruct.ts +121 -0
- package/src/generation-types.ts +727 -3
- package/src/index.ts +56 -1
- package/src/interrupt-manager.ts +1440 -0
- package/src/storage-adapters.ts +242 -0
- package/src/types.ts +301 -9
- package/src/video-generation-client.ts +479 -13
- package/dist/esm/index.js.map +0 -1
|
@@ -1,382 +1,668 @@
|
|
|
1
|
-
import { GENERATION_EVENTS } from "./generation-types.js";
|
|
2
1
|
import { createNoOpVideoDevtoolsBridge } from "./devtools-noop.js";
|
|
2
|
+
import { GENERATION_EVENTS, GENERATION_STREAM_TRUNCATED_MESSAGE, GENERATION_UNRESTORABLE_RESULT_MESSAGE, clientStateFromResumeStatus, createGenerationHydrationError, createGenerationResultSnapshot, parseGenerationResumeSnapshot, updateGenerationResumeSnapshot } from "./generation-types.js";
|
|
3
3
|
import { parseSSEResponse } from "./sse-parser.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
4
|
+
//#region src/video-generation-client.ts
|
|
5
|
+
/**
|
|
6
|
+
* A specialized client for job-based video generation.
|
|
7
|
+
*
|
|
8
|
+
* Video generation is asynchronous: a job is created, then polled for status
|
|
9
|
+
* until completion. This client handles the full lifecycle.
|
|
10
|
+
*
|
|
11
|
+
* Supports two transport modes:
|
|
12
|
+
* - **ConnectConnectionAdapter** — Server handles the polling loop internally and
|
|
13
|
+
* streams status updates via CUSTOM events.
|
|
14
|
+
* - **Fetcher** — Direct async function that returns a completed
|
|
15
|
+
* `VideoGenerateResult`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // With streaming connection adapter (server-driven polling)
|
|
20
|
+
* const client = new VideoGenerationClient({
|
|
21
|
+
* connection: fetchServerSentEvents('/api/generate/video'),
|
|
22
|
+
* onResultChange: setResult,
|
|
23
|
+
* onVideoStatusChange: setVideoStatus,
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* // With fetcher (direct result)
|
|
27
|
+
* const client = new VideoGenerationClient({
|
|
28
|
+
* fetcher: async (input) => {
|
|
29
|
+
* const res = await fetch('/api/video/generate', {
|
|
30
|
+
* method: 'POST',
|
|
31
|
+
* body: JSON.stringify(input),
|
|
32
|
+
* })
|
|
33
|
+
* return res.json() // { jobId, status: 'completed', url, expiresAt }
|
|
34
|
+
* },
|
|
35
|
+
* })
|
|
36
|
+
*
|
|
37
|
+
* await client.generate({ prompt: 'A flying car over a city' })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
var VideoGenerationClient = class {
|
|
41
|
+
connection;
|
|
42
|
+
hydrateGenerationHandler;
|
|
43
|
+
joinRunHandler;
|
|
44
|
+
fetcher;
|
|
45
|
+
uniqueId;
|
|
46
|
+
devtoolsMetadata;
|
|
47
|
+
devtoolsBridge;
|
|
48
|
+
threadId;
|
|
49
|
+
serverDriven = false;
|
|
50
|
+
body;
|
|
51
|
+
result = null;
|
|
52
|
+
input = null;
|
|
53
|
+
progress = null;
|
|
54
|
+
jobId = null;
|
|
55
|
+
videoStatus = null;
|
|
56
|
+
isLoading = false;
|
|
57
|
+
error = void 0;
|
|
58
|
+
status = "idle";
|
|
59
|
+
resumeSnapshot;
|
|
60
|
+
abortController = null;
|
|
61
|
+
rejoinedRunId;
|
|
62
|
+
callbacksRef;
|
|
63
|
+
devtoolsMounted = false;
|
|
64
|
+
disposed = false;
|
|
65
|
+
serverHydrationStarted = false;
|
|
66
|
+
constructor(options) {
|
|
67
|
+
this.uniqueId = options.threadId ?? options.id ?? this.generateUniqueId("video");
|
|
68
|
+
this.threadId = options.threadId ?? this.uniqueId;
|
|
69
|
+
this.connection = options.connection;
|
|
70
|
+
this.fetcher = options.fetcher;
|
|
71
|
+
this.hydrateGenerationHandler = options.hydrateGeneration;
|
|
72
|
+
this.joinRunHandler = options.joinRun;
|
|
73
|
+
this.body = options.body ?? {};
|
|
74
|
+
this.serverDriven = options.persistence === true;
|
|
75
|
+
this.callbacksRef = {
|
|
76
|
+
onResult: options.onResult,
|
|
77
|
+
onError: options.onError,
|
|
78
|
+
onProgress: options.onProgress,
|
|
79
|
+
onChunk: options.onChunk,
|
|
80
|
+
onJobCreated: options.onJobCreated,
|
|
81
|
+
onStatusUpdate: options.onStatusUpdate,
|
|
82
|
+
onResultChange: options.onResultChange,
|
|
83
|
+
onLoadingChange: options.onLoadingChange,
|
|
84
|
+
onErrorChange: options.onErrorChange,
|
|
85
|
+
onStatusChange: options.onStatusChange,
|
|
86
|
+
onJobIdChange: options.onJobIdChange,
|
|
87
|
+
onVideoStatusChange: options.onVideoStatusChange,
|
|
88
|
+
onResumeSnapshotChange: options.onResumeSnapshotChange,
|
|
89
|
+
onResumeStateChange: options.onResumeStateChange
|
|
90
|
+
};
|
|
91
|
+
this.devtoolsMetadata = this.createDevtoolsMetadata(options.devtools);
|
|
92
|
+
this.devtoolsBridge = (options.devtoolsBridgeFactory ?? createNoOpVideoDevtoolsBridge)(this.buildDevtoolsBridgeOptions());
|
|
93
|
+
}
|
|
94
|
+
buildDevtoolsBridgeOptions() {
|
|
95
|
+
return {
|
|
96
|
+
hookId: this.uniqueId,
|
|
97
|
+
clientId: this.uniqueId,
|
|
98
|
+
threadId: this.threadId,
|
|
99
|
+
metadata: this.devtoolsMetadata,
|
|
100
|
+
getCoreState: () => ({
|
|
101
|
+
input: this.input,
|
|
102
|
+
result: this.result,
|
|
103
|
+
progress: this.progress,
|
|
104
|
+
status: this.status,
|
|
105
|
+
isLoading: this.isLoading,
|
|
106
|
+
jobId: this.jobId,
|
|
107
|
+
videoStatus: this.videoStatus,
|
|
108
|
+
...this.error ? { error: this.error.message } : {}
|
|
109
|
+
})
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
mountDevtools() {
|
|
113
|
+
this.disposed = false;
|
|
114
|
+
this.maybeHydrateFromServer();
|
|
115
|
+
this.maybeResumeInFlight();
|
|
116
|
+
if (this.devtoolsMounted) return;
|
|
117
|
+
this.devtoolsMounted = true;
|
|
118
|
+
this.devtoolsBridge.emitRegistered();
|
|
119
|
+
this.devtoolsBridge.emitSnapshot();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Trigger video generation.
|
|
123
|
+
* Only one generation can be in-flight at a time.
|
|
124
|
+
*/
|
|
125
|
+
async generate(input) {
|
|
126
|
+
if (this.disposed) return;
|
|
127
|
+
if (this.isLoading) return;
|
|
128
|
+
this.mountDevtools();
|
|
129
|
+
this.input = input;
|
|
130
|
+
this.progress = null;
|
|
131
|
+
const runId = this.devtoolsBridge.beginRun(input);
|
|
132
|
+
this.setIsLoading(true);
|
|
133
|
+
this.setStatus("generating");
|
|
134
|
+
this.setError(void 0);
|
|
135
|
+
this.setJobId(null);
|
|
136
|
+
this.setVideoStatus(null);
|
|
137
|
+
const abortController = new AbortController();
|
|
138
|
+
this.abortController = abortController;
|
|
139
|
+
const { signal } = abortController;
|
|
140
|
+
try {
|
|
141
|
+
if (this.fetcher) await this.generateWithFetcher(input, signal, runId);
|
|
142
|
+
else if (this.connection) {
|
|
143
|
+
const mergedData = {
|
|
144
|
+
...this.body,
|
|
145
|
+
...input
|
|
146
|
+
};
|
|
147
|
+
const stream = this.connection.connect([], mergedData, signal, this.createRunContext(runId));
|
|
148
|
+
await this.processStream(stream, runId, signal);
|
|
149
|
+
} else throw new Error("VideoGenerationClient requires either a connection or fetcher option");
|
|
150
|
+
if (!signal.aborted && this.status === "success") this.devtoolsBridge.finishRun(this.devtoolsBridge.getActiveRunId() ?? runId, "run:completed", "completed");
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (signal.aborted) return;
|
|
153
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
154
|
+
this.setError(error);
|
|
155
|
+
this.setStatus("error");
|
|
156
|
+
this.recordResumeSnapshotError(error);
|
|
157
|
+
this.devtoolsBridge.finishRun(this.devtoolsBridge.getActiveRunId() ?? runId, "run:errored", "errored", error.message);
|
|
158
|
+
this.callbacksRef.onError?.(error);
|
|
159
|
+
} finally {
|
|
160
|
+
if (this.abortController === abortController) {
|
|
161
|
+
this.abortController = null;
|
|
162
|
+
this.setIsLoading(false);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Direct fetcher mode: call fetcher and set result.
|
|
168
|
+
*/
|
|
169
|
+
async generateWithFetcher(input, signal, runId) {
|
|
170
|
+
if (!this.fetcher) return;
|
|
171
|
+
const result = await this.fetcher(input, { signal });
|
|
172
|
+
if (signal.aborted) return;
|
|
173
|
+
if (result instanceof Response) await this.processStream(parseSSEResponse(result, signal), runId, signal);
|
|
174
|
+
else {
|
|
175
|
+
this.devtoolsBridge.ensureRunStarted(runId);
|
|
176
|
+
this.setResult(result);
|
|
177
|
+
this.setStatus("success");
|
|
178
|
+
this.completePlainFetcherResumeSnapshot(result);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Process a stream of AG-UI events from the streaming connection adapter.
|
|
183
|
+
* The server handles the polling loop and streams status updates.
|
|
184
|
+
*
|
|
185
|
+
* Throws {@link GENERATION_STREAM_TRUNCATED_MESSAGE} when the iteration ends
|
|
186
|
+
* without a terminal chunk — see the note on
|
|
187
|
+
* `GenerationClient.processStream`. Video runs are long enough that a proxy
|
|
188
|
+
* idle timeout mid-poll is the likeliest way to hit it.
|
|
189
|
+
*/
|
|
190
|
+
async processStream(source, fallbackRunId, signal) {
|
|
191
|
+
let streamRunId;
|
|
192
|
+
let sawTerminalChunk = false;
|
|
193
|
+
for await (const chunk of source) {
|
|
194
|
+
if (signal.aborted) break;
|
|
195
|
+
this.callbacksRef.onChunk?.(chunk);
|
|
196
|
+
this.observeResumeSnapshot(chunk);
|
|
197
|
+
const chunkRunId = "runId" in chunk && typeof chunk.runId === "string" ? chunk.runId : void 0;
|
|
198
|
+
switch (chunk.type) {
|
|
199
|
+
case "RUN_STARTED":
|
|
200
|
+
streamRunId = chunk.runId;
|
|
201
|
+
this.devtoolsBridge.ensureRunStarted(chunk.runId);
|
|
202
|
+
break;
|
|
203
|
+
case "CUSTOM":
|
|
204
|
+
this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId);
|
|
205
|
+
if (chunk.name === GENERATION_EVENTS.VIDEO_JOB_CREATED) {
|
|
206
|
+
const { jobId } = chunk.value;
|
|
207
|
+
this.setJobId(jobId);
|
|
208
|
+
this.callbacksRef.onJobCreated?.(jobId);
|
|
209
|
+
} else if (chunk.name === GENERATION_EVENTS.VIDEO_STATUS) {
|
|
210
|
+
const statusInfo = chunk.value;
|
|
211
|
+
this.setVideoStatus(statusInfo);
|
|
212
|
+
this.callbacksRef.onStatusUpdate?.(statusInfo);
|
|
213
|
+
if (statusInfo.progress !== void 0) this.setProgress(statusInfo.progress);
|
|
214
|
+
} else if (chunk.name === GENERATION_EVENTS.RESULT) this.setResult(chunk.value);
|
|
215
|
+
else if (chunk.name === GENERATION_EVENTS.PROGRESS) {
|
|
216
|
+
const { progress, message } = chunk.value;
|
|
217
|
+
this.setProgress(progress, message);
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
case "RUN_FINISHED":
|
|
221
|
+
streamRunId = chunk.runId;
|
|
222
|
+
sawTerminalChunk = true;
|
|
223
|
+
this.devtoolsBridge.ensureRunStarted(chunk.runId);
|
|
224
|
+
this.setStatus("success");
|
|
225
|
+
break;
|
|
226
|
+
case "RUN_ERROR": {
|
|
227
|
+
this.devtoolsBridge.ensureRunStarted(chunkRunId ?? streamRunId ?? fallbackRunId);
|
|
228
|
+
const msg = chunk.message || chunk.error?.message || "An error occurred";
|
|
229
|
+
throw new Error(msg);
|
|
230
|
+
}
|
|
231
|
+
default: break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (!sawTerminalChunk && !signal.aborted) throw new Error(GENERATION_STREAM_TRUNCATED_MESSAGE);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Abort any in-flight generation or polling.
|
|
238
|
+
*/
|
|
239
|
+
stop() {
|
|
240
|
+
const runId = this.devtoolsBridge.getActiveRunId();
|
|
241
|
+
if (this.abortController) {
|
|
242
|
+
this.abortController.abort();
|
|
243
|
+
this.abortController = null;
|
|
244
|
+
}
|
|
245
|
+
this.setIsLoading(false);
|
|
246
|
+
if (this.status === "generating") {
|
|
247
|
+
this.setStatus("idle");
|
|
248
|
+
if (runId) this.devtoolsBridge.finishRun(runId, "run:cancelled", "cancelled");
|
|
249
|
+
}
|
|
250
|
+
if (this.resumeSnapshot && this.resumeSnapshot.status === "running") {
|
|
251
|
+
this.resumeSnapshot = {
|
|
252
|
+
...this.resumeSnapshot,
|
|
253
|
+
resumeState: null,
|
|
254
|
+
status: "idle"
|
|
255
|
+
};
|
|
256
|
+
this.notifyResumeSnapshotChanged();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Clear all state and return to idle. Also drops the client's in-memory
|
|
261
|
+
* resume snapshot, so a remount restores nothing. The server-side record is
|
|
262
|
+
* untouched — this client no longer writes one — so a full page reload under
|
|
263
|
+
* `persistence: true` re-hydrates the last generation again.
|
|
264
|
+
*/
|
|
265
|
+
reset() {
|
|
266
|
+
this.stop();
|
|
267
|
+
this.setResult(null);
|
|
268
|
+
this.input = null;
|
|
269
|
+
this.progress = null;
|
|
270
|
+
this.devtoolsBridge.resetRuns();
|
|
271
|
+
this.setJobId(null);
|
|
272
|
+
this.setVideoStatus(null);
|
|
273
|
+
this.setError(void 0);
|
|
274
|
+
this.setStatus("idle");
|
|
275
|
+
this.clearResumeSnapshot();
|
|
276
|
+
this.devtoolsBridge.emitState();
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Update options without recreating the client.
|
|
280
|
+
*/
|
|
281
|
+
updateOptions(options) {
|
|
282
|
+
if (options.body !== void 0) this.body = options.body ?? {};
|
|
283
|
+
if (options.onResult !== void 0) this.callbacksRef.onResult = options.onResult;
|
|
284
|
+
if (options.onError !== void 0) this.callbacksRef.onError = options.onError;
|
|
285
|
+
if (options.onProgress !== void 0) this.callbacksRef.onProgress = options.onProgress;
|
|
286
|
+
if (options.onChunk !== void 0) this.callbacksRef.onChunk = options.onChunk;
|
|
287
|
+
if (options.onJobCreated !== void 0) this.callbacksRef.onJobCreated = options.onJobCreated;
|
|
288
|
+
if (options.onStatusUpdate !== void 0) this.callbacksRef.onStatusUpdate = options.onStatusUpdate;
|
|
289
|
+
}
|
|
290
|
+
dispose() {
|
|
291
|
+
this.disposed = true;
|
|
292
|
+
if (this.abortController) {
|
|
293
|
+
this.abortController.abort();
|
|
294
|
+
this.abortController = null;
|
|
295
|
+
}
|
|
296
|
+
this.setIsLoading(false);
|
|
297
|
+
this.devtoolsBridge.dispose();
|
|
298
|
+
this.devtoolsMounted = false;
|
|
299
|
+
this.serverHydrationStarted = false;
|
|
300
|
+
this.rejoinedRunId = void 0;
|
|
301
|
+
}
|
|
302
|
+
getResult() {
|
|
303
|
+
return this.result;
|
|
304
|
+
}
|
|
305
|
+
getJobId() {
|
|
306
|
+
return this.jobId;
|
|
307
|
+
}
|
|
308
|
+
getVideoStatus() {
|
|
309
|
+
return this.videoStatus;
|
|
310
|
+
}
|
|
311
|
+
getIsLoading() {
|
|
312
|
+
return this.isLoading;
|
|
313
|
+
}
|
|
314
|
+
getError() {
|
|
315
|
+
return this.error;
|
|
316
|
+
}
|
|
317
|
+
getStatus() {
|
|
318
|
+
return this.status;
|
|
319
|
+
}
|
|
320
|
+
getResumeSnapshot() {
|
|
321
|
+
return this.resumeSnapshot ? {
|
|
322
|
+
...this.resumeSnapshot,
|
|
323
|
+
...this.resumeSnapshot.pendingArtifacts ? { pendingArtifacts: [...this.resumeSnapshot.pendingArtifacts] } : {},
|
|
324
|
+
...this.resumeSnapshot.result ? { result: {
|
|
325
|
+
...this.resumeSnapshot.result,
|
|
326
|
+
...this.resumeSnapshot.result.artifacts ? { artifacts: [...this.resumeSnapshot.result.artifacts] } : {}
|
|
327
|
+
} } : {},
|
|
328
|
+
...this.resumeSnapshot.error ? { error: { ...this.resumeSnapshot.error } } : {},
|
|
329
|
+
...this.resumeSnapshot.lastEvent ? { lastEvent: { ...this.resumeSnapshot.lastEvent } } : {}
|
|
330
|
+
} : void 0;
|
|
331
|
+
}
|
|
332
|
+
setResult(rawResult) {
|
|
333
|
+
if (rawResult === null) {
|
|
334
|
+
this.result = null;
|
|
335
|
+
this.callbacksRef.onResultChange?.(null);
|
|
336
|
+
this.devtoolsBridge.recordResultChange();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const completedStatus = this.createCompletedVideoStatus(rawResult);
|
|
340
|
+
if (this.progress?.value !== 100) this.setProgress(100, this.progress?.message);
|
|
341
|
+
this.setJobId(rawResult.jobId);
|
|
342
|
+
this.setVideoStatus(completedStatus);
|
|
343
|
+
if (this.callbacksRef.onResult) {
|
|
344
|
+
const transformed = this.callbacksRef.onResult(rawResult);
|
|
345
|
+
if (transformed === null) {
|
|
346
|
+
this.devtoolsBridge.emitState();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (transformed !== void 0) {
|
|
350
|
+
this.result = transformed;
|
|
351
|
+
this.callbacksRef.onResultChange?.(this.result);
|
|
352
|
+
this.devtoolsBridge.recordResultChange();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
this.result = rawResult;
|
|
357
|
+
this.callbacksRef.onResultChange?.(this.result);
|
|
358
|
+
this.devtoolsBridge.recordResultChange();
|
|
359
|
+
}
|
|
360
|
+
setJobId(jobId) {
|
|
361
|
+
this.jobId = jobId;
|
|
362
|
+
this.callbacksRef.onJobIdChange?.(jobId);
|
|
363
|
+
this.devtoolsBridge.recordJobIdChange();
|
|
364
|
+
}
|
|
365
|
+
setVideoStatus(status) {
|
|
366
|
+
this.videoStatus = status;
|
|
367
|
+
this.callbacksRef.onVideoStatusChange?.(status);
|
|
368
|
+
this.devtoolsBridge.recordVideoStatusChange();
|
|
369
|
+
}
|
|
370
|
+
setIsLoading(isLoading) {
|
|
371
|
+
this.isLoading = isLoading;
|
|
372
|
+
this.callbacksRef.onLoadingChange?.(isLoading);
|
|
373
|
+
this.devtoolsBridge.recordLoadingChange();
|
|
374
|
+
}
|
|
375
|
+
setError(error) {
|
|
376
|
+
this.error = error;
|
|
377
|
+
this.callbacksRef.onErrorChange?.(error);
|
|
378
|
+
this.devtoolsBridge.recordErrorChange(error);
|
|
379
|
+
}
|
|
380
|
+
setStatus(status) {
|
|
381
|
+
this.status = status;
|
|
382
|
+
this.callbacksRef.onStatusChange?.(status);
|
|
383
|
+
this.devtoolsBridge.recordStatusChange(status);
|
|
384
|
+
}
|
|
385
|
+
setProgress(value, message) {
|
|
386
|
+
this.progress = {
|
|
387
|
+
value,
|
|
388
|
+
...message ? { message } : {}
|
|
389
|
+
};
|
|
390
|
+
if (message === void 0) this.callbacksRef.onProgress?.(value);
|
|
391
|
+
else this.callbacksRef.onProgress?.(value, message);
|
|
392
|
+
this.devtoolsBridge.recordProgressChange();
|
|
393
|
+
}
|
|
394
|
+
createCompletedVideoStatus(result) {
|
|
395
|
+
return {
|
|
396
|
+
jobId: result.jobId,
|
|
397
|
+
status: result.status,
|
|
398
|
+
progress: 100,
|
|
399
|
+
url: result.url
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
createDevtoolsMetadata(metadata) {
|
|
403
|
+
return {
|
|
404
|
+
hookName: metadata?.hookName ?? "useGenerateVideo",
|
|
405
|
+
outputKind: metadata?.outputKind ?? "video",
|
|
406
|
+
...metadata?.framework ? { framework: metadata.framework } : {},
|
|
407
|
+
...metadata?.name ? { name: metadata.name } : {}
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
generateUniqueId(prefix) {
|
|
411
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
412
|
+
}
|
|
413
|
+
createRunContext(runId) {
|
|
414
|
+
return {
|
|
415
|
+
threadId: this.threadId,
|
|
416
|
+
runId
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
observeResumeSnapshot(chunk) {
|
|
420
|
+
this.resumeSnapshot = updateGenerationResumeSnapshot(this.resumeSnapshot, chunk);
|
|
421
|
+
this.notifyResumeSnapshotChanged();
|
|
422
|
+
}
|
|
423
|
+
/** Notify the internal snapshot listener AND emit the public resume state. */
|
|
424
|
+
notifyResumeSnapshotChanged() {
|
|
425
|
+
this.callbacksRef.onResumeSnapshotChange?.(this.resumeSnapshot);
|
|
426
|
+
this.emitResumeState();
|
|
427
|
+
}
|
|
428
|
+
/** Derive the public `resumeState` from the internal snapshot. */
|
|
429
|
+
emitResumeState() {
|
|
430
|
+
const snapshot = this.resumeSnapshot;
|
|
431
|
+
const state = snapshot?.resumeState;
|
|
432
|
+
const resumeState = state ? {
|
|
433
|
+
...state,
|
|
434
|
+
...snapshot?.pendingArtifacts && snapshot.pendingArtifacts.length > 0 ? { pendingArtifacts: [...snapshot.pendingArtifacts] } : {}
|
|
435
|
+
} : null;
|
|
436
|
+
this.callbacksRef.onResumeStateChange?.(resumeState);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Repaint the normal fields from a restored snapshot so a reload presents the
|
|
440
|
+
* video in `result` / `status` / `error` / `jobId`, never a snapshot object.
|
|
441
|
+
* `isLoading` stays false (no auto-tail). Not re-persisted (it came from
|
|
442
|
+
* storage / the server).
|
|
443
|
+
*
|
|
444
|
+
* A `complete` snapshot with no durable video artifact cannot be rebuilt, so
|
|
445
|
+
* it repaints as an error rather than a `success` with a `null` result — see
|
|
446
|
+
* the note on `GenerationClient.repaintFromSnapshot`.
|
|
447
|
+
*/
|
|
448
|
+
repaintFromSnapshot(snapshot) {
|
|
449
|
+
this.resumeSnapshot = snapshot;
|
|
450
|
+
this.notifyResumeSnapshotChanged();
|
|
451
|
+
this.setStatus(clientStateFromResumeStatus(snapshot.status));
|
|
452
|
+
this.setError(snapshot.error ? Object.assign(new Error(snapshot.error.message), snapshot.error.code ? { code: snapshot.error.code } : {}) : void 0);
|
|
453
|
+
if (snapshot.result?.providerJobId) this.setJobId(snapshot.result.providerJobId);
|
|
454
|
+
const restored = this.reconstructVideoResult(snapshot);
|
|
455
|
+
if (restored !== null) this.setResult(restored);
|
|
456
|
+
else if (snapshot.status === "complete") this.reportUnrestorableResult();
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Report a `complete` snapshot with no durable video artifact to rebuild
|
|
460
|
+
* from. Runs after the status/error repaint above, so it wins over the
|
|
461
|
+
* snapshot's own `complete` status.
|
|
462
|
+
*/
|
|
463
|
+
reportUnrestorableResult() {
|
|
464
|
+
const error = new Error(GENERATION_UNRESTORABLE_RESULT_MESSAGE);
|
|
465
|
+
this.setStatus("error");
|
|
466
|
+
this.setError(error);
|
|
467
|
+
this.callbacksRef.onError?.(error);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Repaint a restored snapshot (client store or server hydrate) and, when it
|
|
471
|
+
* reports a run still in flight, tail that run to completion via `joinRun`
|
|
472
|
+
* (from the connection, or the `joinRun` option when the transport can't
|
|
473
|
+
* carry one).
|
|
474
|
+
*
|
|
475
|
+
* A `running` snapshot that no `joinRun` handler can tail is repainted as an
|
|
476
|
+
* interrupted error instead of a `generating` status that would never
|
|
477
|
+
* settle: an interrupted generation cannot be resumed, only re-run.
|
|
478
|
+
*/
|
|
479
|
+
repaintRestoredSnapshot(snapshot, activeRunId) {
|
|
480
|
+
if (snapshot.status !== "running") {
|
|
481
|
+
this.repaintFromSnapshot(snapshot);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const joinRun = this.connection?.joinRun ?? this.joinRunHandler;
|
|
485
|
+
const runId = activeRunId ?? snapshot.resumeState?.runId;
|
|
486
|
+
if (runId && joinRun) {
|
|
487
|
+
this.repaintFromSnapshot(snapshot);
|
|
488
|
+
this.rejoinInFlight(runId);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
this.repaintFromSnapshot({
|
|
492
|
+
...snapshot,
|
|
493
|
+
resumeState: null,
|
|
494
|
+
status: "error",
|
|
495
|
+
error: { message: "The previous generation was interrupted before it finished and cannot be resumed — generate again to retry." }
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Rebuild a `VideoGenerateResult` from a restored snapshot: the video's bytes
|
|
500
|
+
* are served from the durable artifact URL, so the restored result renders
|
|
501
|
+
* from your own origin. Returns `null` when there is no durable video artifact.
|
|
502
|
+
*/
|
|
503
|
+
reconstructVideoResult(snapshot) {
|
|
504
|
+
const result = snapshot.result;
|
|
505
|
+
const artifacts = result?.artifacts ?? [];
|
|
506
|
+
const output = artifacts.find((a) => a.role === "output" && a.source.mediaType === "video" && a.url != null);
|
|
507
|
+
if (!output?.url) return null;
|
|
508
|
+
return {
|
|
509
|
+
jobId: result?.providerJobId ?? "",
|
|
510
|
+
status: "completed",
|
|
511
|
+
url: output.url,
|
|
512
|
+
...result?.expiresAt ? { expiresAt: new Date(result.expiresAt) } : {},
|
|
513
|
+
artifacts
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* The plain (non-Response) fetcher path never observes stream chunks, so
|
|
518
|
+
* the terminal snapshot is built here from the fetcher's own result. A
|
|
519
|
+
* stale `error` from a previous run is intentionally dropped — this run
|
|
520
|
+
* succeeded.
|
|
521
|
+
*/
|
|
522
|
+
completePlainFetcherResumeSnapshot(rawResult) {
|
|
523
|
+
const previous = this.resumeSnapshot;
|
|
524
|
+
const result = createGenerationResultSnapshot(rawResult);
|
|
525
|
+
this.resumeSnapshot = {
|
|
526
|
+
schemaVersion: 1,
|
|
527
|
+
resumeState: null,
|
|
528
|
+
status: "complete",
|
|
529
|
+
...previous?.activity ? { activity: previous.activity } : {},
|
|
530
|
+
...previous?.pendingArtifacts && previous.pendingArtifacts.length > 0 ? { pendingArtifacts: [...previous.pendingArtifacts] } : {},
|
|
531
|
+
...result ? { result } : previous?.result ? { result: { ...previous.result } } : {}
|
|
532
|
+
};
|
|
533
|
+
this.notifyResumeSnapshotChanged();
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Records a transport-level failure (network drop, throwing callback) in
|
|
537
|
+
* the snapshot. Without this, only a server-emitted RUN_ERROR chunk would
|
|
538
|
+
* mark the snapshot `error`, leaving a persisted record that claims the
|
|
539
|
+
* run is still in flight.
|
|
540
|
+
*/
|
|
541
|
+
recordResumeSnapshotError(error) {
|
|
542
|
+
if (this.status !== "error") this.setStatus("error");
|
|
543
|
+
this.setError(error);
|
|
544
|
+
if (this.resumeSnapshot?.status === "error") return;
|
|
545
|
+
if (!this.resumeSnapshot && !this.serverDriven) return;
|
|
546
|
+
const previous = this.resumeSnapshot;
|
|
547
|
+
this.resumeSnapshot = {
|
|
548
|
+
schemaVersion: 1,
|
|
549
|
+
resumeState: null,
|
|
550
|
+
status: "error",
|
|
551
|
+
...previous?.activity ? { activity: previous.activity } : {},
|
|
552
|
+
...previous?.pendingArtifacts && previous.pendingArtifacts.length > 0 ? { pendingArtifacts: [...previous.pendingArtifacts] } : {},
|
|
553
|
+
...previous?.result ? { result: { ...previous.result } } : {},
|
|
554
|
+
error: { message: error.message }
|
|
555
|
+
};
|
|
556
|
+
this.notifyResumeSnapshotChanged();
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Drop the client's in-memory snapshot and re-emit. Purely local — this
|
|
560
|
+
* client writes no storage, so nothing persisted is removed.
|
|
561
|
+
*/
|
|
562
|
+
clearResumeSnapshot() {
|
|
563
|
+
this.resumeSnapshot = void 0;
|
|
564
|
+
this.notifyResumeSnapshotChanged();
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Server-driven mount hydration entry point (`persistence: true`). Runs at
|
|
568
|
+
* most once, from the commit-phase mount path (`mountDevtools`) — never the
|
|
569
|
+
* constructor / render phase — so remounts and speculative renders can't
|
|
570
|
+
* re-fire the hydrate GET.
|
|
571
|
+
*/
|
|
572
|
+
maybeHydrateFromServer() {
|
|
573
|
+
if (!this.serverDriven || this.serverHydrationStarted) return;
|
|
574
|
+
this.serverHydrationStarted = true;
|
|
575
|
+
if (this.connection?.hydrateGeneration ?? this.hydrateGenerationHandler) this.hydrateFromServer();
|
|
576
|
+
else console.warn("[TanStack AI] `persistence: true` (server-driven) needs a `hydrateGeneration` handler — either a connection that implements one (e.g. `fetchServerSentEvents` / `fetchHttpStream`, or `stream()` / `rpcStream()` with persistence handlers) or the `hydrateGeneration` option. Without one, nothing is persisted or restored.");
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Server-driven mount hydration (`persistence: true`). The client holds no
|
|
580
|
+
* local snapshot; on mount it asks the server — keyed by the stable threadId —
|
|
581
|
+
* for the last generation's resume snapshot, validates it, and repaints it. It
|
|
582
|
+
* never auto-starts a run, and never blocks: a `generate()` that starts first
|
|
583
|
+
* owns the client and hydration backs off, mirroring the chat client.
|
|
584
|
+
*
|
|
585
|
+
* A genuine **miss** (no record for the thread) is silent; a genuine
|
|
586
|
+
* **failure** (transport error, authorize rejection, malformed body, a record
|
|
587
|
+
* the validator rejects) surfaces through `status` / `error` / `onError` — see
|
|
588
|
+
* the note on `GenerationClient.hydrateFromServer`.
|
|
589
|
+
*/
|
|
590
|
+
hydrateFromServer() {
|
|
591
|
+
const hydrate = this.connection?.hydrateGeneration ?? this.hydrateGenerationHandler;
|
|
592
|
+
if (!hydrate) return;
|
|
593
|
+
if (this.resumeSnapshot || this.isLoading || this.status !== "idle") return;
|
|
594
|
+
(async () => {
|
|
595
|
+
let res;
|
|
596
|
+
try {
|
|
597
|
+
res = await hydrate(this.threadId);
|
|
598
|
+
} catch (cause) {
|
|
599
|
+
this.failHydration(createGenerationHydrationError("the request to the server did not succeed", cause));
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (!res.resumeSnapshot) return;
|
|
603
|
+
const snapshot = parseGenerationResumeSnapshot(res.resumeSnapshot);
|
|
604
|
+
if (!snapshot) {
|
|
605
|
+
this.failHydration(createGenerationHydrationError("the server returned a record this client cannot read (unknown schema version, or a missing/invalid `status` or `resumeState`)"));
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (this.resumeSnapshot || this.isLoading || this.status !== "idle") return;
|
|
609
|
+
this.repaintRestoredSnapshot(snapshot, res.activeRun?.runId);
|
|
610
|
+
})();
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Surface a hydration failure on the observable fields. Skipped when a
|
|
614
|
+
* `generate()` took ownership while the hydrate GET was in flight.
|
|
615
|
+
*/
|
|
616
|
+
failHydration(error) {
|
|
617
|
+
if (this.resumeSnapshot || this.isLoading || this.status !== "idle") return;
|
|
618
|
+
this.setStatus("error");
|
|
619
|
+
this.setError(error);
|
|
620
|
+
this.callbacksRef.onError?.(error);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Re-attach to an already-loaded `running` snapshot (remount case); see the
|
|
624
|
+
* note in GenerationClient.maybeResumeInFlight. Guarded by `rejoinInFlight`.
|
|
625
|
+
*/
|
|
626
|
+
maybeResumeInFlight() {
|
|
627
|
+
if (this.resumeSnapshot?.status !== "running") return;
|
|
628
|
+
const runId = this.resumeSnapshot.resumeState?.runId;
|
|
629
|
+
if (runId) this.rejoinInFlight(runId);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Re-attach to a video run still generating and stream it to completion,
|
|
633
|
+
* mirroring the chat client's mount-time rejoin. Reuses `processStream`, so
|
|
634
|
+
* the job status and result repaint from the replayed chunks. A live
|
|
635
|
+
* `generate()` owns the client and is never stomped; a run is rejoined once.
|
|
636
|
+
*/
|
|
637
|
+
rejoinInFlight(runId) {
|
|
638
|
+
const joinRun = this.connection?.joinRun ?? this.joinRunHandler;
|
|
639
|
+
if (!joinRun) return;
|
|
640
|
+
if (this.rejoinedRunId === runId) return;
|
|
641
|
+
if (this.isLoading || this.abortController) return;
|
|
642
|
+
this.rejoinedRunId = runId;
|
|
643
|
+
const controller = new AbortController();
|
|
644
|
+
this.abortController = controller;
|
|
645
|
+
this.setIsLoading(true);
|
|
646
|
+
this.setStatus("generating");
|
|
647
|
+
(async () => {
|
|
648
|
+
try {
|
|
649
|
+
await this.processStream(joinRun(runId, controller.signal), runId, controller.signal);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if (!controller.signal.aborted) {
|
|
652
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
653
|
+
this.recordResumeSnapshotError(failure);
|
|
654
|
+
this.callbacksRef.onError?.(failure);
|
|
655
|
+
}
|
|
656
|
+
} finally {
|
|
657
|
+
if (this.abortController === controller) {
|
|
658
|
+
this.abortController = null;
|
|
659
|
+
this.setIsLoading(false);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
})();
|
|
663
|
+
}
|
|
381
664
|
};
|
|
382
|
-
//#
|
|
665
|
+
//#endregion
|
|
666
|
+
export { VideoGenerationClient };
|
|
667
|
+
|
|
668
|
+
//# sourceMappingURL=video-generation-client.js.map
|