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.js
CHANGED
|
@@ -51,6 +51,12 @@ import cron from 'node-cron';
|
|
|
51
51
|
// Cascade AI — Multi-tier AI Orchestration System
|
|
52
52
|
var __defProp = Object.defineProperty;
|
|
53
53
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
54
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
55
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
56
|
+
}) : x)(function(x) {
|
|
57
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
58
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
59
|
+
});
|
|
54
60
|
var __esm = (fn, res) => function __init() {
|
|
55
61
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
56
62
|
};
|
|
@@ -1740,7 +1746,7 @@ var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SEC
|
|
|
1740
1746
|
var init_constants = __esm({
|
|
1741
1747
|
"src/constants.ts"() {
|
|
1742
1748
|
init_pricing();
|
|
1743
|
-
CASCADE_VERSION = "0.
|
|
1749
|
+
CASCADE_VERSION = "0.54.0";
|
|
1744
1750
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1745
1751
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1746
1752
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -2739,26 +2745,14 @@ var init_openai = __esm({
|
|
|
2739
2745
|
// src/providers/azure.ts
|
|
2740
2746
|
var azure_exports = {};
|
|
2741
2747
|
__export(azure_exports, {
|
|
2748
|
+
AZURE_BASE_MODELS: () => AZURE_BASE_MODELS,
|
|
2742
2749
|
AzureOpenAIProvider: () => AzureOpenAIProvider,
|
|
2743
2750
|
azureModelForDeployment: () => azureModelForDeployment,
|
|
2744
2751
|
inferAzureBaseModel: () => inferAzureBaseModel
|
|
2745
2752
|
});
|
|
2746
2753
|
function inferAzureBaseModel(deploymentName) {
|
|
2747
2754
|
const n = deploymentName.toLowerCase();
|
|
2748
|
-
const
|
|
2749
|
-
[/gpt-?5\.5/, "gpt-5.5"],
|
|
2750
|
-
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
2751
|
-
[/gpt-?5\.4/, "gpt-5.4"],
|
|
2752
|
-
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
2753
|
-
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
2754
|
-
[/gpt-?5/, "gpt-5"],
|
|
2755
|
-
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
2756
|
-
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
2757
|
-
[/gpt-?4\.1/, "gpt-4.1"],
|
|
2758
|
-
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
2759
|
-
[/gpt-?4o/, "gpt-4o"]
|
|
2760
|
-
];
|
|
2761
|
-
for (const [re, base] of rules) if (re.test(n)) return base;
|
|
2755
|
+
for (const [re, base] of AZURE_BASE_MODEL_RULES) if (re.test(n)) return base;
|
|
2762
2756
|
return null;
|
|
2763
2757
|
}
|
|
2764
2758
|
function azureModelForDeployment(cfg) {
|
|
@@ -2803,13 +2797,27 @@ function azureModelForDeployment(cfg) {
|
|
|
2803
2797
|
supportsToolUse: true
|
|
2804
2798
|
};
|
|
2805
2799
|
}
|
|
2806
|
-
var DEFAULT_AZURE_API_VERSION, AzureOpenAIProvider;
|
|
2800
|
+
var DEFAULT_AZURE_API_VERSION, AZURE_BASE_MODEL_RULES, AZURE_BASE_MODELS, AzureOpenAIProvider;
|
|
2807
2801
|
var init_azure = __esm({
|
|
2808
2802
|
"src/providers/azure.ts"() {
|
|
2809
2803
|
init_constants();
|
|
2810
2804
|
init_openai();
|
|
2811
2805
|
init_pricing();
|
|
2812
2806
|
DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
|
|
2807
|
+
AZURE_BASE_MODEL_RULES = [
|
|
2808
|
+
[/gpt-?5\.5/, "gpt-5.5"],
|
|
2809
|
+
[/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
|
|
2810
|
+
[/gpt-?5\.4/, "gpt-5.4"],
|
|
2811
|
+
[/gpt-?5.*nano/, "gpt-5-nano"],
|
|
2812
|
+
[/gpt-?5.*mini/, "gpt-5-mini"],
|
|
2813
|
+
[/gpt-?5/, "gpt-5"],
|
|
2814
|
+
[/gpt-?4\.1-nano/, "gpt-4.1-nano"],
|
|
2815
|
+
[/gpt-?4\.1-mini/, "gpt-4.1-mini"],
|
|
2816
|
+
[/gpt-?4\.1/, "gpt-4.1"],
|
|
2817
|
+
[/gpt-?4o-mini/, "gpt-4o-mini"],
|
|
2818
|
+
[/gpt-?4o/, "gpt-4o"]
|
|
2819
|
+
];
|
|
2820
|
+
AZURE_BASE_MODELS = AZURE_BASE_MODEL_RULES.map(([, id]) => id);
|
|
2813
2821
|
AzureOpenAIProvider = class extends OpenAIProvider {
|
|
2814
2822
|
constructor(config, model) {
|
|
2815
2823
|
const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
|
|
@@ -5510,6 +5518,26 @@ var ConfigManager = class {
|
|
|
5510
5518
|
}
|
|
5511
5519
|
};
|
|
5512
5520
|
|
|
5521
|
+
// src/tools/tool-name.ts
|
|
5522
|
+
var MCP_TOOL_PREFIX = "mcp__";
|
|
5523
|
+
function sanitizeToolNameSegment(segment) {
|
|
5524
|
+
const cleaned = segment.replace(/[^a-zA-Z0-9-]+/g, "_");
|
|
5525
|
+
let start = 0;
|
|
5526
|
+
let end = cleaned.length;
|
|
5527
|
+
while (start < end && cleaned[start] === "_") start++;
|
|
5528
|
+
while (end > start && cleaned[end - 1] === "_") end--;
|
|
5529
|
+
return cleaned.slice(start, end) || "_";
|
|
5530
|
+
}
|
|
5531
|
+
function mcpToolName(serverName, toolName) {
|
|
5532
|
+
return `${MCP_TOOL_PREFIX}${sanitizeToolNameSegment(serverName)}__${sanitizeToolNameSegment(toolName)}`;
|
|
5533
|
+
}
|
|
5534
|
+
function isMcpToolName(name) {
|
|
5535
|
+
return name.startsWith(MCP_TOOL_PREFIX);
|
|
5536
|
+
}
|
|
5537
|
+
function mcpServerPrefix(serverName) {
|
|
5538
|
+
return `${MCP_TOOL_PREFIX}${sanitizeToolNameSegment(serverName)}__`;
|
|
5539
|
+
}
|
|
5540
|
+
|
|
5513
5541
|
// src/cli/utils/terminal-input.ts
|
|
5514
5542
|
var PASTE_START = "\x1B[200~";
|
|
5515
5543
|
var PASTE_END = "\x1B[201~";
|
|
@@ -5834,7 +5862,21 @@ var ModelSelector = class {
|
|
|
5834
5862
|
this.availableProviders = availableProviders;
|
|
5835
5863
|
this.availableModels = new Map(Object.entries(MODELS));
|
|
5836
5864
|
}
|
|
5865
|
+
/**
|
|
5866
|
+
* Verdicts from earlier runs about ids that 404 on this account. Consulted
|
|
5867
|
+
* BEFORE a model enters the pool, which is the only placement that helps: a
|
|
5868
|
+
* T3 wave runs concurrently, so filtering after the first failure still lets
|
|
5869
|
+
* every other worker in the wave call the dead id.
|
|
5870
|
+
*/
|
|
5871
|
+
deadModels;
|
|
5872
|
+
setDeadModelStore(store) {
|
|
5873
|
+
this.deadModels = store;
|
|
5874
|
+
for (const [id, m] of [...this.availableModels]) {
|
|
5875
|
+
if (this.deadModels.isDead(m.provider, m.id)) this.availableModels.delete(id);
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5837
5878
|
addDynamicModel(model) {
|
|
5879
|
+
if (this.deadModels?.isDead(model.provider, model.id)) return;
|
|
5838
5880
|
this.availableModels.set(model.id, model);
|
|
5839
5881
|
if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
|
|
5840
5882
|
}
|
|
@@ -6241,6 +6283,121 @@ var LocalRequestQueue = class {
|
|
|
6241
6283
|
}
|
|
6242
6284
|
};
|
|
6243
6285
|
|
|
6286
|
+
// src/core/router/dead-models.ts
|
|
6287
|
+
var DEAD_MODEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6288
|
+
function deadModelKey(provider, modelId) {
|
|
6289
|
+
return `${provider}:${modelId}`;
|
|
6290
|
+
}
|
|
6291
|
+
var DeadModelStore = class {
|
|
6292
|
+
constructor(persistence, opts = {}) {
|
|
6293
|
+
this.persistence = persistence;
|
|
6294
|
+
this.ttlMs = opts.ttlMs ?? DEAD_MODEL_TTL_MS;
|
|
6295
|
+
this.now = opts.now ?? (() => Date.now());
|
|
6296
|
+
this.restore();
|
|
6297
|
+
}
|
|
6298
|
+
persistence;
|
|
6299
|
+
records = /* @__PURE__ */ new Map();
|
|
6300
|
+
ttlMs;
|
|
6301
|
+
now;
|
|
6302
|
+
restore() {
|
|
6303
|
+
if (!this.persistence) return;
|
|
6304
|
+
try {
|
|
6305
|
+
for (const r of this.persistence.load()) {
|
|
6306
|
+
if (!this.isExpired(r)) this.records.set(r.key, r);
|
|
6307
|
+
}
|
|
6308
|
+
} catch {
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
isExpired(r) {
|
|
6312
|
+
return this.now() - r.lastSeenAt > this.ttlMs;
|
|
6313
|
+
}
|
|
6314
|
+
flush() {
|
|
6315
|
+
if (!this.persistence) return;
|
|
6316
|
+
try {
|
|
6317
|
+
this.persistence.save([...this.records.values()]);
|
|
6318
|
+
} catch {
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
/**
|
|
6322
|
+
* Record that a model was found dead. Returns true when this is the first
|
|
6323
|
+
* time (so the caller can log it once rather than once per concurrent
|
|
6324
|
+
* worker in the wave that discovered it).
|
|
6325
|
+
*/
|
|
6326
|
+
record(provider, modelId, reason) {
|
|
6327
|
+
const key = deadModelKey(provider, modelId);
|
|
6328
|
+
const now = this.now();
|
|
6329
|
+
const existing = this.records.get(key);
|
|
6330
|
+
if (existing && !this.isExpired(existing)) {
|
|
6331
|
+
existing.lastSeenAt = now;
|
|
6332
|
+
existing.hits += 1;
|
|
6333
|
+
this.flush();
|
|
6334
|
+
return false;
|
|
6335
|
+
}
|
|
6336
|
+
this.records.set(key, {
|
|
6337
|
+
key,
|
|
6338
|
+
provider,
|
|
6339
|
+
modelId,
|
|
6340
|
+
reason,
|
|
6341
|
+
firstSeenAt: now,
|
|
6342
|
+
lastSeenAt: now,
|
|
6343
|
+
hits: 1
|
|
6344
|
+
});
|
|
6345
|
+
this.flush();
|
|
6346
|
+
return true;
|
|
6347
|
+
}
|
|
6348
|
+
/** True when this model is known dead and the verdict has not expired. */
|
|
6349
|
+
isDead(provider, modelId) {
|
|
6350
|
+
const r = this.records.get(deadModelKey(provider, modelId));
|
|
6351
|
+
if (!r) return false;
|
|
6352
|
+
if (this.isExpired(r)) {
|
|
6353
|
+
this.records.delete(r.key);
|
|
6354
|
+
this.flush();
|
|
6355
|
+
return false;
|
|
6356
|
+
}
|
|
6357
|
+
return true;
|
|
6358
|
+
}
|
|
6359
|
+
/** Why a model is considered dead, for /why and the models command. */
|
|
6360
|
+
reasonFor(provider, modelId) {
|
|
6361
|
+
const r = this.records.get(deadModelKey(provider, modelId));
|
|
6362
|
+
return r && !this.isExpired(r) ? r.reason : null;
|
|
6363
|
+
}
|
|
6364
|
+
/** Live records, expired ones filtered out. */
|
|
6365
|
+
list() {
|
|
6366
|
+
const live = [...this.records.values()].filter((r) => !this.isExpired(r));
|
|
6367
|
+
return live.sort((a, b) => b.lastSeenAt - a.lastSeenAt);
|
|
6368
|
+
}
|
|
6369
|
+
/**
|
|
6370
|
+
* Forget a verdict — used when the user fixes the underlying problem (adds
|
|
6371
|
+
* the model to their project, gets quota) and wants it retried now rather
|
|
6372
|
+
* than after the TTL.
|
|
6373
|
+
*/
|
|
6374
|
+
forget(provider, modelId) {
|
|
6375
|
+
const removed = this.records.delete(deadModelKey(provider, modelId));
|
|
6376
|
+
if (removed) this.flush();
|
|
6377
|
+
return removed;
|
|
6378
|
+
}
|
|
6379
|
+
clear() {
|
|
6380
|
+
this.records.clear();
|
|
6381
|
+
this.flush();
|
|
6382
|
+
}
|
|
6383
|
+
};
|
|
6384
|
+
function fileDeadModelPersistence(filePath) {
|
|
6385
|
+
return {
|
|
6386
|
+
load() {
|
|
6387
|
+
const fs32 = __require("fs");
|
|
6388
|
+
if (!fs32.existsSync(filePath)) return [];
|
|
6389
|
+
const raw = JSON.parse(fs32.readFileSync(filePath, "utf-8"));
|
|
6390
|
+
return Array.isArray(raw) ? raw : [];
|
|
6391
|
+
},
|
|
6392
|
+
save(records) {
|
|
6393
|
+
const fs32 = __require("fs");
|
|
6394
|
+
const path37 = __require("path");
|
|
6395
|
+
fs32.mkdirSync(path37.dirname(filePath), { recursive: true });
|
|
6396
|
+
fs32.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
6397
|
+
}
|
|
6398
|
+
};
|
|
6399
|
+
}
|
|
6400
|
+
|
|
6244
6401
|
// src/core/router/index.ts
|
|
6245
6402
|
init_constants();
|
|
6246
6403
|
init_cost();
|
|
@@ -6830,6 +6987,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
6830
6987
|
localQueue;
|
|
6831
6988
|
taskAnalyzer;
|
|
6832
6989
|
pendingFeedbackSource;
|
|
6990
|
+
/** Models known dead for this account (404 / retired / not enabled). */
|
|
6991
|
+
deadModels = new DeadModelStore();
|
|
6992
|
+
/**
|
|
6993
|
+
* Back the dead-model memory with durable storage (a JSON file on desktop, a
|
|
6994
|
+
* per-user table in the cloud). Without one the verdicts still work, but only
|
|
6995
|
+
* for the life of the process — so tomorrow's run pays the same wave of 404s
|
|
6996
|
+
* to learn the same fact.
|
|
6997
|
+
*/
|
|
6998
|
+
setDeadModelStore(store) {
|
|
6999
|
+
this.deadModels = store;
|
|
7000
|
+
this.selector.setDeadModelStore(store);
|
|
7001
|
+
}
|
|
7002
|
+
/** Models known dead for this account, for `cascade models` and /why. */
|
|
7003
|
+
listDeadModels() {
|
|
7004
|
+
return this.deadModels.list();
|
|
7005
|
+
}
|
|
7006
|
+
/** Retry a model the user has since fixed, without waiting out the TTL. */
|
|
7007
|
+
forgetDeadModel(provider, modelId) {
|
|
7008
|
+
return this.deadModels.forget(provider, modelId);
|
|
7009
|
+
}
|
|
6833
7010
|
worldStateDB;
|
|
6834
7011
|
privacyPaths;
|
|
6835
7012
|
guidanceQueue;
|
|
@@ -7194,6 +7371,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7194
7371
|
}
|
|
7195
7372
|
if (isModelNotFoundError(errMsg)) {
|
|
7196
7373
|
this.selector.removeModel(model.id);
|
|
7374
|
+
const firstSighting = this.deadModels.record(model.provider, model.id, errMsg);
|
|
7375
|
+
if (firstSighting) this.emit("model:dead", { provider: model.provider, modelId: model.id, reason: errMsg });
|
|
7197
7376
|
const depth = (options._notFoundDepth ?? 0) + 1;
|
|
7198
7377
|
const next = depth <= 3 ? this.selector.selectForTier(tier) : null;
|
|
7199
7378
|
if (next && next.id !== model.id) {
|
|
@@ -10997,9 +11176,16 @@ var CAPABILITIES = [
|
|
|
10997
11176
|
api: "openai-images"
|
|
10998
11177
|
},
|
|
10999
11178
|
{
|
|
11179
|
+
// Imagen, not `gemini-3-pro-image`. That id appears on Google's PRICING
|
|
11180
|
+
// page but 404s on `:predict` ("not found for API version v1beta, or is not
|
|
11181
|
+
// supported for predict") — the Gemini image models are called through
|
|
11182
|
+
// generateContent with an image response modality, while `:predict` is the
|
|
11183
|
+
// Imagen shape this executor implements. A billing line item is not proof
|
|
11184
|
+
// of a callable endpoint, which is the assumption that put the wrong id
|
|
11185
|
+
// here in the first place.
|
|
11000
11186
|
modality: "image",
|
|
11001
11187
|
provider: "gemini",
|
|
11002
|
-
modelId: "
|
|
11188
|
+
modelId: "imagen-4.0-generate-001",
|
|
11003
11189
|
api: "gemini-predict"
|
|
11004
11190
|
},
|
|
11005
11191
|
// ── Speech (text → audio) ──
|
|
@@ -13144,7 +13330,7 @@ var McpToolWrapper = class extends BaseTool {
|
|
|
13144
13330
|
this.mcpClient = mcpClient;
|
|
13145
13331
|
this.serverName = serverName;
|
|
13146
13332
|
this.toolName = toolName;
|
|
13147
|
-
this.name =
|
|
13333
|
+
this.name = mcpToolName(serverName, toolName);
|
|
13148
13334
|
this.description = `[MCP:${serverName}] ${description}`;
|
|
13149
13335
|
this.inputSchema = inputSchema;
|
|
13150
13336
|
}
|
|
@@ -16102,6 +16288,13 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
16102
16288
|
}
|
|
16103
16289
|
});
|
|
16104
16290
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
16291
|
+
try {
|
|
16292
|
+
const nodePath = __require("path");
|
|
16293
|
+
this.router.setDeadModelStore(new DeadModelStore(
|
|
16294
|
+
fileDeadModelPersistence(nodePath.join(workspacePath, ".cascade", "dead-models.json"))
|
|
16295
|
+
));
|
|
16296
|
+
} catch {
|
|
16297
|
+
}
|
|
16105
16298
|
this.multimodal = new MultimodalRegistry(
|
|
16106
16299
|
(this.config.providers ?? []).map((p) => p.type)
|
|
16107
16300
|
);
|
|
@@ -19505,13 +19698,13 @@ ${msg.content}`).join("\n\n");
|
|
|
19505
19698
|
const cascade = cascadeRef.current;
|
|
19506
19699
|
if (!cascade) return "Cascade not initialized.";
|
|
19507
19700
|
const toolRegistry = cascade.getToolRegistry();
|
|
19508
|
-
const tools = toolRegistry.getToolDefinitions().filter((t) => t.name
|
|
19701
|
+
const tools = toolRegistry.getToolDefinitions().filter((t) => isMcpToolName(t.name));
|
|
19509
19702
|
if (tools.length === 0) return "No MCP tools connected.";
|
|
19510
19703
|
const servers = /* @__PURE__ */ new Set();
|
|
19511
19704
|
tools.forEach((t) => servers.add(t.name.split("::")[1]));
|
|
19512
19705
|
const lines = [`Connected MCP Servers (${servers.size}):`];
|
|
19513
19706
|
for (const server of servers) {
|
|
19514
|
-
const serverTools = tools.filter((t) => t.name.startsWith(
|
|
19707
|
+
const serverTools = tools.filter((t) => t.name.startsWith(mcpServerPrefix(server)));
|
|
19515
19708
|
lines.push(`
|
|
19516
19709
|
\u25CF ${server} (${serverTools.length} tools)`);
|
|
19517
19710
|
serverTools.forEach((t) => lines.push(` - ${t.name.split("::")[2]} \u2014 ${t.description.replace(`[MCP:${server}] `, "")}`));
|