@pixerate/flair 0.34.0 → 0.35.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/dist/index.d.ts CHANGED
@@ -1448,16 +1448,34 @@ interface SceneOutline {
1448
1448
  }
1449
1449
  type SceneToolResult = {
1450
1450
  ok: true;
1451
+ /** The scene after the tool ran: keep it as the new state. */
1451
1452
  scene: Scene;
1452
1453
  outline?: SceneOutline;
1453
1454
  issues: SceneIssue[];
1454
1455
  review?: Review;
1456
+ /**
1457
+ * What to send back to the model as the tool's result: only what the
1458
+ * call asked for (the outline without the full scene, a summary of an
1459
+ * edit, the review without its markdown), so the conversation stays small.
1460
+ */
1461
+ output: SceneToolOutput;
1455
1462
  } | {
1456
1463
  ok: false;
1457
1464
  error: string;
1465
+ failedStep?: number;
1466
+ output: SceneToolOutput;
1467
+ };
1468
+ /** The model-facing result of a scene tool call (see `SceneToolResult.output`). */
1469
+ type SceneToolOutput = {
1470
+ ok: boolean;
1471
+ [field: string]: unknown;
1458
1472
  };
1473
+ declare function outlineScene(scene: Scene): SceneOutline;
1459
1474
  /**
1460
- * Pure state machine that applies tool operations to a Scene.
1475
+ * Pure state machine that applies tool operations to a Scene. Arguments are
1476
+ * checked against the tool's `input_schema` first; a refusal (`ok: false`)
1477
+ * leaves the scene as it was and says what to fix. Send `result.output` to
1478
+ * the model and keep `result.scene` as the new state.
1461
1479
  */
1462
1480
  declare function applySceneTool(scene: Scene, toolName: string, input?: Record<string, any>): Promise<SceneToolResult>;
1463
1481
 
