cascade-ai 0.53.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 +173 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +173 -2
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +167 -2
- package/dist/index.cjs +173 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +173 -2
- 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";
|
|
@@ -5856,7 +5862,21 @@ var ModelSelector = class {
|
|
|
5856
5862
|
this.availableProviders = availableProviders;
|
|
5857
5863
|
this.availableModels = new Map(Object.entries(MODELS));
|
|
5858
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
|
+
}
|
|
5859
5878
|
addDynamicModel(model) {
|
|
5879
|
+
if (this.deadModels?.isDead(model.provider, model.id)) return;
|
|
5860
5880
|
this.availableModels.set(model.id, model);
|
|
5861
5881
|
if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
|
|
5862
5882
|
}
|
|
@@ -6263,6 +6283,121 @@ var LocalRequestQueue = class {
|
|
|
6263
6283
|
}
|
|
6264
6284
|
};
|
|
6265
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
|
+
|
|
6266
6401
|
// src/core/router/index.ts
|
|
6267
6402
|
init_constants();
|
|
6268
6403
|
init_cost();
|
|
@@ -6852,6 +6987,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
6852
6987
|
localQueue;
|
|
6853
6988
|
taskAnalyzer;
|
|
6854
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
|
+
}
|
|
6855
7010
|
worldStateDB;
|
|
6856
7011
|
privacyPaths;
|
|
6857
7012
|
guidanceQueue;
|
|
@@ -7216,6 +7371,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7216
7371
|
}
|
|
7217
7372
|
if (isModelNotFoundError(errMsg)) {
|
|
7218
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 });
|
|
7219
7376
|
const depth = (options._notFoundDepth ?? 0) + 1;
|
|
7220
7377
|
const next = depth <= 3 ? this.selector.selectForTier(tier) : null;
|
|
7221
7378
|
if (next && next.id !== model.id) {
|
|
@@ -11019,9 +11176,16 @@ var CAPABILITIES = [
|
|
|
11019
11176
|
api: "openai-images"
|
|
11020
11177
|
},
|
|
11021
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.
|
|
11022
11186
|
modality: "image",
|
|
11023
11187
|
provider: "gemini",
|
|
11024
|
-
modelId: "
|
|
11188
|
+
modelId: "imagen-4.0-generate-001",
|
|
11025
11189
|
api: "gemini-predict"
|
|
11026
11190
|
},
|
|
11027
11191
|
// ── Speech (text → audio) ──
|
|
@@ -16124,6 +16288,13 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
16124
16288
|
}
|
|
16125
16289
|
});
|
|
16126
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
|
+
}
|
|
16127
16298
|
this.multimodal = new MultimodalRegistry(
|
|
16128
16299
|
(this.config.providers ?? []).map((p) => p.type)
|
|
16129
16300
|
);
|