vidspotai-shared 1.0.85 → 1.0.86
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/lib/globals/aiModels/providers/runway.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/runway.js +12 -7
- package/lib/globals/types.d.ts +10 -0
- package/lib/globals/types.d.ts.map +1 -1
- package/lib/globals/types.js +11 -0
- package/lib/models/agent.model.d.ts +129 -1
- package/lib/models/agent.model.d.ts.map +1 -1
- package/lib/schemas/agentRunJob.schema.d.ts +21 -0
- package/lib/schemas/agentRunJob.schema.d.ts.map +1 -1
- package/lib/schemas/agentRunJob.schema.js +35 -3
- package/lib/services/agent/executor/core.d.ts.map +1 -1
- package/lib/services/agent/executor/core.js +21 -1
- package/lib/services/agent/executor/types.d.ts +10 -0
- package/lib/services/agent/executor/types.d.ts.map +1 -1
- package/lib/services/agent/index.d.ts +3 -0
- package/lib/services/agent/index.d.ts.map +1 -1
- package/lib/services/agent/index.js +3 -0
- package/lib/services/agent/providerFallback/chains.d.ts +2 -0
- package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
- package/lib/services/agent/providerFallback/chains.js +1 -0
- package/lib/services/agent/recovery/degradeLadder.d.ts +74 -0
- package/lib/services/agent/recovery/degradeLadder.d.ts.map +1 -0
- package/lib/services/agent/recovery/degradeLadder.js +133 -0
- package/lib/services/agent/repair/engine.d.ts +38 -0
- package/lib/services/agent/repair/engine.d.ts.map +1 -0
- package/lib/services/agent/repair/engine.js +175 -0
- package/lib/services/agent/repair/index.d.ts +20 -0
- package/lib/services/agent/repair/index.d.ts.map +1 -0
- package/lib/services/agent/repair/index.js +49 -0
- package/lib/services/agent/repair/strategies/llmRepair.strategy.d.ts +6 -0
- package/lib/services/agent/repair/strategies/llmRepair.strategy.d.ts.map +1 -0
- package/lib/services/agent/repair/strategies/llmRepair.strategy.js +210 -0
- package/lib/services/agent/repair/strategies/paramRepair.strategy.d.ts +3 -0
- package/lib/services/agent/repair/strategies/paramRepair.strategy.d.ts.map +1 -0
- package/lib/services/agent/repair/strategies/paramRepair.strategy.js +151 -0
- package/lib/services/agent/repair/types.d.ts +92 -0
- package/lib/services/agent/repair/types.d.ts.map +1 -0
- package/lib/services/agent/repair/types.js +2 -0
- package/lib/services/agent/runPlanning.d.ts +96 -0
- package/lib/services/agent/runPlanning.d.ts.map +1 -0
- package/lib/services/agent/runPlanning.js +233 -0
- package/lib/services/aiGen/providers/runway/types.d.ts +14 -0
- package/lib/services/aiGen/providers/runway/types.d.ts.map +1 -1
- package/lib/services/aiGen/providers/runway/types.js +22 -1
- package/lib/services/firestore.service.d.ts +7 -1
- package/lib/services/firestore.service.d.ts.map +1 -1
- package/lib/services/firestore.service.js +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.llmWideRepairStrategy = exports.llmNarrowRepairStrategy = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const aiModels_1 = require("../../../../globals/aiModels");
|
|
6
|
+
const logger_1 = require("../../../../utils/logger");
|
|
7
|
+
const llmCallerRegistry_1 = require("../../llmCallerRegistry");
|
|
8
|
+
const modelRouter_1 = require("../../modelRouter");
|
|
9
|
+
const toolRegistry_1 = require("../../toolRegistry");
|
|
10
|
+
/**
|
|
11
|
+
* Tiers 1 & 2 — the AGENTIC repair brain.
|
|
12
|
+
*
|
|
13
|
+
* When the deterministic micro-fix can't help (moderation block, an
|
|
14
|
+
* unrecognised provider rejection, a request that no single param snap can
|
|
15
|
+
* satisfy), an LLM looks at the failure the way a human operator would: it
|
|
16
|
+
* decides WHETHER the issue is fixable and HOW, then returns a reworked tool
|
|
17
|
+
* input. Two scopes, registered as two strategies so the engine escalates
|
|
18
|
+
* least-invasive-first:
|
|
19
|
+
*
|
|
20
|
+
* - "narrow" (rank 10): fix ONLY the offending thing — minimally rewrite a
|
|
21
|
+
* moderation-flagged prompt, correct a bad param — WITHOUT changing the
|
|
22
|
+
* model or the overall approach. Declines if that can't be done without
|
|
23
|
+
* significant quality loss.
|
|
24
|
+
* - "wide" (rank 20): may swap to another capable model, adjust the prompt
|
|
25
|
+
* more substantially, or restructure params — whatever best preserves the
|
|
26
|
+
* user's intent. The last resort before a hard fail.
|
|
27
|
+
*
|
|
28
|
+
* Reuses the host-wired `getLlmCaller()` singleton (same as every other agent
|
|
29
|
+
* tool). When no caller is configured (offline / unit tests), every call here
|
|
30
|
+
* throws and the strategy gracefully DECLINES — deterministic repair still works.
|
|
31
|
+
*/
|
|
32
|
+
const RepairDecisionSchema = zod_1.z.object({
|
|
33
|
+
fixable: zod_1.z
|
|
34
|
+
.boolean()
|
|
35
|
+
.describe("True only if you can produce a reworked input that should succeed."),
|
|
36
|
+
repairedInput: zod_1.z
|
|
37
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
38
|
+
.nullable()
|
|
39
|
+
.describe("The complete reworked tool input to retry with (all fields, not a diff). Null when not fixable."),
|
|
40
|
+
qualityImpact: zod_1.z
|
|
41
|
+
.enum(["none", "minor", "significant"])
|
|
42
|
+
.describe("How much your fix changes the result vs the user's original intent."),
|
|
43
|
+
rationale: zod_1.z
|
|
44
|
+
.string()
|
|
45
|
+
.describe("One sentence: what you changed and why it should now pass."),
|
|
46
|
+
});
|
|
47
|
+
function summarizeModel(modelKey) {
|
|
48
|
+
const cfg = aiModels_1.aiModelConfigs[modelKey];
|
|
49
|
+
if (!cfg)
|
|
50
|
+
return null;
|
|
51
|
+
const allowed = (field) => {
|
|
52
|
+
const v = cfg.fields?.[field]?.allowedValues;
|
|
53
|
+
return Array.isArray(v) ? v : undefined;
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
modelKey,
|
|
57
|
+
type: cfg.type,
|
|
58
|
+
aspectRatio: allowed("aspectRatio")?.filter((x) => typeof x === "string"),
|
|
59
|
+
duration: allowed("duration")?.filter((x) => typeof x === "number"),
|
|
60
|
+
resolution: allowed("resolution")?.filter((x) => typeof x === "string"),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Other models that share at least one generation `type` with the current one —
|
|
64
|
+
* the candidate set for a wide-scope model swap. Capped to keep the prompt tight. */
|
|
65
|
+
function alternativeModels(currentModelKey, limit = 14) {
|
|
66
|
+
const current = summarizeModel(currentModelKey);
|
|
67
|
+
const currentTypes = new Set(current?.type ?? []);
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const key of Object.keys(aiModels_1.aiModelConfigs)) {
|
|
70
|
+
if (key === currentModelKey)
|
|
71
|
+
continue;
|
|
72
|
+
const summary = summarizeModel(key);
|
|
73
|
+
if (!summary?.type)
|
|
74
|
+
continue;
|
|
75
|
+
if (currentTypes.size && !summary.type.some((t) => currentTypes.has(t)))
|
|
76
|
+
continue;
|
|
77
|
+
out.push(summary);
|
|
78
|
+
if (out.length >= limit)
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
function systemPrompt(scope) {
|
|
84
|
+
const common = "You are the repair operator for an AI video-generation agent. A tool call just " +
|
|
85
|
+
"failed at a provider. Decide whether the failure is fixable by reworking the tool " +
|
|
86
|
+
"input, and if so, return the COMPLETE reworked input. Preserve the user's creative " +
|
|
87
|
+
"intent as much as possible. Never invent URLs or ids — keep any imageUrl/inputImageUrl/" +
|
|
88
|
+
"videoUrl fields exactly as given unless the error is about them.";
|
|
89
|
+
if (scope === "narrow") {
|
|
90
|
+
return (common +
|
|
91
|
+
" SCOPE: NARROW. Fix ONLY the specific thing the provider rejected. Do NOT change " +
|
|
92
|
+
"`modelKey`. Do NOT change the overall approach. For a content-moderation/safety block, " +
|
|
93
|
+
"minimally reword the prompt to remove the flagged element while keeping the scene's " +
|
|
94
|
+
"meaning. If the issue cannot be fixed this narrowly, or the only narrow fix would " +
|
|
95
|
+
"SIGNIFICANTLY degrade quality, set fixable=false (a wider repair will be tried).");
|
|
96
|
+
}
|
|
97
|
+
return (common +
|
|
98
|
+
" SCOPE: WIDE. The narrow fix already failed or wasn't possible. You MAY change `modelKey` " +
|
|
99
|
+
"to a different model that supports the request, adjust the prompt more substantially, or " +
|
|
100
|
+
"restructure parameters. Prefer switching to a model whose constraints natively fit the " +
|
|
101
|
+
"request over distorting the request. Pick the option that best preserves the user's intent.");
|
|
102
|
+
}
|
|
103
|
+
function userPrompt(rc, scope) {
|
|
104
|
+
const modelKey = typeof rc.input.modelKey === "string" ? rc.input.modelKey : undefined;
|
|
105
|
+
const current = modelKey ? summarizeModel(modelKey) : null;
|
|
106
|
+
const lines = [];
|
|
107
|
+
lines.push(`TOOL: ${rc.toolName}`);
|
|
108
|
+
lines.push(`FAILED INPUT:\n${JSON.stringify(rc.input, null, 2)}`);
|
|
109
|
+
lines.push(`FAILURE: classification=${rc.failure.classification ?? "unknown"} code=${rc.failure.code}\n${rc.failure.message}`);
|
|
110
|
+
if (rc.failure.attemptedProviders?.length) {
|
|
111
|
+
lines.push(`PROVIDERS ALREADY TRIED (all failed): ${rc.failure.attemptedProviders.join(", ")}`);
|
|
112
|
+
}
|
|
113
|
+
if (current) {
|
|
114
|
+
lines.push(`CURRENT MODEL CONSTRAINTS:\n${JSON.stringify(current, null, 2)}`);
|
|
115
|
+
}
|
|
116
|
+
if (rc.history.length) {
|
|
117
|
+
const prior = rc.history
|
|
118
|
+
.map((h, i) => ` ${i + 1}. ${JSON.stringify(h.input)} → ${h.failure.message}`)
|
|
119
|
+
.join("\n");
|
|
120
|
+
lines.push(`REPAIRS ALREADY TRIED THIS RUN (do not repeat):\n${prior}`);
|
|
121
|
+
}
|
|
122
|
+
if (scope === "wide" && modelKey) {
|
|
123
|
+
const alts = alternativeModels(modelKey);
|
|
124
|
+
if (alts.length) {
|
|
125
|
+
lines.push(`ALTERNATIVE MODELS YOU MAY SWITCH TO (modelKey + constraints):\n${JSON.stringify(alts, null, 2)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return lines.join("\n\n");
|
|
129
|
+
}
|
|
130
|
+
function makeLlmRepairStrategy(scope, rank) {
|
|
131
|
+
return {
|
|
132
|
+
name: `llm-repair-${scope}`,
|
|
133
|
+
rank,
|
|
134
|
+
applies(rc) {
|
|
135
|
+
// The LLM can reason about any non-auth failure. Auth is a deploy bug,
|
|
136
|
+
// already short-circuited by the engine, but guard here too.
|
|
137
|
+
return rc.failure.classification !== "auth";
|
|
138
|
+
},
|
|
139
|
+
async propose(rc) {
|
|
140
|
+
const caller = (0, llmCallerRegistry_1.getLlmCaller)();
|
|
141
|
+
const model = new modelRouter_1.ModelRouter(modelRouter_1.DEFAULT_MODEL_ROUTING).pickFor("planner");
|
|
142
|
+
const messages = [
|
|
143
|
+
{ role: "system", content: systemPrompt(scope) },
|
|
144
|
+
{ role: "user", content: userPrompt(rc, scope) },
|
|
145
|
+
];
|
|
146
|
+
let decision;
|
|
147
|
+
try {
|
|
148
|
+
const res = await caller.structured({
|
|
149
|
+
model,
|
|
150
|
+
messages,
|
|
151
|
+
schema: RepairDecisionSchema,
|
|
152
|
+
schemaName: "RepairDecision",
|
|
153
|
+
temperature: 0.2,
|
|
154
|
+
maxTokens: 1500,
|
|
155
|
+
});
|
|
156
|
+
decision = res.data;
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
// Unconfigured caller (tests/offline) or LLM/transport error → decline.
|
|
160
|
+
logger_1.logger.warn(`repair: ${scope} LLM strategy unavailable — declining`, {
|
|
161
|
+
toolName: rc.toolName,
|
|
162
|
+
err: err instanceof Error ? err.message : String(err),
|
|
163
|
+
});
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
if (!decision.fixable || !decision.repairedInput)
|
|
167
|
+
return null;
|
|
168
|
+
// Narrow scope must not change the model — enforce structurally even if
|
|
169
|
+
// the model ignored the instruction.
|
|
170
|
+
if (scope === "narrow" &&
|
|
171
|
+
typeof rc.input.modelKey === "string" &&
|
|
172
|
+
decision.repairedInput.modelKey !== undefined &&
|
|
173
|
+
decision.repairedInput.modelKey !== rc.input.modelKey) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
// A "significant" narrow fix should defer to the wide tier.
|
|
177
|
+
if (scope === "narrow" && decision.qualityImpact === "significant")
|
|
178
|
+
return null;
|
|
179
|
+
// Validate the reworked input against the tool's own schema before we
|
|
180
|
+
// trust it — a malformed proposal must never reach the provider.
|
|
181
|
+
const tool = (0, toolRegistry_1.getTool)(rc.toolName);
|
|
182
|
+
if (tool) {
|
|
183
|
+
const parsed = tool.inputSchema.safeParse(decision.repairedInput);
|
|
184
|
+
if (!parsed.success) {
|
|
185
|
+
logger_1.logger.warn(`repair: ${scope} LLM proposal failed tool schema — declining`, {
|
|
186
|
+
toolName: rc.toolName,
|
|
187
|
+
issues: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; "),
|
|
188
|
+
});
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
strategy: this.name,
|
|
193
|
+
repairedInput: parsed.data,
|
|
194
|
+
rationale: decision.rationale,
|
|
195
|
+
qualityImpact: decision.qualityImpact,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
strategy: this.name,
|
|
200
|
+
repairedInput: decision.repairedInput,
|
|
201
|
+
rationale: decision.rationale,
|
|
202
|
+
qualityImpact: decision.qualityImpact,
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/** Tier 1 — narrow agentic fix (offending field only, no model change). */
|
|
208
|
+
exports.llmNarrowRepairStrategy = makeLlmRepairStrategy("narrow", 10);
|
|
209
|
+
/** Tier 2 — wide agentic fix (may swap model / restructure). */
|
|
210
|
+
exports.llmWideRepairStrategy = makeLlmRepairStrategy("wide", 20);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paramRepair.strategy.d.ts","sourceRoot":"","sources":["../../../../../src/services/agent/repair/strategies/paramRepair.strategy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiC,cAAc,EAAE,MAAM,UAAU,CAAC;AA0FzE,eAAO,MAAM,mBAAmB,EAAE,cA+DjC,CAAC"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.paramRepairStrategy = void 0;
|
|
4
|
+
const aiModels_1 = require("../../../../globals/aiModels");
|
|
5
|
+
const chains_1 = require("../../providerFallback/chains");
|
|
6
|
+
/**
|
|
7
|
+
* Tier 0 — deterministic, narrow, config-driven micro-fix.
|
|
8
|
+
*
|
|
9
|
+
* "The agent should read the model config and substitute a valid value." This
|
|
10
|
+
* strategy does exactly that, deterministically and for free: when a provider
|
|
11
|
+
* rejected a single constrained parameter (aspect ratio / duration / resolution
|
|
12
|
+
* mismatch), it reads `aiModelConfigs[modelKey]` and snaps ONLY the offending
|
|
13
|
+
* field to the nearest value that model actually supports.
|
|
14
|
+
*
|
|
15
|
+
* It is deliberately conservative — it fixes a field only when the substitution
|
|
16
|
+
* is lossless or a minor adjustment (an aspect of the SAME orientation, a small
|
|
17
|
+
* duration snap). If the only legal value would meaningfully change the result
|
|
18
|
+
* (flip portrait↔landscape, snap 5s→20s), it DECLINES so the engine escalates
|
|
19
|
+
* to an agentic strategy that can make a smarter call (e.g. pick a different
|
|
20
|
+
* model that natively supports the request rather than distorting it).
|
|
21
|
+
*/
|
|
22
|
+
/** Only capability/input class errors are param-shaped. */
|
|
23
|
+
function isParamClass(rc) {
|
|
24
|
+
const c = rc.failure.classification;
|
|
25
|
+
return c === "capability" || c === "input";
|
|
26
|
+
}
|
|
27
|
+
/** Which constrained field the provider complained about, if the message says so. */
|
|
28
|
+
function offendingField(message) {
|
|
29
|
+
const m = message.toLowerCase();
|
|
30
|
+
if (/aspect ?ratio/.test(m))
|
|
31
|
+
return "aspectRatio";
|
|
32
|
+
if (/duration/.test(m))
|
|
33
|
+
return "duration";
|
|
34
|
+
if (/resolution/.test(m))
|
|
35
|
+
return "resolution";
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function aspectRatioValue(canonical) {
|
|
39
|
+
const match = canonical.match(/^(\d+)[:x](\d+)$/);
|
|
40
|
+
if (!match)
|
|
41
|
+
return null;
|
|
42
|
+
const w = parseInt(match[1], 10);
|
|
43
|
+
const h = parseInt(match[2], 10);
|
|
44
|
+
if (!w || !h)
|
|
45
|
+
return null;
|
|
46
|
+
return w / h;
|
|
47
|
+
}
|
|
48
|
+
function aspectClass(ratio) {
|
|
49
|
+
if (Math.abs(ratio - 1) < 0.02)
|
|
50
|
+
return "square";
|
|
51
|
+
return ratio > 1 ? "landscape" : "portrait";
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Nearest supported canonical aspect of the SAME orientation as `requested`.
|
|
55
|
+
* Returns the picked value + quality impact, or null when there's no
|
|
56
|
+
* same-orientation candidate (→ decline, an orientation flip is not "minor").
|
|
57
|
+
*/
|
|
58
|
+
function nearestSupportedAspect(requested, allowedCanonical) {
|
|
59
|
+
const reqRatio = aspectRatioValue(requested);
|
|
60
|
+
if (reqRatio == null)
|
|
61
|
+
return null;
|
|
62
|
+
const normReq = (0, chains_1.normalizeAspect)(requested);
|
|
63
|
+
// Exact (normalized) match already supported → nothing to fix here.
|
|
64
|
+
if (allowedCanonical.some((a) => (0, chains_1.normalizeAspect)(a) === normReq)) {
|
|
65
|
+
return { value: requested, impact: "none" };
|
|
66
|
+
}
|
|
67
|
+
const reqClass = aspectClass(reqRatio);
|
|
68
|
+
let best = null;
|
|
69
|
+
for (const candidate of allowedCanonical) {
|
|
70
|
+
const candRatio = aspectRatioValue(candidate);
|
|
71
|
+
if (candRatio == null)
|
|
72
|
+
continue;
|
|
73
|
+
if (aspectClass(candRatio) !== reqClass)
|
|
74
|
+
continue; // orientation-preserving only
|
|
75
|
+
const dist = Math.abs(Math.log(reqRatio) - Math.log(candRatio));
|
|
76
|
+
if (!best || dist < best.dist)
|
|
77
|
+
best = { value: candidate, dist };
|
|
78
|
+
}
|
|
79
|
+
if (!best)
|
|
80
|
+
return null; // no same-orientation option → not a minor fix
|
|
81
|
+
return { value: best.value, impact: "minor" };
|
|
82
|
+
}
|
|
83
|
+
function allowedAspectRatios(modelKey) {
|
|
84
|
+
const cfg = aiModels_1.aiModelConfigs[modelKey];
|
|
85
|
+
const allowed = cfg?.fields?.aspectRatio?.allowedValues;
|
|
86
|
+
if (!Array.isArray(allowed) || !allowed.length)
|
|
87
|
+
return null;
|
|
88
|
+
return allowed.filter((v) => typeof v === "string");
|
|
89
|
+
}
|
|
90
|
+
exports.paramRepairStrategy = {
|
|
91
|
+
name: "param-repair",
|
|
92
|
+
rank: 0,
|
|
93
|
+
applies(rc) {
|
|
94
|
+
if (!isParamClass(rc))
|
|
95
|
+
return false;
|
|
96
|
+
return typeof rc.input.modelKey === "string";
|
|
97
|
+
},
|
|
98
|
+
async propose(rc) {
|
|
99
|
+
const modelKey = rc.input.modelKey;
|
|
100
|
+
const field = offendingField(rc.failure.message);
|
|
101
|
+
const changes = [];
|
|
102
|
+
let impact = "none";
|
|
103
|
+
const repaired = { ...rc.input };
|
|
104
|
+
// ── Aspect ratio ──────────────────────────────────────────────────────
|
|
105
|
+
// Repair when the message points at aspect, OR when no field was named and
|
|
106
|
+
// an aspectRatio is present (some providers throw an opaque capability error).
|
|
107
|
+
const aspectCandidate = (field === "aspectRatio" || (field === null && typeof rc.input.aspectRatio === "string"))
|
|
108
|
+
? rc.input.aspectRatio
|
|
109
|
+
: undefined;
|
|
110
|
+
if (aspectCandidate) {
|
|
111
|
+
const allowed = allowedAspectRatios(modelKey);
|
|
112
|
+
if (allowed) {
|
|
113
|
+
const snap = nearestSupportedAspect(aspectCandidate, allowed);
|
|
114
|
+
if (!snap)
|
|
115
|
+
return null; // orientation flip needed → escalate (decline)
|
|
116
|
+
if (snap.value !== aspectCandidate) {
|
|
117
|
+
repaired.aspectRatio = snap.value;
|
|
118
|
+
changes.push(`aspectRatio ${aspectCandidate}→${snap.value}`);
|
|
119
|
+
if (snap.impact === "minor")
|
|
120
|
+
impact = "minor";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ── Duration ──────────────────────────────────────────────────────────
|
|
125
|
+
const durationCandidate = (field === "duration" || (field === null && typeof rc.input.durationSec === "number"))
|
|
126
|
+
? rc.input.durationSec
|
|
127
|
+
: undefined;
|
|
128
|
+
if (typeof durationCandidate === "number") {
|
|
129
|
+
const snap = (0, chains_1.snapDurationForModel)(modelKey, durationCandidate);
|
|
130
|
+
// null = drift too large to call "minor"; unconstrained = nothing to fix.
|
|
131
|
+
if (snap === null) {
|
|
132
|
+
// Duration is the offender and can't be snapped cheaply → decline.
|
|
133
|
+
if (field === "duration")
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
else if (!snap.unconstrained && snap.snappedSec !== durationCandidate) {
|
|
137
|
+
repaired.durationSec = snap.snappedSec;
|
|
138
|
+
changes.push(`durationSec ${durationCandidate}→${snap.snappedSec}`);
|
|
139
|
+
impact = "minor";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!changes.length)
|
|
143
|
+
return null; // nothing this strategy can safely fix
|
|
144
|
+
return {
|
|
145
|
+
strategy: this.name,
|
|
146
|
+
repairedInput: repaired,
|
|
147
|
+
rationale: `Snapped to ${modelKey}'s supported values: ${changes.join(", ")}`,
|
|
148
|
+
qualityImpact: impact,
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ToolContext, ToolOutcome } from "../toolRegistry";
|
|
2
|
+
/**
|
|
3
|
+
* Self-healing repair subsystem — REACTIVE, GENERIC, ESCALATING.
|
|
4
|
+
*
|
|
5
|
+
* Contract (per the design constraints):
|
|
6
|
+
* 1. REACTIVE — a repair is only ever proposed AFTER a tool call has actually
|
|
7
|
+
* failed. Nothing here touches the happy path; we never pre-flight-mutate
|
|
8
|
+
* a request that would have succeeded.
|
|
9
|
+
* 2. GENERIC — the engine knows nothing about aspect ratios, moderation, or
|
|
10
|
+
* any specific failure. It walks an ordered list of `RepairStrategy`s and
|
|
11
|
+
* retries with the first proposal. New failure classes are handled by
|
|
12
|
+
* adding a strategy, never by editing the loop.
|
|
13
|
+
* 3. ESCALATING (least-invasive first) — strategies carry an invasiveness
|
|
14
|
+
* `rank`; the engine consults them in ascending order. The narrowest fix
|
|
15
|
+
* that targets ONLY the rejected thing is tried first; a strategy DECLINES
|
|
16
|
+
* (returns null) when it can't fix the issue without widening scope or
|
|
17
|
+
* degrading quality, which escalates to the next, wider strategy.
|
|
18
|
+
*
|
|
19
|
+
* The agentic strategies (LLM-backed) live alongside the deterministic ones and
|
|
20
|
+
* are just higher-ranked entries — the engine treats them identically.
|
|
21
|
+
*/
|
|
22
|
+
export type FailureClass = "transient" | "rate_limit" | "capability" | "safety" | "auth" | "quota" | "input" | "unknown";
|
|
23
|
+
/** The structured error a failed `runTool` returned (subset of ToolError.error). */
|
|
24
|
+
export interface RepairFailure {
|
|
25
|
+
code: string;
|
|
26
|
+
message: string;
|
|
27
|
+
classification?: FailureClass;
|
|
28
|
+
attemptedProviders?: string[];
|
|
29
|
+
}
|
|
30
|
+
/** One prior failed (strategy, input, failure) triple — fed back so a strategy
|
|
31
|
+
* (and the LLM) can see what's already been tried and not repeat it. */
|
|
32
|
+
export interface RepairAttemptRecord {
|
|
33
|
+
/** Strategy that produced the input that then failed; "original" for attempt 0. */
|
|
34
|
+
strategy: string;
|
|
35
|
+
input: Record<string, unknown>;
|
|
36
|
+
failure: RepairFailure;
|
|
37
|
+
}
|
|
38
|
+
export interface RepairContext {
|
|
39
|
+
/** Tool whose call failed (e.g. "generate_video"). */
|
|
40
|
+
toolName: string;
|
|
41
|
+
/** The exact input that just failed. */
|
|
42
|
+
input: Record<string, unknown>;
|
|
43
|
+
/** The failure that input produced. */
|
|
44
|
+
failure: RepairFailure;
|
|
45
|
+
/** 1-based index of the repair attempt about to be proposed. */
|
|
46
|
+
attempt: number;
|
|
47
|
+
/** Prior failed attempts this run, oldest first (attempt 0 = the original call). */
|
|
48
|
+
history: RepairAttemptRecord[];
|
|
49
|
+
/** Tool context — agentRunId / projectId / trace logger. */
|
|
50
|
+
ctx: ToolContext;
|
|
51
|
+
}
|
|
52
|
+
export interface RepairProposal {
|
|
53
|
+
/** Strategy that produced this proposal (trace). */
|
|
54
|
+
strategy: string;
|
|
55
|
+
/** The repaired tool input to retry with. Must satisfy the tool's input schema. */
|
|
56
|
+
repairedInput: Record<string, unknown>;
|
|
57
|
+
/** One-line reason, for trace + post-mortem. */
|
|
58
|
+
rationale: string;
|
|
59
|
+
/**
|
|
60
|
+
* The strategy's own estimate of how much this repair degrades the result vs.
|
|
61
|
+
* the user's original intent. The engine prefers lower-impact proposals (they
|
|
62
|
+
* come from lower-ranked strategies first); a strategy that can only fix the
|
|
63
|
+
* issue at "significant" cost should usually DECLINE and let a wider strategy
|
|
64
|
+
* find a better path (e.g. swap to a model that natively supports the request
|
|
65
|
+
* rather than distorting the request to fit the current model).
|
|
66
|
+
*/
|
|
67
|
+
qualityImpact: "none" | "minor" | "significant";
|
|
68
|
+
}
|
|
69
|
+
export interface RepairStrategy {
|
|
70
|
+
/** Stable identifier, surfaced in trace. */
|
|
71
|
+
name: string;
|
|
72
|
+
/**
|
|
73
|
+
* Invasiveness rank — the engine sorts ascending and consults in that order,
|
|
74
|
+
* so the narrowest fix is always tried first. Convention:
|
|
75
|
+
* 0–9 micro-fix: mutate ONLY the rejected field, deterministically.
|
|
76
|
+
* 10–19 narrow agentic: LLM edits only the offending field(s), no model/strategy change.
|
|
77
|
+
* 20+ wide agentic: LLM may swap model, change strategy, restructure.
|
|
78
|
+
*/
|
|
79
|
+
rank: number;
|
|
80
|
+
/** Cheap synchronous relevance gate — skip strategies that can't apply. */
|
|
81
|
+
applies(rc: RepairContext): boolean;
|
|
82
|
+
/** Propose a repair, or null to DECLINE (engine escalates to the next strategy). */
|
|
83
|
+
propose(rc: RepairContext): Promise<RepairProposal | null>;
|
|
84
|
+
}
|
|
85
|
+
/** The function the engine wraps — same signature as toolRegistry.runTool.
|
|
86
|
+
* (Named distinctly from executor's RunToolFn to keep the barrel unambiguous.) */
|
|
87
|
+
export type RepairableRunToolFn = <O = unknown>(name: string, input: unknown, ctx: ToolContext) => Promise<ToolOutcome<O>>;
|
|
88
|
+
export interface RunWithRepairOptions {
|
|
89
|
+
/** Max repair RETRIES after the first failure (each may run a fresh tool call). Default 3. */
|
|
90
|
+
maxRepairs?: number;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/services/agent/repair/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,QAAQ,GACR,MAAM,GACN,OAAO,GACP,OAAO,GACP,SAAS,CAAC;AAEd,oFAAoF;AACpF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;yEACyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,uCAAuC;IACvC,OAAO,EAAE,aAAa,CAAC;IACvB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,4DAA4D;IAC5D,GAAG,EAAE,WAAW,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,aAAa,EAAE,MAAM,GAAG,OAAO,GAAG,aAAa,CAAC;CACjD;AAED,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,OAAO,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC;IACpC,oFAAoF;IACpF,OAAO,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CAC5D;AAED;mFACmF;AACnF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,GAAG,OAAO,EAC5C,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,WAAW,KACb,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7B,MAAM,WAAW,oBAAoB;IACnC,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { VideoBrief } from "../../schemas/brief.schema";
|
|
2
|
+
import { AgentVisualBible, VideoPlan } from "../../schemas/videoPlan.schema";
|
|
3
|
+
import { AgentPersona } from "../../schemas/agentPersona.schema";
|
|
4
|
+
import { IAgentProjectModel, IBrandKitModel, IStyleMemoryModel, PlanStage } from "../../models/agent.model";
|
|
5
|
+
import { getLlmCaller } from "./llmCallerRegistry";
|
|
6
|
+
/**
|
|
7
|
+
* Stable cache key for the Visual Bible. Bible content is a pure function of
|
|
8
|
+
* (brief, brandKit) — same inputs ⇒ same bible. We key on the brief object
|
|
9
|
+
* plus `brandKit.updatedAt` (cheap version proxy) so any brand-kit edit
|
|
10
|
+
* invalidates without re-hashing the kit body. C0 (no bible) returns "" so
|
|
11
|
+
* we never cache nothing as something.
|
|
12
|
+
*/
|
|
13
|
+
export declare function computeBibleCacheKey(brief: VideoBrief, brandKit: IBrandKitModel | null): string;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the bible to thread into the planner, using the project-level cache
|
|
16
|
+
* when the key matches. On miss runs the full BibleBuilder + image-vision
|
|
17
|
+
* pipeline (both soft-fail per their wrappers). Returns the bible and a
|
|
18
|
+
* `fresh` flag so the caller can decide to persist on cache miss only.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getBibleWithCache(args: {
|
|
21
|
+
project: IAgentProjectModel;
|
|
22
|
+
brief: VideoBrief;
|
|
23
|
+
persona: AgentPersona | undefined;
|
|
24
|
+
brandKit: IBrandKitModel | null;
|
|
25
|
+
llm: ReturnType<typeof getLlmCaller>;
|
|
26
|
+
onStage?: (stage: PlanStage) => void;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
bible: AgentVisualBible | undefined;
|
|
29
|
+
fresh: boolean;
|
|
30
|
+
key: string;
|
|
31
|
+
}>;
|
|
32
|
+
/** Per-stage wall-clock for one planning run (ms). */
|
|
33
|
+
export interface PlanStageTimings {
|
|
34
|
+
bibleMs: number;
|
|
35
|
+
plannerMs: number;
|
|
36
|
+
musicMs: number;
|
|
37
|
+
beatSnapMs: number;
|
|
38
|
+
totalMs: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Latency telemetry — emits ONE structured record per planning run, two ways:
|
|
42
|
+
* 1) `logger.info("agent.planTimings", …)` → Console + Grafana Loki (the
|
|
43
|
+
* existing winston pipeline; no parallel reporter).
|
|
44
|
+
* 2) a durable doc in the `agentPlanTimings` Firestore collection → queryable
|
|
45
|
+
* for aggregate analysis (p50/p95 per stage, web-research/cache incidence)
|
|
46
|
+
* via scripts/analyze-plan-timings.mjs — no per-run guesswork.
|
|
47
|
+
*
|
|
48
|
+
* Fire-and-forget: never on the critical path, and a write failure is swallowed
|
|
49
|
+
* (warn only) so telemetry can't break planning.
|
|
50
|
+
*/
|
|
51
|
+
export declare function recordPlanTimings(args: {
|
|
52
|
+
projectId: string;
|
|
53
|
+
userId: string;
|
|
54
|
+
brief: VideoBrief;
|
|
55
|
+
plan: {
|
|
56
|
+
meta?: Record<string, unknown>;
|
|
57
|
+
scenes?: unknown[];
|
|
58
|
+
};
|
|
59
|
+
timings: PlanStageTimings;
|
|
60
|
+
bibleCacheHit: boolean;
|
|
61
|
+
streaming: boolean;
|
|
62
|
+
dryRun: boolean;
|
|
63
|
+
}): void;
|
|
64
|
+
/** Result of a planning run — caller persists these. */
|
|
65
|
+
export interface RunPlanningResult {
|
|
66
|
+
/** Final musicked + beat-snapped plan. */
|
|
67
|
+
plan: VideoPlan;
|
|
68
|
+
/** The bible threaded into the planner (or undefined for C0 / soft-fail). */
|
|
69
|
+
bible: AgentVisualBible | undefined;
|
|
70
|
+
/** True when the bible was freshly built this run (cache miss). Persist `bibleCache` only when true. */
|
|
71
|
+
bibleFresh: boolean;
|
|
72
|
+
/** Cache key for the bible — persist alongside the bible when `bibleFresh`. */
|
|
73
|
+
bibleKey: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Run the full planning pipeline: bible (cache-or-build) → planner →
|
|
77
|
+
* music-select → beat-snap, stamping `plan.meta.bibleBuilt` and emitting
|
|
78
|
+
* latency telemetry. Pure compute — does NOT write the plan to Firestore;
|
|
79
|
+
* the caller decides where it lands (`.plan` for a live plan, `previewPlan`
|
|
80
|
+
* for a dry-run).
|
|
81
|
+
*/
|
|
82
|
+
export declare function runPlanning(args: {
|
|
83
|
+
/** Project doc (needs `id`, `bibleCache`). */
|
|
84
|
+
project: IAgentProjectModel;
|
|
85
|
+
brief: VideoBrief;
|
|
86
|
+
persona?: AgentPersona;
|
|
87
|
+
brandKit: IBrandKitModel | null;
|
|
88
|
+
styleMemory: IStyleMemoryModel | null;
|
|
89
|
+
/** `true`/`false`/`"auto"` (default) — forwarded to Planner.plan. */
|
|
90
|
+
useWebResearch?: boolean | "auto";
|
|
91
|
+
/** Dry-run flag — telemetry only (caller decides where the plan lands). */
|
|
92
|
+
dryRun?: boolean;
|
|
93
|
+
/** Mirrors each sub-step so the worker can write `planRun.stage`. */
|
|
94
|
+
onStage?: (stage: PlanStage) => void;
|
|
95
|
+
}): Promise<RunPlanningResult>;
|
|
96
|
+
//# sourceMappingURL=runPlanning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runPlanning.d.ts","sourceRoot":"","sources":["../../../src/services/agent/runPlanning.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAoB,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,SAAS,EACV,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAwBnD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,cAAc,GAAG,IAAI,GAC9B,MAAM,CAUR;AAiED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,OAAO,EAAE,kBAAkB,CAAC;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,YAAY,GAAG,SAAS,CAAC;IAClC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,GAAG,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACtC,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBhF;AAED,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;IAC7D,OAAO,EAAE,gBAAgB,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB,GAAG,IAAI,CAkCP;AAED,wDAAwD;AACxD,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,IAAI,EAAE,SAAS,CAAC;IAChB,6EAA6E;IAC7E,KAAK,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpC,wGAAwG;IACxG,UAAU,EAAE,OAAO,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,8CAA8C;IAC9C,OAAO,EAAE,kBAAkB,CAAC;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACtC,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAClC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qEAAqE;IACrE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACtC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAuD7B"}
|