cascade-ai 0.52.0 → 0.54.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 +213 -20
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +213 -20
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +206 -25
- package/dist/index.cjs +204 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -10
- package/dist/index.d.ts +80 -10
- package/dist/index.js +204 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/desktop-core.cjs
CHANGED
|
@@ -224247,7 +224247,7 @@ function reconcilePrice(model, live) {
|
|
|
224247
224247
|
}
|
|
224248
224248
|
|
|
224249
224249
|
// src/constants.ts
|
|
224250
|
-
var CASCADE_VERSION = "0.
|
|
224250
|
+
var CASCADE_VERSION = "0.54.0";
|
|
224251
224251
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
224252
224252
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
224253
224253
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -233085,22 +233085,23 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
233085
233085
|
|
|
233086
233086
|
// src/providers/azure.ts
|
|
233087
233087
|
var DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
|
|
233088
|
+
var AZURE_BASE_MODEL_RULES = [
|
|
233089
|
+
[/gpt-?5\.5/, "gpt-5.5"],
|
|
233090
|
+
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
233091
|
+
[/gpt-?5\.4/, "gpt-5.4"],
|
|
233092
|
+
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
233093
|
+
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
233094
|
+
[/gpt-?5/, "gpt-5"],
|
|
233095
|
+
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
233096
|
+
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
233097
|
+
[/gpt-?4\.1/, "gpt-4.1"],
|
|
233098
|
+
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
233099
|
+
[/gpt-?4o/, "gpt-4o"]
|
|
233100
|
+
];
|
|
233101
|
+
var AZURE_BASE_MODELS = AZURE_BASE_MODEL_RULES.map(([, id]) => id);
|
|
233088
233102
|
function inferAzureBaseModel(deploymentName) {
|
|
233089
233103
|
const n8 = deploymentName.toLowerCase();
|
|
233090
|
-
const
|
|
233091
|
-
[/gpt-?5\.5/, "gpt-5.5"],
|
|
233092
|
-
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
233093
|
-
[/gpt-?5\.4/, "gpt-5.4"],
|
|
233094
|
-
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
233095
|
-
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
233096
|
-
[/gpt-?5/, "gpt-5"],
|
|
233097
|
-
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
233098
|
-
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
233099
|
-
[/gpt-?4\.1/, "gpt-4.1"],
|
|
233100
|
-
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
233101
|
-
[/gpt-?4o/, "gpt-4o"]
|
|
233102
|
-
];
|
|
233103
|
-
for (const [re3, base] of rules) if (re3.test(n8)) return base;
|
|
233104
|
+
for (const [re3, base] of AZURE_BASE_MODEL_RULES) if (re3.test(n8)) return base;
|
|
233104
233105
|
return null;
|
|
233105
233106
|
}
|
|
233106
233107
|
function azureModelForDeployment(cfg) {
|
|
@@ -235059,11 +235060,11 @@ function mcpToolsToGeminiTool(mcpTools, config2 = {}) {
|
|
|
235059
235060
|
const functionDeclarations = [];
|
|
235060
235061
|
const toolNames = /* @__PURE__ */ new Set();
|
|
235061
235062
|
for (const mcpTool of mcpTools) {
|
|
235062
|
-
const
|
|
235063
|
-
if (toolNames.has(
|
|
235064
|
-
throw new Error(`Duplicate function name ${
|
|
235063
|
+
const mcpToolName2 = mcpTool.name;
|
|
235064
|
+
if (toolNames.has(mcpToolName2)) {
|
|
235065
|
+
throw new Error(`Duplicate function name ${mcpToolName2} found in MCP tools. Please ensure function names are unique.`);
|
|
235065
235066
|
}
|
|
235066
|
-
toolNames.add(
|
|
235067
|
+
toolNames.add(mcpToolName2);
|
|
235067
235068
|
const geminiTool = mcpToGeminiTool(mcpTool, config2);
|
|
235068
235069
|
if (geminiTool.functionDeclarations) {
|
|
235069
235070
|
functionDeclarations.push(...geminiTool.functionDeclarations);
|
|
@@ -244710,11 +244711,11 @@ var McpCallableTool = class _McpCallableTool {
|
|
|
244710
244711
|
_d = false;
|
|
244711
244712
|
const mcpTool = _c;
|
|
244712
244713
|
mcpTools.push(mcpTool);
|
|
244713
|
-
const
|
|
244714
|
-
if (functionMap[
|
|
244715
|
-
throw new Error(`Duplicate function name ${
|
|
244714
|
+
const mcpToolName2 = mcpTool.name;
|
|
244715
|
+
if (functionMap[mcpToolName2]) {
|
|
244716
|
+
throw new Error(`Duplicate function name ${mcpToolName2} found in MCP tools. Please ensure function names are unique.`);
|
|
244716
244717
|
}
|
|
244717
|
-
functionMap[
|
|
244718
|
+
functionMap[mcpToolName2] = mcpClient;
|
|
244718
244719
|
}
|
|
244719
244720
|
} catch (e_1_1) {
|
|
244720
244721
|
e_1 = { error: e_1_1 };
|
|
@@ -251962,7 +251963,21 @@ var ModelSelector = class {
|
|
|
251962
251963
|
this.availableProviders = availableProviders;
|
|
251963
251964
|
this.availableModels = new Map(Object.entries(MODELS));
|
|
251964
251965
|
}
|
|
251966
|
+
/**
|
|
251967
|
+
* Verdicts from earlier runs about ids that 404 on this account. Consulted
|
|
251968
|
+
* BEFORE a model enters the pool, which is the only placement that helps: a
|
|
251969
|
+
* T3 wave runs concurrently, so filtering after the first failure still lets
|
|
251970
|
+
* every other worker in the wave call the dead id.
|
|
251971
|
+
*/
|
|
251972
|
+
deadModels;
|
|
251973
|
+
setDeadModelStore(store) {
|
|
251974
|
+
this.deadModels = store;
|
|
251975
|
+
for (const [id, m3] of [...this.availableModels]) {
|
|
251976
|
+
if (this.deadModels.isDead(m3.provider, m3.id)) this.availableModels.delete(id);
|
|
251977
|
+
}
|
|
251978
|
+
}
|
|
251965
251979
|
addDynamicModel(model) {
|
|
251980
|
+
if (this.deadModels?.isDead(model.provider, model.id)) return;
|
|
251966
251981
|
this.availableModels.set(model.id, model);
|
|
251967
251982
|
if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
|
|
251968
251983
|
}
|
|
@@ -252369,6 +252384,121 @@ var LocalRequestQueue = class {
|
|
|
252369
252384
|
}
|
|
252370
252385
|
};
|
|
252371
252386
|
|
|
252387
|
+
// src/core/router/dead-models.ts
|
|
252388
|
+
var DEAD_MODEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
252389
|
+
function deadModelKey(provider, modelId) {
|
|
252390
|
+
return `${provider}:${modelId}`;
|
|
252391
|
+
}
|
|
252392
|
+
var DeadModelStore = class {
|
|
252393
|
+
constructor(persistence, opts = {}) {
|
|
252394
|
+
this.persistence = persistence;
|
|
252395
|
+
this.ttlMs = opts.ttlMs ?? DEAD_MODEL_TTL_MS;
|
|
252396
|
+
this.now = opts.now ?? (() => Date.now());
|
|
252397
|
+
this.restore();
|
|
252398
|
+
}
|
|
252399
|
+
persistence;
|
|
252400
|
+
records = /* @__PURE__ */ new Map();
|
|
252401
|
+
ttlMs;
|
|
252402
|
+
now;
|
|
252403
|
+
restore() {
|
|
252404
|
+
if (!this.persistence) return;
|
|
252405
|
+
try {
|
|
252406
|
+
for (const r4 of this.persistence.load()) {
|
|
252407
|
+
if (!this.isExpired(r4)) this.records.set(r4.key, r4);
|
|
252408
|
+
}
|
|
252409
|
+
} catch {
|
|
252410
|
+
}
|
|
252411
|
+
}
|
|
252412
|
+
isExpired(r4) {
|
|
252413
|
+
return this.now() - r4.lastSeenAt > this.ttlMs;
|
|
252414
|
+
}
|
|
252415
|
+
flush() {
|
|
252416
|
+
if (!this.persistence) return;
|
|
252417
|
+
try {
|
|
252418
|
+
this.persistence.save([...this.records.values()]);
|
|
252419
|
+
} catch {
|
|
252420
|
+
}
|
|
252421
|
+
}
|
|
252422
|
+
/**
|
|
252423
|
+
* Record that a model was found dead. Returns true when this is the first
|
|
252424
|
+
* time (so the caller can log it once rather than once per concurrent
|
|
252425
|
+
* worker in the wave that discovered it).
|
|
252426
|
+
*/
|
|
252427
|
+
record(provider, modelId, reason) {
|
|
252428
|
+
const key = deadModelKey(provider, modelId);
|
|
252429
|
+
const now = this.now();
|
|
252430
|
+
const existing = this.records.get(key);
|
|
252431
|
+
if (existing && !this.isExpired(existing)) {
|
|
252432
|
+
existing.lastSeenAt = now;
|
|
252433
|
+
existing.hits += 1;
|
|
252434
|
+
this.flush();
|
|
252435
|
+
return false;
|
|
252436
|
+
}
|
|
252437
|
+
this.records.set(key, {
|
|
252438
|
+
key,
|
|
252439
|
+
provider,
|
|
252440
|
+
modelId,
|
|
252441
|
+
reason,
|
|
252442
|
+
firstSeenAt: now,
|
|
252443
|
+
lastSeenAt: now,
|
|
252444
|
+
hits: 1
|
|
252445
|
+
});
|
|
252446
|
+
this.flush();
|
|
252447
|
+
return true;
|
|
252448
|
+
}
|
|
252449
|
+
/** True when this model is known dead and the verdict has not expired. */
|
|
252450
|
+
isDead(provider, modelId) {
|
|
252451
|
+
const r4 = this.records.get(deadModelKey(provider, modelId));
|
|
252452
|
+
if (!r4) return false;
|
|
252453
|
+
if (this.isExpired(r4)) {
|
|
252454
|
+
this.records.delete(r4.key);
|
|
252455
|
+
this.flush();
|
|
252456
|
+
return false;
|
|
252457
|
+
}
|
|
252458
|
+
return true;
|
|
252459
|
+
}
|
|
252460
|
+
/** Why a model is considered dead, for /why and the models command. */
|
|
252461
|
+
reasonFor(provider, modelId) {
|
|
252462
|
+
const r4 = this.records.get(deadModelKey(provider, modelId));
|
|
252463
|
+
return r4 && !this.isExpired(r4) ? r4.reason : null;
|
|
252464
|
+
}
|
|
252465
|
+
/** Live records, expired ones filtered out. */
|
|
252466
|
+
list() {
|
|
252467
|
+
const live = [...this.records.values()].filter((r4) => !this.isExpired(r4));
|
|
252468
|
+
return live.sort((a2, b3) => b3.lastSeenAt - a2.lastSeenAt);
|
|
252469
|
+
}
|
|
252470
|
+
/**
|
|
252471
|
+
* Forget a verdict — used when the user fixes the underlying problem (adds
|
|
252472
|
+
* the model to their project, gets quota) and wants it retried now rather
|
|
252473
|
+
* than after the TTL.
|
|
252474
|
+
*/
|
|
252475
|
+
forget(provider, modelId) {
|
|
252476
|
+
const removed = this.records.delete(deadModelKey(provider, modelId));
|
|
252477
|
+
if (removed) this.flush();
|
|
252478
|
+
return removed;
|
|
252479
|
+
}
|
|
252480
|
+
clear() {
|
|
252481
|
+
this.records.clear();
|
|
252482
|
+
this.flush();
|
|
252483
|
+
}
|
|
252484
|
+
};
|
|
252485
|
+
function fileDeadModelPersistence(filePath) {
|
|
252486
|
+
return {
|
|
252487
|
+
load() {
|
|
252488
|
+
const fs31 = __require("fs");
|
|
252489
|
+
if (!fs31.existsSync(filePath)) return [];
|
|
252490
|
+
const raw = JSON.parse(fs31.readFileSync(filePath, "utf-8"));
|
|
252491
|
+
return Array.isArray(raw) ? raw : [];
|
|
252492
|
+
},
|
|
252493
|
+
save(records) {
|
|
252494
|
+
const fs31 = __require("fs");
|
|
252495
|
+
const path28 = __require("path");
|
|
252496
|
+
fs31.mkdirSync(path28.dirname(filePath), { recursive: true });
|
|
252497
|
+
fs31.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
252498
|
+
}
|
|
252499
|
+
};
|
|
252500
|
+
}
|
|
252501
|
+
|
|
252372
252502
|
// src/utils/retry.ts
|
|
252373
252503
|
var CascadeCancelledError = class extends Error {
|
|
252374
252504
|
constructor(reason) {
|
|
@@ -252962,6 +253092,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
252962
253092
|
localQueue;
|
|
252963
253093
|
taskAnalyzer;
|
|
252964
253094
|
pendingFeedbackSource;
|
|
253095
|
+
/** Models known dead for this account (404 / retired / not enabled). */
|
|
253096
|
+
deadModels = new DeadModelStore();
|
|
253097
|
+
/**
|
|
253098
|
+
* Back the dead-model memory with durable storage (a JSON file on desktop, a
|
|
253099
|
+
* per-user table in the cloud). Without one the verdicts still work, but only
|
|
253100
|
+
* for the life of the process — so tomorrow's run pays the same wave of 404s
|
|
253101
|
+
* to learn the same fact.
|
|
253102
|
+
*/
|
|
253103
|
+
setDeadModelStore(store) {
|
|
253104
|
+
this.deadModels = store;
|
|
253105
|
+
this.selector.setDeadModelStore(store);
|
|
253106
|
+
}
|
|
253107
|
+
/** Models known dead for this account, for `cascade models` and /why. */
|
|
253108
|
+
listDeadModels() {
|
|
253109
|
+
return this.deadModels.list();
|
|
253110
|
+
}
|
|
253111
|
+
/** Retry a model the user has since fixed, without waiting out the TTL. */
|
|
253112
|
+
forgetDeadModel(provider, modelId) {
|
|
253113
|
+
return this.deadModels.forget(provider, modelId);
|
|
253114
|
+
}
|
|
252965
253115
|
worldStateDB;
|
|
252966
253116
|
privacyPaths;
|
|
252967
253117
|
guidanceQueue;
|
|
@@ -253326,6 +253476,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253326
253476
|
}
|
|
253327
253477
|
if (isModelNotFoundError(errMsg)) {
|
|
253328
253478
|
this.selector.removeModel(model.id);
|
|
253479
|
+
const firstSighting = this.deadModels.record(model.provider, model.id, errMsg);
|
|
253480
|
+
if (firstSighting) this.emit("model:dead", { provider: model.provider, modelId: model.id, reason: errMsg });
|
|
253329
253481
|
const depth = (options._notFoundDepth ?? 0) + 1;
|
|
253330
253482
|
const next = depth <= 3 ? this.selector.selectForTier(tier) : null;
|
|
253331
253483
|
if (next && next.id !== model.id) {
|
|
@@ -257121,9 +257273,16 @@ var CAPABILITIES = [
|
|
|
257121
257273
|
api: "openai-images"
|
|
257122
257274
|
},
|
|
257123
257275
|
{
|
|
257276
|
+
// Imagen, not `gemini-3-pro-image`. That id appears on Google's PRICING
|
|
257277
|
+
// page but 404s on `:predict` ("not found for API version v1beta, or is not
|
|
257278
|
+
// supported for predict") — the Gemini image models are called through
|
|
257279
|
+
// generateContent with an image response modality, while `:predict` is the
|
|
257280
|
+
// Imagen shape this executor implements. A billing line item is not proof
|
|
257281
|
+
// of a callable endpoint, which is the assumption that put the wrong id
|
|
257282
|
+
// here in the first place.
|
|
257124
257283
|
modality: "image",
|
|
257125
257284
|
provider: "gemini",
|
|
257126
|
-
modelId: "
|
|
257285
|
+
modelId: "imagen-4.0-generate-001",
|
|
257127
257286
|
api: "gemini-predict"
|
|
257128
257287
|
},
|
|
257129
257288
|
// ── Speech (text → audio) ──
|
|
@@ -286141,6 +286300,20 @@ ${text}`;
|
|
|
286141
286300
|
}
|
|
286142
286301
|
};
|
|
286143
286302
|
|
|
286303
|
+
// src/tools/tool-name.ts
|
|
286304
|
+
var MCP_TOOL_PREFIX = "mcp__";
|
|
286305
|
+
function sanitizeToolNameSegment(segment) {
|
|
286306
|
+
const cleaned = segment.replace(/[^a-zA-Z0-9-]+/g, "_");
|
|
286307
|
+
let start = 0;
|
|
286308
|
+
let end = cleaned.length;
|
|
286309
|
+
while (start < end && cleaned[start] === "_") start++;
|
|
286310
|
+
while (end > start && cleaned[end - 1] === "_") end--;
|
|
286311
|
+
return cleaned.slice(start, end) || "_";
|
|
286312
|
+
}
|
|
286313
|
+
function mcpToolName(serverName, toolName) {
|
|
286314
|
+
return `${MCP_TOOL_PREFIX}${sanitizeToolNameSegment(serverName)}__${sanitizeToolNameSegment(toolName)}`;
|
|
286315
|
+
}
|
|
286316
|
+
|
|
286144
286317
|
// src/tools/mcp.ts
|
|
286145
286318
|
var McpToolWrapper = class extends BaseTool {
|
|
286146
286319
|
name;
|
|
@@ -286154,7 +286327,7 @@ var McpToolWrapper = class extends BaseTool {
|
|
|
286154
286327
|
this.mcpClient = mcpClient;
|
|
286155
286328
|
this.serverName = serverName;
|
|
286156
286329
|
this.toolName = toolName;
|
|
286157
|
-
this.name =
|
|
286330
|
+
this.name = mcpToolName(serverName, toolName);
|
|
286158
286331
|
this.description = `[MCP:${serverName}] ${description}`;
|
|
286159
286332
|
this.inputSchema = inputSchema;
|
|
286160
286333
|
}
|
|
@@ -302985,6 +303158,13 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
302985
303158
|
}
|
|
302986
303159
|
});
|
|
302987
303160
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
303161
|
+
try {
|
|
303162
|
+
const nodePath = __require("path");
|
|
303163
|
+
this.router.setDeadModelStore(new DeadModelStore(
|
|
303164
|
+
fileDeadModelPersistence(nodePath.join(workspacePath, ".cascade", "dead-models.json"))
|
|
303165
|
+
));
|
|
303166
|
+
} catch {
|
|
303167
|
+
}
|
|
302988
303168
|
this.multimodal = new MultimodalRegistry(
|
|
302989
303169
|
(this.config.providers ?? []).map((p3) => p3.type)
|
|
302990
303170
|
);
|
|
@@ -308886,6 +309066,7 @@ object-assign/index.js:
|
|
|
308886
309066
|
(*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *)
|
|
308887
309067
|
*/
|
|
308888
309068
|
|
|
309069
|
+
exports.AZURE_BASE_MODELS = AZURE_BASE_MODELS;
|
|
308889
309070
|
exports.AZURE_BASE_URL_TEMPLATE = AZURE_BASE_URL_TEMPLATE;
|
|
308890
309071
|
exports.AuditLogger = AuditLogger;
|
|
308891
309072
|
exports.CASCADE_AUDIT_FILE = CASCADE_AUDIT_FILE;
|
package/dist/index.cjs
CHANGED
|
@@ -87,6 +87,12 @@ var cron__default = /*#__PURE__*/_interopDefault(cron);
|
|
|
87
87
|
// Cascade AI — Multi-tier AI Orchestration System
|
|
88
88
|
var __defProp = Object.defineProperty;
|
|
89
89
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
90
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
91
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
92
|
+
}) : x)(function(x) {
|
|
93
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
94
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
95
|
+
});
|
|
90
96
|
var __esm = (fn, res) => function __init() {
|
|
91
97
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
92
98
|
};
|
|
@@ -1891,7 +1897,7 @@ function reconcilePrice(model, live) {
|
|
|
1891
1897
|
}
|
|
1892
1898
|
|
|
1893
1899
|
// src/constants.ts
|
|
1894
|
-
var CASCADE_VERSION = "0.
|
|
1900
|
+
var CASCADE_VERSION = "0.54.0";
|
|
1895
1901
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1896
1902
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1897
1903
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -2856,22 +2862,23 @@ var OpenAIProvider = class extends BaseProvider {
|
|
|
2856
2862
|
|
|
2857
2863
|
// src/providers/azure.ts
|
|
2858
2864
|
var DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
|
|
2865
|
+
var AZURE_BASE_MODEL_RULES = [
|
|
2866
|
+
[/gpt-?5\.5/, "gpt-5.5"],
|
|
2867
|
+
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
2868
|
+
[/gpt-?5\.4/, "gpt-5.4"],
|
|
2869
|
+
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
2870
|
+
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
2871
|
+
[/gpt-?5/, "gpt-5"],
|
|
2872
|
+
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
2873
|
+
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
2874
|
+
[/gpt-?4\.1/, "gpt-4.1"],
|
|
2875
|
+
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
2876
|
+
[/gpt-?4o/, "gpt-4o"]
|
|
2877
|
+
];
|
|
2878
|
+
var AZURE_BASE_MODELS = AZURE_BASE_MODEL_RULES.map(([, id]) => id);
|
|
2859
2879
|
function inferAzureBaseModel(deploymentName) {
|
|
2860
2880
|
const n = deploymentName.toLowerCase();
|
|
2861
|
-
const
|
|
2862
|
-
[/gpt-?5\.5/, "gpt-5.5"],
|
|
2863
|
-
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
2864
|
-
[/gpt-?5\.4/, "gpt-5.4"],
|
|
2865
|
-
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
2866
|
-
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
2867
|
-
[/gpt-?5/, "gpt-5"],
|
|
2868
|
-
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
2869
|
-
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
2870
|
-
[/gpt-?4\.1/, "gpt-4.1"],
|
|
2871
|
-
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
2872
|
-
[/gpt-?4o/, "gpt-4o"]
|
|
2873
|
-
];
|
|
2874
|
-
for (const [re, base] of rules) if (re.test(n)) return base;
|
|
2881
|
+
for (const [re, base] of AZURE_BASE_MODEL_RULES) if (re.test(n)) return base;
|
|
2875
2882
|
return null;
|
|
2876
2883
|
}
|
|
2877
2884
|
function azureModelForDeployment(cfg) {
|
|
@@ -3703,7 +3710,21 @@ var ModelSelector = class {
|
|
|
3703
3710
|
this.availableProviders = availableProviders;
|
|
3704
3711
|
this.availableModels = new Map(Object.entries(MODELS));
|
|
3705
3712
|
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Verdicts from earlier runs about ids that 404 on this account. Consulted
|
|
3715
|
+
* BEFORE a model enters the pool, which is the only placement that helps: a
|
|
3716
|
+
* T3 wave runs concurrently, so filtering after the first failure still lets
|
|
3717
|
+
* every other worker in the wave call the dead id.
|
|
3718
|
+
*/
|
|
3719
|
+
deadModels;
|
|
3720
|
+
setDeadModelStore(store) {
|
|
3721
|
+
this.deadModels = store;
|
|
3722
|
+
for (const [id, m] of [...this.availableModels]) {
|
|
3723
|
+
if (this.deadModels.isDead(m.provider, m.id)) this.availableModels.delete(id);
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3706
3726
|
addDynamicModel(model) {
|
|
3727
|
+
if (this.deadModels?.isDead(model.provider, model.id)) return;
|
|
3707
3728
|
this.availableModels.set(model.id, model);
|
|
3708
3729
|
if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
|
|
3709
3730
|
}
|
|
@@ -4110,6 +4131,121 @@ var LocalRequestQueue = class {
|
|
|
4110
4131
|
}
|
|
4111
4132
|
};
|
|
4112
4133
|
|
|
4134
|
+
// src/core/router/dead-models.ts
|
|
4135
|
+
var DEAD_MODEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4136
|
+
function deadModelKey(provider, modelId) {
|
|
4137
|
+
return `${provider}:${modelId}`;
|
|
4138
|
+
}
|
|
4139
|
+
var DeadModelStore = class {
|
|
4140
|
+
constructor(persistence, opts = {}) {
|
|
4141
|
+
this.persistence = persistence;
|
|
4142
|
+
this.ttlMs = opts.ttlMs ?? DEAD_MODEL_TTL_MS;
|
|
4143
|
+
this.now = opts.now ?? (() => Date.now());
|
|
4144
|
+
this.restore();
|
|
4145
|
+
}
|
|
4146
|
+
persistence;
|
|
4147
|
+
records = /* @__PURE__ */ new Map();
|
|
4148
|
+
ttlMs;
|
|
4149
|
+
now;
|
|
4150
|
+
restore() {
|
|
4151
|
+
if (!this.persistence) return;
|
|
4152
|
+
try {
|
|
4153
|
+
for (const r of this.persistence.load()) {
|
|
4154
|
+
if (!this.isExpired(r)) this.records.set(r.key, r);
|
|
4155
|
+
}
|
|
4156
|
+
} catch {
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
isExpired(r) {
|
|
4160
|
+
return this.now() - r.lastSeenAt > this.ttlMs;
|
|
4161
|
+
}
|
|
4162
|
+
flush() {
|
|
4163
|
+
if (!this.persistence) return;
|
|
4164
|
+
try {
|
|
4165
|
+
this.persistence.save([...this.records.values()]);
|
|
4166
|
+
} catch {
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
/**
|
|
4170
|
+
* Record that a model was found dead. Returns true when this is the first
|
|
4171
|
+
* time (so the caller can log it once rather than once per concurrent
|
|
4172
|
+
* worker in the wave that discovered it).
|
|
4173
|
+
*/
|
|
4174
|
+
record(provider, modelId, reason) {
|
|
4175
|
+
const key = deadModelKey(provider, modelId);
|
|
4176
|
+
const now = this.now();
|
|
4177
|
+
const existing = this.records.get(key);
|
|
4178
|
+
if (existing && !this.isExpired(existing)) {
|
|
4179
|
+
existing.lastSeenAt = now;
|
|
4180
|
+
existing.hits += 1;
|
|
4181
|
+
this.flush();
|
|
4182
|
+
return false;
|
|
4183
|
+
}
|
|
4184
|
+
this.records.set(key, {
|
|
4185
|
+
key,
|
|
4186
|
+
provider,
|
|
4187
|
+
modelId,
|
|
4188
|
+
reason,
|
|
4189
|
+
firstSeenAt: now,
|
|
4190
|
+
lastSeenAt: now,
|
|
4191
|
+
hits: 1
|
|
4192
|
+
});
|
|
4193
|
+
this.flush();
|
|
4194
|
+
return true;
|
|
4195
|
+
}
|
|
4196
|
+
/** True when this model is known dead and the verdict has not expired. */
|
|
4197
|
+
isDead(provider, modelId) {
|
|
4198
|
+
const r = this.records.get(deadModelKey(provider, modelId));
|
|
4199
|
+
if (!r) return false;
|
|
4200
|
+
if (this.isExpired(r)) {
|
|
4201
|
+
this.records.delete(r.key);
|
|
4202
|
+
this.flush();
|
|
4203
|
+
return false;
|
|
4204
|
+
}
|
|
4205
|
+
return true;
|
|
4206
|
+
}
|
|
4207
|
+
/** Why a model is considered dead, for /why and the models command. */
|
|
4208
|
+
reasonFor(provider, modelId) {
|
|
4209
|
+
const r = this.records.get(deadModelKey(provider, modelId));
|
|
4210
|
+
return r && !this.isExpired(r) ? r.reason : null;
|
|
4211
|
+
}
|
|
4212
|
+
/** Live records, expired ones filtered out. */
|
|
4213
|
+
list() {
|
|
4214
|
+
const live = [...this.records.values()].filter((r) => !this.isExpired(r));
|
|
4215
|
+
return live.sort((a, b) => b.lastSeenAt - a.lastSeenAt);
|
|
4216
|
+
}
|
|
4217
|
+
/**
|
|
4218
|
+
* Forget a verdict — used when the user fixes the underlying problem (adds
|
|
4219
|
+
* the model to their project, gets quota) and wants it retried now rather
|
|
4220
|
+
* than after the TTL.
|
|
4221
|
+
*/
|
|
4222
|
+
forget(provider, modelId) {
|
|
4223
|
+
const removed = this.records.delete(deadModelKey(provider, modelId));
|
|
4224
|
+
if (removed) this.flush();
|
|
4225
|
+
return removed;
|
|
4226
|
+
}
|
|
4227
|
+
clear() {
|
|
4228
|
+
this.records.clear();
|
|
4229
|
+
this.flush();
|
|
4230
|
+
}
|
|
4231
|
+
};
|
|
4232
|
+
function fileDeadModelPersistence(filePath) {
|
|
4233
|
+
return {
|
|
4234
|
+
load() {
|
|
4235
|
+
const fs25 = __require("fs");
|
|
4236
|
+
if (!fs25.existsSync(filePath)) return [];
|
|
4237
|
+
const raw = JSON.parse(fs25.readFileSync(filePath, "utf-8"));
|
|
4238
|
+
return Array.isArray(raw) ? raw : [];
|
|
4239
|
+
},
|
|
4240
|
+
save(records) {
|
|
4241
|
+
const fs25 = __require("fs");
|
|
4242
|
+
const path26 = __require("path");
|
|
4243
|
+
fs25.mkdirSync(path26.dirname(filePath), { recursive: true });
|
|
4244
|
+
fs25.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
4245
|
+
}
|
|
4246
|
+
};
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4113
4249
|
// src/utils/retry.ts
|
|
4114
4250
|
var CascadeCancelledError = class extends Error {
|
|
4115
4251
|
constructor(reason) {
|
|
@@ -4703,6 +4839,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
4703
4839
|
localQueue;
|
|
4704
4840
|
taskAnalyzer;
|
|
4705
4841
|
pendingFeedbackSource;
|
|
4842
|
+
/** Models known dead for this account (404 / retired / not enabled). */
|
|
4843
|
+
deadModels = new DeadModelStore();
|
|
4844
|
+
/**
|
|
4845
|
+
* Back the dead-model memory with durable storage (a JSON file on desktop, a
|
|
4846
|
+
* per-user table in the cloud). Without one the verdicts still work, but only
|
|
4847
|
+
* for the life of the process — so tomorrow's run pays the same wave of 404s
|
|
4848
|
+
* to learn the same fact.
|
|
4849
|
+
*/
|
|
4850
|
+
setDeadModelStore(store) {
|
|
4851
|
+
this.deadModels = store;
|
|
4852
|
+
this.selector.setDeadModelStore(store);
|
|
4853
|
+
}
|
|
4854
|
+
/** Models known dead for this account, for `cascade models` and /why. */
|
|
4855
|
+
listDeadModels() {
|
|
4856
|
+
return this.deadModels.list();
|
|
4857
|
+
}
|
|
4858
|
+
/** Retry a model the user has since fixed, without waiting out the TTL. */
|
|
4859
|
+
forgetDeadModel(provider, modelId) {
|
|
4860
|
+
return this.deadModels.forget(provider, modelId);
|
|
4861
|
+
}
|
|
4706
4862
|
worldStateDB;
|
|
4707
4863
|
privacyPaths;
|
|
4708
4864
|
guidanceQueue;
|
|
@@ -5067,6 +5223,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
5067
5223
|
}
|
|
5068
5224
|
if (isModelNotFoundError(errMsg)) {
|
|
5069
5225
|
this.selector.removeModel(model.id);
|
|
5226
|
+
const firstSighting = this.deadModels.record(model.provider, model.id, errMsg);
|
|
5227
|
+
if (firstSighting) this.emit("model:dead", { provider: model.provider, modelId: model.id, reason: errMsg });
|
|
5070
5228
|
const depth = (options._notFoundDepth ?? 0) + 1;
|
|
5071
5229
|
const next = depth <= 3 ? this.selector.selectForTier(tier) : null;
|
|
5072
5230
|
if (next && next.id !== model.id) {
|
|
@@ -8862,9 +9020,16 @@ var CAPABILITIES = [
|
|
|
8862
9020
|
api: "openai-images"
|
|
8863
9021
|
},
|
|
8864
9022
|
{
|
|
9023
|
+
// Imagen, not `gemini-3-pro-image`. That id appears on Google's PRICING
|
|
9024
|
+
// page but 404s on `:predict` ("not found for API version v1beta, or is not
|
|
9025
|
+
// supported for predict") — the Gemini image models are called through
|
|
9026
|
+
// generateContent with an image response modality, while `:predict` is the
|
|
9027
|
+
// Imagen shape this executor implements. A billing line item is not proof
|
|
9028
|
+
// of a callable endpoint, which is the assumption that put the wrong id
|
|
9029
|
+
// here in the first place.
|
|
8865
9030
|
modality: "image",
|
|
8866
9031
|
provider: "gemini",
|
|
8867
|
-
modelId: "
|
|
9032
|
+
modelId: "imagen-4.0-generate-001",
|
|
8868
9033
|
api: "gemini-predict"
|
|
8869
9034
|
},
|
|
8870
9035
|
// ── Speech (text → audio) ──
|
|
@@ -10993,6 +11158,20 @@ ${text}`;
|
|
|
10993
11158
|
}
|
|
10994
11159
|
};
|
|
10995
11160
|
|
|
11161
|
+
// src/tools/tool-name.ts
|
|
11162
|
+
var MCP_TOOL_PREFIX = "mcp__";
|
|
11163
|
+
function sanitizeToolNameSegment(segment) {
|
|
11164
|
+
const cleaned = segment.replace(/[^a-zA-Z0-9-]+/g, "_");
|
|
11165
|
+
let start = 0;
|
|
11166
|
+
let end = cleaned.length;
|
|
11167
|
+
while (start < end && cleaned[start] === "_") start++;
|
|
11168
|
+
while (end > start && cleaned[end - 1] === "_") end--;
|
|
11169
|
+
return cleaned.slice(start, end) || "_";
|
|
11170
|
+
}
|
|
11171
|
+
function mcpToolName(serverName, toolName) {
|
|
11172
|
+
return `${MCP_TOOL_PREFIX}${sanitizeToolNameSegment(serverName)}__${sanitizeToolNameSegment(toolName)}`;
|
|
11173
|
+
}
|
|
11174
|
+
|
|
10996
11175
|
// src/tools/mcp.ts
|
|
10997
11176
|
var McpToolWrapper = class extends BaseTool {
|
|
10998
11177
|
name;
|
|
@@ -11006,7 +11185,7 @@ var McpToolWrapper = class extends BaseTool {
|
|
|
11006
11185
|
this.mcpClient = mcpClient;
|
|
11007
11186
|
this.serverName = serverName;
|
|
11008
11187
|
this.toolName = toolName;
|
|
11009
|
-
this.name =
|
|
11188
|
+
this.name = mcpToolName(serverName, toolName);
|
|
11010
11189
|
this.description = `[MCP:${serverName}] ${description}`;
|
|
11011
11190
|
this.inputSchema = inputSchema;
|
|
11012
11191
|
}
|
|
@@ -14422,6 +14601,13 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
14422
14601
|
}
|
|
14423
14602
|
});
|
|
14424
14603
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
14604
|
+
try {
|
|
14605
|
+
const nodePath = __require("path");
|
|
14606
|
+
this.router.setDeadModelStore(new DeadModelStore(
|
|
14607
|
+
fileDeadModelPersistence(nodePath.join(workspacePath, ".cascade", "dead-models.json"))
|
|
14608
|
+
));
|
|
14609
|
+
} catch {
|
|
14610
|
+
}
|
|
14425
14611
|
this.multimodal = new MultimodalRegistry(
|
|
14426
14612
|
(this.config.providers ?? []).map((p) => p.type)
|
|
14427
14613
|
);
|
|
@@ -19139,6 +19325,7 @@ function applySyncBundle(bundle, config) {
|
|
|
19139
19325
|
return next;
|
|
19140
19326
|
}
|
|
19141
19327
|
|
|
19328
|
+
exports.AZURE_BASE_MODELS = AZURE_BASE_MODELS;
|
|
19142
19329
|
exports.AZURE_BASE_URL_TEMPLATE = AZURE_BASE_URL_TEMPLATE;
|
|
19143
19330
|
exports.AuditLogger = AuditLogger;
|
|
19144
19331
|
exports.CASCADE_AUDIT_FILE = CASCADE_AUDIT_FILE;
|