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/cli.cjs
CHANGED
|
@@ -104,12 +104,6 @@ var cron__default = /*#__PURE__*/_interopDefault(cron);
|
|
|
104
104
|
// Cascade AI — Multi-tier AI Orchestration System
|
|
105
105
|
var __defProp = Object.defineProperty;
|
|
106
106
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
107
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
108
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
109
|
-
}) : x)(function(x) {
|
|
110
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
111
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
112
|
-
});
|
|
113
107
|
var __esm = (fn, res) => function __init() {
|
|
114
108
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
115
109
|
};
|
|
@@ -1529,6 +1523,23 @@ var init_pricing_data = __esm({
|
|
|
1529
1523
|
],
|
|
1530
1524
|
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."
|
|
1531
1525
|
},
|
|
1526
|
+
{
|
|
1527
|
+
model: "gemini-2.5-flash-image",
|
|
1528
|
+
provider: "gemini",
|
|
1529
|
+
modality: "image",
|
|
1530
|
+
currency: "USD",
|
|
1531
|
+
asOf: "2026-07-27",
|
|
1532
|
+
source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-flash-image",
|
|
1533
|
+
provenance: "litellm-catalog",
|
|
1534
|
+
rates: [
|
|
1535
|
+
{
|
|
1536
|
+
unit: "per_image",
|
|
1537
|
+
amount: 0.039,
|
|
1538
|
+
variant: "default 1024px output"
|
|
1539
|
+
}
|
|
1540
|
+
],
|
|
1541
|
+
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."
|
|
1542
|
+
},
|
|
1532
1543
|
{
|
|
1533
1544
|
model: "imagen-4.0-generate-001",
|
|
1534
1545
|
provider: "vertex",
|
|
@@ -1545,7 +1556,7 @@ var init_pricing_data = __esm({
|
|
|
1545
1556
|
]
|
|
1546
1557
|
},
|
|
1547
1558
|
{
|
|
1548
|
-
model: "veo-3.1-generate-
|
|
1559
|
+
model: "veo-3.1-generate-preview",
|
|
1549
1560
|
provider: "gemini",
|
|
1550
1561
|
modality: "video",
|
|
1551
1562
|
currency: "USD",
|
|
@@ -1799,7 +1810,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
|
|
|
1799
1810
|
var init_constants = __esm({
|
|
1800
1811
|
"src/constants.ts"() {
|
|
1801
1812
|
init_pricing();
|
|
1802
|
-
CASCADE_VERSION = "0.
|
|
1813
|
+
CASCADE_VERSION = "0.64.0";
|
|
1803
1814
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1804
1815
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1805
1816
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -5468,6 +5479,87 @@ function mcpToolName(serverName, toolName) {
|
|
|
5468
5479
|
function isMcpToolName(name) {
|
|
5469
5480
|
return name.startsWith(MCP_TOOL_PREFIX);
|
|
5470
5481
|
}
|
|
5482
|
+
var READ_ONLY_MCP_VERBS = ["list", "get", "search", "read", "describe", "find", "query", "fetch", "show", "view"];
|
|
5483
|
+
var MUTATING_MCP_TOKENS = /* @__PURE__ */ new Set([
|
|
5484
|
+
"create",
|
|
5485
|
+
"delete",
|
|
5486
|
+
"remove",
|
|
5487
|
+
"update",
|
|
5488
|
+
"write",
|
|
5489
|
+
"modify",
|
|
5490
|
+
"set",
|
|
5491
|
+
"put",
|
|
5492
|
+
"post",
|
|
5493
|
+
"patch",
|
|
5494
|
+
"push",
|
|
5495
|
+
"merge",
|
|
5496
|
+
"send",
|
|
5497
|
+
"execute",
|
|
5498
|
+
"exec",
|
|
5499
|
+
"run",
|
|
5500
|
+
"deploy",
|
|
5501
|
+
"install",
|
|
5502
|
+
"uninstall",
|
|
5503
|
+
"upload",
|
|
5504
|
+
"publish",
|
|
5505
|
+
"revoke",
|
|
5506
|
+
"grant",
|
|
5507
|
+
"approve",
|
|
5508
|
+
"reject",
|
|
5509
|
+
"cancel",
|
|
5510
|
+
"enable",
|
|
5511
|
+
"disable",
|
|
5512
|
+
"add",
|
|
5513
|
+
"insert",
|
|
5514
|
+
"drop",
|
|
5515
|
+
"truncate",
|
|
5516
|
+
"clear",
|
|
5517
|
+
"reset",
|
|
5518
|
+
"restart",
|
|
5519
|
+
"stop",
|
|
5520
|
+
"kill",
|
|
5521
|
+
"terminate",
|
|
5522
|
+
"rename",
|
|
5523
|
+
"move",
|
|
5524
|
+
"fork",
|
|
5525
|
+
"sync",
|
|
5526
|
+
"replace",
|
|
5527
|
+
"overwrite",
|
|
5528
|
+
"invite",
|
|
5529
|
+
"ban",
|
|
5530
|
+
"block",
|
|
5531
|
+
"assign",
|
|
5532
|
+
"lock",
|
|
5533
|
+
"unlock",
|
|
5534
|
+
"archive",
|
|
5535
|
+
"star",
|
|
5536
|
+
"comment",
|
|
5537
|
+
"reply",
|
|
5538
|
+
"submit",
|
|
5539
|
+
"apply",
|
|
5540
|
+
"trigger",
|
|
5541
|
+
"invoke",
|
|
5542
|
+
"purge",
|
|
5543
|
+
"migrate",
|
|
5544
|
+
"edit",
|
|
5545
|
+
"save",
|
|
5546
|
+
"commit"
|
|
5547
|
+
]);
|
|
5548
|
+
function mcpTokens(rawToolName) {
|
|
5549
|
+
return rawToolName.split(/[_-]+/).flatMap((segment) => segment.split(/(?<=[a-z0-9])(?=[A-Z])/)).map((t) => t.toLowerCase()).filter(Boolean);
|
|
5550
|
+
}
|
|
5551
|
+
function isReadOnlyMcpToolName(rawToolName) {
|
|
5552
|
+
if (mcpTokens(rawToolName).some((t) => MUTATING_MCP_TOKENS.has(t))) return false;
|
|
5553
|
+
const lower = rawToolName.toLowerCase();
|
|
5554
|
+
for (const verb of READ_ONLY_MCP_VERBS) {
|
|
5555
|
+
if (!lower.startsWith(verb)) continue;
|
|
5556
|
+
const boundary = rawToolName[verb.length];
|
|
5557
|
+
if (boundary === void 0 || boundary === "_" || boundary === "-" || boundary >= "A" && boundary <= "Z") {
|
|
5558
|
+
return true;
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
return false;
|
|
5562
|
+
}
|
|
5471
5563
|
function uniqueMcpServerName(desired, existingNames) {
|
|
5472
5564
|
const taken = new Set(existingNames.map(mcpServerPrefix));
|
|
5473
5565
|
if (!taken.has(mcpServerPrefix(desired))) return desired;
|
|
@@ -6479,8 +6571,6 @@ var LocalRequestQueue = class {
|
|
|
6479
6571
|
};
|
|
6480
6572
|
}
|
|
6481
6573
|
};
|
|
6482
|
-
|
|
6483
|
-
// src/core/router/dead-models.ts
|
|
6484
6574
|
var DEAD_MODEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6485
6575
|
function deadModelKey(provider, modelId) {
|
|
6486
6576
|
return `${provider}:${modelId}`;
|
|
@@ -6581,16 +6671,13 @@ var DeadModelStore = class {
|
|
|
6581
6671
|
function fileDeadModelPersistence(filePath) {
|
|
6582
6672
|
return {
|
|
6583
6673
|
load() {
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
const raw = JSON.parse(fs32.readFileSync(filePath, "utf-8"));
|
|
6674
|
+
if (!fs29.existsSync(filePath)) return [];
|
|
6675
|
+
const raw = JSON.parse(fs29.readFileSync(filePath, "utf-8"));
|
|
6587
6676
|
return Array.isArray(raw) ? raw : [];
|
|
6588
6677
|
},
|
|
6589
6678
|
save(records) {
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
fs32.mkdirSync(path37.dirname(filePath), { recursive: true });
|
|
6593
|
-
fs32.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
6679
|
+
fs29.mkdirSync(path32.dirname(filePath), { recursive: true });
|
|
6680
|
+
fs29.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
6594
6681
|
}
|
|
6595
6682
|
};
|
|
6596
6683
|
}
|
|
@@ -7020,6 +7107,12 @@ var FAMILY_BENCHMARKS = {
|
|
|
7020
7107
|
// Google — strongest at analysis/data and long-context.
|
|
7021
7108
|
"gemini-2.5-pro": { code: 90, analysis: 93, creative: 86, data: 92 },
|
|
7022
7109
|
"gemini-2.5-flash": { code: 82, analysis: 83, creative: 80, data: 82 },
|
|
7110
|
+
// The 2.5-lite tier scores below full 2.5-flash but above the older 2.0
|
|
7111
|
+
// lite tier — it exists because a naive `gemini-?2\.5-flash` regex swallows
|
|
7112
|
+
// "gemini-2.5-flash-lite" as a substring match (no lite variant of its own
|
|
7113
|
+
// to fall into), so the weaker lite model got credited with the full
|
|
7114
|
+
// flash's benchmark score and kept winning Cascade Auto's "best value" pick.
|
|
7115
|
+
"gemini-2.5-flash-lite": { code: 72, analysis: 72, creative: 74, data: 72 },
|
|
7023
7116
|
"gemini-1.5-pro": { code: 82, analysis: 84, creative: 82, data: 85 },
|
|
7024
7117
|
"gemini-2.0-flash": { code: 79, analysis: 80, creative: 79, data: 80 },
|
|
7025
7118
|
"gemini-flash-lite": { code: 68, analysis: 68, creative: 70, data: 68 },
|
|
@@ -7052,6 +7145,11 @@ var FAMILY_MATCHERS = [
|
|
|
7052
7145
|
[/gpt-?4o-mini/i, "gpt-4o-mini"],
|
|
7053
7146
|
[/gpt-?4o/i, "gpt-4o"],
|
|
7054
7147
|
[/gemini-?2\.5-pro/i, "gemini-2.5-pro"],
|
|
7148
|
+
// Must precede the bare `2\.5-flash` rule below: that pattern has no word
|
|
7149
|
+
// boundary after "flash" and matches "gemini-2.5-flash-lite" as a plain
|
|
7150
|
+
// substring, so the lite variant would otherwise be scored as the full
|
|
7151
|
+
// flash model (see the FAMILY_BENCHMARKS comment above).
|
|
7152
|
+
[/gemini-?2\.5-flash-lite/i, "gemini-2.5-flash-lite"],
|
|
7055
7153
|
[/gemini-?2\.5-flash/i, "gemini-2.5-flash"],
|
|
7056
7154
|
[/gemini-?1\.5-pro/i, "gemini-1.5-pro"],
|
|
7057
7155
|
[/gemini-?2\.0-flash-lite/i, "gemini-flash-lite"],
|
|
@@ -8559,6 +8657,92 @@ function truncateForContext(text, maxChars = 12e3) {
|
|
|
8559
8657
|
${tail}`;
|
|
8560
8658
|
}
|
|
8561
8659
|
|
|
8660
|
+
// src/core/router/provider-errors.ts
|
|
8661
|
+
var SYSTEMIC = /* @__PURE__ */ new Set([
|
|
8662
|
+
"rate_limit",
|
|
8663
|
+
"quota_exhausted",
|
|
8664
|
+
"auth",
|
|
8665
|
+
"model_unavailable"
|
|
8666
|
+
]);
|
|
8667
|
+
function isSystemicKind(kind) {
|
|
8668
|
+
return SYSTEMIC.has(kind);
|
|
8669
|
+
}
|
|
8670
|
+
function statusOf(err) {
|
|
8671
|
+
if (!err || typeof err !== "object") return void 0;
|
|
8672
|
+
const e = err;
|
|
8673
|
+
for (const key of ["status", "statusCode", "code"]) {
|
|
8674
|
+
const v = e[key];
|
|
8675
|
+
if (typeof v === "number" && v >= 100 && v < 600) return v;
|
|
8676
|
+
}
|
|
8677
|
+
const resp = e["response"];
|
|
8678
|
+
if (resp && typeof resp["status"] === "number") return resp["status"];
|
|
8679
|
+
const inner = e["error"];
|
|
8680
|
+
if (inner && typeof inner["code"] === "number") return inner["code"];
|
|
8681
|
+
return void 0;
|
|
8682
|
+
}
|
|
8683
|
+
function messageOf(err) {
|
|
8684
|
+
if (err instanceof Error) return err.message;
|
|
8685
|
+
if (typeof err === "string") return err;
|
|
8686
|
+
if (err && typeof err === "object") {
|
|
8687
|
+
const e = err;
|
|
8688
|
+
if (typeof e["message"] === "string") return e["message"];
|
|
8689
|
+
const inner = e["error"];
|
|
8690
|
+
if (inner && typeof inner["message"] === "string") return inner["message"];
|
|
8691
|
+
try {
|
|
8692
|
+
return JSON.stringify(err);
|
|
8693
|
+
} catch {
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8696
|
+
return String(err);
|
|
8697
|
+
}
|
|
8698
|
+
function classifyProviderError(err) {
|
|
8699
|
+
const raw = messageOf(err).trim();
|
|
8700
|
+
const status = statusOf(err);
|
|
8701
|
+
const m = raw.toLowerCase();
|
|
8702
|
+
const kind = (() => {
|
|
8703
|
+
if (status === 429) {
|
|
8704
|
+
return /quota|billing|credit|exceeded your current quota|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
|
|
8705
|
+
}
|
|
8706
|
+
if (status === 401 || status === 403) return "auth";
|
|
8707
|
+
if (status === 404) return "model_unavailable";
|
|
8708
|
+
if (/rate limit|too many requests|resource_exhausted/.test(m)) {
|
|
8709
|
+
return /quota|billing|credit|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
|
|
8710
|
+
}
|
|
8711
|
+
if (/quota|billing|insufficient_quota|insufficient credit|payment required/.test(m)) return "quota_exhausted";
|
|
8712
|
+
if (/api key|unauthorized|authentication|permission denied|invalid[_ ]api/.test(m)) return "auth";
|
|
8713
|
+
if (/model .*(not found|does not exist|unavailable)|no such model|unknown model|not supported for/.test(m)) {
|
|
8714
|
+
return "model_unavailable";
|
|
8715
|
+
}
|
|
8716
|
+
if (/context length|too many tokens|maximum context|prompt is too long|input too long/.test(m)) return "context_length";
|
|
8717
|
+
if (/safety|content filter|blocked|recitation|prohibited/.test(m)) return "content_filter";
|
|
8718
|
+
if (/econnreset|etimedout|enotfound|socket hang up|network|fetch failed|aborted/.test(m)) return "network";
|
|
8719
|
+
return "unknown";
|
|
8720
|
+
})();
|
|
8721
|
+
return { kind, systemic: isSystemicKind(kind), status, raw };
|
|
8722
|
+
}
|
|
8723
|
+
function describeProviderError(c, modelId) {
|
|
8724
|
+
const on = modelId ? ` on ${modelId}` : "";
|
|
8725
|
+
const detail = c.raw ? ` Provider said: ${c.raw}` : "";
|
|
8726
|
+
switch (c.kind) {
|
|
8727
|
+
case "rate_limit":
|
|
8728
|
+
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}`;
|
|
8729
|
+
case "quota_exhausted":
|
|
8730
|
+
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}`;
|
|
8731
|
+
case "auth":
|
|
8732
|
+
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}`;
|
|
8733
|
+
case "model_unavailable":
|
|
8734
|
+
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}`;
|
|
8735
|
+
case "context_length":
|
|
8736
|
+
return `The prompt was too long${on}. Reduce the attached context, or use a model with a larger context window for this tier.${detail}`;
|
|
8737
|
+
case "content_filter":
|
|
8738
|
+
return `The provider's safety filter refused this request${on}.${detail}`;
|
|
8739
|
+
case "network":
|
|
8740
|
+
return `Network error reaching the provider${on}. This is usually transient.${detail}`;
|
|
8741
|
+
default:
|
|
8742
|
+
return `The model call failed${on}.${detail}`;
|
|
8743
|
+
}
|
|
8744
|
+
}
|
|
8745
|
+
|
|
8562
8746
|
// src/core/tiers/t3-worker.ts
|
|
8563
8747
|
var CriticalToolError = class extends Error {
|
|
8564
8748
|
constructor(message, toolName) {
|
|
@@ -8592,8 +8776,15 @@ var KNOWN_TOOLS = [
|
|
|
8592
8776
|
"web_search",
|
|
8593
8777
|
"glob",
|
|
8594
8778
|
"grep",
|
|
8595
|
-
"web_fetch"
|
|
8779
|
+
"web_fetch",
|
|
8780
|
+
"generate_image"
|
|
8596
8781
|
];
|
|
8782
|
+
var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
|
|
8783
|
+
"generate_image",
|
|
8784
|
+
"generate_speech",
|
|
8785
|
+
"generate_video",
|
|
8786
|
+
"transcribe_audio"
|
|
8787
|
+
]);
|
|
8597
8788
|
function buildWorkerRules(has) {
|
|
8598
8789
|
const canWriteFiles = has("file_write") || has("file_edit") || has("run_code");
|
|
8599
8790
|
const hasAnyTool = KNOWN_TOOLS.some(has);
|
|
@@ -8603,9 +8794,22 @@ function buildWorkerRules(has) {
|
|
|
8603
8794
|
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.",
|
|
8604
8795
|
has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
|
|
8605
8796
|
has("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
|
|
8797
|
+
// Without this the model "writes the image" as prose — a bracketed
|
|
8798
|
+
// description sitting in the deck where the picture should be — even with a
|
|
8799
|
+
// working image model registered. Naming the reference syntax matters as
|
|
8800
|
+
// much as naming the tool: a generated image nobody embeds is still a
|
|
8801
|
+
// missing image.
|
|
8802
|
+
//
|
|
8803
|
+
// Scoped to PowerPoint/Word specifically: those are the only exporters
|
|
8804
|
+
// that actually embed a Markdown image reference (cloud/web/exporters.ts
|
|
8805
|
+
// toPptx/toDocx). PDF and plain-text/Markdown deliverables flatten a
|
|
8806
|
+
// reference straight to caption text — telling the model to call
|
|
8807
|
+
// generate_image for THOSE just pays for an image nobody ever sees.
|
|
8808
|
+
has("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.',
|
|
8606
8809
|
has("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.',
|
|
8607
8810
|
"- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
|
|
8608
8811
|
has("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.',
|
|
8812
|
+
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.",
|
|
8609
8813
|
"- Return structured output that directly addresses the expected output specification."
|
|
8610
8814
|
];
|
|
8611
8815
|
return `You are a T3 Worker agent in the Cascade AI system. Your job is to execute a specific subtask completely and accurately.
|
|
@@ -9139,7 +9343,9 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
9139
9343
|
const durationMs = Date.now() - toolStartMs;
|
|
9140
9344
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
9141
9345
|
this.emit("tool:result", { id: tc.id, tierId: this.id, toolName: tc.name, error: errMsg, durationMs });
|
|
9142
|
-
|
|
9346
|
+
const isTaggedSystemic = err instanceof Error && err.systemic === true;
|
|
9347
|
+
const isProviderBacked = PROVIDER_BACKED_TOOLS.has(tc.name);
|
|
9348
|
+
if (isTaggedSystemic || isProviderBacked && classifyProviderError(err).systemic) {
|
|
9143
9349
|
throw new CriticalToolError(errMsg, tc.name);
|
|
9144
9350
|
}
|
|
9145
9351
|
return await this.adaptiveFallback(tc, `Tool error: ${errMsg}`);
|
|
@@ -9427,8 +9633,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
9427
9633
|
} catch {
|
|
9428
9634
|
return {
|
|
9429
9635
|
checksRun: ["completeness", "correctness", "compliance"],
|
|
9430
|
-
passed: [
|
|
9431
|
-
failed: []
|
|
9636
|
+
passed: [],
|
|
9637
|
+
failed: ["completeness"]
|
|
9432
9638
|
};
|
|
9433
9639
|
}
|
|
9434
9640
|
}
|
|
@@ -11504,17 +11710,17 @@ var CAPABILITIES = [
|
|
|
11504
11710
|
api: "openai-images"
|
|
11505
11711
|
},
|
|
11506
11712
|
{
|
|
11507
|
-
//
|
|
11508
|
-
//
|
|
11509
|
-
//
|
|
11510
|
-
// generateContent
|
|
11511
|
-
//
|
|
11512
|
-
//
|
|
11513
|
-
//
|
|
11713
|
+
// Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
|
|
11714
|
+
// ("no longer available to new users") — Imagen's :predict API is deprecated
|
|
11715
|
+
// and shuts down 2026-08-17. Google's own migration path is this model on
|
|
11716
|
+
// the ordinary generateContent endpoint, so the fix was never another Imagen
|
|
11717
|
+
// id: the whole request/response SHAPE changes, which is why `api` moved to
|
|
11718
|
+
// 'gemini-generate-content' rather than just the modelId moving.
|
|
11719
|
+
// https://ai.google.dev/gemini-api/docs/imagen
|
|
11514
11720
|
modality: "image",
|
|
11515
11721
|
provider: "gemini",
|
|
11516
|
-
modelId: "
|
|
11517
|
-
api: "gemini-
|
|
11722
|
+
modelId: "gemini-2.5-flash-image",
|
|
11723
|
+
api: "gemini-generate-content"
|
|
11518
11724
|
},
|
|
11519
11725
|
// ── Speech (text → audio) ──
|
|
11520
11726
|
{
|
|
@@ -11546,9 +11752,17 @@ var CAPABILITIES = [
|
|
|
11546
11752
|
// Long-running: submit returns an operation name, the video lands minutes
|
|
11547
11753
|
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
11548
11754
|
// that polling loop; the registry only records the shape via `api`.
|
|
11755
|
+
//
|
|
11756
|
+
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
11757
|
+
// not the Gemini Developer API's — this file calls
|
|
11758
|
+
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
11759
|
+
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
11760
|
+
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
11761
|
+
// there is `veo-3.1-generate-preview`.
|
|
11762
|
+
// https://ai.google.dev/gemini-api/docs/veo
|
|
11549
11763
|
modality: "video",
|
|
11550
11764
|
provider: "gemini",
|
|
11551
|
-
modelId: "veo-3.1-generate-
|
|
11765
|
+
modelId: "veo-3.1-generate-preview",
|
|
11552
11766
|
api: "gemini-predict-lro"
|
|
11553
11767
|
}
|
|
11554
11768
|
];
|
|
@@ -11569,6 +11783,14 @@ function capabilityCost(cap) {
|
|
|
11569
11783
|
const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
|
|
11570
11784
|
return { label: `$${shown} ${unit}`, amount, unit };
|
|
11571
11785
|
}
|
|
11786
|
+
function explainChoice(c, cost, modality, isBest) {
|
|
11787
|
+
const priced = cost ? ` ${cost.label}.` : "";
|
|
11788
|
+
if (c.quality !== void 0) {
|
|
11789
|
+
return isBest ? `${c.modelId} (${c.provider}) \u2014 ranked highest for ${modality}: ${c.qualityBasis}.${priced}` : `${c.modelId} (${c.provider}) \u2014 ranked alternative for ${modality}: ${c.qualityBasis}.${priced}`;
|
|
11790
|
+
}
|
|
11791
|
+
const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
|
|
11792
|
+
return isBest ? `${c.modelId} (${c.provider}) \u2014 cheapest usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking : `${c.modelId} (${c.provider}) \u2014 alternative usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking;
|
|
11793
|
+
}
|
|
11572
11794
|
var MultimodalRegistry = class {
|
|
11573
11795
|
/** Provider types the user has actually configured a key for. */
|
|
11574
11796
|
available;
|
|
@@ -11586,22 +11808,20 @@ var MultimodalRegistry = class {
|
|
|
11586
11808
|
return [...new Set(this.usable().map((c) => c.modality))];
|
|
11587
11809
|
}
|
|
11588
11810
|
/**
|
|
11589
|
-
*
|
|
11811
|
+
* Every usable capability for a modality, best first.
|
|
11590
11812
|
*
|
|
11591
11813
|
* Ranked models beat unranked ones, then higher quality, then lower unit
|
|
11592
|
-
* price. An explicit `preferModel`
|
|
11593
|
-
* naming a model is a stronger signal than any prior.
|
|
11814
|
+
* price. An explicit `preferModel` is moved to the head when it is usable —
|
|
11815
|
+
* the user naming a model is a stronger signal than any prior.
|
|
11816
|
+
*
|
|
11817
|
+
* `select()` is just the head of this list. The TAIL is the point: a systemic
|
|
11818
|
+
* provider failure (dead key, 404, exhausted quota) is scoped to one
|
|
11819
|
+
* provider+model, so "this model cannot draw" is not the same fact as "this
|
|
11820
|
+
* account cannot draw". A caller holding the ordered alternatives can try the
|
|
11821
|
+
* next provider instead of aborting the whole run — see generate-media.ts.
|
|
11594
11822
|
*/
|
|
11595
|
-
|
|
11596
|
-
const
|
|
11597
|
-
if (!pool.length) return null;
|
|
11598
|
-
if (opts.preferModel) {
|
|
11599
|
-
const wanted = pool.find((c) => c.modelId === opts.preferModel);
|
|
11600
|
-
if (wanted) {
|
|
11601
|
-
return { capability: wanted, cost: capabilityCost(wanted), reason: `Requested explicitly: ${wanted.modelId}.` };
|
|
11602
|
-
}
|
|
11603
|
-
}
|
|
11604
|
-
const scored = pool.map((c) => ({ c, cost: capabilityCost(c) })).sort((a, b) => {
|
|
11823
|
+
rank(modality, opts = {}) {
|
|
11824
|
+
const scored = this.usable(modality).map((c) => ({ c, cost: capabilityCost(c) })).sort((a, b) => {
|
|
11605
11825
|
const qa = a.c.quality ?? -1;
|
|
11606
11826
|
const qb = b.c.quality ?? -1;
|
|
11607
11827
|
if (qa !== qb) return qb - qa;
|
|
@@ -11609,10 +11829,25 @@ var MultimodalRegistry = class {
|
|
|
11609
11829
|
const pb = b.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
11610
11830
|
return pa - pb;
|
|
11611
11831
|
});
|
|
11612
|
-
const
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11832
|
+
const results = scored.map(({ c, cost }, i) => ({
|
|
11833
|
+
capability: c,
|
|
11834
|
+
cost,
|
|
11835
|
+
reason: explainChoice(c, cost, modality, i === 0)
|
|
11836
|
+
}));
|
|
11837
|
+
if (opts.preferModel) {
|
|
11838
|
+
const i = results.findIndex((r) => r.capability.modelId === opts.preferModel);
|
|
11839
|
+
if (i > -1) {
|
|
11840
|
+
const [wanted] = results.splice(i, 1);
|
|
11841
|
+
results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
|
|
11842
|
+
}
|
|
11843
|
+
}
|
|
11844
|
+
return results;
|
|
11845
|
+
}
|
|
11846
|
+
/**
|
|
11847
|
+
* Pick a model for a modality — the best usable one, or null if there is none.
|
|
11848
|
+
*/
|
|
11849
|
+
select(modality, opts = {}) {
|
|
11850
|
+
return this.rank(modality, opts)[0] ?? null;
|
|
11616
11851
|
}
|
|
11617
11852
|
/**
|
|
11618
11853
|
* A plain-language account of what this account can and cannot generate —
|
|
@@ -11665,92 +11900,6 @@ var BaseTool = class {
|
|
|
11665
11900
|
}
|
|
11666
11901
|
};
|
|
11667
11902
|
|
|
11668
|
-
// src/core/router/provider-errors.ts
|
|
11669
|
-
var SYSTEMIC = /* @__PURE__ */ new Set([
|
|
11670
|
-
"rate_limit",
|
|
11671
|
-
"quota_exhausted",
|
|
11672
|
-
"auth",
|
|
11673
|
-
"model_unavailable"
|
|
11674
|
-
]);
|
|
11675
|
-
function isSystemicKind(kind) {
|
|
11676
|
-
return SYSTEMIC.has(kind);
|
|
11677
|
-
}
|
|
11678
|
-
function statusOf(err) {
|
|
11679
|
-
if (!err || typeof err !== "object") return void 0;
|
|
11680
|
-
const e = err;
|
|
11681
|
-
for (const key of ["status", "statusCode", "code"]) {
|
|
11682
|
-
const v = e[key];
|
|
11683
|
-
if (typeof v === "number" && v >= 100 && v < 600) return v;
|
|
11684
|
-
}
|
|
11685
|
-
const resp = e["response"];
|
|
11686
|
-
if (resp && typeof resp["status"] === "number") return resp["status"];
|
|
11687
|
-
const inner = e["error"];
|
|
11688
|
-
if (inner && typeof inner["code"] === "number") return inner["code"];
|
|
11689
|
-
return void 0;
|
|
11690
|
-
}
|
|
11691
|
-
function messageOf(err) {
|
|
11692
|
-
if (err instanceof Error) return err.message;
|
|
11693
|
-
if (typeof err === "string") return err;
|
|
11694
|
-
if (err && typeof err === "object") {
|
|
11695
|
-
const e = err;
|
|
11696
|
-
if (typeof e["message"] === "string") return e["message"];
|
|
11697
|
-
const inner = e["error"];
|
|
11698
|
-
if (inner && typeof inner["message"] === "string") return inner["message"];
|
|
11699
|
-
try {
|
|
11700
|
-
return JSON.stringify(err);
|
|
11701
|
-
} catch {
|
|
11702
|
-
}
|
|
11703
|
-
}
|
|
11704
|
-
return String(err);
|
|
11705
|
-
}
|
|
11706
|
-
function classifyProviderError(err) {
|
|
11707
|
-
const raw = messageOf(err).trim();
|
|
11708
|
-
const status = statusOf(err);
|
|
11709
|
-
const m = raw.toLowerCase();
|
|
11710
|
-
const kind = (() => {
|
|
11711
|
-
if (status === 429) {
|
|
11712
|
-
return /quota|billing|credit|exceeded your current quota|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
|
|
11713
|
-
}
|
|
11714
|
-
if (status === 401 || status === 403) return "auth";
|
|
11715
|
-
if (status === 404) return "model_unavailable";
|
|
11716
|
-
if (/rate limit|too many requests|resource_exhausted/.test(m)) {
|
|
11717
|
-
return /quota|billing|credit|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
|
|
11718
|
-
}
|
|
11719
|
-
if (/quota|billing|insufficient_quota|insufficient credit|payment required/.test(m)) return "quota_exhausted";
|
|
11720
|
-
if (/api key|unauthorized|authentication|permission denied|invalid[_ ]api/.test(m)) return "auth";
|
|
11721
|
-
if (/model .*(not found|does not exist|unavailable)|no such model|unknown model|not supported for/.test(m)) {
|
|
11722
|
-
return "model_unavailable";
|
|
11723
|
-
}
|
|
11724
|
-
if (/context length|too many tokens|maximum context|prompt is too long|input too long/.test(m)) return "context_length";
|
|
11725
|
-
if (/safety|content filter|blocked|recitation|prohibited/.test(m)) return "content_filter";
|
|
11726
|
-
if (/econnreset|etimedout|enotfound|socket hang up|network|fetch failed|aborted/.test(m)) return "network";
|
|
11727
|
-
return "unknown";
|
|
11728
|
-
})();
|
|
11729
|
-
return { kind, systemic: isSystemicKind(kind), status, raw };
|
|
11730
|
-
}
|
|
11731
|
-
function describeProviderError(c, modelId) {
|
|
11732
|
-
const on = modelId ? ` on ${modelId}` : "";
|
|
11733
|
-
const detail = c.raw ? ` Provider said: ${c.raw}` : "";
|
|
11734
|
-
switch (c.kind) {
|
|
11735
|
-
case "rate_limit":
|
|
11736
|
-
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}`;
|
|
11737
|
-
case "quota_exhausted":
|
|
11738
|
-
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}`;
|
|
11739
|
-
case "auth":
|
|
11740
|
-
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}`;
|
|
11741
|
-
case "model_unavailable":
|
|
11742
|
-
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}`;
|
|
11743
|
-
case "context_length":
|
|
11744
|
-
return `The prompt was too long${on}. Reduce the attached context, or use a model with a larger context window for this tier.${detail}`;
|
|
11745
|
-
case "content_filter":
|
|
11746
|
-
return `The provider's safety filter refused this request${on}.${detail}`;
|
|
11747
|
-
case "network":
|
|
11748
|
-
return `Network error reaching the provider${on}. This is usually transient.${detail}`;
|
|
11749
|
-
default:
|
|
11750
|
-
return `The model call failed${on}.${detail}`;
|
|
11751
|
-
}
|
|
11752
|
-
}
|
|
11753
|
-
|
|
11754
11903
|
// src/core/multimodal/generate.ts
|
|
11755
11904
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
11756
11905
|
async function callProvider(fn, modelId) {
|
|
@@ -11789,6 +11938,29 @@ function baseUrlFor(cfg, fallback) {
|
|
|
11789
11938
|
function stamp(ext) {
|
|
11790
11939
|
return `cascade-${Date.now()}.${ext}`;
|
|
11791
11940
|
}
|
|
11941
|
+
var GEMINI_ASPECT_RATIOS = [
|
|
11942
|
+
["1:1", 1 / 1],
|
|
11943
|
+
["3:2", 3 / 2],
|
|
11944
|
+
["2:3", 2 / 3],
|
|
11945
|
+
["3:4", 3 / 4],
|
|
11946
|
+
["4:3", 4 / 3],
|
|
11947
|
+
["4:5", 4 / 5],
|
|
11948
|
+
["5:4", 5 / 4],
|
|
11949
|
+
["9:16", 9 / 16],
|
|
11950
|
+
["16:9", 16 / 9],
|
|
11951
|
+
["21:9", 21 / 9]
|
|
11952
|
+
];
|
|
11953
|
+
function closestGeminiAspectRatio(size) {
|
|
11954
|
+
const m = size.match(/^(\d+)\s*[x×]\s*(\d+)$/i);
|
|
11955
|
+
if (!m) return void 0;
|
|
11956
|
+
const w = Number(m[1]);
|
|
11957
|
+
const h = Number(m[2]);
|
|
11958
|
+
if (!w || !h) return void 0;
|
|
11959
|
+
const ratio = w / h;
|
|
11960
|
+
return GEMINI_ASPECT_RATIOS.reduce(
|
|
11961
|
+
(best, cand) => Math.abs(cand[1] - ratio) < Math.abs(best[1] - ratio) ? cand : best
|
|
11962
|
+
)[0];
|
|
11963
|
+
}
|
|
11792
11964
|
async function generateImage(cap, cfg, req, signal) {
|
|
11793
11965
|
return callProvider(async () => {
|
|
11794
11966
|
if (cap.api === "openai-images") {
|
|
@@ -11813,19 +11985,37 @@ async function generateImage(cap, cfg, req, signal) {
|
|
|
11813
11985
|
...first.revised_prompt ? { revisedPrompt: first.revised_prompt } : {}
|
|
11814
11986
|
};
|
|
11815
11987
|
}
|
|
11816
|
-
if (cap.api === "gemini-
|
|
11988
|
+
if (cap.api === "gemini-generate-content") {
|
|
11817
11989
|
const base = baseUrlFor(cfg, "https://generativelanguage.googleapis.com/v1beta");
|
|
11818
|
-
const url = `${base}/models/${encodeURIComponent(cap.modelId)}:
|
|
11990
|
+
const url = `${base}/models/${encodeURIComponent(cap.modelId)}:generateContent`;
|
|
11991
|
+
const aspectRatio = req.size ? closestGeminiAspectRatio(req.size) : void 0;
|
|
11819
11992
|
const res = await postJson(url, {
|
|
11820
|
-
|
|
11821
|
-
|
|
11993
|
+
contents: [{ parts: [{ text: req.prompt }] }],
|
|
11994
|
+
generationConfig: {
|
|
11995
|
+
// Asked for explicitly: these models can answer with text only, and a
|
|
11996
|
+
// text-only reply to "draw me a cat" is a failed image generation.
|
|
11997
|
+
responseModalities: ["IMAGE", "TEXT"],
|
|
11998
|
+
// Gemini has no free-form pixel size — only a named aspect ratio (see
|
|
11999
|
+
// closestGeminiAspectRatio). Omitted entirely when there's no
|
|
12000
|
+
// requested size, or no supported ratio close enough to bother with.
|
|
12001
|
+
...aspectRatio ? { imageConfig: { aspectRatio } } : {}
|
|
12002
|
+
}
|
|
11822
12003
|
}, { "x-goog-api-key": cfg.apiKey ?? "" }, signal);
|
|
11823
12004
|
const body = await res.json();
|
|
11824
|
-
const
|
|
11825
|
-
|
|
11826
|
-
const
|
|
12005
|
+
const candidate = body.candidates?.[0];
|
|
12006
|
+
const parts = candidate?.content?.parts ?? [];
|
|
12007
|
+
const image = parts.find((p) => p.inlineData?.data)?.inlineData;
|
|
12008
|
+
if (!image?.data) {
|
|
12009
|
+
const blocked = body.promptFeedback?.blockReason ?? (candidate?.finishReason && candidate.finishReason !== "STOP" ? candidate.finishReason : void 0);
|
|
12010
|
+
if (blocked) throw new Error(`Image request was blocked by the provider (${blocked}).`);
|
|
12011
|
+
const said = parts.find((p) => p.text)?.text?.trim();
|
|
12012
|
+
throw new Error(
|
|
12013
|
+
"Image response contained no image data." + (said ? ` The model replied with text instead: ${said}` : "")
|
|
12014
|
+
);
|
|
12015
|
+
}
|
|
12016
|
+
const mimeType = image.mimeType ?? "image/png";
|
|
11827
12017
|
return {
|
|
11828
|
-
data: Buffer.from(
|
|
12018
|
+
data: Buffer.from(image.data, "base64"),
|
|
11829
12019
|
mimeType,
|
|
11830
12020
|
filename: stamp(mimeType.includes("jpeg") ? "jpg" : "png"),
|
|
11831
12021
|
modelId: cap.modelId,
|
|
@@ -11978,6 +12168,49 @@ function resolve(deps, modality, preferModel) {
|
|
|
11978
12168
|
}
|
|
11979
12169
|
return { ok: true, selected, cfg };
|
|
11980
12170
|
}
|
|
12171
|
+
function messageOf2(err) {
|
|
12172
|
+
return err instanceof Error ? err.message : String(err);
|
|
12173
|
+
}
|
|
12174
|
+
async function runWithProviderFallback(deps, modality, first, run, signal) {
|
|
12175
|
+
const failed = first.selected.capability;
|
|
12176
|
+
try {
|
|
12177
|
+
return { value: await run(failed, first.cfg), selected: first.selected };
|
|
12178
|
+
} catch (err) {
|
|
12179
|
+
if (signal?.aborted || !classifyProviderError(err).systemic) throw err;
|
|
12180
|
+
const tried = [`${failed.modelId} (${failed.provider}): ${messageOf2(err)}`];
|
|
12181
|
+
let allSystemic = true;
|
|
12182
|
+
let lastNonSystemic;
|
|
12183
|
+
const seen = /* @__PURE__ */ new Set([failed.provider]);
|
|
12184
|
+
for (const alt of deps.registry.rank(modality)) {
|
|
12185
|
+
if (seen.has(alt.capability.provider)) continue;
|
|
12186
|
+
const cfg = deps.lookupProvider(alt.capability.provider);
|
|
12187
|
+
if (!cfg) continue;
|
|
12188
|
+
seen.add(alt.capability.provider);
|
|
12189
|
+
try {
|
|
12190
|
+
return {
|
|
12191
|
+
value: await run(alt.capability, cfg),
|
|
12192
|
+
selected: alt,
|
|
12193
|
+
fellBackFrom: { capability: failed, error: messageOf2(err) }
|
|
12194
|
+
};
|
|
12195
|
+
} catch (retryErr) {
|
|
12196
|
+
if (signal?.aborted) throw retryErr;
|
|
12197
|
+
tried.push(`${alt.capability.modelId} (${alt.capability.provider}): ${messageOf2(retryErr)}`);
|
|
12198
|
+
if (classifyProviderError(retryErr).systemic) {
|
|
12199
|
+
continue;
|
|
12200
|
+
}
|
|
12201
|
+
allSystemic = false;
|
|
12202
|
+
lastNonSystemic = retryErr;
|
|
12203
|
+
}
|
|
12204
|
+
}
|
|
12205
|
+
if (tried.length === 1) throw err;
|
|
12206
|
+
if (!allSystemic) throw lastNonSystemic;
|
|
12207
|
+
const exhausted = new Error(
|
|
12208
|
+
`${modality} generation failed on every configured provider (${seen.size} tried). ${tried.join(" ")}`
|
|
12209
|
+
);
|
|
12210
|
+
Object.assign(exhausted, { systemic: true });
|
|
12211
|
+
throw exhausted;
|
|
12212
|
+
}
|
|
12213
|
+
}
|
|
11981
12214
|
var GenerateImageTool = class extends BaseTool {
|
|
11982
12215
|
constructor(deps) {
|
|
11983
12216
|
super();
|
|
@@ -12001,16 +12234,32 @@ var GenerateImageTool = class extends BaseTool {
|
|
|
12001
12234
|
const r = resolve(this.deps, "image", input["model"] ? String(input["model"]) : void 0);
|
|
12002
12235
|
if (!r.ok) return r.error;
|
|
12003
12236
|
const size = input["size"] ? String(input["size"]) : void 0;
|
|
12004
|
-
const
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12237
|
+
const attempt = await runWithProviderFallback(
|
|
12238
|
+
this.deps,
|
|
12239
|
+
"image",
|
|
12240
|
+
r,
|
|
12241
|
+
(cap, cfg) => generateImage(cap, cfg, { prompt, ...size ? { size } : {} }, options.signal),
|
|
12008
12242
|
options.signal
|
|
12009
12243
|
);
|
|
12244
|
+
const asset = attempt.value;
|
|
12245
|
+
const fell = attempt.fellBackFrom;
|
|
12010
12246
|
const location = await this.deps.sink(asset);
|
|
12011
12247
|
return [
|
|
12012
12248
|
`Image generated and saved to ${location}.`,
|
|
12013
|
-
|
|
12249
|
+
// Knowing WHERE the image is isn't the same as knowing how to use it: a
|
|
12250
|
+
// model told only "saved to X" tends to describe the picture in prose
|
|
12251
|
+
// instead of embedding it. Spelling out the reference syntax here makes
|
|
12252
|
+
// the worker's "never write a placeholder" rule concretely actionable,
|
|
12253
|
+
// and it works for every host because `location` is whatever that host's
|
|
12254
|
+
// sink returned — a workspace path on desktop/CLI, a URL in the cloud.
|
|
12255
|
+
`To include this image in your output, reference it as: `,
|
|
12256
|
+
// The reported model has to be the one that actually drew the picture, not
|
|
12257
|
+
// the one that was picked and then failed.
|
|
12258
|
+
`Model: ${asset.modelId} \u2014 ${fell ? `used as a fallback after ${fell.capability.modelId} (${fell.capability.provider}) failed with a systemic error.` : attempt.selected.reason}`,
|
|
12259
|
+
// Worth one line: the run succeeded, so nothing else will report that a
|
|
12260
|
+
// configured provider is broken, and "your Gemini key is dead" is exactly
|
|
12261
|
+
// the kind of thing the user wants to hear once rather than never.
|
|
12262
|
+
fell ? `Note: ${fell.capability.provider} failed first \u2014 ${fell.error}` : "",
|
|
12014
12263
|
// DALL·E rewrites the prompt before drawing; the model needs to know what
|
|
12015
12264
|
// was actually rendered, not what it asked for, or its description of the
|
|
12016
12265
|
// result will not match the image.
|
|
@@ -13253,9 +13502,11 @@ var WebSearchTool = class extends BaseTool {
|
|
|
13253
13502
|
);
|
|
13254
13503
|
const errors = [];
|
|
13255
13504
|
let results = [];
|
|
13505
|
+
let anyBackendReachable = false;
|
|
13256
13506
|
if (this.config.searxngUrl) {
|
|
13257
13507
|
try {
|
|
13258
13508
|
results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
|
|
13509
|
+
anyBackendReachable = true;
|
|
13259
13510
|
if (results.length > 0) return this.formatResults(query, results);
|
|
13260
13511
|
errors.push("SearXNG: returned 0 results");
|
|
13261
13512
|
} catch (err) {
|
|
@@ -13265,6 +13516,7 @@ var WebSearchTool = class extends BaseTool {
|
|
|
13265
13516
|
if (this.config.braveApiKey) {
|
|
13266
13517
|
try {
|
|
13267
13518
|
results = await searchBrave(query, this.config.braveApiKey, maxResults);
|
|
13519
|
+
anyBackendReachable = true;
|
|
13268
13520
|
if (results.length > 0) return this.formatResults(query, results);
|
|
13269
13521
|
errors.push("Brave: returned 0 results");
|
|
13270
13522
|
} catch (err) {
|
|
@@ -13274,6 +13526,7 @@ var WebSearchTool = class extends BaseTool {
|
|
|
13274
13526
|
if (this.config.tavilyApiKey) {
|
|
13275
13527
|
try {
|
|
13276
13528
|
results = await searchTavily(query, this.config.tavilyApiKey, maxResults);
|
|
13529
|
+
anyBackendReachable = true;
|
|
13277
13530
|
if (results.length > 0) return this.formatResults(query, results);
|
|
13278
13531
|
errors.push("Tavily: returned 0 results");
|
|
13279
13532
|
} catch (err) {
|
|
@@ -13283,6 +13536,7 @@ var WebSearchTool = class extends BaseTool {
|
|
|
13283
13536
|
for (const variant of ["html", "lite"]) {
|
|
13284
13537
|
try {
|
|
13285
13538
|
results = await searchDuckDuckGo(query, maxResults, variant);
|
|
13539
|
+
anyBackendReachable = true;
|
|
13286
13540
|
if (results.length > 0) return this.formatResults(query, results);
|
|
13287
13541
|
errors.push(`DuckDuckGo ${variant}: returned 0 results`);
|
|
13288
13542
|
} catch (err) {
|
|
@@ -13290,11 +13544,11 @@ var WebSearchTool = class extends BaseTool {
|
|
|
13290
13544
|
}
|
|
13291
13545
|
}
|
|
13292
13546
|
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" : "";
|
|
13293
|
-
|
|
13294
|
-
`Web search for "${query}"
|
|
13547
|
+
throw Object.assign(new Error([
|
|
13548
|
+
`Web search for "${query}" found nothing across all backends:`,
|
|
13295
13549
|
...errors.map((e) => ` \u2022 ${e}`),
|
|
13296
13550
|
configHint
|
|
13297
|
-
].join("\n");
|
|
13551
|
+
].join("\n")), { systemic: !anyBackendReachable });
|
|
13298
13552
|
}
|
|
13299
13553
|
formatResults(query, results) {
|
|
13300
13554
|
const lines = [`Web search results for: "${query}"`, ""];
|
|
@@ -13662,6 +13916,15 @@ var McpToolWrapper = class extends BaseTool {
|
|
|
13662
13916
|
this.description = `[MCP:${serverName}] ${description}`;
|
|
13663
13917
|
this.inputSchema = inputSchema;
|
|
13664
13918
|
}
|
|
13919
|
+
// Every built-in tool of comparable risk (shell, git, file writes, the
|
|
13920
|
+
// built-in github tool) overrides this to true. An MCP tool never did —
|
|
13921
|
+
// BaseTool's default (false) meant a connected server's create/delete/push
|
|
13922
|
+
// actions ran with zero human approval, whatever they did. MCP carries no
|
|
13923
|
+
// standard "this mutates state" flag, so default to dangerous and name the
|
|
13924
|
+
// exception (a read-only-looking verb) rather than the other way around.
|
|
13925
|
+
isDangerous() {
|
|
13926
|
+
return !isReadOnlyMcpToolName(this.toolName);
|
|
13927
|
+
}
|
|
13665
13928
|
async execute(input, _options) {
|
|
13666
13929
|
return this.mcpClient.callTool(this.serverName, this.toolName, input);
|
|
13667
13930
|
}
|
|
@@ -13774,7 +14037,7 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
13774
14037
|
requiresApproval(toolName) {
|
|
13775
14038
|
const defaults = DEFAULT_APPROVAL_REQUIRED;
|
|
13776
14039
|
const configured = this.config.requireApprovalFor;
|
|
13777
|
-
return defaults.includes(toolName) || configured.includes(toolName);
|
|
14040
|
+
return defaults.includes(toolName) || configured.includes(toolName) || this.isDangerous(toolName);
|
|
13778
14041
|
}
|
|
13779
14042
|
isDangerous(toolName) {
|
|
13780
14043
|
return this.tools.get(toolName)?.isDangerous() ?? false;
|
|
@@ -16684,9 +16947,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
16684
16947
|
});
|
|
16685
16948
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
16686
16949
|
try {
|
|
16687
|
-
const nodePath = __require("path");
|
|
16688
16950
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
16689
|
-
fileDeadModelPersistence(
|
|
16951
|
+
fileDeadModelPersistence(path32__default.default.join(workspacePath, ".cascade", "dead-models.json"))
|
|
16690
16952
|
));
|
|
16691
16953
|
} catch {
|
|
16692
16954
|
}
|