@vanillaskyai/video 0.10.3 → 0.10.4

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/PUBLIC-API.md +43 -31
  3. package/README.md +11 -7
  4. package/dist/check-runtime.js +2 -2
  5. package/dist/{chunk-UCXQORDZ.js → chunk-G5TYLTL5.js} +1 -1
  6. package/dist/{chunk-SI6CFFAA.js → chunk-O6FBIB4L.js} +6 -5
  7. package/dist/{chunk-YZZSWGC3.js → chunk-OOBT4X46.js} +8 -3
  8. package/dist/{chunk-LJR5VLEI.js → chunk-QZAZT44G.js} +2 -2
  9. package/dist/{chunk-3U2F7C7C.js → chunk-WZFLEPLM.js} +7 -3
  10. package/dist/{chunk-VVNZCY2U.js → chunk-Z2ZI5G7O.js} +1 -1
  11. package/dist/{cinema-media-4JUYKPQI.js → cinema-media-3SIGVVCY.js} +4 -4
  12. package/dist/{comparison-3QLMUC5V.js → comparison-VDGRWPB5.js} +4 -4
  13. package/dist/{editorial-timeline-IVE2P25N.js → editorial-timeline-QJU4JJWB.js} +4 -4
  14. package/dist/{key-figure-APD56LCO.js → key-figure-YUAQW6OE.js} +4 -4
  15. package/dist/{mobile-message-WLK3WY5B.js → mobile-message-ZMMTUDUQ.js} +4 -4
  16. package/dist/{quote-Z6YET5BO.js → quote-BB6UJT6L.js} +4 -4
  17. package/dist/react.d.ts +3 -1
  18. package/dist/react.js +79 -81
  19. package/dist/{scene-video-backdrop-OWUXPXHZ.js → scene-video-backdrop-4EZVVQY7.js} +2 -2
  20. package/dist/server.d.ts +15 -3
  21. package/dist/server.js +129 -33
  22. package/dist/{types-CdBmNTGD.d.ts → types-BqB8zC9u.d.ts} +1 -1
  23. package/docs/concepts.md +2 -2
  24. package/docs/development.md +23 -0
  25. package/docs/getting-started.md +2 -2
  26. package/docs/media-and-audio.md +26 -13
  27. package/docs/performance.md +21 -10
  28. package/docs/production.md +11 -13
  29. package/docs/prompt-and-input.md +3 -3
  30. package/docs/provider-integration.md +9 -11
  31. package/docs/reference/provider-adapters.md +3 -2
  32. package/package.json +5 -2
  33. package/registry/items/backgrounds.json +2 -2
  34. package/starters/video-chat/README.md +13 -15
  35. package/starters/video-chat/package.json +1 -1
  36. package/starters/video-chat/providers/video.ts +5 -2
  37. package/starters/video-chat/server.ts +1 -1
  38. package/starters/video-chat/stock.ts +59 -31
  39. package/styles/video-chat.css +6 -61
package/dist/react.js CHANGED
@@ -40,9 +40,9 @@ import {
40
40
  VideoFrame,
41
41
  getDimensions,
42
42
  sceneReadinessKey
43
- } from "./chunk-3U2F7C7C.js";
43
+ } from "./chunk-WZFLEPLM.js";
44
44
  import "./chunk-224QNWRA.js";
