cascade-ai 0.56.0 → 0.64.0
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 +415 -153
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +417 -155
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +417 -149
- package/dist/index.cjs +415 -153
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +417 -155
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/desktop-core.cjs
CHANGED
|
@@ -191819,7 +191819,7 @@ var require_view = __commonJS({
|
|
|
191819
191819
|
var debug3 = require_src9()("express:view");
|
|
191820
191820
|
var path28 = __require("path");
|
|
191821
191821
|
var fs31 = __require("fs");
|
|
191822
|
-
var
|
|
191822
|
+
var dirname3 = path28.dirname;
|
|
191823
191823
|
var basename4 = path28.basename;
|
|
191824
191824
|
var extname = path28.extname;
|
|
191825
191825
|
var join = path28.join;
|
|
@@ -191858,7 +191858,7 @@ var require_view = __commonJS({
|
|
|
191858
191858
|
for (var i4 = 0; i4 < roots.length && !path29; i4++) {
|
|
191859
191859
|
var root = roots[i4];
|
|
191860
191860
|
var loc = resolve2(root, name);
|
|
191861
|
-
var dir =
|
|
191861
|
+
var dir = dirname3(loc);
|
|
191862
191862
|
var file = basename4(loc);
|
|
191863
191863
|
path29 = this.resolve(dir, file);
|
|
191864
191864
|
}
|
|
@@ -223993,6 +223993,23 @@ var pricing_data_default = {
|
|
|
223993
223993
|
],
|
|
223994
223994
|
notes: "Google bills image output per output-token ($120 per 1M image output tokens); $0.134/image is that rate applied to one default-resolution image. Cost per image therefore varies with resolution."
|
|
223995
223995
|
},
|
|
223996
|
+
{
|
|
223997
|
+
model: "gemini-2.5-flash-image",
|
|
223998
|
+
provider: "gemini",
|
|
223999
|
+
modality: "image",
|
|
224000
|
+
currency: "USD",
|
|
224001
|
+
asOf: "2026-07-27",
|
|
224002
|
+
source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-flash-image",
|
|
224003
|
+
provenance: "litellm-catalog",
|
|
224004
|
+
rates: [
|
|
224005
|
+
{
|
|
224006
|
+
unit: "per_image",
|
|
224007
|
+
amount: 0.039,
|
|
224008
|
+
variant: "default 1024px output"
|
|
224009
|
+
}
|
|
224010
|
+
],
|
|
224011
|
+
notes: "Google bills image output per output-token ($30 per 1M image output tokens); a default 1024px image is 1290 output tokens, i.e. $0.039. Cost per image therefore varies with resolution."
|
|
224012
|
+
},
|
|
223996
224013
|
{
|
|
223997
224014
|
model: "imagen-4.0-generate-001",
|
|
223998
224015
|
provider: "vertex",
|
|
@@ -224009,7 +224026,7 @@ var pricing_data_default = {
|
|
|
224009
224026
|
]
|
|
224010
224027
|
},
|
|
224011
224028
|
{
|
|
224012
|
-
model: "veo-3.1-generate-
|
|
224029
|
+
model: "veo-3.1-generate-preview",
|
|
224013
224030
|
provider: "gemini",
|
|
224014
224031
|
modality: "video",
|
|
224015
224032
|
currency: "USD",
|
|
@@ -224247,7 +224264,7 @@ function reconcilePrice(model, live) {
|
|
|
224247
224264
|
}
|
|
224248
224265
|
|
|
224249
224266
|
// src/constants.ts
|
|
224250
|
-
var CASCADE_VERSION = "0.
|
|
224267
|
+
var CASCADE_VERSION = "0.64.0";
|
|
224251
224268
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
224252
224269
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
224253
224270
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -252383,8 +252400,6 @@ var LocalRequestQueue = class {
|
|
|
252383
252400
|
};
|
|
252384
252401
|
}
|
|
252385
252402
|
};
|
|
252386
|
-
|
|
252387
|
-
// src/core/router/dead-models.ts
|
|
252388
252403
|
var DEAD_MODEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
252389
252404
|
function deadModelKey(provider, modelId) {
|
|
252390
252405
|
return `${provider}:${modelId}`;
|
|
@@ -252485,16 +252500,13 @@ var DeadModelStore = class {
|
|
|
252485
252500
|
function fileDeadModelPersistence(filePath) {
|
|
252486
252501
|
return {
|
|
252487
252502
|
load() {
|
|
252488
|
-
|
|
252489
|
-
|
|
252490
|
-
const raw = JSON.parse(fs31.readFileSync(filePath, "utf-8"));
|
|
252503
|
+
if (!fs11.existsSync(filePath)) return [];
|
|
252504
|
+
const raw = JSON.parse(fs11.readFileSync(filePath, "utf-8"));
|
|
252491
252505
|
return Array.isArray(raw) ? raw : [];
|
|
252492
252506
|
},
|
|
252493
252507
|
save(records) {
|
|
252494
|
-
|
|
252495
|
-
|
|
252496
|
-
fs31.mkdirSync(path28.dirname(filePath), { recursive: true });
|
|
252497
|
-
fs31.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
252508
|
+
fs11.mkdirSync(path26.dirname(filePath), { recursive: true });
|
|
252509
|
+
fs11.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
252498
252510
|
}
|
|
252499
252511
|
};
|
|
252500
252512
|
}
|
|
@@ -252928,6 +252940,12 @@ var FAMILY_BENCHMARKS = {
|
|
|
252928
252940
|
// Google — strongest at analysis/data and long-context.
|
|
252929
252941
|
"gemini-2.5-pro": { code: 90, analysis: 93, creative: 86, data: 92 },
|
|
252930
252942
|
"gemini-2.5-flash": { code: 82, analysis: 83, creative: 80, data: 82 },
|
|
252943
|
+
// The 2.5-lite tier scores below full 2.5-flash but above the older 2.0
|
|
252944
|
+
// lite tier — it exists because a naive `gemini-?2\.5-flash` regex swallows
|
|
252945
|
+
// "gemini-2.5-flash-lite" as a substring match (no lite variant of its own
|
|
252946
|
+
// to fall into), so the weaker lite model got credited with the full
|
|
252947
|
+
// flash's benchmark score and kept winning Cascade Auto's "best value" pick.
|
|
252948
|
+
"gemini-2.5-flash-lite": { code: 72, analysis: 72, creative: 74, data: 72 },
|
|
252931
252949
|
"gemini-1.5-pro": { code: 82, analysis: 84, creative: 82, data: 85 },
|
|
252932
252950
|
"gemini-2.0-flash": { code: 79, analysis: 80, creative: 79, data: 80 },
|
|
252933
252951
|
"gemini-flash-lite": { code: 68, analysis: 68, creative: 70, data: 68 },
|
|
@@ -252960,6 +252978,11 @@ var FAMILY_MATCHERS = [
|
|
|
252960
252978
|
[/gpt-?4o-mini/i, "gpt-4o-mini"],
|
|
252961
252979
|
[/gpt-?4o/i, "gpt-4o"],
|
|
252962
252980
|
[/gemini-?2\.5-pro/i, "gemini-2.5-pro"],
|
|
252981
|
+
// Must precede the bare `2\.5-flash` rule below: that pattern has no word
|
|
252982
|
+
// boundary after "flash" and matches "gemini-2.5-flash-lite" as a plain
|
|
252983
|
+
// substring, so the lite variant would otherwise be scored as the full
|
|
252984
|
+
// flash model (see the FAMILY_BENCHMARKS comment above).
|
|
252985
|
+
[/gemini-?2\.5-flash-lite/i, "gemini-2.5-flash-lite"],
|
|
252963
252986
|
[/gemini-?2\.5-flash/i, "gemini-2.5-flash"],
|
|
252964
252987
|
[/gemini-?1\.5-pro/i, "gemini-1.5-pro"],
|
|
252965
252988
|
[/gemini-?2\.0-flash-lite/i, "gemini-flash-lite"],
|
|
@@ -254466,6 +254489,92 @@ function truncateForContext(text, maxChars = 12e3) {
|
|
|
254466
254489
|
${tail}`;
|
|
254467
254490
|
}
|
|
254468
254491
|
|
|
254492
|
+
// src/core/router/provider-errors.ts
|
|
254493
|
+
var SYSTEMIC = /* @__PURE__ */ new Set([
|
|
254494
|
+
"rate_limit",
|
|
254495
|
+
"quota_exhausted",
|
|
254496
|
+
"auth",
|
|
254497
|
+
"model_unavailable"
|
|
254498
|
+
]);
|
|
254499
|
+
function isSystemicKind(kind2) {
|
|
254500
|
+
return SYSTEMIC.has(kind2);
|
|
254501
|
+
}
|
|
254502
|
+
function statusOf(err) {
|
|
254503
|
+
if (!err || typeof err !== "object") return void 0;
|
|
254504
|
+
const e3 = err;
|
|
254505
|
+
for (const key of ["status", "statusCode", "code"]) {
|
|
254506
|
+
const v6 = e3[key];
|
|
254507
|
+
if (typeof v6 === "number" && v6 >= 100 && v6 < 600) return v6;
|
|
254508
|
+
}
|
|
254509
|
+
const resp = e3["response"];
|
|
254510
|
+
if (resp && typeof resp["status"] === "number") return resp["status"];
|
|
254511
|
+
const inner = e3["error"];
|
|
254512
|
+
if (inner && typeof inner["code"] === "number") return inner["code"];
|
|
254513
|
+
return void 0;
|
|
254514
|
+
}
|
|
254515
|
+
function messageOf(err) {
|
|
254516
|
+
if (err instanceof Error) return err.message;
|
|
254517
|
+
if (typeof err === "string") return err;
|
|
254518
|
+
if (err && typeof err === "object") {
|
|
254519
|
+
const e3 = err;
|
|
254520
|
+
if (typeof e3["message"] === "string") return e3["message"];
|
|
254521
|
+
const inner = e3["error"];
|
|
254522
|
+
if (inner && typeof inner["message"] === "string") return inner["message"];
|
|
254523
|
+
try {
|
|
254524
|
+
return JSON.stringify(err);
|
|
254525
|
+
} catch {
|
|
254526
|
+
}
|
|
254527
|
+
}
|
|
254528
|
+
return String(err);
|
|
254529
|
+
}
|
|
254530
|
+
function classifyProviderError(err) {
|
|
254531
|
+
const raw = messageOf(err).trim();
|
|
254532
|
+
const status = statusOf(err);
|
|
254533
|
+
const m3 = raw.toLowerCase();
|
|
254534
|
+
const kind2 = (() => {
|
|
254535
|
+
if (status === 429) {
|
|
254536
|
+
return /quota|billing|credit|exceeded your current quota|insufficient/.test(m3) ? "quota_exhausted" : "rate_limit";
|
|
254537
|
+
}
|
|
254538
|
+
if (status === 401 || status === 403) return "auth";
|
|
254539
|
+
if (status === 404) return "model_unavailable";
|
|
254540
|
+
if (/rate limit|too many requests|resource_exhausted/.test(m3)) {
|
|
254541
|
+
return /quota|billing|credit|insufficient/.test(m3) ? "quota_exhausted" : "rate_limit";
|
|
254542
|
+
}
|
|
254543
|
+
if (/quota|billing|insufficient_quota|insufficient credit|payment required/.test(m3)) return "quota_exhausted";
|
|
254544
|
+
if (/api key|unauthorized|authentication|permission denied|invalid[_ ]api/.test(m3)) return "auth";
|
|
254545
|
+
if (/model .*(not found|does not exist|unavailable)|no such model|unknown model|not supported for/.test(m3)) {
|
|
254546
|
+
return "model_unavailable";
|
|
254547
|
+
}
|
|
254548
|
+
if (/context length|too many tokens|maximum context|prompt is too long|input too long/.test(m3)) return "context_length";
|
|
254549
|
+
if (/safety|content filter|blocked|recitation|prohibited/.test(m3)) return "content_filter";
|
|
254550
|
+
if (/econnreset|etimedout|enotfound|socket hang up|network|fetch failed|aborted/.test(m3)) return "network";
|
|
254551
|
+
return "unknown";
|
|
254552
|
+
})();
|
|
254553
|
+
return { kind: kind2, systemic: isSystemicKind(kind2), status, raw };
|
|
254554
|
+
}
|
|
254555
|
+
function describeProviderError(c4, modelId) {
|
|
254556
|
+
const on = modelId ? ` on ${modelId}` : "";
|
|
254557
|
+
const detail = c4.raw ? ` Provider said: ${c4.raw}` : "";
|
|
254558
|
+
switch (c4.kind) {
|
|
254559
|
+
case "rate_limit":
|
|
254560
|
+
return `Rate limit hit${on}. The provider is throttling requests \u2014 lower the number of parallel workers, or use a model with a higher requests-per-minute allowance.${detail}`;
|
|
254561
|
+
case "quota_exhausted":
|
|
254562
|
+
return `Quota or billing limit reached${on}. This will not recover on its own \u2014 check the provider's billing page, or switch this tier to a different provider.${detail}`;
|
|
254563
|
+
case "auth":
|
|
254564
|
+
return `Authentication failed${on}. The API key is missing, expired, or lacks access to this model \u2014 re-check the key in Settings > Providers.${detail}`;
|
|
254565
|
+
case "model_unavailable":
|
|
254566
|
+
return `Model unavailable${on}. The id may be wrong, retired, or not enabled for this API key \u2014 pick a different model for this tier.${detail}`;
|
|
254567
|
+
case "context_length":
|
|
254568
|
+
return `The prompt was too long${on}. Reduce the attached context, or use a model with a larger context window for this tier.${detail}`;
|
|
254569
|
+
case "content_filter":
|
|
254570
|
+
return `The provider's safety filter refused this request${on}.${detail}`;
|
|
254571
|
+
case "network":
|
|
254572
|
+
return `Network error reaching the provider${on}. This is usually transient.${detail}`;
|
|
254573
|
+
default:
|
|
254574
|
+
return `The model call failed${on}.${detail}`;
|
|
254575
|
+
}
|
|
254576
|
+
}
|
|
254577
|
+
|
|
254469
254578
|
// src/core/tiers/t3-worker.ts
|
|
254470
254579
|
var CriticalToolError = class extends Error {
|
|
254471
254580
|
constructor(message, toolName) {
|
|
@@ -254499,8 +254608,15 @@ var KNOWN_TOOLS = [
|
|
|
254499
254608
|
"web_search",
|
|
254500
254609
|
"glob",
|
|
254501
254610
|
"grep",
|
|
254502
|
-
"web_fetch"
|
|
254611
|
+
"web_fetch",
|
|
254612
|
+
"generate_image"
|
|
254503
254613
|
];
|
|
254614
|
+
var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
|
|
254615
|
+
"generate_image",
|
|
254616
|
+
"generate_speech",
|
|
254617
|
+
"generate_video",
|
|
254618
|
+
"transcribe_audio"
|
|
254619
|
+
]);
|
|
254504
254620
|
function buildWorkerRules(has2) {
|
|
254505
254621
|
const canWriteFiles = has2("file_write") || has2("file_edit") || has2("run_code");
|
|
254506
254622
|
const hasAnyTool = KNOWN_TOOLS.some(has2);
|
|
@@ -254510,9 +254626,22 @@ function buildWorkerRules(has2) {
|
|
|
254510
254626
|
canWriteFiles && "- If the task asks for a file or artifact, you must actually create it in the workspace, verify that it exists, and inspect it before claiming success.",
|
|
254511
254627
|
has2("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
|
|
254512
254628
|
has2("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
|
|
254629
|
+
// Without this the model "writes the image" as prose — a bracketed
|
|
254630
|
+
// description sitting in the deck where the picture should be — even with a
|
|
254631
|
+
// working image model registered. Naming the reference syntax matters as
|
|
254632
|
+
// much as naming the tool: a generated image nobody embeds is still a
|
|
254633
|
+
// missing image.
|
|
254634
|
+
//
|
|
254635
|
+
// Scoped to PowerPoint/Word specifically: those are the only exporters
|
|
254636
|
+
// that actually embed a Markdown image reference (cloud/web/exporters.ts
|
|
254637
|
+
// toPptx/toDocx). PDF and plain-text/Markdown deliverables flatten a
|
|
254638
|
+
// reference straight to caption text — telling the model to call
|
|
254639
|
+
// generate_image for THOSE just pays for an image nobody ever sees.
|
|
254640
|
+
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.',
|
|
254513
254641
|
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.',
|
|
254514
254642
|
"- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
|
|
254515
254643
|
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.',
|
|
254644
|
+
hasAnyTool && "- Only use tools directly relevant to THIS subtask. Do not reach for an unrelated connected-service action (e.g. creating, deleting, or modifying a repository, issue, or PR; sending a message) unless the subtask explicitly calls for it.",
|
|
254516
254645
|
"- Return structured output that directly addresses the expected output specification."
|
|
254517
254646
|
];
|
|
254518
254647
|
return `You are a T3 Worker agent in the Cascade AI system. Your job is to execute a specific subtask completely and accurately.
|
|
@@ -255046,7 +255175,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
255046
255175
|
const durationMs = Date.now() - toolStartMs;
|
|
255047
255176
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
255048
255177
|
this.emit("tool:result", { id: tc.id, tierId: this.id, toolName: tc.name, error: errMsg, durationMs });
|
|
255049
|
-
|
|
255178
|
+
const isTaggedSystemic = err instanceof Error && err.systemic === true;
|
|
255179
|
+
const isProviderBacked = PROVIDER_BACKED_TOOLS.has(tc.name);
|
|
255180
|
+
if (isTaggedSystemic || isProviderBacked && classifyProviderError(err).systemic) {
|
|
255050
255181
|
throw new CriticalToolError(errMsg, tc.name);
|
|
255051
255182
|
}
|
|
255052
255183
|
return await this.adaptiveFallback(tc, `Tool error: ${errMsg}`);
|
|
@@ -255334,8 +255465,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
255334
255465
|
} catch {
|
|
255335
255466
|
return {
|
|
255336
255467
|
checksRun: ["completeness", "correctness", "compliance"],
|
|
255337
|
-
passed: [
|
|
255338
|
-
failed: []
|
|
255468
|
+
passed: [],
|
|
255469
|
+
failed: ["completeness"]
|
|
255339
255470
|
};
|
|
255340
255471
|
}
|
|
255341
255472
|
}
|
|
@@ -257404,17 +257535,17 @@ var CAPABILITIES = [
|
|
|
257404
257535
|
api: "openai-images"
|
|
257405
257536
|
},
|
|
257406
257537
|
{
|
|
257407
|
-
//
|
|
257408
|
-
//
|
|
257409
|
-
//
|
|
257410
|
-
// generateContent
|
|
257411
|
-
//
|
|
257412
|
-
//
|
|
257413
|
-
//
|
|
257538
|
+
// Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
|
|
257539
|
+
// ("no longer available to new users") — Imagen's :predict API is deprecated
|
|
257540
|
+
// and shuts down 2026-08-17. Google's own migration path is this model on
|
|
257541
|
+
// the ordinary generateContent endpoint, so the fix was never another Imagen
|
|
257542
|
+
// id: the whole request/response SHAPE changes, which is why `api` moved to
|
|
257543
|
+
// 'gemini-generate-content' rather than just the modelId moving.
|
|
257544
|
+
// https://ai.google.dev/gemini-api/docs/imagen
|
|
257414
257545
|
modality: "image",
|
|
257415
257546
|
provider: "gemini",
|
|
257416
|
-
modelId: "
|
|
257417
|
-
api: "gemini-
|
|
257547
|
+
modelId: "gemini-2.5-flash-image",
|
|
257548
|
+
api: "gemini-generate-content"
|
|
257418
257549
|
},
|
|
257419
257550
|
// ── Speech (text → audio) ──
|
|
257420
257551
|
{
|
|
@@ -257446,9 +257577,17 @@ var CAPABILITIES = [
|
|
|
257446
257577
|
// Long-running: submit returns an operation name, the video lands minutes
|
|
257447
257578
|
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
257448
257579
|
// that polling loop; the registry only records the shape via `api`.
|
|
257580
|
+
//
|
|
257581
|
+
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
257582
|
+
// not the Gemini Developer API's — this file calls
|
|
257583
|
+
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
257584
|
+
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
257585
|
+
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
257586
|
+
// there is `veo-3.1-generate-preview`.
|
|
257587
|
+
// https://ai.google.dev/gemini-api/docs/veo
|
|
257449
257588
|
modality: "video",
|
|
257450
257589
|
provider: "gemini",
|
|
257451
|
-
modelId: "veo-3.1-generate-
|
|
257590
|
+
modelId: "veo-3.1-generate-preview",
|
|
257452
257591
|
api: "gemini-predict-lro"
|
|
257453
257592
|
}
|
|
257454
257593
|
];
|
|
@@ -257469,6 +257608,14 @@ function capabilityCost(cap) {
|
|
|
257469
257608
|
const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
|
|
257470
257609
|
return { label: `$${shown} ${unit}`, amount, unit };
|
|
257471
257610
|
}
|
|
257611
|
+
function explainChoice(c4, cost, modality, isBest) {
|
|
257612
|
+
const priced = cost ? ` ${cost.label}.` : "";
|
|
257613
|
+
if (c4.quality !== void 0) {
|
|
257614
|
+
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}`;
|
|
257615
|
+
}
|
|
257616
|
+
const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
|
|
257617
|
+
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;
|
|
257618
|
+
}
|
|
257472
257619
|
var MultimodalRegistry = class {
|
|
257473
257620
|
/** Provider types the user has actually configured a key for. */
|
|
257474
257621
|
available;
|
|
@@ -257486,22 +257633,20 @@ var MultimodalRegistry = class {
|
|
|
257486
257633
|
return [...new Set(this.usable().map((c4) => c4.modality))];
|
|
257487
257634
|
}
|
|
257488
257635
|
/**
|
|
257489
|
-
*
|
|
257636
|
+
* Every usable capability for a modality, best first.
|
|
257490
257637
|
*
|
|
257491
257638
|
* Ranked models beat unranked ones, then higher quality, then lower unit
|
|
257492
|
-
* price. An explicit `preferModel`
|
|
257493
|
-
* naming a model is a stronger signal than any prior.
|
|
257639
|
+
* price. An explicit `preferModel` is moved to the head when it is usable —
|
|
257640
|
+
* the user naming a model is a stronger signal than any prior.
|
|
257641
|
+
*
|
|
257642
|
+
* `select()` is just the head of this list. The TAIL is the point: a systemic
|
|
257643
|
+
* provider failure (dead key, 404, exhausted quota) is scoped to one
|
|
257644
|
+
* provider+model, so "this model cannot draw" is not the same fact as "this
|
|
257645
|
+
* account cannot draw". A caller holding the ordered alternatives can try the
|
|
257646
|
+
* next provider instead of aborting the whole run — see generate-media.ts.
|
|
257494
257647
|
*/
|
|
257495
|
-
|
|
257496
|
-
const
|
|
257497
|
-
if (!pool.length) return null;
|
|
257498
|
-
if (opts.preferModel) {
|
|
257499
|
-
const wanted = pool.find((c4) => c4.modelId === opts.preferModel);
|
|
257500
|
-
if (wanted) {
|
|
257501
|
-
return { capability: wanted, cost: capabilityCost(wanted), reason: `Requested explicitly: ${wanted.modelId}.` };
|
|
257502
|
-
}
|
|
257503
|
-
}
|
|
257504
|
-
const scored = pool.map((c4) => ({ c: c4, cost: capabilityCost(c4) })).sort((a2, b3) => {
|
|
257648
|
+
rank(modality, opts = {}) {
|
|
257649
|
+
const scored = this.usable(modality).map((c4) => ({ c: c4, cost: capabilityCost(c4) })).sort((a2, b3) => {
|
|
257505
257650
|
const qa = a2.c.quality ?? -1;
|
|
257506
257651
|
const qb = b3.c.quality ?? -1;
|
|
257507
257652
|
if (qa !== qb) return qb - qa;
|
|
@@ -257509,10 +257654,25 @@ var MultimodalRegistry = class {
|
|
|
257509
257654
|
const pb = b3.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
257510
257655
|
return pa - pb;
|
|
257511
257656
|
});
|
|
257512
|
-
const
|
|
257513
|
-
|
|
257514
|
-
|
|
257515
|
-
|
|
257657
|
+
const results = scored.map(({ c: c4, cost }, i4) => ({
|
|
257658
|
+
capability: c4,
|
|
257659
|
+
cost,
|
|
257660
|
+
reason: explainChoice(c4, cost, modality, i4 === 0)
|
|
257661
|
+
}));
|
|
257662
|
+
if (opts.preferModel) {
|
|
257663
|
+
const i4 = results.findIndex((r4) => r4.capability.modelId === opts.preferModel);
|
|
257664
|
+
if (i4 > -1) {
|
|
257665
|
+
const [wanted] = results.splice(i4, 1);
|
|
257666
|
+
results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
|
|
257667
|
+
}
|
|
257668
|
+
}
|
|
257669
|
+
return results;
|
|
257670
|
+
}
|
|
257671
|
+
/**
|
|
257672
|
+
* Pick a model for a modality — the best usable one, or null if there is none.
|
|
257673
|
+
*/
|
|
257674
|
+
select(modality, opts = {}) {
|
|
257675
|
+
return this.rank(modality, opts)[0] ?? null;
|
|
257516
257676
|
}
|
|
257517
257677
|
/**
|
|
257518
257678
|
* A plain-language account of what this account can and cannot generate —
|
|
@@ -257565,92 +257725,6 @@ var BaseTool = class {
|
|
|
257565
257725
|
}
|
|
257566
257726
|
};
|
|
257567
257727
|
|
|
257568
|
-
// src/core/router/provider-errors.ts
|
|
257569
|
-
var SYSTEMIC = /* @__PURE__ */ new Set([
|
|
257570
|
-
"rate_limit",
|
|
257571
|
-
"quota_exhausted",
|
|
257572
|
-
"auth",
|
|
257573
|
-
"model_unavailable"
|
|
257574
|
-
]);
|
|
257575
|
-
function isSystemicKind(kind2) {
|
|
257576
|
-
return SYSTEMIC.has(kind2);
|
|
257577
|
-
}
|
|
257578
|
-
function statusOf(err) {
|
|
257579
|
-
if (!err || typeof err !== "object") return void 0;
|
|
257580
|
-
const e3 = err;
|
|
257581
|
-
for (const key of ["status", "statusCode", "code"]) {
|
|
257582
|
-
const v6 = e3[key];
|
|
257583
|
-
if (typeof v6 === "number" && v6 >= 100 && v6 < 600) return v6;
|
|
257584
|
-
}
|
|
257585
|
-
const resp = e3["response"];
|
|
257586
|
-
if (resp && typeof resp["status"] === "number") return resp["status"];
|
|
257587
|
-
const inner = e3["error"];
|
|
257588
|
-
if (inner && typeof inner["code"] === "number") return inner["code"];
|
|
257589
|
-
return void 0;
|
|
257590
|
-
}
|
|
257591
|
-
function messageOf(err) {
|
|
257592
|
-
if (err instanceof Error) return err.message;
|
|
257593
|
-
if (typeof err === "string") return err;
|
|
257594
|
-
if (err && typeof err === "object") {
|
|
257595
|
-
const e3 = err;
|
|
257596
|
-
if (typeof e3["message"] === "string") return e3["message"];
|
|
257597
|
-
const inner = e3["error"];
|
|
257598
|
-
if (inner && typeof inner["message"] === "string") return inner["message"];
|
|
257599
|
-
try {
|
|
257600
|
-
return JSON.stringify(err);
|
|
257601
|
-
} catch {
|
|
257602
|
-
}
|
|
257603
|
-
}
|
|
257604
|
-
return String(err);
|
|
257605
|
-
}
|
|
257606
|
-
function classifyProviderError(err) {
|
|
257607
|
-
const raw = messageOf(err).trim();
|
|
257608
|
-
const status = statusOf(err);
|
|
257609
|
-
const m3 = raw.toLowerCase();
|
|
257610
|
-
const kind2 = (() => {
|
|
257611
|
-
if (status === 429) {
|
|
257612
|
-
return /quota|billing|credit|exceeded your current quota|insufficient/.test(m3) ? "quota_exhausted" : "rate_limit";
|
|
257613
|
-
}
|
|
257614
|
-
if (status === 401 || status === 403) return "auth";
|
|
257615
|
-
if (status === 404) return "model_unavailable";
|
|
257616
|
-
if (/rate limit|too many requests|resource_exhausted/.test(m3)) {
|
|
257617
|
-
return /quota|billing|credit|insufficient/.test(m3) ? "quota_exhausted" : "rate_limit";
|
|
257618
|
-
}
|
|
257619
|
-
if (/quota|billing|insufficient_quota|insufficient credit|payment required/.test(m3)) return "quota_exhausted";
|
|
257620
|
-
if (/api key|unauthorized|authentication|permission denied|invalid[_ ]api/.test(m3)) return "auth";
|
|
257621
|
-
if (/model .*(not found|does not exist|unavailable)|no such model|unknown model|not supported for/.test(m3)) {
|
|
257622
|
-
return "model_unavailable";
|
|
257623
|
-
}
|
|
257624
|
-
if (/context length|too many tokens|maximum context|prompt is too long|input too long/.test(m3)) return "context_length";
|
|
257625
|
-
if (/safety|content filter|blocked|recitation|prohibited/.test(m3)) return "content_filter";
|
|
257626
|
-
if (/econnreset|etimedout|enotfound|socket hang up|network|fetch failed|aborted/.test(m3)) return "network";
|
|
257627
|
-
return "unknown";
|
|
257628
|
-
})();
|
|
257629
|
-
return { kind: kind2, systemic: isSystemicKind(kind2), status, raw };
|
|
257630
|
-
}
|
|
257631
|
-
function describeProviderError(c4, modelId) {
|
|
257632
|
-
const on = modelId ? ` on ${modelId}` : "";
|
|
257633
|
-
const detail = c4.raw ? ` Provider said: ${c4.raw}` : "";
|
|
257634
|
-
switch (c4.kind) {
|
|
257635
|
-
case "rate_limit":
|
|
257636
|
-
return `Rate limit hit${on}. The provider is throttling requests \u2014 lower the number of parallel workers, or use a model with a higher requests-per-minute allowance.${detail}`;
|
|
257637
|
-
case "quota_exhausted":
|
|
257638
|
-
return `Quota or billing limit reached${on}. This will not recover on its own \u2014 check the provider's billing page, or switch this tier to a different provider.${detail}`;
|
|
257639
|
-
case "auth":
|
|
257640
|
-
return `Authentication failed${on}. The API key is missing, expired, or lacks access to this model \u2014 re-check the key in Settings > Providers.${detail}`;
|
|
257641
|
-
case "model_unavailable":
|
|
257642
|
-
return `Model unavailable${on}. The id may be wrong, retired, or not enabled for this API key \u2014 pick a different model for this tier.${detail}`;
|
|
257643
|
-
case "context_length":
|
|
257644
|
-
return `The prompt was too long${on}. Reduce the attached context, or use a model with a larger context window for this tier.${detail}`;
|
|
257645
|
-
case "content_filter":
|
|
257646
|
-
return `The provider's safety filter refused this request${on}.${detail}`;
|
|
257647
|
-
case "network":
|
|
257648
|
-
return `Network error reaching the provider${on}. This is usually transient.${detail}`;
|
|
257649
|
-
default:
|
|
257650
|
-
return `The model call failed${on}.${detail}`;
|
|
257651
|
-
}
|
|
257652
|
-
}
|
|
257653
|
-
|
|
257654
257728
|
// src/core/multimodal/generate.ts
|
|
257655
257729
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
257656
257730
|
async function callProvider(fn, modelId) {
|
|
@@ -257689,6 +257763,29 @@ function baseUrlFor(cfg, fallback) {
|
|
|
257689
257763
|
function stamp(ext) {
|
|
257690
257764
|
return `cascade-${Date.now()}.${ext}`;
|
|
257691
257765
|
}
|
|
257766
|
+
var GEMINI_ASPECT_RATIOS = [
|
|
257767
|
+
["1:1", 1 / 1],
|
|
257768
|
+
["3:2", 3 / 2],
|
|
257769
|
+
["2:3", 2 / 3],
|
|
257770
|
+
["3:4", 3 / 4],
|
|
257771
|
+
["4:3", 4 / 3],
|
|
257772
|
+
["4:5", 4 / 5],
|
|
257773
|
+
["5:4", 5 / 4],
|
|
257774
|
+
["9:16", 9 / 16],
|
|
257775
|
+
["16:9", 16 / 9],
|
|
257776
|
+
["21:9", 21 / 9]
|
|
257777
|
+
];
|
|
257778
|
+
function closestGeminiAspectRatio(size) {
|
|
257779
|
+
const m3 = size.match(/^(\d+)\s*[x×]\s*(\d+)$/i);
|
|
257780
|
+
if (!m3) return void 0;
|
|
257781
|
+
const w2 = Number(m3[1]);
|
|
257782
|
+
const h4 = Number(m3[2]);
|
|
257783
|
+
if (!w2 || !h4) return void 0;
|
|
257784
|
+
const ratio = w2 / h4;
|
|
257785
|
+
return GEMINI_ASPECT_RATIOS.reduce(
|
|
257786
|
+
(best, cand) => Math.abs(cand[1] - ratio) < Math.abs(best[1] - ratio) ? cand : best
|
|
257787
|
+
)[0];
|
|
257788
|
+
}
|
|
257692
257789
|
async function generateImage(cap, cfg, req, signal) {
|
|
257693
257790
|
return callProvider(async () => {
|
|
257694
257791
|
if (cap.api === "openai-images") {
|
|
@@ -257713,19 +257810,37 @@ async function generateImage(cap, cfg, req, signal) {
|
|
|
257713
257810
|
...first3.revised_prompt ? { revisedPrompt: first3.revised_prompt } : {}
|
|
257714
257811
|
};
|
|
257715
257812
|
}
|
|
257716
|
-
if (cap.api === "gemini-
|
|
257813
|
+
if (cap.api === "gemini-generate-content") {
|
|
257717
257814
|
const base = baseUrlFor(cfg, "https://generativelanguage.googleapis.com/v1beta");
|
|
257718
|
-
const url2 = `${base}/models/${encodeURIComponent(cap.modelId)}:
|
|
257815
|
+
const url2 = `${base}/models/${encodeURIComponent(cap.modelId)}:generateContent`;
|
|
257816
|
+
const aspectRatio = req.size ? closestGeminiAspectRatio(req.size) : void 0;
|
|
257719
257817
|
const res = await postJson(url2, {
|
|
257720
|
-
|
|
257721
|
-
|
|
257818
|
+
contents: [{ parts: [{ text: req.prompt }] }],
|
|
257819
|
+
generationConfig: {
|
|
257820
|
+
// Asked for explicitly: these models can answer with text only, and a
|
|
257821
|
+
// text-only reply to "draw me a cat" is a failed image generation.
|
|
257822
|
+
responseModalities: ["IMAGE", "TEXT"],
|
|
257823
|
+
// Gemini has no free-form pixel size — only a named aspect ratio (see
|
|
257824
|
+
// closestGeminiAspectRatio). Omitted entirely when there's no
|
|
257825
|
+
// requested size, or no supported ratio close enough to bother with.
|
|
257826
|
+
...aspectRatio ? { imageConfig: { aspectRatio } } : {}
|
|
257827
|
+
}
|
|
257722
257828
|
}, { "x-goog-api-key": cfg.apiKey ?? "" }, signal);
|
|
257723
257829
|
const body = await res.json();
|
|
257724
|
-
const
|
|
257725
|
-
|
|
257726
|
-
const
|
|
257830
|
+
const candidate = body.candidates?.[0];
|
|
257831
|
+
const parts = candidate?.content?.parts ?? [];
|
|
257832
|
+
const image = parts.find((p3) => p3.inlineData?.data)?.inlineData;
|
|
257833
|
+
if (!image?.data) {
|
|
257834
|
+
const blocked = body.promptFeedback?.blockReason ?? (candidate?.finishReason && candidate.finishReason !== "STOP" ? candidate.finishReason : void 0);
|
|
257835
|
+
if (blocked) throw new Error(`Image request was blocked by the provider (${blocked}).`);
|
|
257836
|
+
const said = parts.find((p3) => p3.text)?.text?.trim();
|
|
257837
|
+
throw new Error(
|
|
257838
|
+
"Image response contained no image data." + (said ? ` The model replied with text instead: ${said}` : "")
|
|
257839
|
+
);
|
|
257840
|
+
}
|
|
257841
|
+
const mimeType = image.mimeType ?? "image/png";
|
|
257727
257842
|
return {
|
|
257728
|
-
data: Buffer.from(
|
|
257843
|
+
data: Buffer.from(image.data, "base64"),
|
|
257729
257844
|
mimeType,
|
|
257730
257845
|
filename: stamp(mimeType.includes("jpeg") ? "jpg" : "png"),
|
|
257731
257846
|
modelId: cap.modelId,
|
|
@@ -257878,6 +257993,49 @@ function resolve(deps, modality, preferModel) {
|
|
|
257878
257993
|
}
|
|
257879
257994
|
return { ok: true, selected, cfg };
|
|
257880
257995
|
}
|
|
257996
|
+
function messageOf2(err) {
|
|
257997
|
+
return err instanceof Error ? err.message : String(err);
|
|
257998
|
+
}
|
|
257999
|
+
async function runWithProviderFallback(deps, modality, first3, run, signal) {
|
|
258000
|
+
const failed = first3.selected.capability;
|
|
258001
|
+
try {
|
|
258002
|
+
return { value: await run(failed, first3.cfg), selected: first3.selected };
|
|
258003
|
+
} catch (err) {
|
|
258004
|
+
if (signal?.aborted || !classifyProviderError(err).systemic) throw err;
|
|
258005
|
+
const tried = [`${failed.modelId} (${failed.provider}): ${messageOf2(err)}`];
|
|
258006
|
+
let allSystemic = true;
|
|
258007
|
+
let lastNonSystemic;
|
|
258008
|
+
const seen = /* @__PURE__ */ new Set([failed.provider]);
|
|
258009
|
+
for (const alt of deps.registry.rank(modality)) {
|
|
258010
|
+
if (seen.has(alt.capability.provider)) continue;
|
|
258011
|
+
const cfg = deps.lookupProvider(alt.capability.provider);
|
|
258012
|
+
if (!cfg) continue;
|
|
258013
|
+
seen.add(alt.capability.provider);
|
|
258014
|
+
try {
|
|
258015
|
+
return {
|
|
258016
|
+
value: await run(alt.capability, cfg),
|
|
258017
|
+
selected: alt,
|
|
258018
|
+
fellBackFrom: { capability: failed, error: messageOf2(err) }
|
|
258019
|
+
};
|
|
258020
|
+
} catch (retryErr) {
|
|
258021
|
+
if (signal?.aborted) throw retryErr;
|
|
258022
|
+
tried.push(`${alt.capability.modelId} (${alt.capability.provider}): ${messageOf2(retryErr)}`);
|
|
258023
|
+
if (classifyProviderError(retryErr).systemic) {
|
|
258024
|
+
continue;
|
|
258025
|
+
}
|
|
258026
|
+
allSystemic = false;
|
|
258027
|
+
lastNonSystemic = retryErr;
|
|
258028
|
+
}
|
|
258029
|
+
}
|
|
258030
|
+
if (tried.length === 1) throw err;
|
|
258031
|
+
if (!allSystemic) throw lastNonSystemic;
|
|
258032
|
+
const exhausted = new Error(
|
|
258033
|
+
`${modality} generation failed on every configured provider (${seen.size} tried). ${tried.join(" ")}`
|
|
258034
|
+
);
|
|
258035
|
+
Object.assign(exhausted, { systemic: true });
|
|
258036
|
+
throw exhausted;
|
|
258037
|
+
}
|
|
258038
|
+
}
|
|
257881
258039
|
var GenerateImageTool = class extends BaseTool {
|
|
257882
258040
|
constructor(deps) {
|
|
257883
258041
|
super();
|
|
@@ -257901,16 +258059,32 @@ var GenerateImageTool = class extends BaseTool {
|
|
|
257901
258059
|
const r4 = resolve(this.deps, "image", input["model"] ? String(input["model"]) : void 0);
|
|
257902
258060
|
if (!r4.ok) return r4.error;
|
|
257903
258061
|
const size = input["size"] ? String(input["size"]) : void 0;
|
|
257904
|
-
const
|
|
257905
|
-
|
|
257906
|
-
|
|
257907
|
-
|
|
258062
|
+
const attempt = await runWithProviderFallback(
|
|
258063
|
+
this.deps,
|
|
258064
|
+
"image",
|
|
258065
|
+
r4,
|
|
258066
|
+
(cap, cfg) => generateImage(cap, cfg, { prompt, ...size ? { size } : {} }, options.signal),
|
|
257908
258067
|
options.signal
|
|
257909
258068
|
);
|
|
258069
|
+
const asset = attempt.value;
|
|
258070
|
+
const fell = attempt.fellBackFrom;
|
|
257910
258071
|
const location = await this.deps.sink(asset);
|
|
257911
258072
|
return [
|
|
257912
258073
|
`Image generated and saved to ${location}.`,
|
|
257913
|
-
|
|
258074
|
+
// Knowing WHERE the image is isn't the same as knowing how to use it: a
|
|
258075
|
+
// model told only "saved to X" tends to describe the picture in prose
|
|
258076
|
+
// instead of embedding it. Spelling out the reference syntax here makes
|
|
258077
|
+
// the worker's "never write a placeholder" rule concretely actionable,
|
|
258078
|
+
// and it works for every host because `location` is whatever that host's
|
|
258079
|
+
// sink returned — a workspace path on desktop/CLI, a URL in the cloud.
|
|
258080
|
+
`To include this image in your output, reference it as: `,
|
|
258081
|
+
// The reported model has to be the one that actually drew the picture, not
|
|
258082
|
+
// the one that was picked and then failed.
|
|
258083
|
+
`Model: ${asset.modelId} \u2014 ${fell ? `used as a fallback after ${fell.capability.modelId} (${fell.capability.provider}) failed with a systemic error.` : attempt.selected.reason}`,
|
|
258084
|
+
// Worth one line: the run succeeded, so nothing else will report that a
|
|
258085
|
+
// configured provider is broken, and "your Gemini key is dead" is exactly
|
|
258086
|
+
// the kind of thing the user wants to hear once rather than never.
|
|
258087
|
+
fell ? `Note: ${fell.capability.provider} failed first \u2014 ${fell.error}` : "",
|
|
257914
258088
|
// DALL·E rewrites the prompt before drawing; the model needs to know what
|
|
257915
258089
|
// was actually rendered, not what it asked for, or its description of the
|
|
257916
258090
|
// result will not match the image.
|
|
@@ -258156,6 +258330,87 @@ function mcpToolName(serverName, toolName) {
|
|
|
258156
258330
|
function isMcpToolName(name) {
|
|
258157
258331
|
return name.startsWith(MCP_TOOL_PREFIX);
|
|
258158
258332
|
}
|
|
258333
|
+
var READ_ONLY_MCP_VERBS = ["list", "get", "search", "read", "describe", "find", "query", "fetch", "show", "view"];
|
|
258334
|
+
var MUTATING_MCP_TOKENS = /* @__PURE__ */ new Set([
|
|
258335
|
+
"create",
|
|
258336
|
+
"delete",
|
|
258337
|
+
"remove",
|
|
258338
|
+
"update",
|
|
258339
|
+
"write",
|
|
258340
|
+
"modify",
|
|
258341
|
+
"set",
|
|
258342
|
+
"put",
|
|
258343
|
+
"post",
|
|
258344
|
+
"patch",
|
|
258345
|
+
"push",
|
|
258346
|
+
"merge",
|
|
258347
|
+
"send",
|
|
258348
|
+
"execute",
|
|
258349
|
+
"exec",
|
|
258350
|
+
"run",
|
|
258351
|
+
"deploy",
|
|
258352
|
+
"install",
|
|
258353
|
+
"uninstall",
|
|
258354
|
+
"upload",
|
|
258355
|
+
"publish",
|
|
258356
|
+
"revoke",
|
|
258357
|
+
"grant",
|
|
258358
|
+
"approve",
|
|
258359
|
+
"reject",
|
|
258360
|
+
"cancel",
|
|
258361
|
+
"enable",
|
|
258362
|
+
"disable",
|
|
258363
|
+
"add",
|
|
258364
|
+
"insert",
|
|
258365
|
+
"drop",
|
|
258366
|
+
"truncate",
|
|
258367
|
+
"clear",
|
|
258368
|
+
"reset",
|
|
258369
|
+
"restart",
|
|
258370
|
+
"stop",
|
|
258371
|
+
"kill",
|
|
258372
|
+
"terminate",
|
|
258373
|
+
"rename",
|
|
258374
|
+
"move",
|
|
258375
|
+
"fork",
|
|
258376
|
+
"sync",
|
|
258377
|
+
"replace",
|
|
258378
|
+
"overwrite",
|
|
258379
|
+
"invite",
|
|
258380
|
+
"ban",
|
|
258381
|
+
"block",
|
|
258382
|
+
"assign",
|
|
258383
|
+
"lock",
|
|
258384
|
+
"unlock",
|
|
258385
|
+
"archive",
|
|
258386
|
+
"star",
|
|
258387
|
+
"comment",
|
|
258388
|
+
"reply",
|
|
258389
|
+
"submit",
|
|
258390
|
+
"apply",
|
|
258391
|
+
"trigger",
|
|
258392
|
+
"invoke",
|
|
258393
|
+
"purge",
|
|
258394
|
+
"migrate",
|
|
258395
|
+
"edit",
|
|
258396
|
+
"save",
|
|
258397
|
+
"commit"
|
|
258398
|
+
]);
|
|
258399
|
+
function mcpTokens(rawToolName) {
|
|
258400
|
+
return rawToolName.split(/[_-]+/).flatMap((segment) => segment.split(/(?<=[a-z0-9])(?=[A-Z])/)).map((t4) => t4.toLowerCase()).filter(Boolean);
|
|
258401
|
+
}
|
|
258402
|
+
function isReadOnlyMcpToolName(rawToolName) {
|
|
258403
|
+
if (mcpTokens(rawToolName).some((t4) => MUTATING_MCP_TOKENS.has(t4))) return false;
|
|
258404
|
+
const lower2 = rawToolName.toLowerCase();
|
|
258405
|
+
for (const verb of READ_ONLY_MCP_VERBS) {
|
|
258406
|
+
if (!lower2.startsWith(verb)) continue;
|
|
258407
|
+
const boundary = rawToolName[verb.length];
|
|
258408
|
+
if (boundary === void 0 || boundary === "_" || boundary === "-" || boundary >= "A" && boundary <= "Z") {
|
|
258409
|
+
return true;
|
|
258410
|
+
}
|
|
258411
|
+
}
|
|
258412
|
+
return false;
|
|
258413
|
+
}
|
|
258159
258414
|
function uniqueMcpServerName(desired, existingNames) {
|
|
258160
258415
|
const taken = new Set(existingNames.map(mcpServerPrefix));
|
|
258161
258416
|
if (!taken.has(mcpServerPrefix(desired))) return desired;
|
|
@@ -286135,9 +286390,11 @@ var WebSearchTool = class extends BaseTool {
|
|
|
286135
286390
|
);
|
|
286136
286391
|
const errors = [];
|
|
286137
286392
|
let results = [];
|
|
286393
|
+
let anyBackendReachable = false;
|
|
286138
286394
|
if (this.config.searxngUrl) {
|
|
286139
286395
|
try {
|
|
286140
286396
|
results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
|
|
286397
|
+
anyBackendReachable = true;
|
|
286141
286398
|
if (results.length > 0) return this.formatResults(query, results);
|
|
286142
286399
|
errors.push("SearXNG: returned 0 results");
|
|
286143
286400
|
} catch (err) {
|
|
@@ -286147,6 +286404,7 @@ var WebSearchTool = class extends BaseTool {
|
|
|
286147
286404
|
if (this.config.braveApiKey) {
|
|
286148
286405
|
try {
|
|
286149
286406
|
results = await searchBrave(query, this.config.braveApiKey, maxResults);
|
|
286407
|
+
anyBackendReachable = true;
|
|
286150
286408
|
if (results.length > 0) return this.formatResults(query, results);
|
|
286151
286409
|
errors.push("Brave: returned 0 results");
|
|
286152
286410
|
} catch (err) {
|
|
@@ -286156,6 +286414,7 @@ var WebSearchTool = class extends BaseTool {
|
|
|
286156
286414
|
if (this.config.tavilyApiKey) {
|
|
286157
286415
|
try {
|
|
286158
286416
|
results = await searchTavily(query, this.config.tavilyApiKey, maxResults);
|
|
286417
|
+
anyBackendReachable = true;
|
|
286159
286418
|
if (results.length > 0) return this.formatResults(query, results);
|
|
286160
286419
|
errors.push("Tavily: returned 0 results");
|
|
286161
286420
|
} catch (err) {
|
|
@@ -286165,6 +286424,7 @@ var WebSearchTool = class extends BaseTool {
|
|
|
286165
286424
|
for (const variant of ["html", "lite"]) {
|
|
286166
286425
|
try {
|
|
286167
286426
|
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
286427
|
+
anyBackendReachable = true;
|
|
286168
286428
|
if (results.length > 0) return this.formatResults(query, results);
|
|
286169
286429
|
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
286170
286430
|
} catch (err) {
|
|
@@ -286172,11 +286432,11 @@ var WebSearchTool = class extends BaseTool {
|
|
|
286172
286432
|
}
|
|
286173
286433
|
}
|
|
286174
286434
|
const configHint = !this.config.searxngUrl && !this.config.braveApiKey && !this.config.tavilyApiKey ? "\nTip: Configure a search backend for better results:\n \u2022 Self-hosted: set SEARXNG_URL in your environment\n \u2022 Brave Search API: set BRAVE_SEARCH_API_KEY\n \u2022 Tavily API: set TAVILY_API_KEY" : "";
|
|
286175
|
-
|
|
286176
|
-
`Web search for "${query}"
|
|
286435
|
+
throw Object.assign(new Error([
|
|
286436
|
+
`Web search for "${query}" found nothing across all backends:`,
|
|
286177
286437
|
...errors.map((e3) => ` \u2022 ${e3}`),
|
|
286178
286438
|
configHint
|
|
286179
|
-
].join("\n");
|
|
286439
|
+
].join("\n")), { systemic: !anyBackendReachable });
|
|
286180
286440
|
}
|
|
286181
286441
|
formatResults(query, results) {
|
|
286182
286442
|
const lines = [`Web search results for: "${query}"`, ""];
|
|
@@ -286544,6 +286804,15 @@ var McpToolWrapper = class extends BaseTool {
|
|
|
286544
286804
|
this.description = `[MCP:${serverName}] ${description}`;
|
|
286545
286805
|
this.inputSchema = inputSchema;
|
|
286546
286806
|
}
|
|
286807
|
+
// Every built-in tool of comparable risk (shell, git, file writes, the
|
|
286808
|
+
// built-in github tool) overrides this to true. An MCP tool never did —
|
|
286809
|
+
// BaseTool's default (false) meant a connected server's create/delete/push
|
|
286810
|
+
// actions ran with zero human approval, whatever they did. MCP carries no
|
|
286811
|
+
// standard "this mutates state" flag, so default to dangerous and name the
|
|
286812
|
+
// exception (a read-only-looking verb) rather than the other way around.
|
|
286813
|
+
isDangerous() {
|
|
286814
|
+
return !isReadOnlyMcpToolName(this.toolName);
|
|
286815
|
+
}
|
|
286547
286816
|
async execute(input, _options) {
|
|
286548
286817
|
return this.mcpClient.callTool(this.serverName, this.toolName, input);
|
|
286549
286818
|
}
|
|
@@ -286656,7 +286925,7 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
286656
286925
|
requiresApproval(toolName) {
|
|
286657
286926
|
const defaults2 = DEFAULT_APPROVAL_REQUIRED;
|
|
286658
286927
|
const configured = this.config.requireApprovalFor;
|
|
286659
|
-
return defaults2.includes(toolName) || configured.includes(toolName);
|
|
286928
|
+
return defaults2.includes(toolName) || configured.includes(toolName) || this.isDangerous(toolName);
|
|
286660
286929
|
}
|
|
286661
286930
|
isDangerous(toolName) {
|
|
286662
286931
|
return this.tools.get(toolName)?.isDangerous() ?? false;
|
|
@@ -303439,9 +303708,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
303439
303708
|
});
|
|
303440
303709
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
303441
303710
|
try {
|
|
303442
|
-
const nodePath = __require("path");
|
|
303443
303711
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
303444
|
-
fileDeadModelPersistence(
|
|
303712
|
+
fileDeadModelPersistence(path26__namespace.default.join(workspacePath, ".cascade", "dead-models.json"))
|
|
303445
303713
|
));
|
|
303446
303714
|
} catch {
|
|
303447
303715
|
}
|