cascade-ai 0.66.0 → 0.66.2
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/cli.cjs +281 -193
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +281 -193
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +280 -192
- package/dist/index.cjs +280 -192
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +280 -192
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/desktop-core.cjs
CHANGED
|
@@ -224264,7 +224264,7 @@ function reconcilePrice(model, live) {
|
|
|
224264
224264
|
}
|
|
224265
224265
|
|
|
224266
224266
|
// src/constants.ts
|
|
224267
|
-
var CASCADE_VERSION = "0.66.
|
|
224267
|
+
var CASCADE_VERSION = "0.66.2";
|
|
224268
224268
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
224269
224269
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
224270
224270
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -254895,7 +254895,15 @@ var KNOWN_TOOLS = [
|
|
|
254895
254895
|
"glob",
|
|
254896
254896
|
"grep",
|
|
254897
254897
|
"web_fetch",
|
|
254898
|
-
|
|
254898
|
+
// Every media tool buildMediaTools can register, not just the image one.
|
|
254899
|
+
// Omitting three of the four meant a run whose ONLY tools were media ones
|
|
254900
|
+
// (a video-generation subtask on an account with no file/web tools) counted
|
|
254901
|
+
// as "no tools registered" and dropped all tool guidance — the worker was
|
|
254902
|
+
// then told nothing about using tools at all, and wrote the video as prose.
|
|
254903
|
+
"generate_image",
|
|
254904
|
+
"generate_video",
|
|
254905
|
+
"generate_speech",
|
|
254906
|
+
"transcribe_audio"
|
|
254899
254907
|
];
|
|
254900
254908
|
var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
|
|
254901
254909
|
"generate_image",
|
|
@@ -254924,6 +254932,15 @@ function buildWorkerRules(has2) {
|
|
|
254924
254932
|
// reference straight to caption text — telling the model to call
|
|
254925
254933
|
// generate_image for THOSE just pays for an image nobody ever sees.
|
|
254926
254934
|
has2("generate_image") && '- When a slide deck (PowerPoint) or Word document deliverable calls for a decorative image or illustration, you MUST call the "generate_image" tool and then reference its result using Markdown image syntax: , where "location" is exactly the string the tool reported back. NEVER write a text placeholder or a bracketed description such as [image: a cat] in its place. Do NOT use "generate_image" for a chart, graph, or diagram that must show exact data (numbers, axes, labels) \u2014 an image model cannot guarantee those values are correct, so render that data as a Markdown table instead. For any OTHER deliverable format (PDF, plain text, code, etc.) do NOT call "generate_image" \u2014 those cannot embed the result, so describe the visual in words instead.',
|
|
254935
|
+
// The video counterpart of the image rule above, and the fix for the
|
|
254936
|
+
// reported "it writes scripts forever and never makes the video" run. The
|
|
254937
|
+
// failure mode is identical — the model narrates the deliverable instead of
|
|
254938
|
+
// producing it — but the stakes are not: a video subtask that ends in prose
|
|
254939
|
+
// has burned the whole pre-production plan for nothing. Unlike the image
|
|
254940
|
+
// rule this is NOT scoped to a document format, because the clip itself is
|
|
254941
|
+
// the deliverable rather than an illustration inside one, and the reference
|
|
254942
|
+
// syntax is a plain link: Markdown image syntax does not embed a video.
|
|
254943
|
+
has2("generate_video") && '- When the subtask deliverable is a video, you MUST call the "generate_video" tool \u2014 that call IS the deliverable, and the subtask is not done until it has returned. NEVER deliver the video as prose, a script, a storyboard, or a bracketed placeholder such as [video: a cat skating], and NEVER claim a video exists unless the tool reported a location for it. Call it exactly ONCE: it is billed per second of output and renders for minutes, so a second call charges the user again. Then report the location string the tool returned VERBATIM as your result, referencing it as [description](location) if it goes inside a document. If the tool reports a failure or a timeout, report that failure verbatim and stop \u2014 do NOT call it again, and do NOT substitute a written description of a video that was never made.',
|
|
254927
254944
|
has2("run_code") && '- Use the "run_code" tool for any file types (Excel, Zip, csv, etc.) or complex processing not covered by other tools. Always cleanup after code execution.',
|
|
254928
254945
|
"- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
|
|
254929
254946
|
has2("peer_message") && '- Use the "peer_message" tool to communicate with other T3 workers if your tasks have dependencies or shared state. You can send updates or wait for signals.',
|
|
@@ -255466,6 +255483,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
255466
255483
|
if (isTaggedSystemic || isProviderBacked && classifyProviderError(err).systemic) {
|
|
255467
255484
|
throw new CriticalToolError(errMsg, tc.name);
|
|
255468
255485
|
}
|
|
255486
|
+
if (isProviderBacked) {
|
|
255487
|
+
throw new CriticalToolError(
|
|
255488
|
+
`${tc.name} failed and was not retried \u2014 media generation is billed per attempt, so Cascade stops on the first failure instead of paying for a second. Reason: ${errMsg}`,
|
|
255489
|
+
tc.name
|
|
255490
|
+
);
|
|
255491
|
+
}
|
|
255469
255492
|
return await this.adaptiveFallback(tc, `Tool error: ${errMsg}`);
|
|
255470
255493
|
}
|
|
255471
255494
|
}
|
|
@@ -255475,6 +255498,14 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
255475
255498
|
* 1. Find a semantically similar registered tool and retry with same input
|
|
255476
255499
|
* 2. Synthesize a new tool via ToolCreator (if available) and run it
|
|
255477
255500
|
* 3. Return the original error so the agent loop can decide what to do next
|
|
255501
|
+
*
|
|
255502
|
+
* Scoped to CHEAP, task-scoped failures — the class of problem it was built
|
|
255503
|
+
* for is a wrong or missing tool name, where a name-similar sibling plausibly
|
|
255504
|
+
* does the same job for the same (near-zero) cost. PROVIDER_BACKED_TOOLS never
|
|
255505
|
+
* reach it: their siblings are keyword-similar but semantically different
|
|
255506
|
+
* (generate_image is not a stand-in for generate_video), and every attempt —
|
|
255507
|
+
* theirs, or a synthesized replacement's — is separately billed. See the
|
|
255508
|
+
* fast-fail branch in executeTool().
|
|
255478
255509
|
*/
|
|
255479
255510
|
async adaptiveFallback(tc, originalError) {
|
|
255480
255511
|
const altTool = this.findAlternativeTool(tc.name);
|
|
@@ -256227,15 +256258,244 @@ _(incomplete: ${t2Result.issues.join("; ")})_`;
|
|
|
256227
256258
|
return output;
|
|
256228
256259
|
}
|
|
256229
256260
|
|
|
256261
|
+
// src/core/multimodal/registry.ts
|
|
256262
|
+
var CAPABILITIES = [
|
|
256263
|
+
// ── Image ──
|
|
256264
|
+
{
|
|
256265
|
+
modality: "image",
|
|
256266
|
+
provider: "openai",
|
|
256267
|
+
modelId: "dall-e-3",
|
|
256268
|
+
api: "openai-images"
|
|
256269
|
+
},
|
|
256270
|
+
{
|
|
256271
|
+
// Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
|
|
256272
|
+
// ("no longer available to new users") — Imagen's :predict API is deprecated
|
|
256273
|
+
// and shuts down 2026-08-17. Google's own migration path is this model on
|
|
256274
|
+
// the ordinary generateContent endpoint, so the fix was never another Imagen
|
|
256275
|
+
// id: the whole request/response SHAPE changes, which is why `api` moved to
|
|
256276
|
+
// 'gemini-generate-content' rather than just the modelId moving.
|
|
256277
|
+
// https://ai.google.dev/gemini-api/docs/imagen
|
|
256278
|
+
modality: "image",
|
|
256279
|
+
provider: "gemini",
|
|
256280
|
+
modelId: "gemini-2.5-flash-image",
|
|
256281
|
+
api: "gemini-generate-content"
|
|
256282
|
+
},
|
|
256283
|
+
// ── Speech (text → audio) ──
|
|
256284
|
+
{
|
|
256285
|
+
modality: "speech",
|
|
256286
|
+
provider: "openai",
|
|
256287
|
+
modelId: "tts-1",
|
|
256288
|
+
api: "openai-speech"
|
|
256289
|
+
},
|
|
256290
|
+
{
|
|
256291
|
+
// The only quality claim in this table that needs no leaderboard: OpenAI
|
|
256292
|
+
// ships these as the same voice at two fidelities and charges double for
|
|
256293
|
+
// this one. That is the vendor's own ordering, not an inference.
|
|
256294
|
+
modality: "speech",
|
|
256295
|
+
provider: "openai",
|
|
256296
|
+
modelId: "tts-1-hd",
|
|
256297
|
+
api: "openai-speech",
|
|
256298
|
+
quality: 70,
|
|
256299
|
+
qualityBasis: "OpenAI's own hd variant of tts-1, priced 2x"
|
|
256300
|
+
},
|
|
256301
|
+
// ── Transcription (audio → text) ──
|
|
256302
|
+
{
|
|
256303
|
+
modality: "transcription",
|
|
256304
|
+
provider: "openai",
|
|
256305
|
+
modelId: "whisper-1",
|
|
256306
|
+
api: "openai-transcriptions"
|
|
256307
|
+
},
|
|
256308
|
+
// ── Video ──
|
|
256309
|
+
{
|
|
256310
|
+
// Long-running: submit returns an operation name, the video lands minutes
|
|
256311
|
+
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
256312
|
+
// that polling loop; the registry only records the shape via `api`.
|
|
256313
|
+
//
|
|
256314
|
+
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
256315
|
+
// not the Gemini Developer API's — this file calls
|
|
256316
|
+
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
256317
|
+
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
256318
|
+
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
256319
|
+
// there is `veo-3.1-generate-preview`.
|
|
256320
|
+
// https://ai.google.dev/gemini-api/docs/veo
|
|
256321
|
+
modality: "video",
|
|
256322
|
+
provider: "gemini",
|
|
256323
|
+
modelId: "veo-3.1-generate-preview",
|
|
256324
|
+
api: "gemini-predict-lro"
|
|
256325
|
+
}
|
|
256326
|
+
];
|
|
256327
|
+
function capabilityCost(cap) {
|
|
256328
|
+
const match = findPricing(cap.modelId, cap.provider, {
|
|
256329
|
+
fallbackProviders: cap.provider === "gemini" ? ["vertex"] : []
|
|
256330
|
+
});
|
|
256331
|
+
const rate = match?.entry.rates?.[0];
|
|
256332
|
+
if (!rate) return null;
|
|
256333
|
+
const unitLabel = {
|
|
256334
|
+
per_image: "per image",
|
|
256335
|
+
per_second: "per second of video",
|
|
256336
|
+
per_character: "per character",
|
|
256337
|
+
per_audio_minute: "per audio minute"
|
|
256338
|
+
};
|
|
256339
|
+
const unit = unitLabel[rate.unit] ?? rate.unit;
|
|
256340
|
+
const amount = rate.amount;
|
|
256341
|
+
const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
|
|
256342
|
+
return { label: `$${shown} ${unit}`, amount, unit };
|
|
256343
|
+
}
|
|
256344
|
+
function explainChoice(c4, cost, modality, isBest) {
|
|
256345
|
+
const priced = cost ? ` ${cost.label}.` : "";
|
|
256346
|
+
if (c4.quality !== void 0) {
|
|
256347
|
+
return isBest ? `${c4.modelId} (${c4.provider}) \u2014 ranked highest for ${modality}: ${c4.qualityBasis}.${priced}` : `${c4.modelId} (${c4.provider}) \u2014 ranked alternative for ${modality}: ${c4.qualityBasis}.${priced}`;
|
|
256348
|
+
}
|
|
256349
|
+
const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
|
|
256350
|
+
return isBest ? `${c4.modelId} (${c4.provider}) \u2014 cheapest usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking : `${c4.modelId} (${c4.provider}) \u2014 alternative usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking;
|
|
256351
|
+
}
|
|
256352
|
+
var MultimodalRegistry = class {
|
|
256353
|
+
/** Provider types the user has actually configured a key for. */
|
|
256354
|
+
available;
|
|
256355
|
+
constructor(availableProviders) {
|
|
256356
|
+
this.available = new Set(availableProviders);
|
|
256357
|
+
}
|
|
256358
|
+
/** Every capability whose provider is configured AND which Cascade can call. */
|
|
256359
|
+
usable(modality) {
|
|
256360
|
+
return CAPABILITIES.filter(
|
|
256361
|
+
(c4) => !c4.unsupported && this.available.has(c4.provider) && (!modality || c4.modality === modality)
|
|
256362
|
+
);
|
|
256363
|
+
}
|
|
256364
|
+
/** Modalities this account can actually generate right now. */
|
|
256365
|
+
availableModalities() {
|
|
256366
|
+
return [...new Set(this.usable().map((c4) => c4.modality))];
|
|
256367
|
+
}
|
|
256368
|
+
/**
|
|
256369
|
+
* Every usable capability for a modality, best first.
|
|
256370
|
+
*
|
|
256371
|
+
* Ranked models beat unranked ones, then higher quality, then lower unit
|
|
256372
|
+
* price. An explicit `preferModel` is moved to the head when it is usable —
|
|
256373
|
+
* the user naming a model is a stronger signal than any prior.
|
|
256374
|
+
*
|
|
256375
|
+
* `select()` is just the head of this list. The TAIL is the point: a systemic
|
|
256376
|
+
* provider failure (dead key, 404, exhausted quota) is scoped to one
|
|
256377
|
+
* provider+model, so "this model cannot draw" is not the same fact as "this
|
|
256378
|
+
* account cannot draw". A caller holding the ordered alternatives can try the
|
|
256379
|
+
* next provider instead of aborting the whole run — see generate-media.ts.
|
|
256380
|
+
*/
|
|
256381
|
+
rank(modality, opts = {}) {
|
|
256382
|
+
const scored = this.usable(modality).map((c4) => ({ c: c4, cost: capabilityCost(c4) })).sort((a2, b3) => {
|
|
256383
|
+
const qa = a2.c.quality ?? -1;
|
|
256384
|
+
const qb = b3.c.quality ?? -1;
|
|
256385
|
+
if (qa !== qb) return qb - qa;
|
|
256386
|
+
const pa = a2.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
256387
|
+
const pb = b3.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
256388
|
+
return pa - pb;
|
|
256389
|
+
});
|
|
256390
|
+
const results = scored.map(({ c: c4, cost }, i4) => ({
|
|
256391
|
+
capability: c4,
|
|
256392
|
+
cost,
|
|
256393
|
+
reason: explainChoice(c4, cost, modality, i4 === 0)
|
|
256394
|
+
}));
|
|
256395
|
+
if (opts.preferModel) {
|
|
256396
|
+
const i4 = results.findIndex((r4) => r4.capability.modelId === opts.preferModel);
|
|
256397
|
+
if (i4 > -1) {
|
|
256398
|
+
const [wanted] = results.splice(i4, 1);
|
|
256399
|
+
results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
|
|
256400
|
+
}
|
|
256401
|
+
}
|
|
256402
|
+
return results;
|
|
256403
|
+
}
|
|
256404
|
+
/**
|
|
256405
|
+
* Pick a model for a modality — the best usable one, or null if there is none.
|
|
256406
|
+
*/
|
|
256407
|
+
select(modality, opts = {}) {
|
|
256408
|
+
return this.rank(modality, opts)[0] ?? null;
|
|
256409
|
+
}
|
|
256410
|
+
/**
|
|
256411
|
+
* A plain-language account of what this account can and cannot generate —
|
|
256412
|
+
* including the things it cannot, and why.
|
|
256413
|
+
*
|
|
256414
|
+
* This is the USER-facing inventory (model ids and unit prices), keyed on
|
|
256415
|
+
* which providers are configured. It is deliberately NOT what the planner
|
|
256416
|
+
* reads: see `describeGenerationForPlanner()` below for that, and the note
|
|
256417
|
+
* there for why the two cannot be the same string.
|
|
256418
|
+
*/
|
|
256419
|
+
describe() {
|
|
256420
|
+
const lines = [];
|
|
256421
|
+
const usable = this.usable();
|
|
256422
|
+
if (usable.length) {
|
|
256423
|
+
lines.push("Generation models available on your configured providers:");
|
|
256424
|
+
for (const modality of ["image", "speech", "transcription", "video"]) {
|
|
256425
|
+
const forModality = usable.filter((c4) => c4.modality === modality);
|
|
256426
|
+
if (!forModality.length) continue;
|
|
256427
|
+
const shown = forModality.map((c4) => {
|
|
256428
|
+
const cost = capabilityCost(c4);
|
|
256429
|
+
return `${c4.modelId}${cost ? ` (${cost.label})` : ""}`;
|
|
256430
|
+
});
|
|
256431
|
+
lines.push(` ${modality}: ${shown.join(", ")}`);
|
|
256432
|
+
}
|
|
256433
|
+
} else {
|
|
256434
|
+
lines.push("No generation models are available \u2014 none of your configured providers offers one.");
|
|
256435
|
+
}
|
|
256436
|
+
const blocked = CAPABILITIES.filter((c4) => c4.unsupported && this.available.has(c4.provider));
|
|
256437
|
+
for (const c4 of blocked) {
|
|
256438
|
+
lines.push(` ${c4.modality}: ${c4.modelId} exists on your account but is not callable. ${c4.unsupported}`);
|
|
256439
|
+
}
|
|
256440
|
+
lines.push(
|
|
256441
|
+
" music: no supported provider exposes a music-generation API, so Cascade cannot generate music."
|
|
256442
|
+
);
|
|
256443
|
+
return lines.join("\n");
|
|
256444
|
+
}
|
|
256445
|
+
};
|
|
256446
|
+
var PLANNER_FACTS = [
|
|
256447
|
+
{ tool: "generate_image", modality: "image", shape: "one call returns one finished image" },
|
|
256448
|
+
{ tool: "generate_speech", modality: "speech", shape: "one call returns one finished audio file" },
|
|
256449
|
+
{
|
|
256450
|
+
tool: "generate_video",
|
|
256451
|
+
modality: "video",
|
|
256452
|
+
shape: "one call returns one finished clip, after rendering for 1-3 minutes (Cascade stops waiting at 8)"
|
|
256453
|
+
},
|
|
256454
|
+
{
|
|
256455
|
+
tool: "transcribe_audio",
|
|
256456
|
+
modality: "transcription",
|
|
256457
|
+
shape: "one call returns the transcript of one audio file"
|
|
256458
|
+
}
|
|
256459
|
+
];
|
|
256460
|
+
function plannerPriceNote(modality) {
|
|
256461
|
+
const costs = CAPABILITIES.filter((c4) => c4.modality === modality && !c4.unsupported).map((c4) => capabilityCost(c4)).filter((c4) => c4 !== null);
|
|
256462
|
+
if (!costs.length) return "";
|
|
256463
|
+
const labels = new Set(costs.map((c4) => c4.label));
|
|
256464
|
+
if (labels.size === 1) return ` Billed ${costs[0].label}.`;
|
|
256465
|
+
const units = new Set(costs.map((c4) => c4.unit));
|
|
256466
|
+
return units.size === 1 ? ` Billed ${[...units][0]}.` : "";
|
|
256467
|
+
}
|
|
256468
|
+
function describeGenerationForPlanner(has2) {
|
|
256469
|
+
const present = PLANNER_FACTS.filter((f4) => has2(f4.tool));
|
|
256470
|
+
if (!present.length) return "";
|
|
256471
|
+
const lines = ["MEDIA GENERATION \u2014 the only generation tools this run can call:"];
|
|
256472
|
+
for (const f4 of present) {
|
|
256473
|
+
lines.push(`- "${f4.tool}" (${f4.modality}): ${f4.shape}.${plannerPriceNote(f4.modality)}`);
|
|
256474
|
+
}
|
|
256475
|
+
lines.push(
|
|
256476
|
+
"Each one is a single ATOMIC tool call, made by ONE T3 worker, that produces the finished file. It is never a multi-step pipeline, and no worker can satisfy it by describing the result in words.",
|
|
256477
|
+
"No other modality has a tool here \u2014 never plan a step that generates music, 3D or anything not listed above."
|
|
256478
|
+
);
|
|
256479
|
+
if (has2("generate_video")) {
|
|
256480
|
+
lines.push(
|
|
256481
|
+
'VIDEO PLANS MUST END IN THE TOOL CALL. Creative pre-production \u2014 a script, a shot list, direction notes \u2014 is expected and worth planning, but it is NOT the video. Any plan that involves video MUST contain exactly ONE subtask whose deliverable is the "generate_video" call itself; it MUST be the last subtask on that path (dependsOn the pre-production ones), and nothing after it may be required to produce the clip. Never end a video plan on a script, a storyboard or a review step. Never split one clip across several "generate_video" subtasks, and never plan a render \u2192 critique \u2192 re-render loop: every render is billed again and takes minutes.'
|
|
256482
|
+
);
|
|
256483
|
+
}
|
|
256484
|
+
return lines.join("\n");
|
|
256485
|
+
}
|
|
256486
|
+
|
|
256230
256487
|
// src/core/tiers/t2-manager.ts
|
|
256231
|
-
function buildT2SystemPrompt(
|
|
256488
|
+
function buildT2SystemPrompt(has2) {
|
|
256489
|
+
const generation = describeGenerationForPlanner(has2);
|
|
256232
256490
|
return [
|
|
256233
256491
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
256234
256492
|
"Your role is to analyze a section of a task and decompose it into 2-5 discrete subtasks for T3 Workers.",
|
|
256235
256493
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
256236
|
-
|
|
256237
|
-
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text."
|
|
256238
|
-
|
|
256494
|
+
has2("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
256495
|
+
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
256496
|
+
generation && `
|
|
256497
|
+
${generation}`
|
|
256498
|
+
].filter((l2) => l2 !== false && l2 !== "").join("\n");
|
|
256239
256499
|
}
|
|
256240
256500
|
var T2Manager = class extends BaseTier {
|
|
256241
256501
|
router;
|
|
@@ -256532,7 +256792,7 @@ Return ONLY the JSON array.`;
|
|
|
256532
256792
|
const messages = [{ role: "user", content: prompt }];
|
|
256533
256793
|
const result = await this.router.generate("T2", {
|
|
256534
256794
|
messages,
|
|
256535
|
-
systemPrompt: this.systemPromptOverride + buildT2SystemPrompt(this.toolRegistry.hasTool(
|
|
256795
|
+
systemPrompt: this.systemPromptOverride + buildT2SystemPrompt((name) => this.toolRegistry.hasTool(name)) + (this.hierarchyContext ? `
|
|
256536
256796
|
|
|
256537
256797
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
256538
256798
|
maxTokens: 2e3,
|
|
@@ -257082,6 +257342,7 @@ function isStrongKeywordOverlap(a2 = [], b3 = []) {
|
|
|
257082
257342
|
}
|
|
257083
257343
|
function buildT1SystemPrompt(has2) {
|
|
257084
257344
|
const canWriteArtifacts = has2("file_write") || has2("file_edit") || has2("run_code") || has2("pdf_create");
|
|
257345
|
+
const generation = describeGenerationForPlanner(has2);
|
|
257085
257346
|
const rules = [
|
|
257086
257347
|
"- Simple \u2192 1 T3, Moderate \u2192 2-3 T2s, Complex \u2192 3-5 T2s, Highly Complex \u2192 5+ T2s",
|
|
257087
257348
|
"- Return ONLY valid JSON \u2014 no other text",
|
|
@@ -257116,7 +257377,9 @@ DEPENDENCY GUIDANCE:
|
|
|
257116
257377
|
QUALITY RULES:
|
|
257117
257378
|
- Each section must have a clear, testable "expectedOutput" so T2 knows when it is done.
|
|
257118
257379
|
- Do NOT create trivial sections that only move files or print summaries \u2014 fold those into adjacent sections.
|
|
257119
|
-
- If the plan would naturally produce fewer than 2 independent sections, prefer Moderate routing (single T2)
|
|
257380
|
+
- If the plan would naturally produce fewer than 2 independent sections, prefer Moderate routing (single T2).${generation ? `
|
|
257381
|
+
|
|
257382
|
+
${generation}` : ""}`;
|
|
257120
257383
|
}
|
|
257121
257384
|
var T1Administrator = class extends BaseTier {
|
|
257122
257385
|
router;
|
|
@@ -257811,188 +258074,6 @@ Reply with exactly one word: YES, NO, or UNSURE.
|
|
|
257811
258074
|
}
|
|
257812
258075
|
};
|
|
257813
258076
|
|
|
257814
|
-
// src/core/multimodal/registry.ts
|
|
257815
|
-
var CAPABILITIES = [
|
|
257816
|
-
// ── Image ──
|
|
257817
|
-
{
|
|
257818
|
-
modality: "image",
|
|
257819
|
-
provider: "openai",
|
|
257820
|
-
modelId: "dall-e-3",
|
|
257821
|
-
api: "openai-images"
|
|
257822
|
-
},
|
|
257823
|
-
{
|
|
257824
|
-
// Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
|
|
257825
|
-
// ("no longer available to new users") — Imagen's :predict API is deprecated
|
|
257826
|
-
// and shuts down 2026-08-17. Google's own migration path is this model on
|
|
257827
|
-
// the ordinary generateContent endpoint, so the fix was never another Imagen
|
|
257828
|
-
// id: the whole request/response SHAPE changes, which is why `api` moved to
|
|
257829
|
-
// 'gemini-generate-content' rather than just the modelId moving.
|
|
257830
|
-
// https://ai.google.dev/gemini-api/docs/imagen
|
|
257831
|
-
modality: "image",
|
|
257832
|
-
provider: "gemini",
|
|
257833
|
-
modelId: "gemini-2.5-flash-image",
|
|
257834
|
-
api: "gemini-generate-content"
|
|
257835
|
-
},
|
|
257836
|
-
// ── Speech (text → audio) ──
|
|
257837
|
-
{
|
|
257838
|
-
modality: "speech",
|
|
257839
|
-
provider: "openai",
|
|
257840
|
-
modelId: "tts-1",
|
|
257841
|
-
api: "openai-speech"
|
|
257842
|
-
},
|
|
257843
|
-
{
|
|
257844
|
-
// The only quality claim in this table that needs no leaderboard: OpenAI
|
|
257845
|
-
// ships these as the same voice at two fidelities and charges double for
|
|
257846
|
-
// this one. That is the vendor's own ordering, not an inference.
|
|
257847
|
-
modality: "speech",
|
|
257848
|
-
provider: "openai",
|
|
257849
|
-
modelId: "tts-1-hd",
|
|
257850
|
-
api: "openai-speech",
|
|
257851
|
-
quality: 70,
|
|
257852
|
-
qualityBasis: "OpenAI's own hd variant of tts-1, priced 2x"
|
|
257853
|
-
},
|
|
257854
|
-
// ── Transcription (audio → text) ──
|
|
257855
|
-
{
|
|
257856
|
-
modality: "transcription",
|
|
257857
|
-
provider: "openai",
|
|
257858
|
-
modelId: "whisper-1",
|
|
257859
|
-
api: "openai-transcriptions"
|
|
257860
|
-
},
|
|
257861
|
-
// ── Video ──
|
|
257862
|
-
{
|
|
257863
|
-
// Long-running: submit returns an operation name, the video lands minutes
|
|
257864
|
-
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
257865
|
-
// that polling loop; the registry only records the shape via `api`.
|
|
257866
|
-
//
|
|
257867
|
-
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
257868
|
-
// not the Gemini Developer API's — this file calls
|
|
257869
|
-
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
257870
|
-
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
257871
|
-
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
257872
|
-
// there is `veo-3.1-generate-preview`.
|
|
257873
|
-
// https://ai.google.dev/gemini-api/docs/veo
|
|
257874
|
-
modality: "video",
|
|
257875
|
-
provider: "gemini",
|
|
257876
|
-
modelId: "veo-3.1-generate-preview",
|
|
257877
|
-
api: "gemini-predict-lro"
|
|
257878
|
-
}
|
|
257879
|
-
];
|
|
257880
|
-
function capabilityCost(cap) {
|
|
257881
|
-
const match = findPricing(cap.modelId, cap.provider, {
|
|
257882
|
-
fallbackProviders: cap.provider === "gemini" ? ["vertex"] : []
|
|
257883
|
-
});
|
|
257884
|
-
const rate = match?.entry.rates?.[0];
|
|
257885
|
-
if (!rate) return null;
|
|
257886
|
-
const unitLabel = {
|
|
257887
|
-
per_image: "per image",
|
|
257888
|
-
per_second: "per second of video",
|
|
257889
|
-
per_character: "per character",
|
|
257890
|
-
per_audio_minute: "per audio minute"
|
|
257891
|
-
};
|
|
257892
|
-
const unit = unitLabel[rate.unit] ?? rate.unit;
|
|
257893
|
-
const amount = rate.amount;
|
|
257894
|
-
const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
|
|
257895
|
-
return { label: `$${shown} ${unit}`, amount, unit };
|
|
257896
|
-
}
|
|
257897
|
-
function explainChoice(c4, cost, modality, isBest) {
|
|
257898
|
-
const priced = cost ? ` ${cost.label}.` : "";
|
|
257899
|
-
if (c4.quality !== void 0) {
|
|
257900
|
-
return isBest ? `${c4.modelId} (${c4.provider}) \u2014 ranked highest for ${modality}: ${c4.qualityBasis}.${priced}` : `${c4.modelId} (${c4.provider}) \u2014 ranked alternative for ${modality}: ${c4.qualityBasis}.${priced}`;
|
|
257901
|
-
}
|
|
257902
|
-
const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
|
|
257903
|
-
return isBest ? `${c4.modelId} (${c4.provider}) \u2014 cheapest usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking : `${c4.modelId} (${c4.provider}) \u2014 alternative usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking;
|
|
257904
|
-
}
|
|
257905
|
-
var MultimodalRegistry = class {
|
|
257906
|
-
/** Provider types the user has actually configured a key for. */
|
|
257907
|
-
available;
|
|
257908
|
-
constructor(availableProviders) {
|
|
257909
|
-
this.available = new Set(availableProviders);
|
|
257910
|
-
}
|
|
257911
|
-
/** Every capability whose provider is configured AND which Cascade can call. */
|
|
257912
|
-
usable(modality) {
|
|
257913
|
-
return CAPABILITIES.filter(
|
|
257914
|
-
(c4) => !c4.unsupported && this.available.has(c4.provider) && (!modality || c4.modality === modality)
|
|
257915
|
-
);
|
|
257916
|
-
}
|
|
257917
|
-
/** Modalities this account can actually generate right now. */
|
|
257918
|
-
availableModalities() {
|
|
257919
|
-
return [...new Set(this.usable().map((c4) => c4.modality))];
|
|
257920
|
-
}
|
|
257921
|
-
/**
|
|
257922
|
-
* Every usable capability for a modality, best first.
|
|
257923
|
-
*
|
|
257924
|
-
* Ranked models beat unranked ones, then higher quality, then lower unit
|
|
257925
|
-
* price. An explicit `preferModel` is moved to the head when it is usable —
|
|
257926
|
-
* the user naming a model is a stronger signal than any prior.
|
|
257927
|
-
*
|
|
257928
|
-
* `select()` is just the head of this list. The TAIL is the point: a systemic
|
|
257929
|
-
* provider failure (dead key, 404, exhausted quota) is scoped to one
|
|
257930
|
-
* provider+model, so "this model cannot draw" is not the same fact as "this
|
|
257931
|
-
* account cannot draw". A caller holding the ordered alternatives can try the
|
|
257932
|
-
* next provider instead of aborting the whole run — see generate-media.ts.
|
|
257933
|
-
*/
|
|
257934
|
-
rank(modality, opts = {}) {
|
|
257935
|
-
const scored = this.usable(modality).map((c4) => ({ c: c4, cost: capabilityCost(c4) })).sort((a2, b3) => {
|
|
257936
|
-
const qa = a2.c.quality ?? -1;
|
|
257937
|
-
const qb = b3.c.quality ?? -1;
|
|
257938
|
-
if (qa !== qb) return qb - qa;
|
|
257939
|
-
const pa = a2.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
257940
|
-
const pb = b3.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
257941
|
-
return pa - pb;
|
|
257942
|
-
});
|
|
257943
|
-
const results = scored.map(({ c: c4, cost }, i4) => ({
|
|
257944
|
-
capability: c4,
|
|
257945
|
-
cost,
|
|
257946
|
-
reason: explainChoice(c4, cost, modality, i4 === 0)
|
|
257947
|
-
}));
|
|
257948
|
-
if (opts.preferModel) {
|
|
257949
|
-
const i4 = results.findIndex((r4) => r4.capability.modelId === opts.preferModel);
|
|
257950
|
-
if (i4 > -1) {
|
|
257951
|
-
const [wanted] = results.splice(i4, 1);
|
|
257952
|
-
results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
|
|
257953
|
-
}
|
|
257954
|
-
}
|
|
257955
|
-
return results;
|
|
257956
|
-
}
|
|
257957
|
-
/**
|
|
257958
|
-
* Pick a model for a modality — the best usable one, or null if there is none.
|
|
257959
|
-
*/
|
|
257960
|
-
select(modality, opts = {}) {
|
|
257961
|
-
return this.rank(modality, opts)[0] ?? null;
|
|
257962
|
-
}
|
|
257963
|
-
/**
|
|
257964
|
-
* A plain-language account of what this account can and cannot generate —
|
|
257965
|
-
* including the things it cannot, and why. The planner sees this, so being
|
|
257966
|
-
* candid here is what stops it inventing a `generate_music` call.
|
|
257967
|
-
*/
|
|
257968
|
-
describe() {
|
|
257969
|
-
const lines = [];
|
|
257970
|
-
const usable = this.usable();
|
|
257971
|
-
if (usable.length) {
|
|
257972
|
-
lines.push("Generation models available on your configured providers:");
|
|
257973
|
-
for (const modality of ["image", "speech", "transcription", "video"]) {
|
|
257974
|
-
const forModality = usable.filter((c4) => c4.modality === modality);
|
|
257975
|
-
if (!forModality.length) continue;
|
|
257976
|
-
const shown = forModality.map((c4) => {
|
|
257977
|
-
const cost = capabilityCost(c4);
|
|
257978
|
-
return `${c4.modelId}${cost ? ` (${cost.label})` : ""}`;
|
|
257979
|
-
});
|
|
257980
|
-
lines.push(` ${modality}: ${shown.join(", ")}`);
|
|
257981
|
-
}
|
|
257982
|
-
} else {
|
|
257983
|
-
lines.push("No generation models are available \u2014 none of your configured providers offers one.");
|
|
257984
|
-
}
|
|
257985
|
-
const blocked = CAPABILITIES.filter((c4) => c4.unsupported && this.available.has(c4.provider));
|
|
257986
|
-
for (const c4 of blocked) {
|
|
257987
|
-
lines.push(` ${c4.modality}: ${c4.modelId} exists on your account but is not callable. ${c4.unsupported}`);
|
|
257988
|
-
}
|
|
257989
|
-
lines.push(
|
|
257990
|
-
" music: no supported provider exposes a music-generation API, so Cascade cannot generate music."
|
|
257991
|
-
);
|
|
257992
|
-
return lines.join("\n");
|
|
257993
|
-
}
|
|
257994
|
-
};
|
|
257995
|
-
|
|
257996
258077
|
// src/tools/base.ts
|
|
257997
258078
|
var BaseTool = class {
|
|
257998
258079
|
workspaceRoot = process.cwd();
|
|
@@ -258013,10 +258094,17 @@ var BaseTool = class {
|
|
|
258013
258094
|
|
|
258014
258095
|
// src/core/multimodal/generate.ts
|
|
258015
258096
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
258097
|
+
var GenerationGaveUpError = class extends Error {
|
|
258098
|
+
constructor(message) {
|
|
258099
|
+
super(message);
|
|
258100
|
+
this.name = "GenerationGaveUpError";
|
|
258101
|
+
}
|
|
258102
|
+
};
|
|
258016
258103
|
async function callProvider(fn, modelId) {
|
|
258017
258104
|
try {
|
|
258018
258105
|
return await fn();
|
|
258019
258106
|
} catch (err) {
|
|
258107
|
+
if (err instanceof GenerationGaveUpError) throw err;
|
|
258020
258108
|
const classified = classifyProviderError(err);
|
|
258021
258109
|
throw new Error(describeProviderError(classified, modelId));
|
|
258022
258110
|
}
|
|
@@ -258214,8 +258302,8 @@ async function generateVideo(cap, cfg, req, signal, onProgress) {
|
|
|
258214
258302
|
onProgress?.("Still rendering\u2026");
|
|
258215
258303
|
}
|
|
258216
258304
|
if (!operation.done) {
|
|
258217
|
-
throw new
|
|
258218
|
-
|
|
258305
|
+
throw new GenerationGaveUpError(
|
|
258306
|
+
`${cap.modelId} timed out after ${Math.round(VIDEO_TIMEOUT_MS / 6e4)} minutes \u2014 no video was produced. Cascade stopped waiting; the render may still finish on the provider side, but nothing was saved. Try again with a shorter clip or a simpler prompt.`
|
|
258219
258307
|
);
|
|
258220
258308
|
}
|
|
258221
258309
|
if (operation.error?.message) throw new Error(operation.error.message);
|