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.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";
|
|
@@ -5909,7 +5915,21 @@ var ModelSelector = class {
|
|
|
5909
5915
|
this.availableProviders = availableProviders;
|
|
5910
5916
|
this.availableModels = new Map(Object.entries(MODELS));
|
|
5911
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
|
+
}
|
|
5912
5931
|
addDynamicModel(model) {
|
|
5932
|
+
if (this.deadModels?.isDead(model.provider, model.id)) return;
|
|
5913
5933
|
this.availableModels.set(model.id, model);
|
|
5914
5934
|
if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
|
|
5915
5935
|
}
|
|
@@ -6316,6 +6336,121 @@ var LocalRequestQueue = class {
|
|
|
6316
6336
|
}
|
|
6317
6337
|
};
|
|
6318
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
|
+
|
|
6319
6454
|
// src/core/router/index.ts
|
|
6320
6455
|
init_constants();
|
|
6321
6456
|
init_cost();
|
|
@@ -6905,6 +7040,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
6905
7040
|
localQueue;
|
|
6906
7041
|
taskAnalyzer;
|
|
6907
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
|
+
}
|
|
6908
7063
|
worldStateDB;
|
|
6909
7064
|
privacyPaths;
|
|
6910
7065
|
guidanceQueue;
|
|
@@ -7269,6 +7424,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7269
7424
|
}
|
|
7270
7425
|
if (isModelNotFoundError(errMsg)) {
|
|
7271
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 });
|
|
7272
7429
|
const depth = (options._notFoundDepth ?? 0) + 1;
|
|
7273
7430
|
const next = depth <= 3 ? this.selector.selectForTier(tier) : null;
|
|
7274
7431
|
if (next && next.id !== model.id) {
|
|
@@ -11072,9 +11229,16 @@ var CAPABILITIES = [
|
|
|
11072
11229
|
api: "openai-images"
|
|
11073
11230
|
},
|
|
11074
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.
|
|
11075
11239
|
modality: "image",
|
|
11076
11240
|
provider: "gemini",
|
|
11077
|
-
modelId: "
|
|
11241
|
+
modelId: "imagen-4.0-generate-001",
|
|
11078
11242
|
api: "gemini-predict"
|
|
11079
11243
|
},
|
|
11080
11244
|
// ── Speech (text → audio) ──
|
|
@@ -16177,6 +16341,13 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
16177
16341
|
}
|
|
16178
16342
|
});
|
|
16179
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
|
+
}
|
|
16180
16351
|
this.multimodal = new MultimodalRegistry(
|
|
16181
16352
|
(this.config.providers ?? []).map((p) => p.type)
|
|
16182
16353
|
);
|