@vanillaskyai/video 0.10.14 → 0.10.15

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/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.10.15
8
+
9
+ - Recover a mislabeled first chat brief only when its complete authored content validates, preserving its shots and ending. Keep malformed JSON and incomplete or later records rejected.
10
+
7
11
  ## 0.10.14
8
12
 
9
13
  - Attach content-free record-shape diagnostics to rejected chat plans without accepting alternate formats or exposing model output.
package/dist/server.js CHANGED
@@ -952,6 +952,21 @@ function readShot(value, clipDurationSec, answerSubject = "") {
952
952
  continuity: item?.continuity === "continue" ? "continue" : "cut"
953
953
  };
954
954
  }
955
+ function recoverFirstBrief(part, clipDurationSec) {
956
+ if (!part || typeof part.type !== "string" || !part.type.trim() || part.type === "answer" || part.type === "shot") return;
957
+ const bounded = (value, maximum, allowEmpty = false) => typeof value === "string" && value.trim().length <= maximum && (allowEmpty || Boolean(value.trim()));
958
+ if (!["opening", "subject", "development", "visualDirection", "ending"].every((key) => Object.hasOwn(part, key)) || !bounded(part.opening, 300) || !bounded(part.subject, 80) || !bounded(part.development, 2e3, true) || !bounded(part.visualDirection, 600)) return;
959
+ const ending = object(part.ending);
960
+ if (!ending || !bounded(ending.narration, 2e3) || !bounded(ending.subject, 80, true) || !bounded(ending.action, 600, true) || Object.hasOwn(ending, "title") && !bounded(ending.title, 65) || typeof ending.durationSec !== "number" || !Number.isFinite(ending.durationSec) || ending.continuity !== "cut" && ending.continuity !== "continue") return;
961
+ const subject = text(part.subject, 80);
962
+ return {
963
+ opening: text(part.opening, 300),
964
+ subject,
965
+ development: text(part.development, 2e3),
966
+ visualDirection: text(part.visualDirection, 600),
967
+ ending: readShot(ending, clipDurationSec, subject)
968
+ };
969
+ }
955
970
  function replaceStream(source, textStream) {
956
971
  if (!(typeof source === "object" && source != null && "textStream" in source)) return textStream;
957
972
  return new Proxy({ textStream }, {
@@ -984,7 +999,7 @@ function createChatShotPlanner(options) {
984
999
  const upstream = typeof source === "object" && source != null && "textStream" in source ? source.textStream : source;
985
1000
  const translated = (async function* () {
986
1001
  let brief, buffer = "", index = 0, bodyDuration = 0, lastNarration = "";
987
- let firstBody = true;
1002
+ let firstBody = true, recordsSeen = 0;
988
1003
  const reject = (cause) => {
989
1004
  incomplete.add(context);
990
1005
  const error = cause instanceof Error ? cause : new Error(String(cause));
@@ -1011,8 +1026,15 @@ function createChatShotPlanner(options) {
1011
1026
  const line = (raw) => {
1012
1027
  const trimmed = raw.trim();
1013
1028
  if (!trimmed || /^```(?:json|ndjson)?$/i.test(trimmed)) return;
1029
+ const firstRecord = recordsSeen++ === 0;
1014
1030
  const value = JSON.parse(trimmed);
1015
1031
  const part = object(value);
1032
+ const recovered = firstRecord && !brief && index === 0 ? recoverFirstBrief(part, clipDurationSec) : void 0;
1033
+ if (recovered) {
1034
+ brief = recovered;
1035
+ options.publishOpening({ line: brief.opening, keyword: brief.subject });
1036
+ return;
1037
+ }
1016
1038
  if (part?.type === "answer") {
1017
1039
  if (brief) throw new Error("Chat answer brief was emitted more than once");
1018
1040
  brief = { opening: text(part.opening, 300), subject: text(part.subject, 80), visualDirection: text(part.visualDirection, 600), development: text(part.development, 2e3) };
@@ -1193,6 +1215,7 @@ async function* resolveShots(parts, context, options) {
1193
1215
  // src/server/video-chat-prompts.ts
1194
1216
  function createVideoChatResponseInstructions(generatedVideoAvailable, openingAlreadyProvided = false, maxGeneratedVideos = 5, clipDurationSec = 5, mode = "cinematic") {
1195
1217
  return [
1218
+ 'Use the exact record type "answer" for the first brief and "shot" for developing beats. Output JSON records only, with no prose outside them, including when explaining a limitation.',
1196
1219
  "Write a complete, intentful video answer as newline-delimited JSON. Match the user's form and tone; mixed intents can combine directions.",
1197
1220
  `First write one brief: {"type":"answer","intent":"explanation|story|comedy|imagination|practical","opening":"a short inviting spoken introduction of 6\u20139 words","subject":"literal visual subject","development":"the essential development of this answer","visualDirection":"consistent subjects, appearance and visual approach","ending":{"title":"short meaningful chapter title, at most 65 characters","narration":"the authored payoff","subject":"literal subject","action":"visible action or change","durationSec":${clipDurationSec},"continuity":"cut|continue"}}.`,
1198
1221
  `Then stream each developing shot on its own line: {"type":"shot","title":"short meaningful chapter title, at most 65 characters","narration":"the exact spoken beat","subject":"2\u20138 literal filmable words, at most 80 characters","action":"concrete subject, action or visible change and useful framing","durationSec":${clipDurationSec},"continuity":"cut|continue"}.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanillaskyai/video",
3
- "version": "0.10.14",
3
+ "version": "0.10.15",
4
4
  "description": "Open-source voice-and-video chat SDK for AI applications.",
5
5
  "keywords": [
6
6
  "video-chat",
@@ -9,7 +9,7 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@vanillaskyai/video": "0.10.14",
12
+ "@vanillaskyai/video": "0.10.15",
13
13
  "react": "^19.2.8",
14
14
  "react-dom": "^19.2.8",
15
15
  "@ai-sdk/anthropic": "^3.0.0",