@tanstack/ai-client 0.23.2 → 0.24.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 +10 -1
- package/dist/esm/chat-client.d.ts +6 -8
- package/dist/esm/chat-client.js +32 -18
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +49 -0
- package/dist/esm/connection-adapters.js +266 -26
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/devtools.d.ts +0 -1
- package/dist/esm/devtools.js +11 -14
- package/dist/esm/devtools.js.map +1 -1
- package/dist/esm/generation-client.d.ts +5 -11
- package/dist/esm/generation-client.js +18 -6
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-types.d.ts +15 -45
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/types.d.ts +27 -36
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +5 -11
- package/dist/esm/video-generation-client.js +18 -6
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +5 -5
- package/src/chat-client.ts +52 -37
- package/src/connection-adapters.ts +491 -45
- package/src/devtools.ts +6 -22
- package/src/generation-client.ts +36 -24
- package/src/generation-types.ts +15 -47
- package/src/index.ts +3 -0
- package/src/types.ts +34 -39
- package/src/video-generation-client.ts +33 -20
package/src/generation-client.ts
CHANGED
|
@@ -26,9 +26,11 @@ import type {
|
|
|
26
26
|
GenerationClientOptions,
|
|
27
27
|
GenerationClientState,
|
|
28
28
|
GenerationFetcher,
|
|
29
|
+
GenerationPersistenceOptions,
|
|
29
30
|
GenerationRestoredResult,
|
|
30
31
|
GenerationResumeSnapshot,
|
|
31
32
|
GenerationResumeState,
|
|
33
|
+
GenerationTransport,
|
|
32
34
|
} from './generation-types'
|
|
33
35
|
|
|
34
36
|
/**
|
|
@@ -108,10 +110,10 @@ export class GenerationClient<
|
|
|
108
110
|
private readonly joinRunHandler:
|
|
109
111
|
| ConnectConnectionAdapter['joinRun']
|
|
110
112
|
| undefined
|
|
111
|
-
private
|
|
113
|
+
private uniqueId: string
|
|
112
114
|
private readonly devtoolsMetadata: AIDevtoolsClientMetadata
|
|
113
115
|
private readonly devtoolsBridge: GenerationDevtoolsBridge<TOutput>
|
|
114
|
-
private
|
|
116
|
+
private threadId: string
|
|
115
117
|
private readonly persistenceScope: string | undefined
|
|
116
118
|
// Server-driven mode (`persistence: true`): no local snapshot store; on mount
|
|
117
119
|
// the client hydrates the last generation for `threadId` from the server.
|
|
@@ -133,26 +135,20 @@ export class GenerationClient<
|
|
|
133
135
|
private serverHydrationStarted = false
|
|
134
136
|
|
|
135
137
|
constructor(
|
|
136
|
-
options:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
),
|
|
138
|
+
options: Omit<
|
|
139
|
+
GenerationClientOptions<TInput, TResult, TOutput>,
|
|
140
|
+
'persistence' | 'threadId'
|
|
141
|
+
> &
|
|
142
|
+
GenerationPersistenceOptions &
|
|
143
|
+
GenerationTransport<TInput, TResult>,
|
|
144
144
|
) {
|
|
145
|
-
// `threadId` is the
|
|
146
|
-
//
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
this.threadId = options.threadId ?? this.uniqueId
|
|
153
|
-
// The persistence scope: the explicit `threadId` and nothing else. The
|
|
154
|
-
// types require it whenever `persistence` is set; this field keeps the
|
|
155
|
-
// fallback from silently becoming a storage key for JS callers.
|
|
145
|
+
// `threadId` is the only identity. Do not mint a random id during
|
|
146
|
+
// construct: hooks build this client during render.
|
|
147
|
+
this.threadId = options.threadId ?? ''
|
|
148
|
+
this.uniqueId = this.threadId
|
|
149
|
+
// The persistence scope is the explicit `threadId` and nothing else.
|
|
150
|
+
// The types require it whenever `persistence` is set. This field keeps a
|
|
151
|
+
// generated wire id from becoming a storage key for JS callers.
|
|
156
152
|
this.persistenceScope = options.threadId
|
|
157
153
|
this.connection = options.connection
|
|
158
154
|
this.fetcher = options.fetcher
|
|
@@ -199,10 +195,17 @@ export class GenerationClient<
|
|
|
199
195
|
}
|
|
200
196
|
|
|
201
197
|
private buildDevtoolsBridgeOptions(): GenerationDevtoolsBridgeOptions<TOutput> {
|
|
198
|
+
const client = this
|
|
202
199
|
return {
|
|
203
|
-
hookId
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
get hookId() {
|
|
201
|
+
return client.uniqueId
|
|
202
|
+
},
|
|
203
|
+
get clientId() {
|
|
204
|
+
return client.uniqueId
|
|
205
|
+
},
|
|
206
|
+
get threadId() {
|
|
207
|
+
return client.threadId
|
|
208
|
+
},
|
|
206
209
|
metadata: this.devtoolsMetadata,
|
|
207
210
|
getCoreState: () => ({
|
|
208
211
|
input: this.input,
|
|
@@ -216,6 +219,7 @@ export class GenerationClient<
|
|
|
216
219
|
}
|
|
217
220
|
|
|
218
221
|
mountDevtools(): void {
|
|
222
|
+
this.ensureThreadId()
|
|
219
223
|
// Mounting revives a disposed client. Framework hooks call this from
|
|
220
224
|
// their mount effect, so a dispose → remount cycle (e.g. React
|
|
221
225
|
// StrictMode's mount → cleanup → mount replay against the same memoized
|
|
@@ -629,6 +633,14 @@ export class GenerationClient<
|
|
|
629
633
|
}
|
|
630
634
|
}
|
|
631
635
|
|
|
636
|
+
private ensureThreadId(): string {
|
|
637
|
+
if (!this.threadId) {
|
|
638
|
+
this.threadId = this.generateUniqueId('generation')
|
|
639
|
+
}
|
|
640
|
+
this.uniqueId = this.threadId
|
|
641
|
+
return this.threadId
|
|
642
|
+
}
|
|
643
|
+
|
|
632
644
|
private generateUniqueId(prefix: string): string {
|
|
633
645
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`
|
|
634
646
|
}
|
package/src/generation-types.ts
CHANGED
|
@@ -201,27 +201,23 @@ export interface GenerationResumeSnapshot {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
|
-
* The `persistence` / `threadId`
|
|
204
|
+
* The `persistence` / `threadId` identity shared by every generation hook.
|
|
205
205
|
*
|
|
206
206
|
* Turning persistence on **requires** a `threadId`, the stable scope runs are
|
|
207
207
|
* filed under. Without one the client would hydrate by a generated id that
|
|
208
208
|
* changes every reload, so nothing would ever restore; making it a type error
|
|
209
209
|
* means the compiler asks for the scope instead of the runtime inventing one.
|
|
210
210
|
*
|
|
211
|
-
* `threadId` is the
|
|
212
|
-
* persistence.
|
|
213
|
-
* `threadId` is supplied — pass one scope, not two.
|
|
211
|
+
* `threadId` is the only identity for the hook, the AG-UI wire thread, and
|
|
212
|
+
* persistence. There is no separate instance `id`.
|
|
214
213
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* USAGE: intersect this onto a hook's parameter and subtract the three keys
|
|
220
|
-
* from the options interface, leaving that interface a plain (non-union)
|
|
214
|
+
* USAGE: intersect this onto a hook's parameter and subtract `persistence`
|
|
215
|
+
* and `threadId` from the options interface (plus any other keys the hook
|
|
216
|
+
* redeclares, such as `onResult`), leaving that interface a plain (non-union)
|
|
221
217
|
* object so `Pick` / `Omit` composition elsewhere keeps working:
|
|
222
218
|
*
|
|
223
219
|
* ```ts
|
|
224
|
-
* options: Omit<UseGenerateImageOptions, 'onResult' | 'persistence' | 'threadId'
|
|
220
|
+
* options: Omit<UseGenerateImageOptions, 'onResult' | 'persistence' | 'threadId'> & {
|
|
225
221
|
* onResult?: (result: ImageGenerationResult) => TTransformed
|
|
226
222
|
* } & GenerationPersistenceOptions
|
|
227
223
|
* ```
|
|
@@ -233,31 +229,13 @@ export interface GenerationResumeSnapshot {
|
|
|
233
229
|
export type GenerationPersistenceOptions =
|
|
234
230
|
| {
|
|
235
231
|
persistence: true
|
|
236
|
-
/** Required by `persistence
|
|
237
|
-
threadId: string
|
|
238
|
-
/**
|
|
239
|
-
* @deprecated Prefer `threadId`. Not allowed when `threadId` is set —
|
|
240
|
-
* `threadId` is the single identity for the hook, the wire, and persistence.
|
|
241
|
-
*/
|
|
242
|
-
id?: never
|
|
243
|
-
}
|
|
244
|
-
| {
|
|
245
|
-
persistence?: false | undefined
|
|
246
|
-
/** Stable scope for the generation slot (also the wire / devtools identity). */
|
|
232
|
+
/** Required by `persistence`. The stable scope runs are filed under. */
|
|
247
233
|
threadId: string
|
|
248
|
-
/**
|
|
249
|
-
* @deprecated Prefer `threadId`. Not allowed when `threadId` is set.
|
|
250
|
-
*/
|
|
251
|
-
id?: never
|
|
252
234
|
}
|
|
253
235
|
| {
|
|
254
236
|
persistence?: false | undefined
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
* @deprecated Prefer `threadId` as the single identity. Only allowed when
|
|
258
|
-
* `threadId` is omitted — legacy wire/devtools fallback for ephemeral runs.
|
|
259
|
-
*/
|
|
260
|
-
id?: string
|
|
237
|
+
/** Stable scope for the generation slot (also the wire / DevTools identity). */
|
|
238
|
+
threadId?: string
|
|
261
239
|
}
|
|
262
240
|
|
|
263
241
|
// ===========================
|
|
@@ -330,28 +308,19 @@ export type GenerationTransport<TInput, TResult> =
|
|
|
330
308
|
*/
|
|
331
309
|
// eslint-disable-next-line @typescript-eslint/naming-convention -- _TInput is unused in the interface body but part of the public positional generic API (callers supply it for inference)
|
|
332
310
|
export interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {
|
|
333
|
-
/**
|
|
334
|
-
* @deprecated Prefer {@link GenerationClientOptions.threadId}. Legacy instance
|
|
335
|
-
* id used only as a wire/devtools fallback when `threadId` is omitted. When
|
|
336
|
-
* both are passed, `threadId` wins and `id` is ignored. Framework hooks type
|
|
337
|
-
* `id` as `never` whenever `threadId` is set — see
|
|
338
|
-
* {@link GenerationPersistenceOptions}.
|
|
339
|
-
*/
|
|
340
|
-
id?: string
|
|
341
|
-
|
|
342
311
|
/**
|
|
343
312
|
* The **scope** this generation belongs to: a stable, app-chosen name for the
|
|
344
313
|
* slot successive runs fill, not a link to a chat conversation. This is the
|
|
345
|
-
*
|
|
314
|
+
* only identity for the client: wire thread id, DevTools hook id, and
|
|
346
315
|
* persistence key.
|
|
347
316
|
*
|
|
348
|
-
* A generation hook starts empty and produces many runs over its life
|
|
317
|
+
* A generation hook starts empty and produces many runs over its life. Each
|
|
349
318
|
* run gets its own `runId`, but they all belong to one scope. Persistence
|
|
350
319
|
* keys on this: server-driven hydrates the last run for it on mount. It is
|
|
351
320
|
* also sent as the AG-UI thread id on the wire, since the protocol requires
|
|
352
321
|
* one.
|
|
353
322
|
*
|
|
354
|
-
* Derive it from your own domain
|
|
323
|
+
* Derive it from your own domain. It must be meaningful before any media
|
|
355
324
|
* exists and identical after a reload:
|
|
356
325
|
*
|
|
357
326
|
* ```ts
|
|
@@ -360,9 +329,8 @@ export interface GenerationClientOptions<_TInput, TResult, TOutput = TResult> {
|
|
|
360
329
|
*
|
|
361
330
|
* **Required whenever `persistence` is set.** An app that cannot name the
|
|
362
331
|
* scope has nothing to restore *to*, and a generated fallback would key each
|
|
363
|
-
* reload differently
|
|
364
|
-
* ephemeral runs,
|
|
365
|
-
* purely to satisfy the wire and nothing is written.
|
|
332
|
+
* reload differently, silently restoring nothing. Optional only for
|
|
333
|
+
* ephemeral runs. If omitted, the client mints a wire id after mount.
|
|
366
334
|
*/
|
|
367
335
|
threadId?: string
|
|
368
336
|
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export type {
|
|
|
30
30
|
ChatClientPersistence,
|
|
31
31
|
ChatPersistedState,
|
|
32
32
|
ChatPersistenceOption,
|
|
33
|
+
ChatPersistenceOptions,
|
|
33
34
|
ChatStorageAdapter,
|
|
34
35
|
ChatClientOptions,
|
|
35
36
|
ChatPendingInterrupt,
|
|
@@ -143,6 +144,7 @@ export {
|
|
|
143
144
|
xhrHttpStream,
|
|
144
145
|
stream,
|
|
145
146
|
rpcStream,
|
|
147
|
+
webSocket,
|
|
146
148
|
StreamTruncatedError,
|
|
147
149
|
DurableStreamIncompleteError,
|
|
148
150
|
StreamReconnectLimitError,
|
|
@@ -155,6 +157,7 @@ export {
|
|
|
155
157
|
type RunAgentInputContext,
|
|
156
158
|
type StreamConnectionHandlers,
|
|
157
159
|
type SubscribeConnectionAdapter,
|
|
160
|
+
type WebSocketConnectionOptions,
|
|
158
161
|
type XhrConnectionOptions,
|
|
159
162
|
} from './connection-adapters'
|
|
160
163
|
|
package/src/types.ts
CHANGED
|
@@ -628,6 +628,36 @@ export type ChatPersistenceOption<
|
|
|
628
628
|
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
629
629
|
> = boolean | ChatClientPersistence<TTools>
|
|
630
630
|
|
|
631
|
+
/**
|
|
632
|
+
* The `persistence` / `threadId` pairing for `ChatClient` and the chat hooks.
|
|
633
|
+
*
|
|
634
|
+
* Persistence that is on (`true` or a storage adapter) requires a `threadId`.
|
|
635
|
+
* A minted id changes every reload, so nothing would restore. The compiler
|
|
636
|
+
* asks for the conversation id instead.
|
|
637
|
+
*
|
|
638
|
+
* Omit `persistence`, or set it to `false`, and `threadId` stays optional.
|
|
639
|
+
* The client then mints one after mount for the wire and DevTools.
|
|
640
|
+
*
|
|
641
|
+
* Intersect this onto `ChatClientOptions`. Do not apply a later plain `Omit`
|
|
642
|
+
* to that type: it collapses the union and the requirement disappears. Use
|
|
643
|
+
* {@link DistributedOmit}.
|
|
644
|
+
*/
|
|
645
|
+
export type ChatPersistenceOptions<
|
|
646
|
+
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
647
|
+
> =
|
|
648
|
+
| {
|
|
649
|
+
persistence: true
|
|
650
|
+
threadId: string
|
|
651
|
+
}
|
|
652
|
+
| {
|
|
653
|
+
persistence: ChatClientPersistence<TTools>
|
|
654
|
+
threadId: string
|
|
655
|
+
}
|
|
656
|
+
| {
|
|
657
|
+
persistence?: false | undefined
|
|
658
|
+
threadId?: string
|
|
659
|
+
}
|
|
660
|
+
|
|
631
661
|
type IsUnknown<T> = unknown extends T
|
|
632
662
|
? [T] extends [unknown]
|
|
633
663
|
? true
|
|
@@ -718,43 +748,6 @@ export interface ChatClientBaseOptions<
|
|
|
718
748
|
*/
|
|
719
749
|
initialMessages?: Array<UIMessage<TTools>>
|
|
720
750
|
|
|
721
|
-
/**
|
|
722
|
-
* How this chat persists across reloads. See {@link ChatPersistenceOption}.
|
|
723
|
-
*
|
|
724
|
-
* - Omit or `false`: ephemeral, in-memory only.
|
|
725
|
-
* - `true`: server-authoritative. The client caches nothing and hydrates the
|
|
726
|
-
* thread from the server by its `threadId` on mount (needs a connection with
|
|
727
|
-
* a `hydrate` handler). Big transcripts never touch the browser, and the same
|
|
728
|
-
* thread opens the same way on another device.
|
|
729
|
-
* - a {@link ChatClientPersistence} adapter: client-authoritative. The combined
|
|
730
|
-
* {@link ChatPersistedState} record (transcript plus resume pointer) is cached
|
|
731
|
-
* in the browser, restoring the transcript, pending interrupts, and an
|
|
732
|
-
* in-flight run on reload.
|
|
733
|
-
*
|
|
734
|
-
* Use `initialResumeSnapshot` for a host-supplied in-memory rehydrate instead.
|
|
735
|
-
*/
|
|
736
|
-
persistence?: ChatPersistenceOption<TTools>
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
* Optional storage-key override for this chat instance, and the devtools
|
|
740
|
-
* instance id. Persistence keys on `threadId` by default; set `id` only when
|
|
741
|
-
* you need the persisted record keyed separately from the wire thread.
|
|
742
|
-
* Prefer a stable `threadId` for the common case.
|
|
743
|
-
*
|
|
744
|
-
* The framework hooks (`useChat` / `createChat`) do NOT expose `id`: a hook's
|
|
745
|
-
* identity is its `threadId`. This lower-level escape hatch exists only for
|
|
746
|
-
* direct `ChatClient` construction.
|
|
747
|
-
*/
|
|
748
|
-
id?: string
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* The conversation id for this chat, stable across sends and reloads. It is
|
|
752
|
-
* the AG-UI thread key on the wire AND the key client persistence stores the
|
|
753
|
-
* conversation under, so set a stable `threadId` to have a reload restore the
|
|
754
|
-
* same conversation. If omitted, a unique thread id is generated per session.
|
|
755
|
-
*/
|
|
756
|
-
threadId?: string
|
|
757
|
-
|
|
758
751
|
/**
|
|
759
752
|
* Initial resumable run state, useful when rehydrating a persisted client
|
|
760
753
|
* after a full page reload. This restores the client-side interrupt
|
|
@@ -932,14 +925,16 @@ export interface ChatClientBaseOptions<
|
|
|
932
925
|
|
|
933
926
|
/**
|
|
934
927
|
* Options for `ChatClient`. Exactly one of `connection` or `fetcher` must be
|
|
935
|
-
* provided — the type-level XOR is enforced via `ChatTransport`.
|
|
928
|
+
* provided — the type-level XOR is enforced via `ChatTransport`. Persistence
|
|
929
|
+
* that is on requires a `threadId` via {@link ChatPersistenceOptions}.
|
|
936
930
|
*/
|
|
937
931
|
export type ChatClientOptions<
|
|
938
932
|
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
939
933
|
TContext = InferredClientContext<TTools>,
|
|
940
934
|
> = DistributedOmit<ChatClientBaseOptions<TTools, TContext>, 'context'> &
|
|
941
935
|
ClientContextOptionFromTools<TTools, TContext> &
|
|
942
|
-
ChatTransport
|
|
936
|
+
ChatTransport &
|
|
937
|
+
ChatPersistenceOptions<TTools>
|
|
943
938
|
|
|
944
939
|
export interface ChatRequestBody {
|
|
945
940
|
messages: Array<ModelMessage>
|
|
@@ -25,8 +25,10 @@ import type {
|
|
|
25
25
|
import type {
|
|
26
26
|
GenerationClientState,
|
|
27
27
|
GenerationFetcher,
|
|
28
|
+
GenerationPersistenceOptions,
|
|
28
29
|
GenerationResumeSnapshot,
|
|
29
30
|
GenerationResumeState,
|
|
31
|
+
GenerationTransport,
|
|
30
32
|
VideoGenerateInput,
|
|
31
33
|
VideoGenerateResult,
|
|
32
34
|
VideoGenerationClientOptions,
|
|
@@ -111,10 +113,10 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
111
113
|
private readonly fetcher:
|
|
112
114
|
| GenerationFetcher<VideoGenerateInput, VideoGenerateResult>
|
|
113
115
|
| undefined
|
|
114
|
-
private
|
|
116
|
+
private uniqueId: string
|
|
115
117
|
private readonly devtoolsMetadata: AIDevtoolsClientMetadata
|
|
116
118
|
private readonly devtoolsBridge: VideoDevtoolsBridge<TOutput>
|
|
117
|
-
private
|
|
119
|
+
private threadId: string
|
|
118
120
|
// Server-driven mode (`persistence: true`): no local snapshot store; on mount
|
|
119
121
|
// the client hydrates the last generation for `threadId` from the server.
|
|
120
122
|
private readonly serverDriven: boolean = false
|
|
@@ -137,22 +139,17 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
137
139
|
private serverHydrationStarted = false
|
|
138
140
|
|
|
139
141
|
constructor(
|
|
140
|
-
options:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
),
|
|
142
|
+
options: Omit<
|
|
143
|
+
VideoGenerationClientOptions<TOutput>,
|
|
144
|
+
'persistence' | 'threadId'
|
|
145
|
+
> &
|
|
146
|
+
GenerationPersistenceOptions &
|
|
147
|
+
GenerationTransport<VideoGenerateInput, VideoGenerateResult>,
|
|
148
148
|
) {
|
|
149
|
-
// `threadId` is the
|
|
150
|
-
//
|
|
151
|
-
this.
|
|
152
|
-
|
|
153
|
-
// The wire/hydration thread key. Server-driven mode needs a stable key, so
|
|
154
|
-
// prefer an explicit `threadId`, then legacy `id`, then a generated id.
|
|
155
|
-
this.threadId = options.threadId ?? this.uniqueId
|
|
149
|
+
// `threadId` is the only identity. Do not mint a random id during
|
|
150
|
+
// construct: hooks build this client during render.
|
|
151
|
+
this.threadId = options.threadId ?? ''
|
|
152
|
+
this.uniqueId = this.threadId
|
|
156
153
|
this.connection = options.connection
|
|
157
154
|
this.fetcher = options.fetcher
|
|
158
155
|
this.hydrateGenerationHandler = options.hydrateGeneration
|
|
@@ -192,10 +189,17 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
192
189
|
}
|
|
193
190
|
|
|
194
191
|
private buildDevtoolsBridgeOptions(): VideoDevtoolsBridgeOptions<TOutput> {
|
|
192
|
+
const client = this
|
|
195
193
|
return {
|
|
196
|
-
hookId
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
get hookId() {
|
|
195
|
+
return client.uniqueId
|
|
196
|
+
},
|
|
197
|
+
get clientId() {
|
|
198
|
+
return client.uniqueId
|
|
199
|
+
},
|
|
200
|
+
get threadId() {
|
|
201
|
+
return client.threadId
|
|
202
|
+
},
|
|
199
203
|
metadata: this.devtoolsMetadata,
|
|
200
204
|
getCoreState: () => ({
|
|
201
205
|
input: this.input,
|
|
@@ -211,6 +215,7 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
211
215
|
}
|
|
212
216
|
|
|
213
217
|
mountDevtools(): void {
|
|
218
|
+
this.ensureThreadId()
|
|
214
219
|
// Mounting revives a disposed client. Framework hooks call this from
|
|
215
220
|
// their mount effect, so a dispose → remount cycle (e.g. React
|
|
216
221
|
// StrictMode's mount → cleanup → mount replay against the same memoized
|
|
@@ -677,6 +682,14 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
677
682
|
}
|
|
678
683
|
}
|
|
679
684
|
|
|
685
|
+
private ensureThreadId(): string {
|
|
686
|
+
if (!this.threadId) {
|
|
687
|
+
this.threadId = this.generateUniqueId('video')
|
|
688
|
+
}
|
|
689
|
+
this.uniqueId = this.threadId
|
|
690
|
+
return this.threadId
|
|
691
|
+
}
|
|
692
|
+
|
|
680
693
|
private generateUniqueId(prefix: string): string {
|
|
681
694
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`
|
|
682
695
|
}
|