@tanstack/ai-client 0.12.0 → 0.14.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/dist/esm/chat-client.d.ts +16 -2
- package/dist/esm/chat-client.js +197 -31
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +21 -1
- package/dist/esm/connection-adapters.js +262 -68
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/devtools-noop.d.ts +60 -0
- package/dist/esm/devtools-noop.js +135 -0
- package/dist/esm/devtools-noop.js.map +1 -0
- package/dist/esm/devtools.d.ts +276 -0
- package/dist/esm/devtools.js +1178 -0
- package/dist/esm/devtools.js.map +1 -0
- package/dist/esm/events.d.ts +51 -8
- package/dist/esm/events.js +65 -20
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/generation-client.d.ts +14 -0
- package/dist/esm/generation-client.js +135 -5
- package/dist/esm/generation-client.js.map +1 -1
- package/dist/esm/generation-types.d.ts +16 -2
- package/dist/esm/generation-types.js.map +1 -1
- package/dist/esm/index.d.ts +6 -4
- package/dist/esm/index.js +9 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/realtime-client.d.ts +1 -1
- package/dist/esm/realtime-client.js +1 -1
- package/dist/esm/realtime-client.js.map +1 -1
- package/dist/esm/realtime-types.d.ts +1 -1
- package/dist/esm/response-stream.d.ts +7 -0
- package/dist/esm/response-stream.js +32 -0
- package/dist/esm/response-stream.js.map +1 -0
- package/dist/esm/sse-parser.d.ts +1 -1
- package/dist/esm/sse-parser.js +5 -6
- package/dist/esm/sse-parser.js.map +1 -1
- package/dist/esm/sse-utils.d.ts +1 -0
- package/dist/esm/sse-utils.js +11 -0
- package/dist/esm/sse-utils.js.map +1 -0
- package/dist/esm/tool-types.d.ts +1 -1
- package/dist/esm/types.d.ts +23 -5
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/video-generation-client.d.ts +15 -0
- package/dist/esm/video-generation-client.js +147 -8
- package/dist/esm/video-generation-client.js.map +1 -1
- package/package.json +7 -3
- package/src/chat-client.ts +250 -33
- package/src/connection-adapters.ts +360 -68
- package/src/devtools-noop.ts +205 -0
- package/src/devtools.ts +1866 -0
- package/src/events.ts +143 -12
- package/src/generation-client.ts +177 -10
- package/src/generation-types.ts +24 -5
- package/src/index.ts +16 -3
- package/src/realtime-client.ts +2 -2
- package/src/realtime-types.ts +1 -1
- package/src/response-stream.ts +42 -0
- package/src/sse-parser.ts +9 -7
- package/src/sse-utils.ts +8 -0
- package/src/tool-types.ts +1 -1
- package/src/types.ts +31 -4
- package/src/video-generation-client.ts +187 -12
package/src/index.ts
CHANGED
|
@@ -43,13 +43,23 @@ export type {
|
|
|
43
43
|
VideoGenerateInput,
|
|
44
44
|
} from './generation-types'
|
|
45
45
|
export { GENERATION_EVENTS } from './generation-types'
|
|
46
|
+
export { UnsupportedResponseStreamError } from './response-stream'
|
|
46
47
|
export { clientTools, createChatClientOptions } from './types'
|
|
48
|
+
export {
|
|
49
|
+
createAIDevtoolsGenerationPreview,
|
|
50
|
+
type AIDevtoolsClientMetadata,
|
|
51
|
+
type AIDevtoolsDisplayOptions,
|
|
52
|
+
type AIDevtoolsGenerationMediaItem,
|
|
53
|
+
type AIDevtoolsGenerationPreview,
|
|
54
|
+
type AIDevtoolsGenerationProgress,
|
|
55
|
+
type AIDevtoolsGenerationVideoJob,
|
|
56
|
+
} from './devtools'
|
|
47
57
|
export type {
|
|
48
58
|
ExtractToolNames,
|
|
49
59
|
ExtractToolInput,
|
|
50
60
|
ExtractToolOutput,
|
|
51
61
|
} from './tool-types'
|
|
52
|
-
export type { AnyClientTool } from '@tanstack/ai'
|
|
62
|
+
export type { AnyClientTool } from '@tanstack/ai/client'
|
|
53
63
|
export type {
|
|
54
64
|
RealtimeAdapter,
|
|
55
65
|
RealtimeConnection,
|
|
@@ -60,6 +70,8 @@ export type {
|
|
|
60
70
|
export {
|
|
61
71
|
fetchServerSentEvents,
|
|
62
72
|
fetchHttpStream,
|
|
73
|
+
xhrServerSentEvents,
|
|
74
|
+
xhrHttpStream,
|
|
63
75
|
stream,
|
|
64
76
|
rpcStream,
|
|
65
77
|
StreamTruncatedError,
|
|
@@ -68,6 +80,7 @@ export {
|
|
|
68
80
|
type FetchConnectionOptions,
|
|
69
81
|
type RunAgentInputContext,
|
|
70
82
|
type SubscribeConnectionAdapter,
|
|
83
|
+
type XhrConnectionOptions,
|
|
71
84
|
} from './connection-adapters'
|
|
72
85
|
|
|
73
86
|
// Re-export message converters from @tanstack/ai
|
|
@@ -78,7 +91,7 @@ export {
|
|
|
78
91
|
convertMessagesToModelMessages,
|
|
79
92
|
normalizeToUIMessage,
|
|
80
93
|
generateMessageId,
|
|
81
|
-
} from '@tanstack/ai'
|
|
94
|
+
} from '@tanstack/ai/client'
|
|
82
95
|
|
|
83
96
|
// Re-export stream processing from @tanstack/ai (shared implementation)
|
|
84
97
|
export {
|
|
@@ -101,4 +114,4 @@ export {
|
|
|
101
114
|
type ChunkRecording,
|
|
102
115
|
type ProcessorResult,
|
|
103
116
|
type ProcessorState,
|
|
104
|
-
} from '@tanstack/ai'
|
|
117
|
+
} from '@tanstack/ai/client'
|
package/src/realtime-client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { convertSchemaToJsonSchema } from '@tanstack/ai'
|
|
1
|
+
import { convertSchemaToJsonSchema } from '@tanstack/ai/client'
|
|
2
2
|
import type {
|
|
3
3
|
AnyClientTool,
|
|
4
4
|
AudioVisualization,
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
RealtimeMode,
|
|
7
7
|
RealtimeStatus,
|
|
8
8
|
RealtimeToken,
|
|
9
|
-
} from '@tanstack/ai'
|
|
9
|
+
} from '@tanstack/ai/client'
|
|
10
10
|
import type {
|
|
11
11
|
RealtimeClientOptions,
|
|
12
12
|
RealtimeClientState,
|
package/src/realtime-types.ts
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type MissingResponseStreamFeature =
|
|
2
|
+
| 'TextDecoder'
|
|
3
|
+
| 'Response.body'
|
|
4
|
+
| 'Response.body.getReader'
|
|
5
|
+
|
|
6
|
+
export class UnsupportedResponseStreamError extends Error {
|
|
7
|
+
readonly missingFeature: MissingResponseStreamFeature
|
|
8
|
+
|
|
9
|
+
constructor(missingFeature: MissingResponseStreamFeature) {
|
|
10
|
+
super(createUnsupportedResponseStreamMessage(missingFeature))
|
|
11
|
+
this.name = 'UnsupportedResponseStreamError'
|
|
12
|
+
this.missingFeature = missingFeature
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createUnsupportedResponseStreamMessage(
|
|
17
|
+
missingFeature: MissingResponseStreamFeature,
|
|
18
|
+
): string {
|
|
19
|
+
return `Streaming fetch responses are not supported in this runtime because ${missingFeature} is unavailable. React Native users need a compatible fetch/stream/TextDecoder polyfill, or should use a fetch, XHR, or custom transport that can deliver streaming chunks.`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getResponseStreamReader(
|
|
23
|
+
response: Response,
|
|
24
|
+
): ReadableStreamDefaultReader<Uint8Array> {
|
|
25
|
+
if (!response.body) {
|
|
26
|
+
throw new UnsupportedResponseStreamError('Response.body')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (typeof response.body.getReader !== 'function') {
|
|
30
|
+
throw new UnsupportedResponseStreamError('Response.body.getReader')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return response.body.getReader()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createResponseStreamTextDecoder(): TextDecoder {
|
|
37
|
+
if (typeof globalThis.TextDecoder !== 'function') {
|
|
38
|
+
throw new UnsupportedResponseStreamError('TextDecoder')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return new globalThis.TextDecoder()
|
|
42
|
+
}
|
package/src/sse-parser.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
createResponseStreamTextDecoder,
|
|
3
|
+
getResponseStreamReader,
|
|
4
|
+
} from './response-stream'
|
|
5
|
+
import { parseSseDataLine } from './sse-utils'
|
|
6
|
+
import type { StreamChunk } from '@tanstack/ai/client'
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* Read lines from a stream (newline-delimited)
|
|
@@ -8,7 +13,7 @@ async function* readStreamLines(
|
|
|
8
13
|
abortSignal?: AbortSignal,
|
|
9
14
|
): AsyncGenerator<string> {
|
|
10
15
|
try {
|
|
11
|
-
const decoder =
|
|
16
|
+
const decoder = createResponseStreamTextDecoder()
|
|
12
17
|
let buffer = ''
|
|
13
18
|
|
|
14
19
|
while (!abortSignal?.aborted) {
|
|
@@ -51,13 +56,10 @@ export async function* parseSSEResponse(
|
|
|
51
56
|
)
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
const reader = response
|
|
55
|
-
if (!reader) {
|
|
56
|
-
throw new Error('Response body is not readable')
|
|
57
|
-
}
|
|
59
|
+
const reader = getResponseStreamReader(response)
|
|
58
60
|
|
|
59
61
|
for await (const line of readStreamLines(reader, abortSignal)) {
|
|
60
|
-
const data =
|
|
62
|
+
const data = parseSseDataLine(line)
|
|
61
63
|
|
|
62
64
|
if (data === '[DONE]') {
|
|
63
65
|
console.warn(
|
package/src/sse-utils.ts
ADDED
package/src/tool-types.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -11,10 +11,12 @@ import type {
|
|
|
11
11
|
StreamChunk,
|
|
12
12
|
StructuredOutputPart,
|
|
13
13
|
VideoPart,
|
|
14
|
-
} from '@tanstack/ai'
|
|
14
|
+
} from '@tanstack/ai/client'
|
|
15
15
|
import type { ConnectionAdapter } from './connection-adapters'
|
|
16
|
+
import type { AIDevtoolsClientMetadata } from './devtools'
|
|
17
|
+
import type { ChatDevtoolsBridgeFactory } from './devtools-noop'
|
|
16
18
|
|
|
17
|
-
export type { StructuredOutputPart } from '@tanstack/ai'
|
|
19
|
+
export type { StructuredOutputPart } from '@tanstack/ai/client'
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* `messages` is the full UIMessage history (not a delta). `data` is the
|
|
@@ -270,6 +272,7 @@ export interface UIMessage<
|
|
|
270
272
|
*/
|
|
271
273
|
export type ChatClientOptions<
|
|
272
274
|
TTools extends ReadonlyArray<AnyClientTool> = any,
|
|
275
|
+
TContext = unknown,
|
|
273
276
|
> = {
|
|
274
277
|
/**
|
|
275
278
|
* Initial messages to populate the chat
|
|
@@ -387,6 +390,25 @@ export type ChatClientOptions<
|
|
|
387
390
|
*/
|
|
388
391
|
tools?: TTools
|
|
389
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Client-local context passed to client-side tool execution.
|
|
395
|
+
*/
|
|
396
|
+
context?: TContext
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Devtools hook metadata for this client instance.
|
|
400
|
+
*/
|
|
401
|
+
devtools?: Partial<AIDevtoolsClientMetadata>
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Factory that constructs the devtools bridge. Default is a no-op
|
|
405
|
+
* factory, which keeps `@tanstack/ai-client/devtools` (the heavy
|
|
406
|
+
* bridge implementation) out of the main entry's bundle. Frameworks
|
|
407
|
+
* that need live devtools should pass the real factory from
|
|
408
|
+
* `@tanstack/ai-client/devtools`.
|
|
409
|
+
*/
|
|
410
|
+
devtoolsBridgeFactory?: ChatDevtoolsBridgeFactory
|
|
411
|
+
|
|
390
412
|
/**
|
|
391
413
|
* Stream processing options (optional)
|
|
392
414
|
* Configure chunking strategy
|
|
@@ -444,7 +466,10 @@ export function clientTools<const T extends Array<AnyClientTool>>(
|
|
|
444
466
|
*/
|
|
445
467
|
export function createChatClientOptions<
|
|
446
468
|
const TTools extends ReadonlyArray<AnyClientTool>,
|
|
447
|
-
|
|
469
|
+
TContext = unknown,
|
|
470
|
+
>(
|
|
471
|
+
options: ChatClientOptions<TTools, TContext>,
|
|
472
|
+
): ChatClientOptions<TTools, TContext> {
|
|
448
473
|
return options
|
|
449
474
|
}
|
|
450
475
|
|
|
@@ -463,4 +488,6 @@ export function createChatClientOptions<
|
|
|
463
488
|
* ```
|
|
464
489
|
*/
|
|
465
490
|
export type InferChatMessages<T> =
|
|
466
|
-
T extends ChatClientOptions<infer TTools>
|
|
491
|
+
T extends ChatClientOptions<infer TTools, any>
|
|
492
|
+
? Array<UIMessage<TTools>>
|
|
493
|
+
: never
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { GENERATION_EVENTS } from './generation-types'
|
|
2
|
+
import { createNoOpVideoDevtoolsBridge } from './devtools-noop'
|
|
2
3
|
import { parseSSEResponse } from './sse-parser'
|
|
3
|
-
import type { StreamChunk } from '@tanstack/ai'
|
|
4
|
-
import type {
|
|
4
|
+
import type { StreamChunk } from '@tanstack/ai/client'
|
|
5
|
+
import type {
|
|
6
|
+
ConnectConnectionAdapter,
|
|
7
|
+
RunAgentInputContext,
|
|
8
|
+
} from './connection-adapters'
|
|
9
|
+
import type {
|
|
10
|
+
AIDevtoolsClientMetadata,
|
|
11
|
+
AIDevtoolsGenerationProgress,
|
|
12
|
+
VideoDevtoolsBridge,
|
|
13
|
+
VideoDevtoolsBridgeOptions,
|
|
14
|
+
} from './devtools'
|
|
5
15
|
import type {
|
|
6
16
|
GenerationClientState,
|
|
7
17
|
GenerationFetcher,
|
|
@@ -74,9 +84,15 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
74
84
|
private readonly fetcher:
|
|
75
85
|
| GenerationFetcher<VideoGenerateInput, VideoGenerateResult>
|
|
76
86
|
| undefined
|
|
87
|
+
private readonly uniqueId: string
|
|
88
|
+
private readonly devtoolsMetadata: AIDevtoolsClientMetadata
|
|
89
|
+
private readonly devtoolsBridge: VideoDevtoolsBridge<TOutput>
|
|
90
|
+
private readonly threadId: string
|
|
77
91
|
private body: Record<string, any>
|
|
78
92
|
|
|
79
93
|
private result: TOutput | null = null
|
|
94
|
+
private input: VideoGenerateInput | null = null
|
|
95
|
+
private progress: AIDevtoolsGenerationProgress | null = null
|
|
80
96
|
private jobId: string | null = null
|
|
81
97
|
private videoStatus: VideoStatusInfo | null = null
|
|
82
98
|
private isLoading = false
|
|
@@ -84,6 +100,7 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
84
100
|
private status: GenerationClientState = 'idle'
|
|
85
101
|
private abortController: AbortController | null = null
|
|
86
102
|
private readonly callbacksRef: VideoCallbacks<TOutput>
|
|
103
|
+
private devtoolsMounted = false
|
|
87
104
|
|
|
88
105
|
constructor(
|
|
89
106
|
options: VideoGenerationClientOptions<TOutput> &
|
|
@@ -95,6 +112,8 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
95
112
|
}
|
|
96
113
|
),
|
|
97
114
|
) {
|
|
115
|
+
this.uniqueId = options.id ?? this.generateUniqueId('video')
|
|
116
|
+
this.threadId = this.uniqueId
|
|
98
117
|
this.connection = options.connection
|
|
99
118
|
this.fetcher = options.fetcher
|
|
100
119
|
this.body = options.body ?? {}
|
|
@@ -113,6 +132,40 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
113
132
|
onJobIdChange: options.onJobIdChange,
|
|
114
133
|
onVideoStatusChange: options.onVideoStatusChange,
|
|
115
134
|
}
|
|
135
|
+
|
|
136
|
+
this.devtoolsMetadata = this.createDevtoolsMetadata(options.devtools)
|
|
137
|
+
this.devtoolsBridge = (
|
|
138
|
+
options.devtoolsBridgeFactory ?? createNoOpVideoDevtoolsBridge
|
|
139
|
+
)<TOutput>(this.buildDevtoolsBridgeOptions())
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private buildDevtoolsBridgeOptions(): VideoDevtoolsBridgeOptions<TOutput> {
|
|
143
|
+
return {
|
|
144
|
+
hookId: this.uniqueId,
|
|
145
|
+
clientId: this.uniqueId,
|
|
146
|
+
threadId: this.threadId,
|
|
147
|
+
metadata: this.devtoolsMetadata,
|
|
148
|
+
getCoreState: () => ({
|
|
149
|
+
input: this.input,
|
|
150
|
+
result: this.result,
|
|
151
|
+
progress: this.progress,
|
|
152
|
+
status: this.status,
|
|
153
|
+
isLoading: this.isLoading,
|
|
154
|
+
jobId: this.jobId,
|
|
155
|
+
videoStatus: this.videoStatus,
|
|
156
|
+
...(this.error ? { error: this.error.message } : {}),
|
|
157
|
+
}),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
mountDevtools(): void {
|
|
162
|
+
if (this.devtoolsMounted) {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.devtoolsMounted = true
|
|
167
|
+
this.devtoolsBridge.emitRegistered()
|
|
168
|
+
this.devtoolsBridge.emitSnapshot()
|
|
116
169
|
}
|
|
117
170
|
|
|
118
171
|
/**
|
|
@@ -120,8 +173,12 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
120
173
|
* Only one generation can be in-flight at a time.
|
|
121
174
|
*/
|
|
122
175
|
async generate(input: VideoGenerateInput): Promise<void> {
|
|
176
|
+
this.mountDevtools()
|
|
123
177
|
if (this.isLoading) return
|
|
124
178
|
|
|
179
|
+
this.input = input
|
|
180
|
+
this.progress = null
|
|
181
|
+
const runId = this.devtoolsBridge.beginRun(input)
|
|
125
182
|
this.setIsLoading(true)
|
|
126
183
|
this.setStatus('generating')
|
|
127
184
|
this.setError(undefined)
|
|
@@ -134,21 +191,39 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
134
191
|
|
|
135
192
|
try {
|
|
136
193
|
if (this.fetcher) {
|
|
137
|
-
await this.generateWithFetcher(input, signal)
|
|
194
|
+
await this.generateWithFetcher(input, signal, runId)
|
|
138
195
|
} else if (this.connection) {
|
|
139
196
|
const mergedData = { ...this.body, ...input }
|
|
140
|
-
const stream = this.connection.connect(
|
|
141
|
-
|
|
197
|
+
const stream = this.connection.connect(
|
|
198
|
+
[],
|
|
199
|
+
mergedData,
|
|
200
|
+
signal,
|
|
201
|
+
this.createRunContext(runId),
|
|
202
|
+
)
|
|
203
|
+
await this.processStream(stream, runId)
|
|
142
204
|
} else {
|
|
143
205
|
throw new Error(
|
|
144
206
|
'VideoGenerationClient requires either a connection or fetcher option',
|
|
145
207
|
)
|
|
146
208
|
}
|
|
147
|
-
|
|
209
|
+
if (!signal.aborted && this.status === 'success') {
|
|
210
|
+
this.devtoolsBridge.finishRun(
|
|
211
|
+
this.devtoolsBridge.getActiveRunId() ?? runId,
|
|
212
|
+
'run:completed',
|
|
213
|
+
'completed',
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
} catch (err: unknown) {
|
|
148
217
|
if (signal.aborted) return
|
|
149
218
|
const error = err instanceof Error ? err : new Error(String(err))
|
|
150
219
|
this.setError(error)
|
|
151
220
|
this.setStatus('error')
|
|
221
|
+
this.devtoolsBridge.finishRun(
|
|
222
|
+
this.devtoolsBridge.getActiveRunId() ?? runId,
|
|
223
|
+
'run:errored',
|
|
224
|
+
'errored',
|
|
225
|
+
error.message,
|
|
226
|
+
)
|
|
152
227
|
this.callbacksRef.onError?.(error)
|
|
153
228
|
} finally {
|
|
154
229
|
this.abortController = null
|
|
@@ -162,6 +237,7 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
162
237
|
private async generateWithFetcher(
|
|
163
238
|
input: VideoGenerateInput,
|
|
164
239
|
signal: AbortSignal,
|
|
240
|
+
runId: string,
|
|
165
241
|
): Promise<void> {
|
|
166
242
|
if (!this.fetcher) return
|
|
167
243
|
|
|
@@ -171,8 +247,9 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
171
247
|
|
|
172
248
|
if (result instanceof Response) {
|
|
173
249
|
// Server function returned SSE Response — parse stream
|
|
174
|
-
await this.processStream(parseSSEResponse(result, signal))
|
|
250
|
+
await this.processStream(parseSSEResponse(result, signal), runId)
|
|
175
251
|
} else {
|
|
252
|
+
this.devtoolsBridge.ensureRunStarted(runId)
|
|
176
253
|
this.setResult(result)
|
|
177
254
|
this.setStatus('success')
|
|
178
255
|
}
|
|
@@ -184,15 +261,28 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
184
261
|
*/
|
|
185
262
|
private async processStream(
|
|
186
263
|
source: AsyncIterable<StreamChunk>,
|
|
264
|
+
fallbackRunId: string,
|
|
187
265
|
): Promise<void> {
|
|
266
|
+
let streamRunId: string | undefined
|
|
267
|
+
|
|
188
268
|
for await (const chunk of source) {
|
|
189
269
|
if (this.abortController?.signal.aborted) break
|
|
190
270
|
|
|
191
271
|
this.callbacksRef.onChunk?.(chunk)
|
|
272
|
+
const chunkRunId =
|
|
273
|
+
'runId' in chunk && typeof chunk.runId === 'string'
|
|
274
|
+
? chunk.runId
|
|
275
|
+
: undefined
|
|
192
276
|
|
|
193
277
|
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType has ~22 variants; this consumer only handles the subset relevant to video generation lifecycle.
|
|
194
278
|
switch (chunk.type) {
|
|
279
|
+
case 'RUN_STARTED': {
|
|
280
|
+
streamRunId = chunk.runId
|
|
281
|
+
this.devtoolsBridge.ensureRunStarted(chunk.runId)
|
|
282
|
+
break
|
|
283
|
+
}
|
|
195
284
|
case 'CUSTOM': {
|
|
285
|
+
this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId)
|
|
196
286
|
if (chunk.name === GENERATION_EVENTS.VIDEO_JOB_CREATED) {
|
|
197
287
|
const { jobId } = chunk.value as { jobId: string }
|
|
198
288
|
this.setJobId(jobId)
|
|
@@ -202,7 +292,7 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
202
292
|
this.setVideoStatus(statusInfo)
|
|
203
293
|
this.callbacksRef.onStatusUpdate?.(statusInfo)
|
|
204
294
|
if (statusInfo.progress !== undefined) {
|
|
205
|
-
this.
|
|
295
|
+
this.setProgress(statusInfo.progress)
|
|
206
296
|
}
|
|
207
297
|
} else if (chunk.name === GENERATION_EVENTS.RESULT) {
|
|
208
298
|
this.setResult(chunk.value as VideoGenerateResult)
|
|
@@ -211,15 +301,20 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
211
301
|
progress: number
|
|
212
302
|
message?: string
|
|
213
303
|
}
|
|
214
|
-
this.
|
|
304
|
+
this.setProgress(progress, message)
|
|
215
305
|
}
|
|
216
306
|
break
|
|
217
307
|
}
|
|
218
308
|
case 'RUN_FINISHED': {
|
|
309
|
+
streamRunId = chunk.runId
|
|
310
|
+
this.devtoolsBridge.ensureRunStarted(chunk.runId)
|
|
219
311
|
this.setStatus('success')
|
|
220
312
|
break
|
|
221
313
|
}
|
|
222
314
|
case 'RUN_ERROR': {
|
|
315
|
+
this.devtoolsBridge.ensureRunStarted(
|
|
316
|
+
chunkRunId ?? streamRunId ?? fallbackRunId,
|
|
317
|
+
)
|
|
223
318
|
// Prefer spec `message`; fall back to deprecated `error.message`
|
|
224
319
|
const msg =
|
|
225
320
|
(chunk.message as string | undefined) ||
|
|
@@ -237,6 +332,7 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
237
332
|
* Abort any in-flight generation or polling.
|
|
238
333
|
*/
|
|
239
334
|
stop(): void {
|
|
335
|
+
const runId = this.devtoolsBridge.getActiveRunId()
|
|
240
336
|
if (this.abortController) {
|
|
241
337
|
this.abortController.abort()
|
|
242
338
|
this.abortController = null
|
|
@@ -244,6 +340,9 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
244
340
|
this.setIsLoading(false)
|
|
245
341
|
if (this.status === 'generating') {
|
|
246
342
|
this.setStatus('idle')
|
|
343
|
+
if (runId) {
|
|
344
|
+
this.devtoolsBridge.finishRun(runId, 'run:cancelled', 'cancelled')
|
|
345
|
+
}
|
|
247
346
|
}
|
|
248
347
|
}
|
|
249
348
|
|
|
@@ -253,10 +352,14 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
253
352
|
reset(): void {
|
|
254
353
|
this.stop()
|
|
255
354
|
this.setResult(null)
|
|
355
|
+
this.input = null
|
|
356
|
+
this.progress = null
|
|
357
|
+
this.devtoolsBridge.resetRuns()
|
|
256
358
|
this.setJobId(null)
|
|
257
359
|
this.setVideoStatus(null)
|
|
258
360
|
this.setError(undefined)
|
|
259
361
|
this.setStatus('idle')
|
|
362
|
+
this.devtoolsBridge.emitState()
|
|
260
363
|
}
|
|
261
364
|
|
|
262
365
|
/**
|
|
@@ -299,6 +402,12 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
299
402
|
}
|
|
300
403
|
}
|
|
301
404
|
|
|
405
|
+
dispose(): void {
|
|
406
|
+
this.stop()
|
|
407
|
+
this.devtoolsBridge.dispose()
|
|
408
|
+
this.devtoolsMounted = false
|
|
409
|
+
}
|
|
410
|
+
|
|
302
411
|
// ===========================
|
|
303
412
|
// Getters
|
|
304
413
|
// ===========================
|
|
@@ -335,50 +444,116 @@ export class VideoGenerationClient<TOutput = VideoGenerateResult> {
|
|
|
335
444
|
if (rawResult === null) {
|
|
336
445
|
this.result = null
|
|
337
446
|
this.callbacksRef.onResultChange?.(null)
|
|
447
|
+
this.devtoolsBridge.recordResultChange()
|
|
338
448
|
return
|
|
339
449
|
}
|
|
340
450
|
|
|
451
|
+
const completedStatus = this.createCompletedVideoStatus(rawResult)
|
|
452
|
+
if (this.progress?.value !== 100) {
|
|
453
|
+
this.setProgress(100, this.progress?.message)
|
|
454
|
+
}
|
|
455
|
+
this.setJobId(rawResult.jobId)
|
|
456
|
+
this.setVideoStatus(completedStatus)
|
|
457
|
+
|
|
341
458
|
if (this.callbacksRef.onResult) {
|
|
342
459
|
const transformed = this.callbacksRef.onResult(rawResult)
|
|
343
460
|
if (transformed === null) {
|
|
344
|
-
// null return → keep previous result unchanged
|
|
461
|
+
// null return → keep previous result unchanged, just re-emit
|
|
462
|
+
this.devtoolsBridge.emitState()
|
|
345
463
|
return
|
|
346
464
|
}
|
|
347
465
|
if (transformed !== undefined) {
|
|
348
466
|
// Non-null, non-undefined → use transformed value
|
|
349
467
|
this.result = transformed
|
|
350
468
|
this.callbacksRef.onResultChange?.(this.result)
|
|
469
|
+
this.devtoolsBridge.recordResultChange()
|
|
351
470
|
return
|
|
352
471
|
}
|
|
353
472
|
}
|
|
354
473
|
|
|
355
|
-
// No onResult callback, or callback returned void → use raw value
|
|
356
|
-
|
|
474
|
+
// No onResult callback, or callback returned void → use raw value as
|
|
475
|
+
// TOutput. When the caller did not supply an onResult transform,
|
|
476
|
+
// `TOutput` defaults to `VideoGenerateResult`, so the runtime cast is
|
|
477
|
+
// sound.
|
|
478
|
+
// eslint-disable-next-line no-restricted-syntax -- TOutput defaults to VideoGenerateResult when no onResult transform is supplied
|
|
479
|
+
this.result = rawResult as unknown as TOutput
|
|
357
480
|
this.callbacksRef.onResultChange?.(this.result)
|
|
481
|
+
this.devtoolsBridge.recordResultChange()
|
|
358
482
|
}
|
|
359
483
|
|
|
360
484
|
private setJobId(jobId: string | null): void {
|
|
361
485
|
this.jobId = jobId
|
|
362
486
|
this.callbacksRef.onJobIdChange?.(jobId)
|
|
487
|
+
this.devtoolsBridge.recordJobIdChange()
|
|
363
488
|
}
|
|
364
489
|
|
|
365
490
|
private setVideoStatus(status: VideoStatusInfo | null): void {
|
|
366
491
|
this.videoStatus = status
|
|
367
492
|
this.callbacksRef.onVideoStatusChange?.(status)
|
|
493
|
+
this.devtoolsBridge.recordVideoStatusChange()
|
|
368
494
|
}
|
|
369
495
|
|
|
370
496
|
private setIsLoading(isLoading: boolean): void {
|
|
371
497
|
this.isLoading = isLoading
|
|
372
498
|
this.callbacksRef.onLoadingChange?.(isLoading)
|
|
499
|
+
this.devtoolsBridge.recordLoadingChange()
|
|
373
500
|
}
|
|
374
501
|
|
|
375
502
|
private setError(error: Error | undefined): void {
|
|
376
503
|
this.error = error
|
|
377
504
|
this.callbacksRef.onErrorChange?.(error)
|
|
505
|
+
this.devtoolsBridge.recordErrorChange(error)
|
|
378
506
|
}
|
|
379
507
|
|
|
380
508
|
private setStatus(status: GenerationClientState): void {
|
|
381
509
|
this.status = status
|
|
382
510
|
this.callbacksRef.onStatusChange?.(status)
|
|
511
|
+
this.devtoolsBridge.recordStatusChange(status)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
private setProgress(value: number, message?: string): void {
|
|
515
|
+
this.progress = {
|
|
516
|
+
value,
|
|
517
|
+
...(message ? { message } : {}),
|
|
518
|
+
}
|
|
519
|
+
if (message === undefined) {
|
|
520
|
+
this.callbacksRef.onProgress?.(value)
|
|
521
|
+
} else {
|
|
522
|
+
this.callbacksRef.onProgress?.(value, message)
|
|
523
|
+
}
|
|
524
|
+
this.devtoolsBridge.recordProgressChange()
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private createCompletedVideoStatus(
|
|
528
|
+
result: VideoGenerateResult,
|
|
529
|
+
): VideoStatusInfo {
|
|
530
|
+
return {
|
|
531
|
+
jobId: result.jobId,
|
|
532
|
+
status: result.status,
|
|
533
|
+
progress: 100,
|
|
534
|
+
url: result.url,
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
private createDevtoolsMetadata(
|
|
539
|
+
metadata?: Partial<AIDevtoolsClientMetadata>,
|
|
540
|
+
): AIDevtoolsClientMetadata {
|
|
541
|
+
return {
|
|
542
|
+
hookName: metadata?.hookName ?? 'useGenerateVideo',
|
|
543
|
+
outputKind: metadata?.outputKind ?? 'video',
|
|
544
|
+
...(metadata?.framework ? { framework: metadata.framework } : {}),
|
|
545
|
+
...(metadata?.name ? { name: metadata.name } : {}),
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private generateUniqueId(prefix: string): string {
|
|
550
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
private createRunContext(runId: string): RunAgentInputContext {
|
|
554
|
+
return {
|
|
555
|
+
threadId: this.threadId,
|
|
556
|
+
runId,
|
|
557
|
+
}
|
|
383
558
|
}
|
|
384
559
|
}
|