@@ -1470,7 +1488,7 @@ declare function applySceneTool(scene: Scene, toolName: string, input?: Record<s
1470
1488
  */
1471
1489
 
1472
1490
  interface BatchCommand {
1473
- /** Name of the tool to execute (e.g. 'update_shot', 'insert_shots', 'set_rig_widget'). */
1491
+ /** Name of the tool to execute (e.g. 'update_shot', 'insert_shots', 'set_rig_widget'; not 'batch_scene'). */
1474
1492
  tool: string;
1475
1493
  /** Parameters for the tool call. */
1476
1494
  params?: Record<string, any>;
@@ -1489,7 +1507,9 @@ interface BatchExecutionResult {
1489
1507
  }
1490
1508
  /**
1491
1509
  * Recursively substitutes `$stepIndex.path` references in parameters using previous step results.
1492
- * E.g., "$1.scene.shots[0].duration" -> substituted with the duration value from step 1 result.
1510
+ * E.g., "$1.scene.shots[0].hold" -> the hold of the first shot after step 1. A string that is
1511
+ * only a reference takes the value as it is; a reference inside a longer string is filled in
1512
+ * as text. Throws when a reference names a step that has not run or a path that is not there.
1493
1513
  */
1494
1514
  declare function substituteVariables(value: any, stepResults: Map<number, SceneToolResult>): any;
1495
1515
  /**
@@ -1672,4 +1692,4 @@ declare function exportFcp7Xml(scene: Scene, options?: Fcp7ExportOptions): strin
1672
1692
  */
1673
1693
  declare function exportCmx3600Edl(scene: Scene, options?: EdlExportOptions): string;
1674
1694
 
1675
- export { ALL_BLEND_MODES, ALL_TRANSITION_TYPES, type AgentToolDefinition, type AscCdl, type BatchCommand, type BatchExecutionResult, type BatchExecutionStepResult, type Behavior, type BehaviorBase, type BlendMode, type BlockDissolveOptions, type BlurEffect, type BurstEffect, CAMERA_MS, CAMERA_STEPS, CAMERA_STEP_MS, CAMERA_TRANSITIONS, COMMON_FRAME_RATES, type CameraAngle, type CameraLens, type CameraSample, type CameraShot, type CameraTransition, type ClearCommand, Clock, type ColorRGB, type ColorRGBA, type ColorRgba, type ColorSpace, ControlValue, type CounterEffect, type CrossZoomOptions, type CustomCommand, DEFAULT_PERSPECTIVE, DEFAULT_STAGE, type DeleteCommand, type DynamicFraming, EASY_EASE, EFFECT_FADE_MS, EFFECT_TYPES, type EasingCurve, type EdlExportOptions, type Effect, type EffectBase, type ElementCommand, FLAIR_AGENT_TOOLS, type Fcp7ExportOptions, type FlowEffect, type FramingOptions, type GifOptions, type GlassWipeOptions, type GlowEffect, type GradientEffect, type GridWipeOptions, type HighlightEffect, type KeyframeEase, type LiftGammaGain, type LightLeakOptions, type LinearWipeOptions, type LottieAnimatable, type LottieAnimation, type LottieExportOptions, type LottieKeyframe, type LottieLayer, type LottieShapeItem, type LottieTransform, MAX_TILT, type MediaEffect, MemorySceneBoard, type MotnExportOptions, NO_CHECK, type OscillateBehavior, type OvershootBehavior, type PathWaypoint, type PlayOptions, type PlayResult, type Point, type PopupWidget, type RadialIrisOptions, type RampBehavior, type RateBehavior, type RationalFrameRate, type Rect, type ReplicatorConfig, type ReplicatorPattern, type ReplicatorSequence, type Review, type ReviewOptions, type RigWidget, type RippleEffect, type RunningEffect, SCROLL_SETTLE, SCROLL_SMOOTHING, SMALL_TEXT_PX, type SMPTEFields, type Scene, type SceneBoard, type SceneCommand, type SceneElement, type SceneIssue, type SceneMedia, type SceneOutline, type SceneOutlineShot, type SceneRig, type SceneToolResult, ScrollTracker, type ShimmerEffect, type Shot, type ShotCamera, type ShotCheck, type ShotLog, type ShotTiming, type SliderSnapshot, type SliderWidget, type SpatialMotionPath, type SpatialPathSample, type SpotlightEffect, type Stage, type StandaloneOptions, type StandardFrameRateKey, TRANSITION_BLUR, type ThrowBehavior, type ThumbnailHandle, ThumbnailPool, type ThumbnailPoolOptions, type TimerEffect, type ToggleWidget, type TransitionOptions, type TransitionSample, type TransitionType, type TwisterOptions, type UpdateCommand, type Vec2, type Vec3, type View, type WriggleBehavior, applyAscCdl, applyLiftGammaGain, applyRig, applySceneBatch, applySceneTool, applyVibrance, areaOf, between, bez1d, bez1dDerivative, blendRgba, blendTransitionPixel, boardHeight, boundsOf, calculateFramingRect, calculateShotTimings, cameraAngle, cameraAt, cameraBlur, cameraFilter, cameraSamples, cameraTrack, cameraTransform, centreOf, checkShot, convertColorSpace, createFrameRate, createLottieEase, createMotionPath, cutsHalfway, easeInOutCubic, easeProgress, effectGone, effectOpacity, evaluateBehavior, evaluateKeyframeSegment, expandReplicator, exportCmx3600Edl, exportFcp7Xml, exportLottie, exportMotn, formatTimecode, fractalWiggle, frame, framesToSMPTE, framesToTimecode, getColorGradingGLSL, getDropFrameCount, getGlslBlendFunction, getOklabGLSL, getTimecodeBase, getTransitionGLSL, getWhiteBalanceGains, hash64, holdTime, interpolateOklab, interpolateOklch, isAngled, isDeleteCommand, isElementCommand, isUpdateCommand, linearRgbToSrgb, linearSrgbToOklab, linearToSrgb, loopTime, mixSeeds, motionBlur, motionBlurAngle, moveBlur, noise1d, noise3d, oklabToLinearSrgb, oklabToOklch, oklabToSrgb, oklchToOklab, overlapArea, parseTimecode, pingPongTime, playScene, projectPoint, quinticFade, recordGif, registerThumbnail, reviewScene, sampleMotionPath, sampleMotionPathAtTime, sampleTransition, sceneStage, screenBox, setRigWidget, sharedThumbnailPool, shotMoveMs, shotView, simulateScene, smpteToFrames, solveBezierU, speedRampTime, srgbToLinear, srgbToLinearRgb, srgbToOklab, standaloneHtml, standaloneSceneHtml, strobeTime, substituteVariables, supportsDropFrame, timecodeToFrames, titleBox, touchedIds, transitionStrength, transitionView, unitFloat, validateScene, viewAt, viewportProgress, visibleRect };
1695
+ export { ALL_BLEND_MODES, ALL_TRANSITION_TYPES, type AgentToolDefinition, type AscCdl, type BatchCommand, type BatchExecutionResult, type BatchExecutionStepResult, type Behavior, type BehaviorBase, type BlendMode, type BlockDissolveOptions, type BlurEffect, type BurstEffect, CAMERA_MS, CAMERA_STEPS, CAMERA_STEP_MS, CAMERA_TRANSITIONS, COMMON_FRAME_RATES, type CameraAngle, type CameraLens, type CameraSample, type CameraShot, type CameraTransition, type ClearCommand, Clock, type ColorRGB, type ColorRGBA, type ColorRgba, type ColorSpace, ControlValue, type CounterEffect, type CrossZoomOptions, type CustomCommand, DEFAULT_PERSPECTIVE, DEFAULT_STAGE, type DeleteCommand, type DynamicFraming, EASY_EASE, EFFECT_FADE_MS, EFFECT_TYPES, type EasingCurve, type EdlExportOptions, type Effect, type EffectBase, type ElementCommand, FLAIR_AGENT_TOOLS, type Fcp7ExportOptions, type FlowEffect, type FramingOptions, type GifOptions, type GlassWipeOptions, type GlowEffect, type GradientEffect, type GridWipeOptions, type HighlightEffect, type KeyframeEase, type LiftGammaGain, type LightLeakOptions, type LinearWipeOptions, type LottieAnimatable, type LottieAnimation, type LottieExportOptions, type LottieKeyframe, type LottieLayer, type LottieShapeItem, type LottieTransform, MAX_TILT, type MediaEffect, MemorySceneBoard, type MotnExportOptions, NO_CHECK, type OscillateBehavior, type OvershootBehavior, type PathWaypoint, type PlayOptions, type PlayResult, type Point, type PopupWidget, type RadialIrisOptions, type RampBehavior, type RateBehavior, type RationalFrameRate, type Rect, type ReplicatorConfig, type ReplicatorPattern, type ReplicatorSequence, type Review, type ReviewOptions, type RigWidget, type RippleEffect, type RunningEffect, SCROLL_SETTLE, SCROLL_SMOOTHING, SMALL_TEXT_PX, type SMPTEFields, type Scene, type SceneBoard, type SceneCommand, type SceneElement, type SceneIssue, type SceneMedia, type SceneOutline, type SceneOutlineShot, type SceneRig, type SceneToolOutput, type SceneToolResult, ScrollTracker, type ShimmerEffect, type Shot, type ShotCamera, type ShotCheck, type ShotLog, type ShotTiming, type SliderSnapshot, type SliderWidget, type SpatialMotionPath, type SpatialPathSample, type SpotlightEffect, type Stage, type StandaloneOptions, type StandardFrameRateKey, TRANSITION_BLUR, type ThrowBehavior, type ThumbnailHandle, ThumbnailPool, type ThumbnailPoolOptions, type TimerEffect, type ToggleWidget, type TransitionOptions, type TransitionSample, type TransitionType, type TwisterOptions, type UpdateCommand, type Vec2, type Vec3, type View, type WriggleBehavior, applyAscCdl, applyLiftGammaGain, applyRig, applySceneBatch, applySceneTool, applyVibrance, areaOf, between, bez1d, bez1dDerivative, blendRgba, blendTransitionPixel, boardHeight, boundsOf, calculateFramingRect, calculateShotTimings, cameraAngle, cameraAt, cameraBlur, cameraFilter, cameraSamples, cameraTrack, cameraTransform, centreOf, checkShot, convertColorSpace, createFrameRate, createLottieEase, createMotionPath, cutsHalfway, easeInOutCubic, easeProgress, effectGone, effectOpacity, evaluateBehavior, evaluateKeyframeSegment, expandReplicator, exportCmx3600Edl, exportFcp7Xml, exportLottie, exportMotn, formatTimecode, fractalWiggle, frame, framesToSMPTE, framesToTimecode, getColorGradingGLSL, getDropFrameCount, getGlslBlendFunction, getOklabGLSL, getTimecodeBase, getTransitionGLSL, getWhiteBalanceGains, hash64, holdTime, interpolateOklab, interpolateOklch, isAngled, isDeleteCommand, isElementCommand, isUpdateCommand, linearRgbToSrgb, linearSrgbToOklab, linearToSrgb, loopTime, mixSeeds, motionBlur, motionBlurAngle, moveBlur, noise1d, noise3d, oklabToLinearSrgb, oklabToOklch, oklabToSrgb, oklchToOklab, outlineScene, overlapArea, parseTimecode, pingPongTime, playScene, projectPoint, quinticFade, recordGif, registerThumbnail, reviewScene, sampleMotionPath, sampleMotionPathAtTime, sampleTransition, sceneStage, screenBox, setRigWidget, sharedThumbnailPool, shotMoveMs, shotView, simulateScene, smpteToFrames, solveBezierU, speedRampTime, srgbToLinear, srgbToLinearRgb, srgbToOklab, standaloneHtml, standaloneSceneHtml, strobeTime, substituteVariables, supportsDropFrame, timecodeToFrames, titleBox, touchedIds, transitionStrength, transitionView, unitFloat, validateScene, viewAt, viewportProgress, visibleRect };
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  standaloneHtml,
10
10
  standaloneSceneHtml,
11
11
  validateScene
12
- } from "./chunk-ZULFHADW.js";
12
+ } from "./chunk-OLTY4APV.js";
13
13
  import {
14
14
  calculateShotTimings,
15
15
  createLottieEase,
@@ -870,11 +870,164 @@ function sampleMotionPathAtTime(path, time, duration, ease) {
870
870
  return sampleMotionPath(path, distanceFraction);
871
871
  }
872
872
 
873
+ // src/agent/batch.ts
874
+ function getByPath(obj, path) {
875
+ if (!obj || typeof obj !== "object") return void 0;
876
+ const parts = path.replace(/\[(\w+)\]/g, ".$1").split(".");
877
+ let curr = obj;
878
+ for (const part of parts) {
879
+ if (curr === void 0 || curr === null) return void 0;
880
+ curr = curr[part];
881
+ }
882
+ return curr;
883
+ }
884
+ var REFERENCE = /\$(\d+)\.([A-Za-z_][\w.[\]]*)/g;
885
+ function resolve(stepResults, stepStr, path, text) {
886
+ const stepIdx = parseInt(stepStr, 10);
887
+ const prevResult = stepResults.get(stepIdx);
888
+ if (!prevResult) {
889
+ throw new Error(`"${text}" refers to step ${stepIdx}, which has not run before this step.`);
890
+ }
891
+ const resolved = getByPath(prevResult, path);
892
+ if (resolved === void 0) {
893
+ throw new Error(`"${text}" does not resolve: step ${stepIdx}'s result has nothing at "${path}".`);
894
+ }
895
+ return resolved;
896
+ }
897
+ function substituteVariables(value, stepResults) {
898
+ if (typeof value === "string") {
899
+ const match = /^\$(\d+)\.([A-Za-z_][\w.[\]]*)$/.exec(value);
900
+ if (match) return resolve(stepResults, match[1], match[2], value);
901
+ return value.replace(
902
+ REFERENCE,
903
+ (fullMatch, stepStr, path) => String(resolve(stepResults, stepStr, path, fullMatch))
904
+ );
905
+ }
906
+ if (Array.isArray(value)) {
907
+ return value.map((item) => substituteVariables(item, stepResults));
908
+ }
909
+ if (value !== null && typeof value === "object") {
910
+ const copy = {};
911
+ for (const [k, v] of Object.entries(value)) {
912
+ copy[k] = substituteVariables(v, stepResults);
913
+ }
914
+ return copy;
915
+ }
916
+ return value;
917
+ }
918
+ async function applySceneBatch(scene, commands) {
919
+ let currentScene = scene;
920
+ const stepResults = /* @__PURE__ */ new Map();
921
+ const executedSteps = [];
922
+ for (let i = 0; i < commands.length; i++) {
923
+ const stepNumber = i + 1;
924
+ const cmd = commands[i];
925
+ if (cmd.tool === "batch_scene") {
926
+ return {
927
+ ok: false,
928
+ scene,
929
+ // Rollback
930
+ steps: executedSteps,
931
+ error: `Step ${stepNumber} is a batch_scene; batches cannot be nested, so list its steps here instead.`,
932
+ failedStep: stepNumber
933
+ };
934
+ }
935
+ let resolvedParams;
936
+ try {
937
+ resolvedParams = substituteVariables(cmd.params || {}, stepResults);
938
+ } catch (err) {
939
+ return {
940
+ ok: false,
941
+ scene,
942
+ // Rollback
943
+ steps: executedSteps,
944
+ error: `Step ${stepNumber} (${cmd.tool}) failed: ${err.message}`,
945
+ failedStep: stepNumber
946
+ };
947
+ }
948
+ const stepResult = await applySceneTool(currentScene, cmd.tool, resolvedParams);
949
+ stepResults.set(stepNumber, stepResult);
950
+ executedSteps.push({
951
+ step: stepNumber,
952
+ tool: cmd.tool,
953
+ result: stepResult
954
+ });
955
+ if (!stepResult.ok) {
956
+ return {
957
+ ok: false,
958
+ scene,
959
+ // Rollback to initial
960
+ steps: executedSteps,
961
+ error: `Step ${stepNumber} (${cmd.tool}) failed: ${stepResult.error}`,
962
+ failedStep: stepNumber
963
+ };
964
+ }
965
+ currentScene = stepResult.scene;
966
+ }
967
+ return {
968
+ ok: true,
969
+ scene: currentScene,
970
+ steps: executedSteps
971
+ };
972
+ }
973
+
974
+ // src/agent/schema.ts
975
+ function typeOf(value) {
976
+ if (value === null) return "null";
977
+ if (Array.isArray(value)) return "array";
978
+ if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
979
+ return typeof value;
980
+ }
981
+ function described(type) {
982
+ if (type === "integer") return "a whole number";
983
+ if (type === "null") return "null";
984
+ return `${/^[aeiou]/.test(type) ? "an" : "a"} ${type}`;
985
+ }
986
+ function fits(value, type) {
987
+ const actual = typeOf(value);
988
+ return actual === type || type === "number" && actual === "integer";
989
+ }
990
+ function checkSchema(schema, value, at = "arguments") {
991
+ const problems = [];
992
+ if (schema.type !== void 0) {
993
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
994
+ if (!types.some((t) => fits(value, t))) {
995
+ const actual = typeOf(value) === "integer" ? "number" : typeOf(value);
996
+ problems.push(`${at} must be ${types.map(described).join(" or ")}, not ${described(actual)}.`);
997
+ return problems;
998
+ }
999
+ }
1000
+ if (schema.enum && !schema.enum.includes(value)) {
1001
+ problems.push(`${at} must be one of ${schema.enum.map((v) => JSON.stringify(v)).join(", ")}.`);
1002
+ }
1003
+ if (typeof value === "number" && typeof schema.minimum === "number" && value < schema.minimum) {
1004
+ problems.push(`${at} must be at least ${schema.minimum}.`);
1005
+ }
1006
+ if (Array.isArray(value) && schema.items) {
1007
+ value.forEach((item, i) => problems.push(...checkSchema(schema.items, item, `${at}[${i}]`)));
1008
+ }
1009
+ if (typeOf(value) === "object") {
1010
+ const obj = value;
1011
+ const props = schema.properties ?? {};
1012
+ for (const name of schema.required ?? []) {
1013
+ if (obj[name] === void 0) problems.push(`${at}.${name} is required.`);
1014
+ }
1015
+ for (const [name, v] of Object.entries(obj)) {
1016
+ if (props[name]) problems.push(...checkSchema(props[name], v, `${at}.${name}`));
1017
+ else if (schema.additionalProperties === false) {
1018
+ const known = Object.keys(props);
1019
+ problems.push(`${at}.${name} is not a parameter${known.length ? `; expected ${known.map((k) => `"${k}"`).join(", ")}` : ""}.`);
1020
+ }
1021
+ }
1022
+ }
1023
+ return problems;
1024
+ }
1025
+
873
1026
  // src/agent/tools.ts
874
1027
  var FLAIR_AGENT_TOOLS = [
875
1028
  {
876
1029
  name: "get_scene",
877
- description: "Returns the current scene JSON (or a concise structural outline) and any static validation issues.",
1030
+ description: 'Returns the current scene JSON, or with view "outline" a concise structural outline instead, and any static validation issues. Start here.',
878
1031
  input_schema: {
879
1032
  type: "object",
880
1033
  properties: {
@@ -889,7 +1042,7 @@ var FLAIR_AGENT_TOOLS = [
889
1042
  },
890
1043
  {
891
1044
  name: "write_scene",
892
- description: "Overwrites the entire scene with a new specification and validates it.",
1045
+ description: "Overwrites the entire scene with a new specification and validates it. Prefer update_shot, insert_shots and delete_shots for smaller changes.",
893
1046
  input_schema: {
894
1047
  type: "object",
895
1048
  properties: {
@@ -909,7 +1062,10 @@ var FLAIR_AGENT_TOOLS = [
909
1062
  type: "object",
910
1063
  properties: {
911
1064
  shot: { type: "integer", minimum: 1, description: "1-based index of the shot to update." },
912
- set: { type: "object", description: "Fields to set or replace on this shot." },
1065
+ set: {
1066
+ type: "object",
1067
+ description: "Fields to set or replace on this shot: caption, title, camera, hold (ms), run, effects, stopEffects, behaviors."
1068
+ },
913
1069
  remove: {
914
1070
  type: "array",
915
1071
  items: { type: "string" },
@@ -934,7 +1090,7 @@ var FLAIR_AGENT_TOOLS = [
934
1090
  shots: {
935
1091
  type: "array",
936
1092
  items: { type: "object" },
937
- description: "Array of Shot objects to insert."
1093
+ description: "Shot objects to insert (fields as for update_shot)."
938
1094
  }
939
1095
  },
940
1096
  required: ["after", "shots"],
@@ -966,7 +1122,7 @@ var FLAIR_AGENT_TOOLS = [
966
1122
  id: { type: "string", description: "Media identifier." },
967
1123
  media: {
968
1124
  type: ["object", "null"],
969
- description: 'SceneMedia object (e.g. { type: "shader", source: "..." }), or null to remove.'
1125
+ description: 'SceneMedia object, or null to remove. A shader is { type: "shader", source } with a GLSL ES 3.00 Shadertoy-style void mainImage(out vec4 fragColor, in vec2 fragCoord); it is only compiled when the scene is rendered on a GPU.'
970
1126
  }
971
1127
  },
972
1128
  required: ["id", "media"],
@@ -975,7 +1131,7 @@ var FLAIR_AGENT_TOOLS = [
975
1131
  },
976
1132
  {
977
1133
  name: "preview_scene",
978
- description: "Simulates the scene fast and off-screen, returning timing, layout checks, and review report.",
1134
+ description: "Simulates the scene fast and off-screen, returning its total length, layout and timing warnings, and notes. Run it after a round of edits.",
979
1135
  input_schema: {
980
1136
  type: "object",
981
1137
  properties: {},
@@ -997,208 +1153,174 @@ var FLAIR_AGENT_TOOLS = [
997
1153
  required: ["name", "value"],
998
1154
  additionalProperties: false
999
1155
  }
1156
+ },
1157
+ {
1158
+ name: "batch_scene",
1159
+ description: `Runs several of these tools in order as one all-or-nothing change: if a step fails, none of the steps apply. A string param "$N.path" is replaced by that path in step N's result (1-based; e.g. "$1.output.outline.shots[0].hold" or "$2.scene.shots[1].title"), and "$N.path" inside a longer string is filled in; a reference that does not resolve fails its step.`,
1160
+ input_schema: {
1161
+ type: "object",
1162
+ properties: {
1163
+ steps: {
1164
+ type: "array",
1165
+ items: {
1166
+ type: "object",
1167
+ properties: {
1168
+ tool: { type: "string", description: "A tool name (not batch_scene)." },
1169
+ params: { type: "object", description: "That tool's arguments." }
1170
+ },
1171
+ required: ["tool"],
1172
+ additionalProperties: false
1173
+ },
1174
+ description: "The tool calls to run, in order."
1175
+ }
1176
+ },
1177
+ required: ["steps"],
1178
+ additionalProperties: false
1179
+ }
1000
1180
  }
1001
1181
  ];
1182
+ var TOOL_NAMES = FLAIR_AGENT_TOOLS.map((t) => t.name);
1183
+ function fail(error, failedStep) {
1184
+ return { ok: false, error, ...failedStep !== void 0 && { failedStep }, output: { ok: false, error } };
1185
+ }
1186
+ function edited(scene, summary) {
1187
+ const issues = validateScene(scene);
1188
+ return { ok: true, scene, issues, output: { ok: true, summary, shotCount: scene.shots.length, issues } };
1189
+ }
1190
+ var shotsWord = (n) => `${n} shot${n === 1 ? "" : "s"}`;
1191
+ function outlineScene(scene) {
1192
+ return {
1193
+ name: scene.name,
1194
+ title: scene.title,
1195
+ description: scene.description,
1196
+ stage: scene.stage,
1197
+ target: scene.target,
1198
+ mediaIds: Object.keys(scene.media || {}),
1199
+ shots: scene.shots.map((s, i) => ({
1200
+ shot: i + 1,
1201
+ caption: s.caption,
1202
+ title: s.title,
1203
+ hold: s.hold,
1204
+ camera: s.camera,
1205
+ commandCount: Array.isArray(s.run) ? s.run.length : 0,
1206
+ effects: Array.isArray(s.effects) ? s.effects.map((e) => e.type) : void 0
1207
+ }))
1208
+ };
1209
+ }
1002
1210
  async function applySceneTool(scene, toolName, input = {}) {
1211
+ const definition = FLAIR_AGENT_TOOLS.find((t) => t.name === toolName);
1212
+ if (!definition) {
1213
+ return fail(`There is no tool "${toolName}"; the tools are ${TOOL_NAMES.join(", ")}.`);
1214
+ }
1215
+ const problems = checkSchema(definition.input_schema, input ?? {});
1216
+ if (problems.length) return fail(`Invalid arguments for ${toolName}: ${problems.join(" ")}`);
1217
+ const shotCount = scene.shots.length;
1218
+ const shotRange = `the scene has ${shotsWord(shotCount)} (1 to ${shotCount})`;
1003
1219
  switch (toolName) {
1004
1220
  case "get_scene": {
1005
1221
  const issues = validateScene(scene);
1006
1222
  if (input.view === "outline") {
1007
- const outline = {
1008
- name: scene.name,
1009
- title: scene.title,
1010
- description: scene.description,
1011
- stage: scene.stage,
1012
- target: scene.target,
1013
- mediaIds: Object.keys(scene.media || {}),
1014
- shots: scene.shots.map((s, i) => ({
1015
- shot: i + 1,
1016
- caption: s.caption,
1017
- title: s.title,
1018
- hold: s.hold,
1019
- camera: s.camera,
1020
- commandCount: s.run?.length ?? 0,
1021
- effects: s.effects?.map((e) => e.type)
1022
- }))
1023
- };
1024
- return { ok: true, scene, outline, issues };
1223
+ const outline = outlineScene(scene);
1224
+ return { ok: true, scene, outline, issues, output: { ok: true, outline, issues } };
1025
1225
  }
1026
- return { ok: true, scene, issues };
1226
+ return { ok: true, scene, issues, output: { ok: true, scene, issues } };
1027
1227
  }
1028
1228
  case "write_scene": {
1029
- if (!input.scene || typeof input.scene !== "object") {
1030
- return { ok: false, error: "Expected scene object in arguments." };
1229
+ const next = input.scene;
1230
+ if (!Array.isArray(next.shots) || next.shots.some((s) => !s || typeof s !== "object" || Array.isArray(s))) {
1231
+ return fail("The scene needs a shots array of shot objects; nothing was written.");
1031
1232
  }
1032
- const newScene = input.scene;
1033
- const issues = validateScene(newScene);
1034
- return { ok: true, scene: newScene, issues };
1233
+ if (next.shots.length === 0) return fail("The scene needs at least one shot; nothing was written.");
1234
+ return edited(next, `Wrote the scene "${next.name}" with ${shotsWord(next.shots.length)}.`);
1035
1235
  }
1036
1236
  case "update_shot": {
1037
1237
  const idx = input.shot - 1;
1038
- if (idx < 0 || idx >= scene.shots.length) {
1039
- return { ok: false, error: `Invalid shot number ${input.shot}; scene has ${scene.shots.length} shots.` };
1040
- }
1041
- const existing = scene.shots[idx];
1042
- const updated = { ...existing, ...input.set || {} };
1043
- if (Array.isArray(input.remove)) {
1044
- for (const k of input.remove) {
1045
- delete updated[k];
1046
- }
1238
+ if (idx >= shotCount) return fail(`There is no shot ${input.shot}; ${shotRange}.`);
1239
+ if (input.set === void 0 && input.remove === void 0) {
1240
+ return fail('Give fields to change in "set", field names to delete in "remove", or both.');
1047
1241
  }
1242
+ const updated = { ...scene.shots[idx], ...input.set || {} };
1243
+ for (const k of input.remove ?? []) delete updated[k];
1048
1244
  const shots = [...scene.shots];
1049
1245
  shots[idx] = updated;
1050
- const newScene = { ...scene, shots };
1051
- return { ok: true, scene: newScene, issues: validateScene(newScene) };
1246
+ const changed = [...Object.keys(input.set ?? {}), ...(input.remove ?? []).map((k) => `-${k}`)];
1247
+ return edited({ ...scene, shots }, `Updated shot ${input.shot} (${changed.join(", ")}).`);
1052
1248
  }
1053
1249
  case "insert_shots": {
1054
- const after = input.after ?? scene.shots.length;
1055
- if (after < 0 || after > scene.shots.length) {
1056
- return { ok: false, error: `Invalid after index ${after}.` };
1057
- }
1058
- if (!Array.isArray(input.shots) || input.shots.length === 0) {
1059
- return { ok: false, error: "Expected non-empty shots array to insert." };
1060
- }
1250
+ const after = input.after;
1251
+ if (after > shotCount) return fail(`Cannot insert after shot ${after}; ${shotRange}, so "after" is 0 to ${shotCount}.`);
1252
+ if (input.shots.length === 0) return fail("Give at least one shot to insert.");
1061
1253
  const shots = [...scene.shots.slice(0, after), ...input.shots, ...scene.shots.slice(after)];
1062
- const newScene = { ...scene, shots };
1063
- return { ok: true, scene: newScene, issues: validateScene(newScene) };
1254
+ const first = after + 1;
1255
+ const last = after + input.shots.length;
1256
+ return edited(
1257
+ { ...scene, shots },
1258
+ `Inserted ${shotsWord(input.shots.length)} as ${first === last ? `shot ${first}` : `shots ${first}-${last}`}; later shots moved down.`
1259
+ );
1064
1260
  }
1065
1261
  case "delete_shots": {
1066
- if (!Array.isArray(input.shots) || input.shots.length === 0) {
1067
- return { ok: false, error: "Expected non-empty shots array to delete." };
1068
- }
1069
- const toDelete = new Set(input.shots.map((n) => n - 1));
1262
+ const numbers = input.shots;
1263
+ if (numbers.length === 0) return fail("Give the shot numbers to delete.");
1264
+ const missing = numbers.filter((n) => n > shotCount);
1265
+ if (missing.length) return fail(`There is no shot ${missing.join(", ")}; ${shotRange}. Nothing was deleted.`);
1266
+ const toDelete = new Set(numbers.map((n) => n - 1));
1070
1267
  const shots = scene.shots.filter((_, i) => !toDelete.has(i));
1071
- if (shots.length === 0) {
1072
- return { ok: false, error: "Cannot delete all shots in scene." };
1073
- }
1074
- const newScene = { ...scene, shots };
1075
- return { ok: true, scene: newScene, issues: validateScene(newScene) };
1268
+ if (shots.length === 0) return fail("Cannot delete every shot; a scene needs at least one. Nothing was deleted.");
1269
+ return edited({ ...scene, shots }, `Deleted ${shotsWord(toDelete.size)}; the scene has ${shotsWord(shots.length)} now.`);
1076
1270
  }
1077
1271
  case "set_media": {
1078
- if (!input.id || typeof input.id !== "string") {
1079
- return { ok: false, error: "Expected string media id." };
1080
- }
1081
1272
  const media = { ...scene.media || {} };
1082
1273
  if (input.media === null) {
1274
+ if (!(input.id in media)) {
1275
+ const ids = Object.keys(media);
1276
+ return fail(`There is no media "${input.id}" to remove; ${ids.length ? `the media are ${ids.join(", ")}` : "the scene has no media"}.`);
1277
+ }
1083
1278
  delete media[input.id];
1084
- } else {
1085
- media[input.id] = input.media;
1279
+ return edited({ ...scene, media }, `Removed media "${input.id}".`);
1086
1280
  }
1087
- const newScene = { ...scene, media };
1088
- return { ok: true, scene: newScene, issues: validateScene(newScene) };
1281
+ if (typeof input.media.type !== "string") return fail('Media needs a string type, e.g. { type: "shader", source: "..." }.');
1282
+ const existed = input.id in media;
1283
+ media[input.id] = input.media;
1284
+ return edited({ ...scene, media }, `${existed ? "Replaced" : "Added"} ${input.media.type} media "${input.id}".`);
1089
1285
  }
1090
1286
  case "preview_scene": {
1091
1287
  const { review } = await simulateScene(scene);
1092
- return { ok: true, scene, issues: validateScene(scene), review };
1288
+ const issues = validateScene(scene);
1289
+ const { markdown: _markdown, ...summary } = review;
1290
+ return { ok: true, scene, issues, review, output: { ok: true, issues, review: summary } };
1093
1291
  }
1094
1292
  case "set_rig_widget": {
1095
- if (!scene.rig || !scene.rig.widgets || !scene.rig.widgets[input.name]) {
1096
- return { ok: false, error: `Scene does not have a rig widget named "${input.name}".` };
1293
+ const widgets = scene.rig?.widgets ?? {};
1294
+ if (!scene.rig || !widgets[input.name]) {
1295
+ const names = Object.keys(widgets);
1296
+ return fail(`Scene does not have a rig widget named "${input.name}"; ${names.length ? `its widgets are ${names.join(", ")}` : "it has no rig widgets"}.`);
1097
1297
  }
1098
1298
  try {
1099
1299
  const updatedRig = setRigWidget(scene.rig, input.name, input.value);
1100
- const newScene = applyRig(updatedRig, scene);
1101
- return { ok: true, scene: newScene, issues: validateScene(newScene) };
1300
+ return edited(applyRig(updatedRig, scene), `Set rig widget "${input.name}" to ${JSON.stringify(input.value)}.`);
1102
1301
  } catch (err) {
1103
- return { ok: false, error: err.message || String(err) };
1302
+ return fail(err.message || String(err));
1104
1303
  }
1105
1304
  }
1106
- default:
1107
- return { ok: false, error: `Unknown tool name: ${toolName}` };
1108
- }
1109
- }
1110
-
1111
- // src/agent/batch.ts
1112
- function getByPath(obj, path) {
1113
- if (!obj || typeof obj !== "object") return void 0;
1114
- const parts = path.replace(/\[(\w+)\]/g, ".$1").split(".");
1115
- let curr = obj;
1116
- for (const part of parts) {
1117
- if (curr === void 0 || curr === null) return void 0;
1118
- curr = curr[part];
1119
- }
1120
- return curr;
1121
- }
1122
- function substituteVariables(value, stepResults) {
1123
- if (typeof value === "string") {
1124
- const match = /^\$(\d+)\.(.+)$/.exec(value);
1125
- if (match) {
1126
- const stepIdx = parseInt(match[1], 10);
1127
- const path = match[2];
1128
- const prevResult = stepResults.get(stepIdx);
1129
- if (prevResult) {
1130
- const resolved = getByPath(prevResult, path);
1131
- if (resolved !== void 0) {
1132
- return resolved;
1133
- }
1134
- }
1135
- }
1136
- return value.replace(/\$(\d+)\.([a-zA-Z0-9_.[\]]+)/g, (fullMatch, stepStr, path) => {
1137
- const stepIdx = parseInt(stepStr, 10);
1138
- const prevResult = stepResults.get(stepIdx);
1139
- if (prevResult) {
1140
- const resolved = getByPath(prevResult, path);
1141
- if (resolved !== void 0) {
1142
- return String(resolved);
1143
- }
1144
- }
1145
- return fullMatch;
1146
- });
1147
- }
1148
- if (Array.isArray(value)) {
1149
- return value.map((item) => substituteVariables(item, stepResults));
1150
- }
1151
- if (value !== null && typeof value === "object") {
1152
- const copy = {};
1153
- for (const [k, v] of Object.entries(value)) {
1154
- copy[k] = substituteVariables(v, stepResults);
1155
- }
1156
- return copy;
1157
- }
1158
- return value;
1159
- }
1160
- async function applySceneBatch(scene, commands) {
1161
- let currentScene = scene;
1162
- const stepResults = /* @__PURE__ */ new Map();
1163
- const executedSteps = [];
1164
- for (let i = 0; i < commands.length; i++) {
1165
- const stepNumber = i + 1;
1166
- const cmd = commands[i];
1167
- const resolvedParams = substituteVariables(cmd.params || {}, stepResults);
1168
- if (cmd.tool === "batch_scene_tool") {
1169
- return {
1170
- ok: false,
1171
- scene,
1172
- // Rollback
1173
- steps: executedSteps,
1174
- error: `Nested batch_scene_tool is not allowed at step ${stepNumber}.`,
1175
- failedStep: stepNumber
1176
- };
1177
- }
1178
- const stepResult = await applySceneTool(currentScene, cmd.tool, resolvedParams);
1179
- stepResults.set(stepNumber, stepResult);
1180
- executedSteps.push({
1181
- step: stepNumber,
1182
- tool: cmd.tool,
1183
- result: stepResult
1184
- });
1185
- if (!stepResult.ok) {
1305
+ case "batch_scene": {
1306
+ const batch = await applySceneBatch(scene, input.steps);
1307
+ if (!batch.ok) return fail(`${batch.error} No steps were applied.`, batch.failedStep);
1308
+ const issues = validateScene(batch.scene);
1186
1309
  return {
1187
- ok: false,
1188
- scene,
1189
- // Rollback to initial
1190
- steps: executedSteps,
1191
- error: `Step ${stepNumber} (${cmd.tool}) failed: ${stepResult.error}`,
1192
- failedStep: stepNumber
1310
+ ok: true,
1311
+ scene: batch.scene,
1312
+ issues,
1313
+ output: {
1314
+ ok: true,
1315
+ summary: `Ran ${batch.steps.length} step${batch.steps.length === 1 ? "" : "s"}.`,
1316
+ steps: batch.steps.map((s) => ({ step: s.step, tool: s.tool, ...s.result.output })),
1317
+ shotCount: batch.scene.shots.length,
1318
+ issues
1319
+ }
1193
1320
  };
1194
1321
  }
1195
- currentScene = stepResult.scene;
1196
1322
  }
1197
- return {
1198
- ok: true,
1199
- scene: currentScene,
1200
- steps: executedSteps
1201
- };
1323
+ return fail(`There is no tool "${toolName}".`);
1202
1324
  }
1203
1325
  export {
1204
1326
  ALL_BLEND_MODES,
@@ -1353,6 +1475,7 @@ export {
1353
1475
  oklabToOklch,
1354
1476
  oklabToSrgb,
1355
1477
  oklchToOklab,
1478
+ outlineScene,
1356
1479
  overlapArea,
1357
1480
  pageHidden,
1358
1481
  parseColor,