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/cli.cjs
CHANGED
|
@@ -104,6 +104,12 @@ 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
|
+
});
|
|
107
113
|
var __esm = (fn, res) => function __init() {
|
|
108
114
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
109
115
|
};
|
|
@@ -1793,7 +1799,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
|
|
|
1793
1799
|
var init_constants = __esm({
|
|
1794
1800
|
"src/constants.ts"() {
|
|
1795
1801
|
init_pricing();
|
|
1796
|
-
CASCADE_VERSION = "0.
|
|
1802
|
+
CASCADE_VERSION = "0.54.0";
|
|
1797
1803
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1798
1804
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1799
1805
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -2792,26 +2798,14 @@ var init_openai = __esm({
|
|
|
2792
2798
|
// src/providers/azure.ts
|
|
2793
2799
|
var azure_exports = {};
|
|
2794
2800
|
__export(azure_exports, {
|
|
2801
|
+
AZURE_BASE_MODELS: () => AZURE_BASE_MODELS,
|
|
2795
2802
|
AzureOpenAIProvider: () => AzureOpenAIProvider,
|
|
2796
2803
|
azureModelForDeployment: () => azureModelForDeployment,
|
|
2797
2804
|
inferAzureBaseModel: () => inferAzureBaseModel
|
|
2798
2805
|
});
|
|
2799
2806
|
function inferAzureBaseModel(deploymentName) {
|
|
2800
2807
|
const n = deploymentName.toLowerCase();
|
|
2801
|
-
const
|
|
2802
|
-
[/gpt-?5\.5/, "gpt-5.5"],
|
|
2803
|
-
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
2804
|
-
[/gpt-?5\.4/, "gpt-5.4"],
|
|
2805
|
-
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
2806
|
-
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
2807
|
-
[/gpt-?5/, "gpt-5"],
|
|
2808
|
-
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
2809
|
-
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
2810
|
-
[/gpt-?4\.1/, "gpt-4.1"],
|
|
2811
|
-
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
2812
|
-
[/gpt-?4o/, "gpt-4o"]
|
|
2813
|
-
];
|
|
2814
|
-
for (const [re, base] of rules) if (re.test(n)) return base;
|
|
2808
|
+
for (const [re, base] of AZURE_BASE_MODEL_RULES) if (re.test(n)) return base;
|
|
2815
2809
|
return null;
|
|
2816
2810
|
}
|
|
2817
2811
|
function azureModelForDeployment(cfg) {
|
|
@@ -2856,13 +2850,27 @@ function azureModelForDeployment(cfg) {
|
|
|
2856
2850
|
supportsToolUse: true
|
|
2857
2851
|
};
|
|
2858
2852
|
}
|
|
2859
|
-
var DEFAULT_AZURE_API_VERSION, AzureOpenAIProvider;
|
|
2853
|
+
var DEFAULT_AZURE_API_VERSION, AZURE_BASE_MODEL_RULES, AZURE_BASE_MODELS, AzureOpenAIProvider;
|
|
2860
2854
|
var init_azure = __esm({
|
|
2861
2855
|
"src/providers/azure.ts"() {
|
|
2862
2856
|
init_constants();
|
|
2863
2857
|
init_openai();
|
|
2864
2858
|
init_pricing();
|
|
2865
2859
|
DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
|
|
2860
|
+
AZURE_BASE_MODEL_RULES = [
|
|
2861
|
+
[/gpt-?5\.5/, "gpt-5.5"],
|
|
2862
|
+
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
2863
|
+
[/gpt-?5\.4/, "gpt-5.4"],
|
|
2864
|
+
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
2865
|
+
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
2866
|
+
[/gpt-?5/, "gpt-5"],
|
|
2867
|
+
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
2868
|
+
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
2869
|
+
[/gpt-?4\.1/, "gpt-4.1"],
|
|
2870
|
+
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
2871
|
+
[/gpt-?4o/, "gpt-4o"]
|
|
2872
|
+
];
|
|
2873
|
+
AZURE_BASE_MODELS = AZURE_BASE_MODEL_RULES.map(([, id]) => id);
|
|
2866
2874
|
AzureOpenAIProvider = class extends OpenAIProvider {
|
|
2867
2875
|
constructor(config, model) {
|
|
2868
2876
|
const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
|
|
@@ -5563,6 +5571,26 @@ var ConfigManager = class {
|
|
|
5563
5571
|
}
|
|
5564
5572
|
};
|
|
5565
5573
|
|
|
5574
|
+
// src/tools/tool-name.ts
|
|
5575
|
+
var MCP_TOOL_PREFIX = "mcp__";
|
|
5576
|
+
function sanitizeToolNameSegment(segment) {
|
|
5577
|
+
const cleaned = segment.replace(/[^a-zA-Z0-9-]+/g, "_");
|
|
5578
|
+
let start = 0;
|
|
5579
|
+
let end = cleaned.length;
|
|
5580
|
+
while (start < end && cleaned[start] === "_") start++;
|
|
5581
|
+
while (end > start && cleaned[end - 1] === "_") end--;
|
|
5582
|
+
return cleaned.slice(start, end) || "_";
|
|
5583
|
+
}
|
|
5584
|
+
function mcpToolName(serverName, toolName) {
|
|
5585
|
+
return `${MCP_TOOL_PREFIX}${sanitizeToolNameSegment(serverName)}__${sanitizeToolNameSegment(toolName)}`;
|
|
5586
|
+
}
|
|
5587
|
+
function isMcpToolName(name) {
|
|
5588
|
+
return name.startsWith(MCP_TOOL_PREFIX);
|
|
5589
|
+
}
|
|
5590
|
+
function mcpServerPrefix(serverName) {
|
|
5591
|
+
return `${MCP_TOOL_PREFIX}${sanitizeToolNameSegment(serverName)}__`;
|
|
5592
|
+
}
|
|
5593
|
+
|
|
5566
5594
|
// src/cli/utils/terminal-input.ts
|
|
5567
5595
|
var PASTE_START = "\x1B[200~";
|
|
5568
5596
|
var PASTE_END = "\x1B[201~";
|
|
@@ -5887,7 +5915,21 @@ var ModelSelector = class {
|
|
|
5887
5915
|
this.availableProviders = availableProviders;
|
|
5888
5916
|
this.availableModels = new Map(Object.entries(MODELS));
|
|
5889
5917
|
}
|
|
5918
|
+
/**
|
|
5919
|
+
* Verdicts from earlier runs about ids that 404 on this account. Consulted
|
|
5920
|
+
* BEFORE a model enters the pool, which is the only placement that helps: a
|
|
5921
|
+
* T3 wave runs concurrently, so filtering after the first failure still lets
|
|
5922
|
+
* every other worker in the wave call the dead id.
|
|
5923
|
+
*/
|
|
5924
|
+
deadModels;
|
|
5925
|
+
setDeadModelStore(store) {
|
|
5926
|
+
this.deadModels = store;
|
|
5927
|
+
for (const [id, m] of [...this.availableModels]) {
|
|
5928
|
+
if (this.deadModels.isDead(m.provider, m.id)) this.availableModels.delete(id);
|
|
5929
|
+
}
|
|
5930
|
+
}
|
|
5890
5931
|
addDynamicModel(model) {
|
|
5932
|
+
if (this.deadModels?.isDead(model.provider, model.id)) return;
|
|
5891
5933
|
this.availableModels.set(model.id, model);
|
|
5892
5934
|
if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
|
|
5893
5935
|
}
|
|
@@ -6294,6 +6336,121 @@ var LocalRequestQueue = class {
|
|
|
6294
6336
|
}
|
|
6295
6337
|
};
|
|
6296
6338
|
|
|
6339
|
+
// src/core/router/dead-models.ts
|
|
6340
|
+
var DEAD_MODEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6341
|
+
function deadModelKey(provider, modelId) {
|
|
6342
|
+
return `${provider}:${modelId}`;
|
|
6343
|
+
}
|
|
6344
|
+
var DeadModelStore = class {
|
|
6345
|
+
constructor(persistence, opts = {}) {
|
|
6346
|
+
this.persistence = persistence;
|
|
6347
|
+
this.ttlMs = opts.ttlMs ?? DEAD_MODEL_TTL_MS;
|
|
6348
|
+
this.now = opts.now ?? (() => Date.now());
|
|
6349
|
+
this.restore();
|
|
6350
|
+
}
|
|
6351
|
+
persistence;
|
|
6352
|
+
records = /* @__PURE__ */ new Map();
|
|
6353
|
+
ttlMs;
|
|
6354
|
+
now;
|
|
6355
|
+
restore() {
|
|
6356
|
+
if (!this.persistence) return;
|
|
6357
|
+
try {
|
|
6358
|
+
for (const r of this.persistence.load()) {
|
|
6359
|
+
if (!this.isExpired(r)) this.records.set(r.key, r);
|
|
6360
|
+
}
|
|
6361
|
+
} catch {
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
isExpired(r) {
|
|
6365
|
+
return this.now() - r.lastSeenAt > this.ttlMs;
|
|
6366
|
+
}
|
|
6367
|
+
flush() {
|
|
6368
|
+
if (!this.persistence) return;
|
|
6369
|
+
try {
|
|
6370
|
+
this.persistence.save([...this.records.values()]);
|
|
6371
|
+
} catch {
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
/**
|
|
6375
|
+
* Record that a model was found dead. Returns true when this is the first
|
|
6376
|
+
* time (so the caller can log it once rather than once per concurrent
|
|
6377
|
+
* worker in the wave that discovered it).
|
|
6378
|
+
*/
|
|
6379
|
+
record(provider, modelId, reason) {
|
|
6380
|
+
const key = deadModelKey(provider, modelId);
|
|
6381
|
+
const now = this.now();
|
|
6382
|
+
const existing = this.records.get(key);
|
|
6383
|
+
if (existing && !this.isExpired(existing)) {
|
|
6384
|
+
existing.lastSeenAt = now;
|
|
6385
|
+
existing.hits += 1;
|
|
6386
|
+
this.flush();
|
|
6387
|
+
return false;
|
|
6388
|
+
}
|
|
6389
|
+
this.records.set(key, {
|
|
6390
|
+
key,
|
|
6391
|
+
provider,
|
|
6392
|
+
modelId,
|
|
6393
|
+
reason,
|
|
6394
|
+
firstSeenAt: now,
|
|
6395
|
+
lastSeenAt: now,
|
|
6396
|
+
hits: 1
|
|
6397
|
+
});
|
|
6398
|
+
this.flush();
|
|
6399
|
+
return true;
|
|
6400
|
+
}
|
|
6401
|
+
/** True when this model is known dead and the verdict has not expired. */
|
|
6402
|
+
isDead(provider, modelId) {
|
|
6403
|
+
const r = this.records.get(deadModelKey(provider, modelId));
|
|
6404
|
+
if (!r) return false;
|
|
6405
|
+
if (this.isExpired(r)) {
|
|
6406
|
+
this.records.delete(r.key);
|
|
6407
|
+
this.flush();
|
|
6408
|
+
return false;
|
|
6409
|
+
}
|
|
6410
|
+
return true;
|
|
6411
|
+
}
|
|
6412
|
+
/** Why a model is considered dead, for /why and the models command. */
|
|
6413
|
+
reasonFor(provider, modelId) {
|
|
6414
|
+
const r = this.records.get(deadModelKey(provider, modelId));
|
|
6415
|
+
return r && !this.isExpired(r) ? r.reason : null;
|
|
6416
|
+
}
|
|
6417
|
+
/** Live records, expired ones filtered out. */
|
|
6418
|
+
list() {
|
|
6419
|
+
const live = [...this.records.values()].filter((r) => !this.isExpired(r));
|
|
6420
|
+
return live.sort((a, b) => b.lastSeenAt - a.lastSeenAt);
|
|
6421
|
+
}
|
|
6422
|
+
/**
|
|
6423
|
+
* Forget a verdict — used when the user fixes the underlying problem (adds
|
|
6424
|
+
* the model to their project, gets quota) and wants it retried now rather
|
|
6425
|
+
* than after the TTL.
|
|
6426
|
+
*/
|
|
6427
|
+
forget(provider, modelId) {
|
|
6428
|
+
const removed = this.records.delete(deadModelKey(provider, modelId));
|
|
6429
|
+
if (removed) this.flush();
|
|
6430
|
+
return removed;
|
|
6431
|
+
}
|
|
6432
|
+
clear() {
|
|
6433
|
+
this.records.clear();
|
|
6434
|
+
this.flush();
|
|
6435
|
+
}
|
|
6436
|
+
};
|
|
6437
|
+
function fileDeadModelPersistence(filePath) {
|
|
6438
|
+
return {
|
|
6439
|
+
load() {
|
|
6440
|
+
const fs32 = __require("fs");
|
|
6441
|
+
if (!fs32.existsSync(filePath)) return [];
|
|
6442
|
+
const raw = JSON.parse(fs32.readFileSync(filePath, "utf-8"));
|
|
6443
|
+
return Array.isArray(raw) ? raw : [];
|
|
6444
|
+
},
|
|
6445
|
+
save(records) {
|
|
6446
|
+
const fs32 = __require("fs");
|
|
6447
|
+
const path37 = __require("path");
|
|
6448
|
+
fs32.mkdirSync(path37.dirname(filePath), { recursive: true });
|
|
6449
|
+
fs32.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
6450
|
+
}
|
|
6451
|
+
};
|
|
6452
|
+
}
|
|
6453
|
+
|
|
6297
6454
|
// src/core/router/index.ts
|
|
6298
6455
|
init_constants();
|
|
6299
6456
|
init_cost();
|
|
@@ -6883,6 +7040,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
6883
7040
|
localQueue;
|
|
6884
7041
|
taskAnalyzer;
|
|
6885
7042
|
pendingFeedbackSource;
|
|
7043
|
+
/** Models known dead for this account (404 / retired / not enabled). */
|
|
7044
|
+
deadModels = new DeadModelStore();
|
|
7045
|
+
/**
|
|
7046
|
+
* Back the dead-model memory with durable storage (a JSON file on desktop, a
|
|
7047
|
+
* per-user table in the cloud). Without one the verdicts still work, but only
|
|
7048
|
+
* for the life of the process — so tomorrow's run pays the same wave of 404s
|
|
7049
|
+
* to learn the same fact.
|
|
7050
|
+
*/
|
|
7051
|
+
setDeadModelStore(store) {
|
|
7052
|
+
this.deadModels = store;
|
|
7053
|
+
this.selector.setDeadModelStore(store);
|
|
7054
|
+
}
|
|
7055
|
+
/** Models known dead for this account, for `cascade models` and /why. */
|
|
7056
|
+
listDeadModels() {
|
|
7057
|
+
return this.deadModels.list();
|
|
7058
|
+
}
|
|
7059
|
+
/** Retry a model the user has since fixed, without waiting out the TTL. */
|
|
7060
|
+
forgetDeadModel(provider, modelId) {
|
|
7061
|
+
return this.deadModels.forget(provider, modelId);
|
|
7062
|
+
}
|
|
6886
7063
|
worldStateDB;
|
|
6887
7064
|
privacyPaths;
|
|
6888
7065
|
guidanceQueue;
|
|
@@ -7247,6 +7424,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7247
7424
|
}
|
|
7248
7425
|
if (isModelNotFoundError(errMsg)) {
|
|
7249
7426
|
this.selector.removeModel(model.id);
|
|
7427
|
+
const firstSighting = this.deadModels.record(model.provider, model.id, errMsg);
|
|
7428
|
+
if (firstSighting) this.emit("model:dead", { provider: model.provider, modelId: model.id, reason: errMsg });
|
|
7250
7429
|
const depth = (options._notFoundDepth ?? 0) + 1;
|
|
7251
7430
|
const next = depth <= 3 ? this.selector.selectForTier(tier) : null;
|
|
7252
7431
|
if (next && next.id !== model.id) {
|
|
@@ -11050,9 +11229,16 @@ var CAPABILITIES = [
|
|
|
11050
11229
|
api: "openai-images"
|
|
11051
11230
|
},
|
|
11052
11231
|
{
|
|
11232
|
+
// Imagen, not `gemini-3-pro-image`. That id appears on Google's PRICING
|
|
11233
|
+
// page but 404s on `:predict` ("not found for API version v1beta, or is not
|
|
11234
|
+
// supported for predict") — the Gemini image models are called through
|
|
11235
|
+
// generateContent with an image response modality, while `:predict` is the
|
|
11236
|
+
// Imagen shape this executor implements. A billing line item is not proof
|
|
11237
|
+
// of a callable endpoint, which is the assumption that put the wrong id
|
|
11238
|
+
// here in the first place.
|
|
11053
11239
|
modality: "image",
|
|
11054
11240
|
provider: "gemini",
|
|
11055
|
-
modelId: "
|
|
11241
|
+
modelId: "imagen-4.0-generate-001",
|
|
11056
11242
|
api: "gemini-predict"
|
|
11057
11243
|
},
|
|
11058
11244
|
// ── Speech (text → audio) ──
|
|
@@ -13197,7 +13383,7 @@ var McpToolWrapper = class extends BaseTool {
|
|
|
13197
13383
|
this.mcpClient = mcpClient;
|
|
13198
13384
|
this.serverName = serverName;
|
|
13199
13385
|
this.toolName = toolName;
|
|
13200
|
-
this.name =
|
|
13386
|
+
this.name = mcpToolName(serverName, toolName);
|
|
13201
13387
|
this.description = `[MCP:${serverName}] ${description}`;
|
|
13202
13388
|
this.inputSchema = inputSchema;
|
|
13203
13389
|
}
|
|
@@ -16155,6 +16341,13 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
16155
16341
|
}
|
|
16156
16342
|
});
|
|
16157
16343
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
16344
|
+
try {
|
|
16345
|
+
const nodePath = __require("path");
|
|
16346
|
+
this.router.setDeadModelStore(new DeadModelStore(
|
|
16347
|
+
fileDeadModelPersistence(nodePath.join(workspacePath, ".cascade", "dead-models.json"))
|
|
16348
|
+
));
|
|
16349
|
+
} catch {
|
|
16350
|
+
}
|
|
16158
16351
|
this.multimodal = new MultimodalRegistry(
|
|
16159
16352
|
(this.config.providers ?? []).map((p) => p.type)
|
|
16160
16353
|
);
|
|
@@ -19558,13 +19751,13 @@ ${msg.content}`).join("\n\n");
|
|
|
19558
19751
|
const cascade = cascadeRef.current;
|
|
19559
19752
|
if (!cascade) return "Cascade not initialized.";
|
|
19560
19753
|
const toolRegistry = cascade.getToolRegistry();
|
|
19561
|
-
const tools = toolRegistry.getToolDefinitions().filter((t) => t.name
|
|
19754
|
+
const tools = toolRegistry.getToolDefinitions().filter((t) => isMcpToolName(t.name));
|
|
19562
19755
|
if (tools.length === 0) return "No MCP tools connected.";
|
|
19563
19756
|
const servers = /* @__PURE__ */ new Set();
|
|
19564
19757
|
tools.forEach((t) => servers.add(t.name.split("::")[1]));
|
|
19565
19758
|
const lines = [`Connected MCP Servers (${servers.size}):`];
|
|
19566
19759
|
for (const server of servers) {
|
|
19567
|
-
const serverTools = tools.filter((t) => t.name.startsWith(
|
|
19760
|
+
const serverTools = tools.filter((t) => t.name.startsWith(mcpServerPrefix(server)));
|
|
19568
19761
|
lines.push(`
|
|
19569
19762
|
\u25CF ${server} (${serverTools.length} tools)`);
|
|
19570
19763
|
serverTools.forEach((t) => lines.push(` - ${t.name.split("::")[2]} \u2014 ${t.description.replace(`[MCP:${server}] `, "")}`));
|