45
- import "./chunk-YZZSWGC3.js";
45
+ import "./chunk-OOBT4X46.js";
46
46
  import {
47
47
  getVideoDuration,
48
48
  resolveVideoTimeline
@@ -94,13 +94,13 @@ import { createElement } from "react";
94
94
 
95
95
  // src/visual-system/catalog/builtin-loaders.generated.ts
96
96
  var GENERATED_BUILTIN_TEMPLATE_LOADERS = {
97
- "cinemaMedia": () => import("./cinema-media-4JUYKPQI.js").then((module) => ({ default: module.MediaSceneTemplate })),
97
+ "cinemaMedia": () => import("./cinema-media-3SIGVVCY.js").then((module) => ({ default: module.MediaSceneTemplate })),
98
98
  "chapterTitle": () => import("./chapter-title-YRLIQY3V.js").then((module) => ({ default: module.TitleSceneTemplate })),
99
- "editorialTimeline": () => import("./editorial-timeline-IVE2P25N.js").then((module) => ({ default: module.TimelineSceneTemplate })),
100
- "mobileMessage": () => import("./mobile-message-WLK3WY5B.js").then((module) => ({ default: module.NotificationSceneTemplate })),
101
- "comparison": () => import("./comparison-3QLMUC5V.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
102
- "quote": () => import("./quote-Z6YET5BO.js").then((module) => ({ default: module.QuoteSceneTemplate })),
103
- "keyFigure": () => import("./key-figure-APD56LCO.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
99
+ "editorialTimeline": () => import("./editorial-timeline-QJU4JJWB.js").then((module) => ({ default: module.TimelineSceneTemplate })),
100
+ "mobileMessage": () => import("./mobile-message-ZMMTUDUQ.js").then((module) => ({ default: module.NotificationSceneTemplate })),
101
+ "comparison": () => import("./comparison-VDGRWPB5.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
102
+ "quote": () => import("./quote-BB6UJT6L.js").then((module) => ({ default: module.QuoteSceneTemplate })),
103
+ "keyFigure": () => import("./key-figure-YUAQW6OE.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
104
104
  };
105
105
 
106
106
  // src/visual-system/catalog/builtin-player.generated.ts
@@ -2095,7 +2095,7 @@ function errorFrom(cause) {
2095
2095
  return new VideoError("Video chat could not produce a playable response", { code: "video_chat_failed", recoverable: false });
2096
2096
  }
2097
2097
  async function responseError(response) {
2098
- return new VideoError(response.status === 429 ? "The conversation limit has been reached. Please try again later." : "Video chat could not produce a playable response", {
2098
+ return new VideoError(response.status === 429 ? "Too many requests right now. Please try again shortly." : "Video chat could not produce a playable response", {
2099
2099
  code: "http_error",
2100
2100
  status: response.status,
2101
2101
  recoverable: false
@@ -2288,7 +2288,7 @@ function useVideoChatSession(options = {}) {
2288
2288
  throw new VideoError("timeoutMs must be positive", { code: "invalid_option" });
2289
2289
  }
2290
2290
  const timeout = setTimeout(() => controller.abort(new DOMException("Video chat timed out", "TimeoutError")), timeoutMs);
2291
- const mode = "cinematic";
2291
+ const mode = currentOptions.mode ?? "cinematic";
2292
2292
  const orientation = currentOptions.orientation ?? "landscape";
2293
2293
  const id = (currentOptions.createTurnId ?? defaultTurnId)();
2294
2294
  const conversation = conversationFor(stateRef.current.turns);
@@ -2300,6 +2300,7 @@ function useVideoChatSession(options = {}) {
2300
2300
  completed: false,
2301
2301
  orientation,
2302
2302
  fixedOrientation: true,
2303
+ mode,
2303
2304
  suggestions: [],
2304
2305
  ...openingMedia ? { openingMedia } : {}
2305
2306
  };
@@ -2368,26 +2369,30 @@ function useVideoChatSession(options = {}) {
2368
2369
  }
2369
2370
  };
2370
2371
  flushRef.current = flush;
2371
- const resolveOpeningMedia = async (keyword, fallbackKeyword) => {
2372
- try {
2373
- const response = await request("opening-media", {
2374
- method: "POST",
2375
- body: JSON.stringify({ keyword, ...fallbackKeyword ? { fallbackKeyword } : {}, orientation })
2376
- }, openingController.signal);
2377
- if (!response.ok || !isOpeningCurrent() || timeline) return;
2378
- const payload = await response.json();
2379
- const media = sanitizeVideoChatMedia(payload.media);
2380
- if (media && isOpeningCurrent() && !timeline) dispatch({ type: "opening-media", id, media });
2381
- } catch {
2382
- }
2383
- };
2384
- const prepareSpeech = async (text, signal) => {
2385
- try {
2386
- return currentOptions.voice ? await withDeadline((child) => voiceRef.current.prepare(text, { signal: child }), 3e3, signal) : await voiceRef.current.prepare(text, { signal });
2387
- } catch (cause) {
2388
- if (currentOptions.voice && !signal.aborted && isCurrent()) unavailableVoiceLines.current.add(text);
2389
- throw cause;
2390
- }
2372
+ const speechLoads = /* @__PURE__ */ new Map();
2373
+ const speechLanes = [Promise.resolve(), Promise.resolve()];
2374
+ let nextSpeechLane = 0;
2375
+ let customVoiceFailed = false;
2376
+ const prepareSpeech = (text, signal) => {
2377
+ const cached = speechLoads.get(text);
2378
+ if (cached) return cached;
2379
+ const lane = nextSpeechLane++ % speechLanes.length;
2380
+ const prepared = speechLanes[lane].then(async () => {
2381
+ signal.throwIfAborted();
2382
+ try {
2383
+ if (customVoiceFailed) throw new Error("Voice preparation did not respond");
2384
+ return currentOptions.voice ? await withDeadline((child) => voiceRef.current.prepare(text, { signal: child }), 3e3, signal) : await voiceRef.current.prepare(text, { signal });
2385
+ } catch (cause) {
2386
+ if (currentOptions.voice && !signal.aborted && isCurrent()) {
2387
+ unavailableVoiceLines.current.add(text);
2388
+ if (cause instanceof DOMException && cause.name === "TimeoutError") customVoiceFailed = true;
2389
+ }
2390
+ throw cause;
2391
+ }
2392
+ });
2393
+ speechLoads.set(text, prepared);
2394
+ speechLanes[lane] = prepared.catch(() => void 0);
2395
+ return prepared;
2391
2396
  };
2392
2397
  const speakOpening = async (text) => {
2393
2398
  try {
@@ -2440,12 +2445,16 @@ function useVideoChatSession(options = {}) {
2440
2445
  if (event.type === "response.warning" || event.type === "response.error" && !event.data.terminal) {
2441
2446
  warn(event.type === "response.warning" && event.data.warning.message === MEDIA_RECOVERY_NOTICE ? MEDIA_RECOVERY_NOTICE : "Some parts were simplified so the response could continue.");
2442
2447
  }
2448
+ if (event.type === "data.video-chat-preparation") {
2449
+ const data = event.data;
2450
+ if (data && typeof data.sceneId === "string" && data.sceneId.length <= 200 && typeof data.narration === "string" && data.narration.trim() && data.narration.length <= 2e3 && speechLoads.size < 32) {
2451
+ void prepareSpeech(data.narration.trim(), controller.signal).catch(() => void 0);
2452
+ }
2453
+ continue;
2454
+ }
2443
2455
  if (event.type === "data.video-chat-opening") {
2444
2456
  const payload = event.data && typeof event.data === "object" && !Array.isArray(event.data) ? event.data : {};
2445
2457
  const line = typeof payload.line === "string" ? payload.line.trim().slice(0, 300) : "";
2446
- const keyword = typeof payload.keyword === "string" ? payload.keyword.trim().slice(0, 80) : "";
2447
- const fallbackKeyword = typeof payload.fallbackKeyword === "string" ? payload.fallbackKeyword.trim().slice(0, 80) : void 0;
2448
- if (!openingMedia && keyword) void resolveOpeningMedia(keyword, fallbackKeyword);
2449
2458
  if (!openingRequested && line) {
2450
2459
  spokenHook = line;
2451
2460
  lines.push(line);
@@ -3328,6 +3337,13 @@ function Logo() {
3328
3337
  return /* @__PURE__ */ jsx7("img", { className: "brand-logo", src: new URL("./assets/vanillasky-logo.svg", import.meta.url).href, alt: "VanillaSky", width: 144, height: 36 });
3329
3338
  }
3330
3339
 
3340
+ // src/video-chat/modes.ts
3341
+ var visualModes = [
3342
+ { id: "cinematic", label: "AI video", note: "Generated footage shaped by your prompt" },
3343
+ { id: "pexels", label: "Pexels", note: "Stock footage with lower generation costs" }
3344
+ ];
3345
+ var defaultMode = visualModes[0];
3346
+
3331
3347
  // src/video-chat/video-chat.tsx
3332
3348
  import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
3333
3349
  var DESKTOP_WIDTH = 900;
@@ -3343,28 +3359,13 @@ function useViewportOrientation() {
3343
3359
  }, []);
3344
3360
  return portrait ? "portrait" : "landscape";
3345
3361
  }
3346
- var STEPS = [
3347
- "Rolling camera\u2026",
3348
- "Framing the shots\u2026",
3349
- "Filming your response\u2026",
3350
- "Still filming\u2026"
3351
- ];
3352
- function useFilmingStep(active, key) {
3353
- const [step, setStep] = useState7(0);
3354
- useEffect9(() => {
3355
- if (!active) return;
3356
- setStep(0);
3357
- const stepper = window.setInterval(() => setStep((current) => current + 1), 3e3);
3358
- return () => window.clearInterval(stepper);
3359
- }, [active, key]);
3360
- return STEPS[Math.min(step, STEPS.length - 1)];
3361
- }
3362
3362
  function Waveform({ active, listening }) {
3363
3363
  return /* @__PURE__ */ jsx8("span", { className: `waveform${active ? " on" : ""}${listening ? " hearing" : ""}`, "aria-hidden": "true", children: [0, 1, 2, 3, 4].map((bar) => /* @__PURE__ */ jsx8("span", { style: { animationDelay: `${bar * 140}ms`, animationDuration: `${900 + bar * 130}ms` } }, bar)) });
3364
3364
  }
3365
3365
  function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice = false }) {
3366
3366
  const [dismissedNoticeTurn, setDismissedNoticeTurn] = useState7();
3367
3367
  const [draft, setDraft] = useState7("");
3368
+ const [selectedMode, setSelectedMode] = useState7();
3368
3369
  const [savedSessions, setSavedSessions] = useState7([]);
3369
3370
  const [historyOpen, setHistoryOpen] = useState7(false);
3370
3371
  const [settingsOpen, setSettingsOpen] = useState7(false);
@@ -3380,7 +3381,8 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3380
3381
  const sessionOrientation = options.orientation ?? viewportOrientation;
3381
3382
  const { chat, restoreSession } = useVideoChatSession({
3382
3383
  ...options,
3383
- orientation: sessionOrientation
3384
+ orientation: sessionOrientation,
3385
+ mode: selectedMode ?? options.mode
3384
3386
  });
3385
3387
  const instanceId = useId();
3386
3388
  const historyId = `${instanceId}-history`;
@@ -3417,7 +3419,6 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3417
3419
  resumeAfterInput.current = false;
3418
3420
  inputRef.current?.blur();
3419
3421
  void chat.ask(prompt, typeof value === "string" ? void 0 : {
3420
- openingMedia: value.media,
3421
3422
  opening: value.opening
3422
3423
  });
3423
3424
  }, [chat, listen]);
@@ -3440,16 +3441,10 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3440
3441
  resumeAfterInput.current = false;
3441
3442
  inputRef.current?.blur();
3442
3443
  }, [chat, listen]);
3443
- const current = chat.currentTurn;
3444
3444
  const shown = chat.shownTurn;
3445
3445
  const showing = chat.playerProps != null;
3446
- const [openingAsset, setOpeningAsset] = useState7();
3447
- const openingAssetKey = `${shown?.id ?? ""}:${shown?.openingMedia?.url ?? ""}`;
3448
- const openingMediaFailed = openingAsset?.key === openingAssetKey && openingAsset.state === "failed";
3449
- const openingMediaReady = openingAsset?.key === openingAssetKey && openingAsset.state === "ready";
3450
- const openingChapter = !showing && Boolean(shown?.opening) && (!shown?.openingMedia || openingMediaFailed || !openingMediaReady);
3451
- const waitingForPicture = chat.turns.length > 0 && !showing && (chat.status === "composing" || chat.status === "playing" || chat.status === "paused");
3452
- const filmingStep = useFilmingStep(waitingForPicture, current?.id);
3446
+ const openingChapter = !showing && Boolean(shown?.prompt);
3447
+ const openingTitle = shown?.opening ?? shown?.prompt ?? "";
3453
3448
  const status = chat.turns.length === 0 ? "idle" : chat.status === "composing" ? "drawing" : chat.status === "playing" ? "narrating" : chat.status === "error" || chat.status === "cancelled" ? "ended" : chat.status;
3454
3449
  const shownOrientation = shown?.orientation ?? sessionOrientation;
3455
3450
  const stageOrientation = shown?.fixedOrientation ? shownOrientation : sessionOrientation;
@@ -3509,7 +3504,10 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3509
3504
  onKeyDownCapture: controls.reveal,
3510
3505
  children: [
3511
3506
  /* @__PURE__ */ jsxs6("header", { className: "chrome", ...controlEvents, children: [
3512
- /* @__PURE__ */ jsx8("div", { className: "session-brand", children: /* @__PURE__ */ jsx8(Logo, {}) }),
3507
+ /* @__PURE__ */ jsxs6("div", { className: "session-brand", children: [
3508
+ /* @__PURE__ */ jsx8(Logo, {}),
3509
+ (shown?.mode ?? selectedMode ?? options.mode) === "pexels" && /* @__PURE__ */ jsx8("a", { className: "media-credit", href: "https://www.pexels.com", target: "_blank", rel: "noopener noreferrer", children: "Videos by Pexels" })
3510
+ ] }),
3513
3511
  /* @__PURE__ */ jsxs6("div", { className: "group", children: [
3514
3512
  /* @__PURE__ */ jsxs6(
3515
3513
  "button",
@@ -3564,27 +3562,8 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3564
3562
  /* @__PURE__ */ jsxs6("div", { className: "stage-area", children: [
3565
3563
  /* @__PURE__ */ jsxs6("div", { className: "stage", style: { background: "#000" }, children: [
3566
3564
  !showing && /* @__PURE__ */ jsxs6(Fragment4, { children: [
3567
- /* @__PURE__ */ jsx8("div", { className: "ground", "aria-hidden": "true" }),
3568
- openingChapter && /* @__PURE__ */ jsx8(OpeningChapter, { title: shown.opening }, shown.id),
3569
- shown?.openingMedia && !openingMediaFailed && /* @__PURE__ */ jsxs6(Fragment4, { children: [
3570
- /* @__PURE__ */ jsx8(
3571
- Frame,
3572
- {
3573
- media: shown.openingMedia,
3574
- poster: true,
3575
- revealWhenReady: true,
3576
- onReady: () => setOpeningAsset({ key: openingAssetKey, state: "ready" }),
3577
- onError: () => setOpeningAsset({ key: openingAssetKey, state: "failed" })
3578
- },
3579
- openingAssetKey
3580
- ),
3581
- openingMediaReady && /* @__PURE__ */ jsx8("div", { className: "opening-wash", "aria-hidden": "true" })
3582
- ] }),
3583
- chat.turns.length === 0 && /* @__PURE__ */ jsx8(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle }),
3584
- shown?.prompt && !shown.opening && /* @__PURE__ */ jsxs6("div", { className: "asked", children: [
3585
- /* @__PURE__ */ jsx8("p", { className: "asked-prompt", children: shown.prompt }),
3586
- waitingForPicture && /* @__PURE__ */ jsx8("p", { className: "asked-step", "aria-live": "polite", children: filmingStep })
3587
- ] })
3565
+ openingChapter && /* @__PURE__ */ jsx8(OpeningChapter, { title: openingTitle.length > 120 ? `${openingTitle.slice(0, 117).trimEnd()}\u2026` : openingTitle }, shown.id),
3566
+ chat.turns.length === 0 && /* @__PURE__ */ jsx8(Welcome, { data: chat.welcome, onAsk: ask, title: welcomeTitle })
3588
3567
  ] }),
3589
3568
  chat.playerProps && /* @__PURE__ */ jsx8("div", { className: "player-fit", style: { width: stageOrientation === "portrait" ? "min(100cqw, 56.25cqh)" : "min(100cqw, 177.7778cqh)" }, children: /* @__PURE__ */ jsx8(
3590
3569
  VideoPlayer,
@@ -3683,6 +3662,25 @@ function VideoChat({ options = {}, className, welcomeTitle, showRecoveryNotice =
3683
3662
  /* @__PURE__ */ jsx8("h2", { children: "Settings" }),
3684
3663
  /* @__PURE__ */ jsx8("button", { type: "button", className: "round", "aria-label": "Close settings", onClick: closeSettings, children: /* @__PURE__ */ jsx8(Close, {}) })
3685
3664
  ] }),
3665
+ /* @__PURE__ */ jsxs6("fieldset", { className: "playback-options", children: [
3666
+ /* @__PURE__ */ jsx8("legend", { children: "Video source" }),
3667
+ visualModes.filter((mode) => chat.availableModes.includes(mode.id)).map((mode) => /* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
3668
+ /* @__PURE__ */ jsxs6("span", { children: [
3669
+ /* @__PURE__ */ jsx8("strong", { children: mode.label }),
3670
+ /* @__PURE__ */ jsx8("small", { children: mode.note })
3671
+ ] }),
3672
+ /* @__PURE__ */ jsx8(
3673
+ "input",
3674
+ {
3675
+ type: "radio",
3676
+ name: `${instanceId}-source`,
3677
+ value: mode.id,
3678
+ checked: (selectedMode ?? options.mode ?? "cinematic") === mode.id,
3679
+ onChange: () => setSelectedMode(mode.id)
3680
+ }
3681
+ )
3682
+ ] }, mode.id))
3683
+ ] }),
3686
3684
  /* @__PURE__ */ jsxs6("fieldset", { className: "playback-options", children: [
3687
3685
  /* @__PURE__ */ jsx8("legend", { children: "Watching" }),
3688
3686
  /* @__PURE__ */ jsxs6("label", { className: "switch-row", children: [
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SceneVideoBackdrop
3
- } from "./chunk-SI6CFFAA.js";
4
- import "./chunk-YZZSWGC3.js";
3
+ } from "./chunk-O6FBIB4L.js";
4
+ import "./chunk-OOBT4X46.js";
5
5
  export {
6
6
  SceneVideoBackdrop
7
7
  };
package/dist/server.d.ts CHANGED
@@ -2,8 +2,8 @@ import { k as VideoGenerationContext, l as VideoPlanner, m as VideoRequest, a as
2
2
  import { a as VideoFinishReason, V as VideoWarning, b as VideoEvent } from './events-B4YCc4vc.js';
3
3
  export { c as VideoWarningCategory } from './events-B4YCc4vc.js';
4
4
  import { S as SceneTemplateMetadata } from './catalog-types-WTbLP6Jh.js';
5
- import { f as VideoChatWelcomeOptions } from './types-CdBmNTGD.js';
6
- export { d as VideoChatCapabilities, g as VideoChatConversationTurn, V as VideoChatMode, h as VideoChatWelcomePrompt } from './types-CdBmNTGD.js';
5
+ import { V as VideoChatMode, f as VideoChatWelcomeOptions } from './types-BqB8zC9u.js';
6
+ export { d as VideoChatCapabilities, g as VideoChatConversationTurn, h as VideoChatWelcomePrompt } from './types-BqB8zC9u.js';
7
7
 
8
8
  interface VideoProviderUsage {
9
9
  inputTokens?: number;
@@ -236,6 +236,18 @@ interface VideoChatHandlerOptions extends Pick<VideoHandlerOptions, "templates"
236
236
  maxGeneratedVideos?: number;
237
237
  /** Generated media deadline in milliseconds, 1–120000. Defaults to 15000. Host providers must honor cancellation. */
238
238
  generateVideoTimeoutMs?: number;
239
+ /** Provider-supported clip duration in seconds, 2–20. Defaults to 5; does not change provider billing configuration. */
240
+ generatedClipDurationSec?: number;
241
+ /** Safe host-only phase timings; never includes prompts, narration, media URLs or provider error text. */
242
+ onDiagnostic?: (event: {
243
+ requestId: string;
244
+ mode: VideoChatMode;
245
+ phase: "request-accepted" | "opening-authored" | "shot-authored" | "media-start" | "media-end" | "media-skipped";
246
+ elapsedMs: number;
247
+ sceneId?: string;
248
+ durationMs?: number;
249
+ reason?: "ready" | "empty" | "provider-error" | "timeout" | "cancelled" | "allowance" | "deadline" | "not-configured";
250
+ }) => unknown;
239
251
  /** Trusted application guidance appended to the general-purpose response brief. */
240
252
  instructions?: string;
241
253
  /** Application-owned prompts and visual searches shown before the first turn. */
@@ -254,4 +266,4 @@ type VideoChatHandler = (request: Request) => Promise<Response>;
254
266
  */
255
267
  declare function createVideoChatHandler(options: VideoChatHandlerOptions): VideoChatHandler;
256
268
 
257
- export { SceneTemplateMetadata as ServerTemplateMetadata, type ServerTemplateRegistry, type VideoChatHandlerOptions, VideoChatWelcomeOptions, VideoFinishReason, type VideoGenerationSummary, type VideoProviderUsage, VideoWarning, createServerTemplateRegistry, createVideoChatHandler };
269
+ export { SceneTemplateMetadata as ServerTemplateMetadata, type ServerTemplateRegistry, type VideoChatHandlerOptions, VideoChatMode, VideoChatWelcomeOptions, VideoFinishReason, type VideoGenerationSummary, type VideoProviderUsage, VideoWarning, createServerTemplateRegistry, createVideoChatHandler };