@vanillaskyai/video 0.10.13 → 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 +8 -0
- package/dist/server.js +45 -3
- package/package.json +1 -1
- package/starters/video-chat/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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
|
+
|
|
11
|
+
## 0.10.14
|
|
12
|
+
|
|
13
|
+
- Attach content-free record-shape diagnostics to rejected chat plans without accepting alternate formats or exposing model output.
|
|
14
|
+
|
|
7
15
|
## 0.10.13
|
|
8
16
|
|
|
9
17
|
- Plan concise AI answers around the configured video-attempt budget, including the ending, while retaining complete authored narration and chapter recovery.
|
package/dist/server.js
CHANGED
|
@@ -892,6 +892,24 @@ function continueAfterOpening(narration, earlier) {
|
|
|
892
892
|
|
|
893
893
|
// src/server/chat-shot-planner.ts
|
|
894
894
|
var object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
895
|
+
function planShapeError(value) {
|
|
896
|
+
const part = object(value);
|
|
897
|
+
const has = (key) => Boolean(part && Object.hasOwn(part, key));
|
|
898
|
+
const shape = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
899
|
+
const discriminator = !has("type") ? "missing" : part.type === "answer" || part.type === "shot" ? part.type : typeof part.type === "string" ? "other-string" : "non-string";
|
|
900
|
+
return new Error("Chat plan requires an answer brief followed by shots", { cause: {
|
|
901
|
+
code: "chat_plan_shape",
|
|
902
|
+
shape,
|
|
903
|
+
discriminator,
|
|
904
|
+
fields: {
|
|
905
|
+
opening: has("opening"),
|
|
906
|
+
subject: has("subject"),
|
|
907
|
+
development: has("development"),
|
|
908
|
+
visualDirection: has("visualDirection"),
|
|
909
|
+
ending: has("ending")
|
|
910
|
+
}
|
|
911
|
+
} });
|
|
912
|
+
}
|
|
895
913
|
function text(value, maximum) {
|
|
896
914
|
return typeof value === "string" && value.trim().length <= maximum ? value.trim() : "";
|
|
897
915
|
}
|
|
@@ -934,6 +952,21 @@ function readShot(value, clipDurationSec, answerSubject = "") {
|
|
|
934
952
|
continuity: item?.continuity === "continue" ? "continue" : "cut"
|
|
935
953
|
};
|
|
936
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
|
+
}
|
|
937
970
|
function replaceStream(source, textStream) {
|
|
938
971
|
if (!(typeof source === "object" && source != null && "textStream" in source)) return textStream;
|
|
939
972
|
return new Proxy({ textStream }, {
|
|
@@ -966,7 +999,7 @@ function createChatShotPlanner(options) {
|
|
|
966
999
|
const upstream = typeof source === "object" && source != null && "textStream" in source ? source.textStream : source;
|
|
967
1000
|
const translated = (async function* () {
|
|
968
1001
|
let brief, buffer = "", index = 0, bodyDuration = 0, lastNarration = "";
|
|
969
|
-
let firstBody = true;
|
|
1002
|
+
let firstBody = true, recordsSeen = 0;
|
|
970
1003
|
const reject = (cause) => {
|
|
971
1004
|
incomplete.add(context);
|
|
972
1005
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
@@ -993,7 +1026,15 @@ function createChatShotPlanner(options) {
|
|
|
993
1026
|
const line = (raw) => {
|
|
994
1027
|
const trimmed = raw.trim();
|
|
995
1028
|
if (!trimmed || /^```(?:json|ndjson)?$/i.test(trimmed)) return;
|
|
996
|
-
const
|
|
1029
|
+
const firstRecord = recordsSeen++ === 0;
|
|
1030
|
+
const value = JSON.parse(trimmed);
|
|
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
|
+
}
|
|
997
1038
|
if (part?.type === "answer") {
|
|
998
1039
|
if (brief) throw new Error("Chat answer brief was emitted more than once");
|
|
999
1040
|
brief = { opening: text(part.opening, 300), subject: text(part.subject, 80), visualDirection: text(part.visualDirection, 600), development: text(part.development, 2e3) };
|
|
@@ -1007,7 +1048,7 @@ function createChatShotPlanner(options) {
|
|
|
1007
1048
|
options.publishOpening(brief.opening ? { line: brief.opening, keyword: brief.subject } : void 0);
|
|
1008
1049
|
return;
|
|
1009
1050
|
}
|
|
1010
|
-
if (part?.type !== "shot") throw
|
|
1051
|
+
if (part?.type !== "shot") throw planShapeError(value);
|
|
1011
1052
|
if (!brief) throw new Error("Chat shot arrived before its answer brief");
|
|
1012
1053
|
const shot = readShot(part, clipDurationSec, brief.subject);
|
|
1013
1054
|
if (shot.narration === brief.ending?.narration) return;
|
|
@@ -1174,6 +1215,7 @@ async function* resolveShots(parts, context, options) {
|
|
|
1174
1215
|
// src/server/video-chat-prompts.ts
|
|
1175
1216
|
function createVideoChatResponseInstructions(generatedVideoAvailable, openingAlreadyProvided = false, maxGeneratedVideos = 5, clipDurationSec = 5, mode = "cinematic") {
|
|
1176
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.',
|
|
1177
1219
|
"Write a complete, intentful video answer as newline-delimited JSON. Match the user's form and tone; mixed intents can combine directions.",
|
|
1178
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"}}.`,
|
|
1179
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