@tanstack/ai 0.42.0 → 0.43.1
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/activities/chat/adapter.js +23 -16
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/agent-loop-strategies.d.ts +5 -36
- package/dist/esm/activities/chat/agent-loop-strategies.js +75 -21
- package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -1
- package/dist/esm/activities/chat/cancel.d.ts +40 -0
- package/dist/esm/activities/chat/cancel.js +54 -0
- package/dist/esm/activities/chat/cancel.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +28 -21
- package/dist/esm/activities/chat/index.js +2100 -1813
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/mcp/manager.d.ts +2 -2
- package/dist/esm/activities/chat/mcp/manager.js +90 -77
- package/dist/esm/activities/chat/mcp/manager.js.map +1 -1
- package/dist/esm/activities/chat/mcp/types.d.ts +2 -2
- package/dist/esm/activities/chat/messages.js +397 -346
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/builder.js +17 -15
- package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
- package/dist/esm/activities/chat/middleware/capabilities.js +78 -43
- package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +94 -1
- package/dist/esm/activities/chat/middleware/compose.js +623 -531
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/define.js +12 -5
- package/dist/esm/activities/chat/middleware/define.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +5 -1
- package/dist/esm/activities/chat/middleware/locks.d.ts +50 -0
- package/dist/esm/activities/chat/middleware/locks.js +71 -0
- package/dist/esm/activities/chat/middleware/locks.js.map +1 -0
- package/dist/esm/activities/chat/middleware/pending-turn.d.ts +15 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js +35 -0
- package/dist/esm/activities/chat/middleware/pending-turn.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.d.ts +23 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js +42 -0
- package/dist/esm/activities/chat/middleware/run-disconnect.js.map +1 -0
- package/dist/esm/activities/chat/middleware/run-store.d.ts +283 -0
- package/dist/esm/activities/chat/middleware/run-store.js +176 -0
- package/dist/esm/activities/chat/middleware/run-store.js.map +1 -0
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js +14 -8
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -1
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +79 -70
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +59 -2
- package/dist/esm/activities/chat/middleware/validate.js +23 -28
- package/dist/esm/activities/chat/middleware/validate.js.map +1 -1
- package/dist/esm/activities/chat/stream/json-parser.js +39 -25
- package/dist/esm/activities/chat/stream/json-parser.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +275 -234
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +24 -4
- package/dist/esm/activities/chat/stream/processor.js +1341 -1542
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/strategies.js +69 -53
- package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
- package/dist/esm/activities/chat/tools/approval-schema.d.ts +19 -0
- package/dist/esm/activities/chat/tools/approval-schema.js +117 -0
- package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -0
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js +164 -191
- package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
- package/dist/esm/activities/chat/tools/lazy-tools.js +24 -12
- package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.js +293 -146
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +18 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +522 -531
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +75 -16
- package/dist/esm/activities/chat/tools/tool-definition.js +95 -23
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/error-payload.js +85 -47
- package/dist/esm/activities/error-payload.js.map +1 -1
- package/dist/esm/activities/generateAudio/adapter.js +22 -15
- package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.d.ts +4 -0
- package/dist/esm/activities/generateAudio/index.js +141 -105
- package/dist/esm/activities/generateAudio/index.js.map +1 -1
- package/dist/esm/activities/generateImage/adapter.js +22 -15
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.d.ts +4 -0
- package/dist/esm/activities/generateImage/index.js +155 -111
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/adapter.js +22 -15
- package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.d.ts +4 -0
- package/dist/esm/activities/generateSpeech/index.js +159 -110
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/adapter.js +22 -15
- package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.d.ts +4 -0
- package/dist/esm/activities/generateTranscription/index.js +159 -100
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.js +36 -29
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +143 -19
- package/dist/esm/activities/generateVideo/index.js +456 -279
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/snap.js +60 -48
- package/dist/esm/activities/generateVideo/snap.js.map +1 -1
- package/dist/esm/activities/index.js +8 -34
- package/dist/esm/activities/middleware/index.d.ts +1 -1
- package/dist/esm/activities/middleware/run.d.ts +10 -0
- package/dist/esm/activities/middleware/run.js +53 -29
- package/dist/esm/activities/middleware/run.js.map +1 -1
- package/dist/esm/activities/middleware/types.d.ts +44 -6
- package/dist/esm/activities/stream-generation-result.d.ts +4 -1
- package/dist/esm/activities/stream-generation-result.js +79 -44
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/adapter.js +22 -15
- package/dist/esm/activities/summarize/adapter.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +252 -202
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.d.ts +27 -0
- package/dist/esm/activities/summarize/index.js +268 -102
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -1
- package/dist/esm/adapter-internals.js +4 -11
- package/dist/esm/client.d.ts +25 -3
- package/dist/esm/client.js +131 -64
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/custom-events.d.ts +76 -0
- package/dist/esm/custom-events.js +37 -0
- package/dist/esm/custom-events.js.map +1 -0
- package/dist/esm/delivery-detach.d.ts +50 -0
- package/dist/esm/delivery-detach.js +71 -0
- package/dist/esm/delivery-detach.js.map +1 -0
- package/dist/esm/delivery-disconnect.d.ts +62 -0
- package/dist/esm/delivery-disconnect.js +81 -0
- package/dist/esm/delivery-disconnect.js.map +1 -0
- package/dist/esm/extend-adapter.js +19 -17
- package/dist/esm/extend-adapter.js.map +1 -1
- package/dist/esm/index.d.ts +24 -6
- package/dist/esm/index.js +30 -98
- package/dist/esm/interrupt-resume.d.ts +71 -0
- package/dist/esm/interrupt-resume.js +438 -0
- package/dist/esm/interrupt-resume.js.map +1 -0
- package/dist/esm/interrupt-serialization.d.ts +12 -0
- package/dist/esm/interrupt-serialization.js +178 -0
- package/dist/esm/interrupt-serialization.js.map +1 -0
- package/dist/esm/interrupts.d.ts +84 -0
- package/dist/esm/interrupts.js +31 -0
- package/dist/esm/interrupts.js.map +1 -0
- package/dist/esm/locks.d.ts +10 -0
- package/dist/esm/locks.js +2 -0
- package/dist/esm/logger/console-logger.js +101 -78
- package/dist/esm/logger/console-logger.js.map +1 -1
- package/dist/esm/logger/internal-logger.js +104 -89
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/logger/resolve.js +54 -49
- package/dist/esm/logger/resolve.js.map +1 -1
- package/dist/esm/logger/types.d.ts +1 -1
- package/dist/esm/middlewares/content-guard.js +142 -148
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/middlewares/index.js +2 -6
- package/dist/esm/middlewares/otel.d.ts +3 -1
- package/dist/esm/middlewares/otel.js +599 -732
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/middlewares/usage-attributes.js +47 -40
- package/dist/esm/middlewares/usage-attributes.js.map +1 -1
- package/dist/esm/realtime/event-emitter.js +24 -25
- package/dist/esm/realtime/event-emitter.js.map +1 -1
- package/dist/esm/realtime/index.d.ts +5 -9
- package/dist/esm/realtime/index.js +29 -6
- package/dist/esm/realtime/index.js.map +1 -1
- package/dist/esm/scope.d.ts +47 -0
- package/dist/esm/stream-durability.d.ts +171 -0
- package/dist/esm/stream-durability.js +295 -0
- package/dist/esm/stream-durability.js.map +1 -0
- package/dist/esm/stream-to-response.d.ts +178 -13
- package/dist/esm/stream-to-response.js +663 -115
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js +30 -16
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/system-prompts.js +27 -21
- package/dist/esm/system-prompts.js.map +1 -1
- package/dist/esm/tool-registry.js +72 -45
- package/dist/esm/tool-registry.js.map +1 -1
- package/dist/esm/tools/provider-tool.js +14 -5
- package/dist/esm/tools/provider-tool.js.map +1 -1
- package/dist/esm/types.d.ts +321 -42
- package/dist/esm/types.js +2 -0
- package/dist/esm/utilities/ag-ui-wire.js +79 -93
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +26 -4
- package/dist/esm/utilities/chat-params.js +218 -92
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/errors.js +28 -18
- package/dist/esm/utilities/errors.js.map +1 -1
- package/dist/esm/utilities/media-prompt.js +46 -41
- package/dist/esm/utilities/media-prompt.js.map +1 -1
- package/dist/esm/utilities/numbers.js +13 -10
- package/dist/esm/utilities/numbers.js.map +1 -1
- package/dist/esm/utilities/provider-executed.js +20 -11
- package/dist/esm/utilities/provider-executed.js.map +1 -1
- package/dist/esm/utilities/sampling-keys.js +31 -19
- package/dist/esm/utilities/sampling-keys.js.map +1 -1
- package/dist/esm/utilities/tool-result.js +42 -30
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/dist/esm/utilities/usage.js +27 -9
- package/dist/esm/utilities/usage.js.map +1 -1
- package/dist/esm/utils.js +26 -18
- package/dist/esm/utils.js.map +1 -1
- package/package.json +10 -6
- package/skills/ai-core/SKILL.md +69 -18
- package/skills/ai-core/adapter-configuration/SKILL.md +44 -21
- package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +1 -3
- package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +148 -0
- package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/groq-adapter.md +2 -6
- package/skills/ai-core/adapter-configuration/references/openai-adapter.md +1 -3
- package/skills/ai-core/ag-ui-protocol/SKILL.md +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +98 -11
- package/skills/ai-core/client-persistence/SKILL.md +277 -0
- package/skills/ai-core/custom-backend-integration/SKILL.md +1 -1
- package/skills/ai-core/debug-logging/SKILL.md +1 -1
- package/skills/ai-core/locks/SKILL.md +143 -0
- package/skills/ai-core/media-generation/SKILL.md +144 -12
- package/skills/ai-core/middleware/SKILL.md +258 -33
- package/skills/ai-core/structured-outputs/SKILL.md +1 -1
- package/skills/ai-core/tool-calling/SKILL.md +54 -61
- package/src/activities/chat/agent-loop-strategies.ts +5 -39
- package/src/activities/chat/cancel.ts +81 -0
- package/src/activities/chat/index.ts +1091 -200
- package/src/activities/chat/mcp/manager.ts +4 -4
- package/src/activities/chat/mcp/types.ts +2 -2
- package/src/activities/chat/messages.ts +5 -3
- package/src/activities/chat/middleware/builder.ts +1 -1
- package/src/activities/chat/middleware/compose.ts +186 -9
- package/src/activities/chat/middleware/index.ts +26 -0
- package/src/activities/chat/middleware/locks.ts +102 -0
- package/src/activities/chat/middleware/pending-turn.ts +47 -0
- package/src/activities/chat/middleware/run-disconnect.ts +62 -0
- package/src/activities/chat/middleware/run-store.ts +412 -0
- package/src/activities/chat/middleware/types.ts +62 -1
- package/src/activities/chat/stream/processor.ts +189 -5
- package/src/activities/chat/tools/approval-schema.ts +205 -0
- package/src/activities/chat/tools/tool-calls.ts +106 -13
- package/src/activities/chat/tools/tool-definition.ts +210 -39
- package/src/activities/generateAudio/index.ts +20 -3
- package/src/activities/generateImage/index.ts +20 -3
- package/src/activities/generateSpeech/index.ts +25 -3
- package/src/activities/generateTranscription/index.ts +26 -3
- package/src/activities/generateVideo/index.ts +345 -82
- package/src/activities/middleware/index.ts +2 -0
- package/src/activities/middleware/run.ts +31 -0
- package/src/activities/middleware/types.ts +49 -5
- package/src/activities/stream-generation-result.ts +30 -2
- package/src/activities/summarize/chat-stream-summarize.ts +5 -0
- package/src/activities/summarize/index.ts +200 -10
- package/src/adapter-internals.ts +10 -1
- package/src/client.ts +244 -0
- package/src/custom-events.ts +107 -0
- package/src/delivery-detach.ts +72 -0
- package/src/delivery-disconnect.ts +84 -0
- package/src/index.ts +138 -1
- package/src/interrupt-resume.ts +824 -0
- package/src/interrupt-serialization.ts +183 -0
- package/src/interrupts.ts +146 -0
- package/src/locks.ts +17 -0
- package/src/logger/types.ts +1 -1
- package/src/middlewares/otel.ts +23 -5
- package/src/realtime/index.ts +5 -9
- package/src/scope.ts +47 -0
- package/src/stream-durability.ts +598 -0
- package/src/stream-to-response.ts +1051 -95
- package/src/strip-to-spec-middleware.ts +3 -3
- package/src/types.ts +405 -45
- package/src/utilities/chat-params.ts +245 -55
- package/dist/esm/activities/index.js.map +0 -1
- package/dist/esm/adapter-internals.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/middlewares/index.js.map +0 -1
|
@@ -11,6 +11,7 @@ import { aiEventClient } from '@tanstack/ai-event-client'
|
|
|
11
11
|
import { toRunErrorPayload } from '../error-payload'
|
|
12
12
|
import { resolveDebugOption } from '../../logger/resolve'
|
|
13
13
|
import {
|
|
14
|
+
applyGenerationResultTransforms,
|
|
14
15
|
createGenerationContext,
|
|
15
16
|
runGenerationAbort,
|
|
16
17
|
runGenerationError,
|
|
@@ -20,11 +21,15 @@ import {
|
|
|
20
21
|
} from '../middleware/run'
|
|
21
22
|
import type { InternalLogger } from '../../logger/internal-logger'
|
|
22
23
|
import type { DebugOption } from '../../logger/types'
|
|
23
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
GenerationMiddleware,
|
|
26
|
+
GenerationMiddlewareContext,
|
|
27
|
+
} from '../middleware/types'
|
|
24
28
|
import type { VideoAdapter } from './adapter'
|
|
25
29
|
import type {
|
|
26
30
|
MediaPrompt,
|
|
27
31
|
MediaPromptFor,
|
|
32
|
+
PersistedArtifactRef,
|
|
28
33
|
StreamChunk,
|
|
29
34
|
TokenUsage,
|
|
30
35
|
VideoJobResult,
|
|
@@ -175,8 +180,28 @@ export type VideoCreateOptions<
|
|
|
175
180
|
pollingInterval?: number
|
|
176
181
|
/** Maximum time to wait before timing out in milliseconds (stream mode only). @default 600000 */
|
|
177
182
|
maxDuration?: number
|
|
178
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Custom run id (stream mode only) — the id stamped on the emitted
|
|
185
|
+
* `RUN_STARTED` / `RUN_FINISHED` chunks.
|
|
186
|
+
*
|
|
187
|
+
* IGNORED by a non-streaming submit. That run spans two calls, and its id is
|
|
188
|
+
* derived from the provider's job instead, so {@link getVideoJobStatus} can
|
|
189
|
+
* recompute it from the `jobId` you already have to poll with. Honoring a
|
|
190
|
+
* custom id here would reintroduce the failure this avoids: a caller who set
|
|
191
|
+
* it on the submit and forgot it on the poll would silently open a second
|
|
192
|
+
* record while the first sat unfinished forever.
|
|
193
|
+
*/
|
|
179
194
|
runId?: string
|
|
195
|
+
/**
|
|
196
|
+
* Stable conversation/thread id for correlating this run when persisted.
|
|
197
|
+
*
|
|
198
|
+
* Also the `threadId` stamped on the emitted `RUN_STARTED` / `RUN_FINISHED`
|
|
199
|
+
* chunks; when omitted a throwaway id is minted for those chunks only, and
|
|
200
|
+
* the persisted run record carries NO thread link rather than a fabricated
|
|
201
|
+
* one. Pass it whenever persistence is on — it is the slot a reloading client
|
|
202
|
+
* hydrates by, so a run stored without it can only be fetched by run id.
|
|
203
|
+
*/
|
|
204
|
+
threadId?: string
|
|
180
205
|
/**
|
|
181
206
|
* Enable debug logging. Pass `true` to enable all categories, `false` to
|
|
182
207
|
* silence everything including errors, or a `DebugConfig` object for granular
|
|
@@ -185,10 +210,29 @@ export type VideoCreateOptions<
|
|
|
185
210
|
debug?: DebugOption
|
|
186
211
|
/**
|
|
187
212
|
* Observe-only middleware notified on start, usage, success, and error. Pass
|
|
188
|
-
* `otelMiddleware()` to emit OpenTelemetry spans,
|
|
189
|
-
* `GenerationMiddleware` contract for a
|
|
190
|
-
*
|
|
191
|
-
*
|
|
213
|
+
* `otelMiddleware()` to emit OpenTelemetry spans, `withGenerationPersistence()`
|
|
214
|
+
* to persist the run, or implement the `GenerationMiddleware` contract for a
|
|
215
|
+
* custom backend.
|
|
216
|
+
*
|
|
217
|
+
* In streaming mode one run covers the full create→poll→complete lifecycle:
|
|
218
|
+
* `onStart` at submission, a terminal `onFinish`/`onError` when the job
|
|
219
|
+
* settles, and `onAbort` if the consumer abandons the stream.
|
|
220
|
+
*
|
|
221
|
+
* In NON-streaming mode the call only SUBMITS the job, so it only opens the
|
|
222
|
+
* run: no terminal hook fires here, because the video does not exist yet.
|
|
223
|
+
* Pass the same `middleware` and `threadId` to {@link getVideoJobStatus}; the
|
|
224
|
+
* poll that observes a terminal job state finishes the run and is where the
|
|
225
|
+
* result and its artifacts are recorded. Nothing else has to be threaded
|
|
226
|
+
* through — both calls derive the run id from the provider's `jobId`, the one
|
|
227
|
+
* id a poller cannot be missing.
|
|
228
|
+
*
|
|
229
|
+
* Because the job id only exists once the provider accepts the job, `onStart`
|
|
230
|
+
* fires AFTER the submit request rather than before it — an observer's span
|
|
231
|
+
* therefore covers the run from acceptance onward, not the submit round-trip.
|
|
232
|
+
* A submission that FAILS has no job to key on, so it opens and immediately
|
|
233
|
+
* fails a run under this call's `requestId`: the thread's latest run reports
|
|
234
|
+
* the failure (a client hydrating the slot sees it) even though there is no
|
|
235
|
+
* job to resume.
|
|
192
236
|
*/
|
|
193
237
|
middleware?: Array<GenerationMiddleware>
|
|
194
238
|
} & ({} extends VideoProviderOptions<TAdapter>
|
|
@@ -282,7 +326,7 @@ export type VideoActivityResult<
|
|
|
282
326
|
*
|
|
283
327
|
* @example Create a video generation job
|
|
284
328
|
* ```ts
|
|
285
|
-
* import { generateVideo } from '@tanstack/ai'
|
|
329
|
+
* import { generateVideo, getVideoJobStatus } from '@tanstack/ai'
|
|
286
330
|
* import { openaiVideo } from '@tanstack/ai-openai'
|
|
287
331
|
*
|
|
288
332
|
* // Start a video generation job
|
|
@@ -292,6 +336,14 @@ export type VideoActivityResult<
|
|
|
292
336
|
* })
|
|
293
337
|
*
|
|
294
338
|
* console.log('Job started:', jobId)
|
|
339
|
+
*
|
|
340
|
+
* // The submission only OPENS the run; the poll that sees a terminal state is
|
|
341
|
+
* // what completes it. The `jobId` is the whole correlation — pass the same
|
|
342
|
+
* // `middleware` and `threadId` when you use them.
|
|
343
|
+
* const status = await getVideoJobStatus({
|
|
344
|
+
* adapter: openaiVideo('sora-2'),
|
|
345
|
+
* jobId,
|
|
346
|
+
* })
|
|
295
347
|
* ```
|
|
296
348
|
*
|
|
297
349
|
* @example Stream the full video generation lifecycle
|
|
@@ -324,8 +376,39 @@ export function generateVideo<
|
|
|
324
376
|
return runCreateVideoJob(options) as VideoActivityResult<'create', TStream>
|
|
325
377
|
}
|
|
326
378
|
|
|
379
|
+
/**
|
|
380
|
+
* The run id a non-streaming video job is filed under, derived from the
|
|
381
|
+
* provider job itself.
|
|
382
|
+
*
|
|
383
|
+
* A submit-and-poll run spans two calls in two different requests, so the two
|
|
384
|
+
* halves need to agree on an id. Deriving it from the `jobId` — the one id a
|
|
385
|
+
* poller structurally cannot be missing, because it cannot poll without it —
|
|
386
|
+
* means no correlation state has to survive the boundary and there is no
|
|
387
|
+
* "forgot to pass the run id" failure to document. The provider is part of the
|
|
388
|
+
* key so two providers' job-id spaces cannot collide, and both halves are
|
|
389
|
+
* percent-encoded so the joined string stays unambiguous (and url-safe, since
|
|
390
|
+
* run ids end up in storage keys and query strings).
|
|
391
|
+
*/
|
|
392
|
+
function videoRunIdForJob(provider: string, jobId: string): string {
|
|
393
|
+
return `video:${encodeURIComponent(provider)}:${encodeURIComponent(jobId)}`
|
|
394
|
+
}
|
|
395
|
+
|
|
327
396
|
/**
|
|
328
397
|
* Internal implementation of non-streaming video job creation.
|
|
398
|
+
*
|
|
399
|
+
* Submitting a job OPENS a run, it does not complete one: the video does not
|
|
400
|
+
* exist yet, and the bytes only appear on a later poll. So this fires `onStart`
|
|
401
|
+
* and runs the result transforms over the submission result — the jobId lands
|
|
402
|
+
* on the run record, which is what lets a later request resume polling — but
|
|
403
|
+
* fires NO terminal hook. {@link getVideoJobStatus} finishes the run when the
|
|
404
|
+
* job settles, keyed on the same derived id.
|
|
405
|
+
*
|
|
406
|
+
* `onStart` therefore runs AFTER the submit request: the run's id comes from
|
|
407
|
+
* the job, which does not exist until the provider accepts it. A submission
|
|
408
|
+
* that fails has no job, so it opens and immediately fails a run under this
|
|
409
|
+
* call's `requestId` — terminal and unresumable by construction, but it puts
|
|
410
|
+
* the failure where a client hydrating the thread will see it instead of
|
|
411
|
+
* showing nothing.
|
|
329
412
|
*/
|
|
330
413
|
async function runCreateVideoJob<
|
|
331
414
|
TAdapter extends VideoAdapter<string, any, any, any, any, any>,
|
|
@@ -340,24 +423,35 @@ async function runCreateVideoJob<
|
|
|
340
423
|
(adapter as { name?: string }).name ??
|
|
341
424
|
'unknown'
|
|
342
425
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
426
|
+
// `runId` is resolved per outcome (from the job, or absent on failure), so the
|
|
427
|
+
// context is built once the outcome is known. `options.runId` is deliberately
|
|
428
|
+
// not consulted: in non-streaming mode the run id is always the derived one,
|
|
429
|
+
// the single rule that keeps the two calls in agreement.
|
|
430
|
+
const contextFor = (runId?: string): GenerationMiddlewareContext =>
|
|
431
|
+
createGenerationContext({
|
|
432
|
+
requestId,
|
|
433
|
+
activity: 'video',
|
|
434
|
+
provider: adapter.name,
|
|
435
|
+
model,
|
|
436
|
+
modelOptions,
|
|
437
|
+
// Deliberately the CALLER's `threadId` — no minted fallback. A thread id
|
|
438
|
+
// nobody else knows would file the run in a slot no client could hydrate,
|
|
439
|
+
// which is worse than no link because it looks like one. Mirrors the
|
|
440
|
+
// streaming path.
|
|
441
|
+
threadId: options.threadId,
|
|
442
|
+
runId,
|
|
443
|
+
artifactInputs: { prompt },
|
|
444
|
+
createId,
|
|
445
|
+
})
|
|
353
446
|
|
|
354
447
|
logger.request(`activity=generateVideo provider=${providerName}`, {
|
|
355
448
|
provider: providerName,
|
|
356
449
|
model,
|
|
357
450
|
})
|
|
358
451
|
|
|
452
|
+
let jobResult: VideoJobResult
|
|
359
453
|
try {
|
|
360
|
-
|
|
454
|
+
jobResult = await adapter.createVideoJob({
|
|
361
455
|
model,
|
|
362
456
|
prompt,
|
|
363
457
|
size,
|
|
@@ -365,18 +459,13 @@ async function runCreateVideoJob<
|
|
|
365
459
|
modelOptions,
|
|
366
460
|
logger,
|
|
367
461
|
})
|
|
368
|
-
logger.output(`activity=generateVideo jobId=${result.jobId}`, {
|
|
369
|
-
jobId: result.jobId,
|
|
370
|
-
model: result.model,
|
|
371
|
-
})
|
|
372
|
-
// Non-streaming create only submits the job; usage isn't known until the
|
|
373
|
-
// job completes via polling, so the span covers submission only.
|
|
374
|
-
await runGenerationFinish(middleware, mwCtx, {
|
|
375
|
-
duration: Date.now() - startTime,
|
|
376
|
-
})
|
|
377
|
-
return result
|
|
378
462
|
} catch (error) {
|
|
379
|
-
|
|
463
|
+
// No jobId exists, so this run can only be keyed on the request. Start it
|
|
464
|
+
// just to fail it: `generationRuns.update` on an unknown run id is a no-op
|
|
465
|
+
// by contract, so without the `onStart` the failure would persist nowhere.
|
|
466
|
+
const failedCtx = contextFor()
|
|
467
|
+
await runGenerationStart(middleware, failedCtx)
|
|
468
|
+
await runGenerationError(middleware, failedCtx, {
|
|
380
469
|
error,
|
|
381
470
|
duration: Date.now() - startTime,
|
|
382
471
|
})
|
|
@@ -386,6 +475,18 @@ async function runCreateVideoJob<
|
|
|
386
475
|
})
|
|
387
476
|
throw error
|
|
388
477
|
}
|
|
478
|
+
|
|
479
|
+
logger.output(`activity=generateVideo jobId=${jobResult.jobId}`, {
|
|
480
|
+
jobId: jobResult.jobId,
|
|
481
|
+
model: jobResult.model,
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
const mwCtx = contextFor(videoRunIdForJob(adapter.name, jobResult.jobId))
|
|
485
|
+
await runGenerationStart(middleware, mwCtx)
|
|
486
|
+
// Transforms see the submission result (no url yet, so nothing to copy into a
|
|
487
|
+
// blob store) purely so the run record captures the jobId and any prompt
|
|
488
|
+
// inputs. No finish hook: the run is still running.
|
|
489
|
+
return await applyGenerationResultTransforms(mwCtx, jobResult)
|
|
389
490
|
}
|
|
390
491
|
|
|
391
492
|
function sleep(ms: number): Promise<void> {
|
|
@@ -412,12 +513,15 @@ async function* runStreamingVideoGeneration<
|
|
|
412
513
|
(adapter as { name?: string }).name ??
|
|
413
514
|
'unknown'
|
|
414
515
|
|
|
415
|
-
|
|
516
|
+
// The wire needs a thread id on every RUN_* chunk, so one is minted when the
|
|
517
|
+
// caller passes none — matching `streamGenerationResult`, which the other
|
|
518
|
+
// activities stream through.
|
|
519
|
+
const wireThreadId = options.threadId ?? createId('thread')
|
|
416
520
|
|
|
417
521
|
yield {
|
|
418
522
|
type: 'RUN_STARTED',
|
|
419
523
|
runId,
|
|
420
|
-
threadId,
|
|
524
|
+
threadId: wireThreadId,
|
|
421
525
|
timestamp: Date.now(),
|
|
422
526
|
} as StreamChunk
|
|
423
527
|
|
|
@@ -427,6 +531,17 @@ async function* runStreamingVideoGeneration<
|
|
|
427
531
|
provider: adapter.name,
|
|
428
532
|
model,
|
|
429
533
|
modelOptions,
|
|
534
|
+
// Identity has to reach the middleware, not just the chunks: persistence
|
|
535
|
+
// keys the run record on these, and without them it falls back to the
|
|
536
|
+
// internal `requestId` and records no thread link at all.
|
|
537
|
+
//
|
|
538
|
+
// Deliberately the CALLER's `threadId`, never `wireThreadId`: a minted id is
|
|
539
|
+
// known to nobody, so persisting it would file the run in a slot no client
|
|
540
|
+
// could ever hydrate — worse than recording no link, because it looks like
|
|
541
|
+
// one. This mirrors `generateImage`.
|
|
542
|
+
threadId: options.threadId,
|
|
543
|
+
runId,
|
|
544
|
+
artifactInputs: { prompt },
|
|
430
545
|
createId,
|
|
431
546
|
})
|
|
432
547
|
|
|
@@ -459,7 +574,7 @@ async function* runStreamingVideoGeneration<
|
|
|
459
574
|
name: 'video:job:created',
|
|
460
575
|
value: { jobId: jobResult.jobId },
|
|
461
576
|
timestamp: Date.now(),
|
|
462
|
-
}
|
|
577
|
+
}
|
|
463
578
|
|
|
464
579
|
// Poll for completion
|
|
465
580
|
const startTime = Date.now()
|
|
@@ -478,7 +593,7 @@ async function* runStreamingVideoGeneration<
|
|
|
478
593
|
error: statusResult.error,
|
|
479
594
|
},
|
|
480
595
|
timestamp: Date.now(),
|
|
481
|
-
}
|
|
596
|
+
}
|
|
482
597
|
|
|
483
598
|
if (statusResult.status === 'completed') {
|
|
484
599
|
const urlResult = await adapter.getVideoUrl(jobResult.jobId)
|
|
@@ -491,6 +606,21 @@ async function* runStreamingVideoGeneration<
|
|
|
491
606
|
},
|
|
492
607
|
)
|
|
493
608
|
|
|
609
|
+
// Run the result transforms before anything observes the result, the
|
|
610
|
+
// same as every other media activity. This is what lets persistence
|
|
611
|
+
// copy the video into a blob store, attach its artifact refs, and
|
|
612
|
+
// rewrite `url` to a durable app-origin one — so the chunk below and
|
|
613
|
+
// the stored run record carry the SAME urls. Skipping it leaves a
|
|
614
|
+
// result whose only url is the provider's expiring link.
|
|
615
|
+
const rawResult = {
|
|
616
|
+
jobId: jobResult.jobId,
|
|
617
|
+
status: 'completed' as const,
|
|
618
|
+
url: urlResult.url,
|
|
619
|
+
expiresAt: urlResult.expiresAt,
|
|
620
|
+
...(urlResult.usage ? { usage: urlResult.usage } : {}),
|
|
621
|
+
}
|
|
622
|
+
const result = await applyGenerationResultTransforms(mwCtx, rawResult)
|
|
623
|
+
|
|
494
624
|
// Fire finish before yielding the terminal chunks: the generation has
|
|
495
625
|
// succeeded, so a consumer that stops reading after `generation:result`
|
|
496
626
|
// (without pulling `RUN_FINISHED`) must not trip the abandonment path in
|
|
@@ -506,20 +636,14 @@ async function* runStreamingVideoGeneration<
|
|
|
506
636
|
yield {
|
|
507
637
|
type: 'CUSTOM',
|
|
508
638
|
name: 'generation:result',
|
|
509
|
-
value:
|
|
510
|
-
jobId: jobResult.jobId,
|
|
511
|
-
status: 'completed',
|
|
512
|
-
url: urlResult.url,
|
|
513
|
-
expiresAt: urlResult.expiresAt,
|
|
514
|
-
...(urlResult.usage ? { usage: urlResult.usage } : {}),
|
|
515
|
-
},
|
|
639
|
+
value: result,
|
|
516
640
|
timestamp: Date.now(),
|
|
517
|
-
}
|
|
641
|
+
}
|
|
518
642
|
|
|
519
643
|
yield {
|
|
520
644
|
type: 'RUN_FINISHED',
|
|
521
645
|
runId,
|
|
522
|
-
threadId,
|
|
646
|
+
threadId: wireThreadId,
|
|
523
647
|
finishReason: 'stop',
|
|
524
648
|
timestamp: Date.now(),
|
|
525
649
|
} as StreamChunk
|
|
@@ -550,7 +674,7 @@ async function* runStreamingVideoGeneration<
|
|
|
550
674
|
yield {
|
|
551
675
|
type: 'RUN_ERROR',
|
|
552
676
|
runId,
|
|
553
|
-
threadId,
|
|
677
|
+
threadId: wireThreadId,
|
|
554
678
|
message: payload.message,
|
|
555
679
|
code: payload.code,
|
|
556
680
|
error: payload,
|
|
@@ -570,12 +694,73 @@ async function* runStreamingVideoGeneration<
|
|
|
570
694
|
}
|
|
571
695
|
}
|
|
572
696
|
|
|
697
|
+
/**
|
|
698
|
+
* Options for {@link getVideoJobStatus}.
|
|
699
|
+
*
|
|
700
|
+
* The run this poll finishes is identified by `adapter` + `jobId` alone — the
|
|
701
|
+
* same pair the submitting `generateVideo()` call derived it from — so there is
|
|
702
|
+
* no run id to thread through. Pass the submission's `threadId` and the same
|
|
703
|
+
* `middleware`.
|
|
704
|
+
*
|
|
705
|
+
* @experimental Video generation is an experimental feature and may change.
|
|
706
|
+
*/
|
|
707
|
+
export interface VideoJobStatusOptions<
|
|
708
|
+
TAdapter extends VideoAdapter<string, any, any, any, any, any>,
|
|
709
|
+
> {
|
|
710
|
+
/** The video adapter to use (must be created with a model) */
|
|
711
|
+
adapter: TAdapter & { kind: typeof kind }
|
|
712
|
+
/** The job ID to check status for */
|
|
713
|
+
jobId: string
|
|
714
|
+
/**
|
|
715
|
+
* The scope the run is filed under. Must match the submission's `threadId` —
|
|
716
|
+
* generation persistence REFUSES a run without a scope (a run filed under
|
|
717
|
+
* none can never be hydrated by one), so omitting it throws rather than
|
|
718
|
+
* quietly filing the finished video somewhere unreachable.
|
|
719
|
+
*/
|
|
720
|
+
threadId?: string
|
|
721
|
+
/**
|
|
722
|
+
* Observe-only middleware. Hooks fire ONLY on the poll that observes a
|
|
723
|
+
* terminal job state: `onStart` (resuming the submission's run), then the
|
|
724
|
+
* result transforms — which is where persistence copies the video into a blob
|
|
725
|
+
* store and rewrites `url` to a durable one, so the returned result carries
|
|
726
|
+
* the same urls as the stored record — then `onFinish`, or `onError` when the
|
|
727
|
+
* job failed. Intermediate polls invoke nothing, so a middleware is not
|
|
728
|
+
* charged for the wait.
|
|
729
|
+
*/
|
|
730
|
+
middleware?: Array<GenerationMiddleware>
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* The status of a video job, plus the video itself once the job completed.
|
|
735
|
+
*
|
|
736
|
+
* @experimental Video generation is an experimental feature and may change.
|
|
737
|
+
*/
|
|
738
|
+
export interface VideoJobStatusResult {
|
|
739
|
+
/** Job identifier */
|
|
740
|
+
jobId: string
|
|
741
|
+
status: 'pending' | 'processing' | 'completed' | 'failed'
|
|
742
|
+
progress?: number
|
|
743
|
+
url?: string
|
|
744
|
+
/** When the provider url expires, if it reported one. */
|
|
745
|
+
expiresAt?: Date
|
|
746
|
+
error?: string
|
|
747
|
+
usage?: TokenUsage
|
|
748
|
+
/** Durable artifact references, when generation persistence is wired. */
|
|
749
|
+
artifacts?: Array<PersistedArtifactRef>
|
|
750
|
+
}
|
|
751
|
+
|
|
573
752
|
/**
|
|
574
753
|
* Get video job status - returns the current status, progress, and URL if available.
|
|
575
754
|
*
|
|
576
755
|
* This function combines status checking and URL retrieval. If the job is completed,
|
|
577
756
|
* it will automatically fetch and include the video URL.
|
|
578
757
|
*
|
|
758
|
+
* It is also where a non-streaming `generateVideo()` run ENDS: pass the same
|
|
759
|
+
* `middleware` and `threadId`, and the poll that first sees a terminal job state
|
|
760
|
+
* finishes the run (recording the result and its artifacts) or fails it. The run
|
|
761
|
+
* is identified by `adapter` + `jobId`, exactly what the submission derived it
|
|
762
|
+
* from, so there is nothing else to carry between the two calls.
|
|
763
|
+
*
|
|
579
764
|
* @experimental Video generation is an experimental feature and may change.
|
|
580
765
|
*
|
|
581
766
|
* @example Check job status
|
|
@@ -594,23 +779,63 @@ async function* runStreamingVideoGeneration<
|
|
|
594
779
|
* console.log('Video URL:', result.url)
|
|
595
780
|
* }
|
|
596
781
|
* ```
|
|
782
|
+
*
|
|
783
|
+
* @example Submit and poll one persisted run
|
|
784
|
+
* ```ts
|
|
785
|
+
* import { generateVideo, getVideoJobStatus } from '@tanstack/ai'
|
|
786
|
+
* import { withGenerationPersistence } from '@tanstack/ai-persistence'
|
|
787
|
+
* import { openaiVideo } from '@tanstack/ai-openai'
|
|
788
|
+
*
|
|
789
|
+
* const adapter = openaiVideo('sora-2')
|
|
790
|
+
* const middleware = [withGenerationPersistence(persistence)]
|
|
791
|
+
*
|
|
792
|
+
* // Opens the run (status `running`, jobId recorded). Its run id is derived
|
|
793
|
+
* // from the provider job, so nothing has to be stored to resume it.
|
|
794
|
+
* const { jobId } = await generateVideo({
|
|
795
|
+
* adapter,
|
|
796
|
+
* prompt: 'A cat chasing a dog in a sunny park',
|
|
797
|
+
* threadId,
|
|
798
|
+
* middleware,
|
|
799
|
+
* })
|
|
800
|
+
*
|
|
801
|
+
* // Completes the SAME run once the job settles — this is what writes the
|
|
802
|
+
* // video, its artifacts, and the terminal status. Works from a different
|
|
803
|
+
* // request or process: the jobId is the only correlation.
|
|
804
|
+
* const status = await getVideoJobStatus({
|
|
805
|
+
* adapter,
|
|
806
|
+
* jobId,
|
|
807
|
+
* threadId,
|
|
808
|
+
* middleware,
|
|
809
|
+
* })
|
|
810
|
+
* ```
|
|
597
811
|
*/
|
|
598
812
|
export async function getVideoJobStatus<
|
|
599
813
|
TAdapter extends VideoAdapter<string, any, any, any, any, any>,
|
|
600
|
-
>(options: {
|
|
601
|
-
|
|
602
|
-
jobId: string
|
|
603
|
-
}): Promise<{
|
|
604
|
-
status: 'pending' | 'processing' | 'completed' | 'failed'
|
|
605
|
-
progress?: number
|
|
606
|
-
url?: string
|
|
607
|
-
error?: string
|
|
608
|
-
usage?: TokenUsage
|
|
609
|
-
}> {
|
|
610
|
-
const { adapter, jobId } = options
|
|
814
|
+
>(options: VideoJobStatusOptions<TAdapter>): Promise<VideoJobStatusResult> {
|
|
815
|
+
const { adapter, jobId, middleware } = options
|
|
611
816
|
const requestId = createId('video-status')
|
|
612
817
|
const startTime = Date.now()
|
|
613
818
|
|
|
819
|
+
// Built per call but only USED on a terminal poll — `onStart` is what
|
|
820
|
+
// registers the result transforms, so it has to run in the same call that
|
|
821
|
+
// applies them.
|
|
822
|
+
const terminalContext = (): GenerationMiddlewareContext =>
|
|
823
|
+
createGenerationContext({
|
|
824
|
+
requestId,
|
|
825
|
+
activity: 'video',
|
|
826
|
+
provider: adapter.name,
|
|
827
|
+
model: adapter.model,
|
|
828
|
+
threadId: options.threadId,
|
|
829
|
+
// Recomputed, never passed in: the submitting call derived the same id
|
|
830
|
+
// from the same provider + job, so the two halves agree without the
|
|
831
|
+
// caller carrying anything but the jobId they must already have.
|
|
832
|
+
runId: videoRunIdForJob(adapter.name, jobId),
|
|
833
|
+
// Deliberately no `artifactInputs`: the submission already persisted any
|
|
834
|
+
// prompt inputs under this run, and passing them again would store a
|
|
835
|
+
// second copy of every input image.
|
|
836
|
+
createId,
|
|
837
|
+
})
|
|
838
|
+
|
|
614
839
|
aiEventClient.emit('video:request:started', {
|
|
615
840
|
requestId,
|
|
616
841
|
provider: adapter.name,
|
|
@@ -625,34 +850,12 @@ export async function getVideoJobStatus<
|
|
|
625
850
|
|
|
626
851
|
// If completed, also get the URL
|
|
627
852
|
if (statusResult.status === 'completed') {
|
|
853
|
+
let urlResult: VideoUrlResult
|
|
854
|
+
// Scoped tightly to the provider call: a middleware hook that throws must
|
|
855
|
+
// surface as itself, not be relabelled "failed to get video URL" and then
|
|
856
|
+
// re-reported to the very middleware that threw.
|
|
628
857
|
try {
|
|
629
|
-
|
|
630
|
-
aiEventClient.emit('video:request:completed', {
|
|
631
|
-
requestId,
|
|
632
|
-
provider: adapter.name,
|
|
633
|
-
model: adapter.model,
|
|
634
|
-
requestType: 'status',
|
|
635
|
-
jobId,
|
|
636
|
-
status: statusResult.status,
|
|
637
|
-
progress: statusResult.progress,
|
|
638
|
-
url: urlResult.url,
|
|
639
|
-
duration: Date.now() - startTime,
|
|
640
|
-
timestamp: Date.now(),
|
|
641
|
-
})
|
|
642
|
-
if (urlResult.usage) {
|
|
643
|
-
aiEventClient.emit('video:usage', {
|
|
644
|
-
requestId,
|
|
645
|
-
model: adapter.model,
|
|
646
|
-
usage: urlResult.usage,
|
|
647
|
-
timestamp: Date.now(),
|
|
648
|
-
})
|
|
649
|
-
}
|
|
650
|
-
return {
|
|
651
|
-
status: statusResult.status,
|
|
652
|
-
progress: statusResult.progress,
|
|
653
|
-
url: urlResult.url,
|
|
654
|
-
...(urlResult.usage ? { usage: urlResult.usage } : {}),
|
|
655
|
-
}
|
|
858
|
+
urlResult = await adapter.getVideoUrl(jobId)
|
|
656
859
|
} catch (error) {
|
|
657
860
|
const errorMessage =
|
|
658
861
|
error instanceof Error ? error.message : 'Failed to get video URL'
|
|
@@ -668,13 +871,63 @@ export async function getVideoJobStatus<
|
|
|
668
871
|
duration: Date.now() - startTime,
|
|
669
872
|
timestamp: Date.now(),
|
|
670
873
|
})
|
|
671
|
-
// Provider reported completed but result fetch failed — treat as failed
|
|
874
|
+
// Provider reported completed but result fetch failed — treat as failed,
|
|
875
|
+
// and fail the run with it: the job is terminal, so nothing later will.
|
|
876
|
+
await runGenerationError(middleware, terminalContext(), {
|
|
877
|
+
error,
|
|
878
|
+
duration: Date.now() - startTime,
|
|
879
|
+
})
|
|
672
880
|
return {
|
|
881
|
+
jobId,
|
|
673
882
|
status: 'failed' as const,
|
|
674
883
|
progress: statusResult.progress,
|
|
675
884
|
error: errorMessage,
|
|
676
885
|
}
|
|
677
886
|
}
|
|
887
|
+
|
|
888
|
+
aiEventClient.emit('video:request:completed', {
|
|
889
|
+
requestId,
|
|
890
|
+
provider: adapter.name,
|
|
891
|
+
model: adapter.model,
|
|
892
|
+
requestType: 'status',
|
|
893
|
+
jobId,
|
|
894
|
+
status: statusResult.status,
|
|
895
|
+
progress: statusResult.progress,
|
|
896
|
+
url: urlResult.url,
|
|
897
|
+
duration: Date.now() - startTime,
|
|
898
|
+
timestamp: Date.now(),
|
|
899
|
+
})
|
|
900
|
+
if (urlResult.usage) {
|
|
901
|
+
aiEventClient.emit('video:usage', {
|
|
902
|
+
requestId,
|
|
903
|
+
model: adapter.model,
|
|
904
|
+
usage: urlResult.usage,
|
|
905
|
+
timestamp: Date.now(),
|
|
906
|
+
})
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const mwCtx = terminalContext()
|
|
910
|
+
await runGenerationStart(middleware, mwCtx)
|
|
911
|
+
const result = await applyGenerationResultTransforms<VideoJobStatusResult>(
|
|
912
|
+
mwCtx,
|
|
913
|
+
{
|
|
914
|
+
jobId,
|
|
915
|
+
status: 'completed',
|
|
916
|
+
...(statusResult.progress !== undefined
|
|
917
|
+
? { progress: statusResult.progress }
|
|
918
|
+
: {}),
|
|
919
|
+
url: urlResult.url,
|
|
920
|
+
...(urlResult.expiresAt ? { expiresAt: urlResult.expiresAt } : {}),
|
|
921
|
+
...(urlResult.usage ? { usage: urlResult.usage } : {}),
|
|
922
|
+
},
|
|
923
|
+
)
|
|
924
|
+
if (urlResult.usage)
|
|
925
|
+
await runGenerationUsage(middleware, mwCtx, urlResult.usage)
|
|
926
|
+
await runGenerationFinish(middleware, mwCtx, {
|
|
927
|
+
duration: Date.now() - startTime,
|
|
928
|
+
usage: urlResult.usage,
|
|
929
|
+
})
|
|
930
|
+
return result
|
|
678
931
|
}
|
|
679
932
|
|
|
680
933
|
aiEventClient.emit('video:request:completed', {
|
|
@@ -690,8 +943,18 @@ export async function getVideoJobStatus<
|
|
|
690
943
|
timestamp: Date.now(),
|
|
691
944
|
})
|
|
692
945
|
|
|
946
|
+
// A failed job is terminal for the run too: without this the record would sit
|
|
947
|
+
// at `running` forever, indistinguishable from a job still being worked on.
|
|
948
|
+
if (statusResult.status === 'failed') {
|
|
949
|
+
await runGenerationError(middleware, terminalContext(), {
|
|
950
|
+
error: new Error(statusResult.error || 'Video generation failed'),
|
|
951
|
+
duration: Date.now() - startTime,
|
|
952
|
+
})
|
|
953
|
+
}
|
|
954
|
+
|
|
693
955
|
// Return status for non-completed jobs
|
|
694
956
|
return {
|
|
957
|
+
jobId,
|
|
695
958
|
status: statusResult.status,
|
|
696
959
|
progress: statusResult.progress,
|
|
697
960
|
error: statusResult.error,
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
GenerationFinishInfo,
|
|
5
5
|
GenerationMiddleware,
|
|
6
6
|
GenerationMiddlewareContext,
|
|
7
|
+
GenerationResultTransformContext,
|
|
7
8
|
GenerationUsageInfo,
|
|
8
9
|
} from './types'
|
|
9
10
|
|
|
@@ -19,6 +20,9 @@ export function createGenerationContext(args: {
|
|
|
19
20
|
provider: string
|
|
20
21
|
model: string
|
|
21
22
|
modelOptions?: unknown
|
|
23
|
+
threadId?: string
|
|
24
|
+
runId?: string
|
|
25
|
+
artifactInputs?: unknown
|
|
22
26
|
createId: (prefix: string) => string
|
|
23
27
|
}): GenerationMiddlewareContext {
|
|
24
28
|
return {
|
|
@@ -27,9 +31,13 @@ export function createGenerationContext(args: {
|
|
|
27
31
|
provider: args.provider,
|
|
28
32
|
model: args.model,
|
|
29
33
|
modelOptions: args.modelOptions,
|
|
34
|
+
threadId: args.threadId,
|
|
35
|
+
runId: args.runId,
|
|
30
36
|
source: 'server',
|
|
31
37
|
createId: args.createId,
|
|
32
38
|
context: undefined,
|
|
39
|
+
resultTransforms: [],
|
|
40
|
+
artifactInputs: args.artifactInputs,
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
43
|
|
|
@@ -86,3 +94,26 @@ export function runGenerationError(
|
|
|
86
94
|
): Promise<void> {
|
|
87
95
|
return run(middleware, (mw) => mw.onError?.(ctx, info))
|
|
88
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Apply the result transforms middleware registered on the context, in order,
|
|
100
|
+
* to the raw adapter result. Each transform may return a replacement result or
|
|
101
|
+
* `undefined` to leave it unchanged. Runs after the adapter result exists and
|
|
102
|
+
* before the final result is returned or streamed.
|
|
103
|
+
*/
|
|
104
|
+
export async function applyGenerationResultTransforms<TResult>(
|
|
105
|
+
ctx: GenerationMiddlewareContext,
|
|
106
|
+
result: TResult,
|
|
107
|
+
): Promise<TResult> {
|
|
108
|
+
let current = result
|
|
109
|
+
const transformCtx: GenerationResultTransformContext = { middleware: ctx }
|
|
110
|
+
|
|
111
|
+
for (const transform of ctx.resultTransforms ?? []) {
|
|
112
|
+
const transformed = await transform(current, transformCtx)
|
|
113
|
+
if (transformed !== undefined) {
|
|
114
|
+
current = transformed as TResult
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return current
|
|
119
|
+
}
|