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/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";
|
|
@@ -251963,7 +251963,21 @@ var ModelSelector = class {
|
|
|
251963
251963
|
this.availableProviders = availableProviders;
|
|
251964
251964
|
this.availableModels = new Map(Object.entries(MODELS));
|
|
251965
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
|
+
}
|
|
251966
251979
|
addDynamicModel(model) {
|
|
251980
|
+
if (this.deadModels?.isDead(model.provider, model.id)) return;
|
|
251967
251981
|
this.availableModels.set(model.id, model);
|
|
251968
251982
|
if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
|
|
251969
251983
|
}
|
|
@@ -252370,6 +252384,121 @@ var LocalRequestQueue = class {
|
|
|
252370
252384
|
}
|
|
252371
252385
|
};
|
|
252372
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
|
+
|
|
252373
252502
|
// src/utils/retry.ts
|
|
252374
252503
|
var CascadeCancelledError = class extends Error {
|
|
252375
252504
|
constructor(reason) {
|
|
@@ -252963,6 +253092,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
252963
253092
|
localQueue;
|
|
252964
253093
|
taskAnalyzer;
|
|
252965
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
|
+
}
|
|
252966
253115
|
worldStateDB;
|
|
252967
253116
|
privacyPaths;
|
|
252968
253117
|
guidanceQueue;
|
|
@@ -253327,6 +253476,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
253327
253476
|
}
|
|
253328
253477
|
if (isModelNotFoundError(errMsg)) {
|
|
253329
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 });
|
|
253330
253481
|
const depth = (options._notFoundDepth ?? 0) + 1;
|
|
253331
253482
|
const next = depth <= 3 ? this.selector.selectForTier(tier) : null;
|
|
253332
253483
|
if (next && next.id !== model.id) {
|
|
@@ -257122,9 +257273,16 @@ var CAPABILITIES = [
|
|
|
257122
257273
|
api: "openai-images"
|
|
257123
257274
|
},
|
|
257124
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.
|
|
257125
257283
|
modality: "image",
|
|
257126
257284
|
provider: "gemini",
|
|
257127
|
-
modelId: "
|
|
257285
|
+
modelId: "imagen-4.0-generate-001",
|
|
257128
257286
|
api: "gemini-predict"
|
|
257129
257287
|
},
|
|
257130
257288
|
// ── Speech (text → audio) ──
|
|
@@ -303000,6 +303158,13 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
303000
303158
|
}
|
|
303001
303159
|
});
|
|
303002
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
|
+
}
|
|
303003
303168
|
this.multimodal = new MultimodalRegistry(
|
|
303004
303169
|
(this.config.providers ?? []).map((p3) => p3.type)
|
|
303005
303170
|
);
|
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";
|
|
@@ -3704,7 +3710,21 @@ var ModelSelector = class {
|
|
|
3704
3710
|
this.availableProviders = availableProviders;
|
|
3705
3711
|
this.availableModels = new Map(Object.entries(MODELS));
|
|
3706
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
|
+
}
|
|
3707
3726
|
addDynamicModel(model) {
|
|
3727
|
+
if (this.deadModels?.isDead(model.provider, model.id)) return;
|
|
3708
3728
|
this.availableModels.set(model.id, model);
|
|
3709
3729
|
if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
|
|
3710
3730
|
}
|
|
@@ -4111,6 +4131,121 @@ var LocalRequestQueue = class {
|
|
|
4111
4131
|
}
|
|
4112
4132
|
};
|
|
4113
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
|
+
|
|
4114
4249
|
// src/utils/retry.ts
|
|
4115
4250
|
var CascadeCancelledError = class extends Error {
|
|
4116
4251
|
constructor(reason) {
|
|
@@ -4704,6 +4839,26 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
4704
4839
|
localQueue;
|
|
4705
4840
|
taskAnalyzer;
|
|
4706
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
|
+
}
|
|
4707
4862
|
worldStateDB;
|
|
4708
4863
|
privacyPaths;
|
|
4709
4864
|
guidanceQueue;
|
|
@@ -5068,6 +5223,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
5068
5223
|
}
|
|
5069
5224
|
if (isModelNotFoundError(errMsg)) {
|
|
5070
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 });
|
|
5071
5228
|
const depth = (options._notFoundDepth ?? 0) + 1;
|
|
5072
5229
|
const next = depth <= 3 ? this.selector.selectForTier(tier) : null;
|
|
5073
5230
|
if (next && next.id !== model.id) {
|
|
@@ -8863,9 +9020,16 @@ var CAPABILITIES = [
|
|
|
8863
9020
|
api: "openai-images"
|
|
8864
9021
|
},
|
|
8865
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.
|
|
8866
9030
|
modality: "image",
|
|
8867
9031
|
provider: "gemini",
|
|
8868
|
-
modelId: "
|
|
9032
|
+
modelId: "imagen-4.0-generate-001",
|
|
8869
9033
|
api: "gemini-predict"
|
|
8870
9034
|
},
|
|
8871
9035
|
// ── Speech (text → audio) ──
|
|
@@ -14437,6 +14601,13 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
14437
14601
|
}
|
|
14438
14602
|
});
|
|
14439
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
|
+
}
|
|
14440
14611
|
this.multimodal = new MultimodalRegistry(
|
|
14441
14612
|
(this.config.providers ?? []).map((p) => p.type)
|
|
14442
14613
|
);
|