cascade-ai 0.70.0 → 0.72.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 +248 -357
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +248 -357
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +207 -311
- package/dist/index.cjs +207 -311
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -15
- package/dist/index.d.ts +69 -15
- package/dist/index.js +207 -309
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1862,7 +1862,7 @@ function reconcilePrice(model, live) {
|
|
|
1862
1862
|
}
|
|
1863
1863
|
|
|
1864
1864
|
// src/constants.ts
|
|
1865
|
-
var CASCADE_VERSION = "0.
|
|
1865
|
+
var CASCADE_VERSION = "0.72.0";
|
|
1866
1866
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1867
1867
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1868
1868
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -2269,9 +2269,6 @@ var DEFAULT_THEME = "cascade";
|
|
|
2269
2269
|
var OLLAMA_BASE_URL = "http://localhost:11434";
|
|
2270
2270
|
var LM_STUDIO_BASE_URL = "http://localhost:1234";
|
|
2271
2271
|
var AZURE_BASE_URL_TEMPLATE = "https://{resource}.openai.azure.com";
|
|
2272
|
-
var GITHUB_MODELS_INFERENCE_URL = "https://models.github.ai/inference";
|
|
2273
|
-
var GITHUB_MODELS_CATALOG_URL = "https://models.github.ai/catalog/models";
|
|
2274
|
-
var GITHUB_MODELS_API_VERSION = "2026-03-10";
|
|
2275
2272
|
var TOOL_NAMES = {
|
|
2276
2273
|
SHELL: "shell",
|
|
2277
2274
|
FILE_READ: "file_read",
|
|
@@ -2306,8 +2303,7 @@ var PROVIDER_DISPLAY_NAMES = {
|
|
|
2306
2303
|
gemini: "Google Gemini",
|
|
2307
2304
|
azure: "Azure OpenAI",
|
|
2308
2305
|
"openai-compatible": "OpenAI-Compatible",
|
|
2309
|
-
ollama: "Ollama (Local)"
|
|
2310
|
-
"github-models": "GitHub Models"
|
|
2306
|
+
ollama: "Ollama (Local)"
|
|
2311
2307
|
};
|
|
2312
2308
|
|
|
2313
2309
|
// src/utils/cost.ts
|
|
@@ -3350,206 +3346,6 @@ async function nodeHttpFetch(input, init = {}, redirectCount = 0, opts = {}) {
|
|
|
3350
3346
|
});
|
|
3351
3347
|
}
|
|
3352
3348
|
|
|
3353
|
-
// src/providers/github-models.ts
|
|
3354
|
-
var GITHUB_MODELS_MAX_OUTPUT_TOKENS = 4e3;
|
|
3355
|
-
var GITHUB_MODELS_MAX_INPUT_TOKENS = 8e3;
|
|
3356
|
-
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
3357
|
-
var GITHUB_MODELS_MAX_CATALOG_PAGES = 20;
|
|
3358
|
-
var GITHUB_MODELS_CATALOG_ORIGIN = new URL(GITHUB_MODELS_CATALOG_URL).origin;
|
|
3359
|
-
function stripModelOwnerPrefix(id) {
|
|
3360
|
-
const slash = (id || "").indexOf("/");
|
|
3361
|
-
return slash === -1 ? id : id.slice(slash + 1);
|
|
3362
|
-
}
|
|
3363
|
-
function pickString(o, keys) {
|
|
3364
|
-
for (const k of keys) {
|
|
3365
|
-
const v = o[k];
|
|
3366
|
-
if (typeof v === "string" && v.length > 0) return v;
|
|
3367
|
-
}
|
|
3368
|
-
return void 0;
|
|
3369
|
-
}
|
|
3370
|
-
function pickNumber(o, keys) {
|
|
3371
|
-
for (const k of keys) {
|
|
3372
|
-
const v = k.includes(".") ? o[k.split(".")[0]]?.[k.split(".")[1]] : o[k];
|
|
3373
|
-
if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
|
|
3374
|
-
}
|
|
3375
|
-
return void 0;
|
|
3376
|
-
}
|
|
3377
|
-
function looksVisionCapable(o) {
|
|
3378
|
-
for (const k of ["supported_input_modalities", "input_modalities", "modalities", "capabilities"]) {
|
|
3379
|
-
const v = o[k];
|
|
3380
|
-
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /image|vision/i.test(x))) return true;
|
|
3381
|
-
}
|
|
3382
|
-
return false;
|
|
3383
|
-
}
|
|
3384
|
-
function looksToolCapable(o) {
|
|
3385
|
-
const params = o["supported_parameters"];
|
|
3386
|
-
if (Array.isArray(params) && params.some((x) => typeof x === "string" && /^tools?$|function.?call/i.test(x))) {
|
|
3387
|
-
return true;
|
|
3388
|
-
}
|
|
3389
|
-
for (const k of ["capabilities", "supported_output_modalities"]) {
|
|
3390
|
-
const v = o[k];
|
|
3391
|
-
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /tool|function.?call/i.test(x))) return true;
|
|
3392
|
-
}
|
|
3393
|
-
const caps = o["capabilities"];
|
|
3394
|
-
if (caps && typeof caps === "object" && !Array.isArray(caps)) {
|
|
3395
|
-
const c = caps;
|
|
3396
|
-
if (c["function_calling"] === true || c["tool_calling"] === true || c["tools"] === true) return true;
|
|
3397
|
-
}
|
|
3398
|
-
return false;
|
|
3399
|
-
}
|
|
3400
|
-
function parseNextLink(linkHeader) {
|
|
3401
|
-
if (!linkHeader) return null;
|
|
3402
|
-
for (const part of linkHeader.split(",")) {
|
|
3403
|
-
const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
|
|
3404
|
-
if (match) return match[1];
|
|
3405
|
-
}
|
|
3406
|
-
return null;
|
|
3407
|
-
}
|
|
3408
|
-
var GitHubModelsProvider = class extends OpenAIProvider {
|
|
3409
|
-
constructor(config, model) {
|
|
3410
|
-
super({ ...config, apiKey: config.apiKey ?? "" }, model);
|
|
3411
|
-
this.useMaxCompletionTokens = isReasoningModel(stripModelOwnerPrefix(model.id));
|
|
3412
|
-
this.client = new OpenAI({
|
|
3413
|
-
apiKey: config.apiKey ?? "",
|
|
3414
|
-
baseURL: GITHUB_MODELS_INFERENCE_URL,
|
|
3415
|
-
defaultHeaders: { "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION, "User-Agent": "Cascade-AI" }
|
|
3416
|
-
});
|
|
3417
|
-
}
|
|
3418
|
-
/**
|
|
3419
|
-
* `OpenAIProvider.generateStream()` builds its request budget as
|
|
3420
|
-
* `options.maxTokens ?? this.model.maxOutputTokens` — an explicit per-call
|
|
3421
|
-
* `maxTokens` (T1Administrator's final compilation asks for 8,000, well
|
|
3422
|
-
* above GitHub's real ~4K hard cap) wins outright and is sent unclamped.
|
|
3423
|
-
* GitHub then rejects the request instead of answering, so a run routed to
|
|
3424
|
-
* this provider for that step fails outright rather than getting a
|
|
3425
|
-
* (perfectly serviceable, just shorter) completion. Clamp here, once, right
|
|
3426
|
-
* before delegating to the inherited implementation, rather than teaching
|
|
3427
|
-
* every caller GitHub's specific cap.
|
|
3428
|
-
*/
|
|
3429
|
-
async generateStream(options, onChunk) {
|
|
3430
|
-
const clamped = options.maxTokens !== void 0 && options.maxTokens > GITHUB_MODELS_MAX_OUTPUT_TOKENS ? { ...options, maxTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS } : options;
|
|
3431
|
-
return super.generateStream(clamped, onChunk);
|
|
3432
|
-
}
|
|
3433
|
-
/**
|
|
3434
|
-
* The catalog is a GitHub REST resource, not an OpenAI `/models` list, and
|
|
3435
|
-
* wants GitHub's own header trio — the plain `Accept: application/json` the
|
|
3436
|
-
* openai-compatible provider sends is not the same request.
|
|
3437
|
-
*/
|
|
3438
|
-
catalogHeaders() {
|
|
3439
|
-
return {
|
|
3440
|
-
Accept: "application/vnd.github+json",
|
|
3441
|
-
Authorization: `Bearer ${this.config.apiKey ?? ""}`,
|
|
3442
|
-
"X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION,
|
|
3443
|
-
// GitHub's REST API rejects any request with no User-Agent — a plain 403
|
|
3444
|
-
// regardless of how valid the PAT is. nodeHttpFetch is a thin wrapper
|
|
3445
|
-
// over node:http/https, neither of which sets one the way a browser's
|
|
3446
|
-
// fetch or a full HTTP client library would, so this has to be explicit.
|
|
3447
|
-
"User-Agent": "Cascade-AI"
|
|
3448
|
-
};
|
|
3449
|
-
}
|
|
3450
|
-
async listModels() {
|
|
3451
|
-
const raw = [];
|
|
3452
|
-
let url = GITHUB_MODELS_CATALOG_URL;
|
|
3453
|
-
let pages = 0;
|
|
3454
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3455
|
-
while (url && pages < GITHUB_MODELS_MAX_CATALOG_PAGES) {
|
|
3456
|
-
if (visited.has(url)) break;
|
|
3457
|
-
visited.add(url);
|
|
3458
|
-
const res = await nodeHttpFetch(url, { headers: this.catalogHeaders() }, 0, {
|
|
3459
|
-
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3460
|
-
});
|
|
3461
|
-
if (!res.ok) throw new Error(`GitHub Models catalog ${url} returned HTTP ${res.status}`);
|
|
3462
|
-
const body = await res.json();
|
|
3463
|
-
const pageItems = Array.isArray(body) ? body : Array.isArray(body?.models) ? body.models : Array.isArray(body?.data) ? body.data : [];
|
|
3464
|
-
raw.push(...pageItems);
|
|
3465
|
-
const next = parseNextLink(res.headers.get("link"));
|
|
3466
|
-
if (next) {
|
|
3467
|
-
const resolved = new URL(next, url);
|
|
3468
|
-
if (resolved.origin !== GITHUB_MODELS_CATALOG_ORIGIN) {
|
|
3469
|
-
throw new Error(`Refusing cross-origin GitHub Models pagination URL: ${resolved.origin}`);
|
|
3470
|
-
}
|
|
3471
|
-
url = resolved.href;
|
|
3472
|
-
} else {
|
|
3473
|
-
url = null;
|
|
3474
|
-
}
|
|
3475
|
-
pages++;
|
|
3476
|
-
}
|
|
3477
|
-
const entries = raw.map((m) => {
|
|
3478
|
-
if (typeof m === "string") {
|
|
3479
|
-
return { id: m, name: m, contextWindow: DEFAULT_CONTEXT_WINDOW, vision: false, tools: false };
|
|
3480
|
-
}
|
|
3481
|
-
if (!m || typeof m !== "object") return void 0;
|
|
3482
|
-
const o = m;
|
|
3483
|
-
const id = pickString(o, ["id", "name", "model"]);
|
|
3484
|
-
if (!id) return void 0;
|
|
3485
|
-
return {
|
|
3486
|
-
id,
|
|
3487
|
-
name: pickString(o, ["friendly_name", "display_name", "name"]) ?? id,
|
|
3488
|
-
contextWindow: pickNumber(o, [
|
|
3489
|
-
"context_window",
|
|
3490
|
-
"context_length",
|
|
3491
|
-
"max_input_tokens",
|
|
3492
|
-
"limits.max_input_tokens",
|
|
3493
|
-
"limits.max_context_tokens"
|
|
3494
|
-
]) ?? DEFAULT_CONTEXT_WINDOW,
|
|
3495
|
-
vision: looksVisionCapable(o),
|
|
3496
|
-
tools: looksToolCapable(o)
|
|
3497
|
-
};
|
|
3498
|
-
}).filter((e) => e !== void 0);
|
|
3499
|
-
if (entries.length === 0) return [];
|
|
3500
|
-
const chat = entries.filter((e) => isChatModel(e.id));
|
|
3501
|
-
return (chat.length ? chat : entries).map((e) => ({
|
|
3502
|
-
id: e.id,
|
|
3503
|
-
// full `owner/model` — what the API is addressed by
|
|
3504
|
-
name: e.name,
|
|
3505
|
-
provider: "github-models",
|
|
3506
|
-
// GitHub's real per-request input cap, not the base model's larger one
|
|
3507
|
-
// (see GITHUB_MODELS_MAX_INPUT_TOKENS above) — the same reasoning
|
|
3508
|
-
// already applied to maxOutputTokens below, for the input side.
|
|
3509
|
-
contextWindow: Math.min(e.contextWindow, GITHUB_MODELS_MAX_INPUT_TOKENS),
|
|
3510
|
-
isVisionCapable: e.vision,
|
|
3511
|
-
// A REAL $0, not a missing number — usage is bundled into the quota of
|
|
3512
|
-
// the plan the PAT belongs to and is never billed per token, so
|
|
3513
|
-
// `pricingUnknown: false` is the honest value (see the flag's contract in
|
|
3514
|
-
// core/router/pricing.ts: 0 alone is ambiguous). Set here rather than in
|
|
3515
|
-
// pricing-data.json because resolvePricing() matches exactly on
|
|
3516
|
-
// provider|model|region with no wildcard: every catalog model would need
|
|
3517
|
-
// a hand-curated row, and the next one GitHub adds would read as "cost
|
|
3518
|
-
// unknown" — an inconsistent picture for a billing reality that is
|
|
3519
|
-
// identical across the entire catalog.
|
|
3520
|
-
//
|
|
3521
|
-
// `isLocal` is the OTHER way to reach a real $0, and it is the wrong one:
|
|
3522
|
-
// it means "runs on this machine" and carries consequences that are
|
|
3523
|
-
// simply false for a hosted API — the shared concurrency-1
|
|
3524
|
-
// LocalRequestQueue (serialising these calls behind actual Ollama GPU
|
|
3525
|
-
// inference), the 300s local rather than 120s cloud inference timeout,
|
|
3526
|
-
// and a literal "[local]" tag in `cascade models`.
|
|
3527
|
-
inputCostPer1kTokens: 0,
|
|
3528
|
-
outputCostPer1kTokens: 0,
|
|
3529
|
-
pricingUnknown: false,
|
|
3530
|
-
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3531
|
-
supportsStreaming: true,
|
|
3532
|
-
// Not every catalog model supports function/tool calling — a hardcoded
|
|
3533
|
-
// `true` sent `tools` to models that reject the parameter outright and
|
|
3534
|
-
// never engaged t3-worker.ts's existing text-tool fallback (only an
|
|
3535
|
-
// explicit `false` does). See looksToolCapable's own comment for why
|
|
3536
|
-
// "not positively advertised" defaults to false rather than true here.
|
|
3537
|
-
supportsToolUse: e.tools,
|
|
3538
|
-
isLocal: false
|
|
3539
|
-
}));
|
|
3540
|
-
}
|
|
3541
|
-
async isAvailable() {
|
|
3542
|
-
try {
|
|
3543
|
-
const res = await nodeHttpFetch(GITHUB_MODELS_CATALOG_URL, { headers: this.catalogHeaders() }, 0, {
|
|
3544
|
-
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3545
|
-
});
|
|
3546
|
-
return res.ok;
|
|
3547
|
-
} catch {
|
|
3548
|
-
return false;
|
|
3549
|
-
}
|
|
3550
|
-
}
|
|
3551
|
-
};
|
|
3552
|
-
|
|
3553
3349
|
// src/providers/ollama.ts
|
|
3554
3350
|
var TOOL_CAPABLE_FAMILIES = [
|
|
3555
3351
|
"llama3.1",
|
|
@@ -4058,7 +3854,7 @@ var ModelSelector = class {
|
|
|
4058
3854
|
if (overrideModelId.includes(":")) {
|
|
4059
3855
|
const parts = overrideModelId.split(":");
|
|
4060
3856
|
const prefix = parts[0].toLowerCase();
|
|
4061
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
3857
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
4062
3858
|
if (validProviders.includes(prefix)) {
|
|
4063
3859
|
providerStr = prefix;
|
|
4064
3860
|
actualId = parts.slice(1).join(":");
|
|
@@ -4078,31 +3874,6 @@ var ModelSelector = class {
|
|
|
4078
3874
|
else if (this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
|
|
4079
3875
|
else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
|
|
4080
3876
|
}
|
|
4081
|
-
if (providerStr === "github-models" && this.availableProviders.has("github-models")) {
|
|
4082
|
-
const dynamicModel = {
|
|
4083
|
-
id: actualId,
|
|
4084
|
-
name: actualId,
|
|
4085
|
-
provider: "github-models",
|
|
4086
|
-
contextWindow: GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
4087
|
-
isVisionCapable: false,
|
|
4088
|
-
inputCostPer1kTokens: 0,
|
|
4089
|
-
outputCostPer1kTokens: 0,
|
|
4090
|
-
pricingUnknown: false,
|
|
4091
|
-
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
4092
|
-
supportsStreaming: true,
|
|
4093
|
-
// Unconfirmed (no live catalog entry to read a real capability from)
|
|
4094
|
-
// must default to false, not left undefined: t3-worker.ts's text-tool
|
|
4095
|
-
// fallback only engages on a strict `=== false`, so an undefined
|
|
4096
|
-
// value here sends native `tools` to an unverified multi-vendor
|
|
4097
|
-
// model and any that don't support function calling reject the
|
|
4098
|
-
// request outright. Same default-to-false-when-unconfirmed policy
|
|
4099
|
-
// listModels() already applies for a real catalog entry.
|
|
4100
|
-
supportsToolUse: false,
|
|
4101
|
-
isLocal: false
|
|
4102
|
-
};
|
|
4103
|
-
this.addDynamicModel(dynamicModel);
|
|
4104
|
-
return dynamicModel;
|
|
4105
|
-
}
|
|
4106
3877
|
if (providerStr && this.availableProviders.has(providerStr)) {
|
|
4107
3878
|
const dynamicModel = withResolvedPricing({
|
|
4108
3879
|
id: actualId,
|
|
@@ -4195,20 +3966,7 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
4195
3966
|
gemini: 6e4,
|
|
4196
3967
|
azure: 3e4,
|
|
4197
3968
|
"openai-compatible": 3e4,
|
|
4198
|
-
ollama: Number.POSITIVE_INFINITY
|
|
4199
|
-
// An order of magnitude below the others, on purpose. GitHub Models is
|
|
4200
|
-
// rate-limited by REQUESTS (Free ≈10 RPM, Pro ≈20, plus low daily caps), and
|
|
4201
|
-
// this bucket meters TOKENS — there is no request-count limiter in this
|
|
4202
|
-
// codebase, and building one is not in scope. The approximation works because
|
|
4203
|
-
// the router reserves `model.maxOutputTokens + 512` per call, and the
|
|
4204
|
-
// provider reports GitHub's real ~4K per-request cap rather than the
|
|
4205
|
-
// underlying model's larger one — so every call withdraws a near-constant
|
|
4206
|
-
// ~4.5K regardless of how short the reply actually turns out to be. At 8K/min
|
|
4207
|
-
// that lands around 1–2 calls per minute of headroom: comfortably inside even
|
|
4208
|
-
// the Free tier, with room for the daily cap to matter more than this does.
|
|
4209
|
-
// Users on a higher tier can raise it with no code change through the generic
|
|
4210
|
-
// `config.rateLimits.providerTpm['github-models']` override.
|
|
4211
|
-
"github-models": 8e3
|
|
3969
|
+
ollama: Number.POSITIVE_INFINITY
|
|
4212
3970
|
};
|
|
4213
3971
|
var TpmLimiter = class {
|
|
4214
3972
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -4564,7 +4322,6 @@ var ModelProfiler = class {
|
|
|
4564
4322
|
(m) => !alreadyProfiled.has(m.id) && !SKIP_PATTERN.test(m.id) && !SKIP_PATTERN.test(m.name)
|
|
4565
4323
|
);
|
|
4566
4324
|
if (toProfile.length === 0) return;
|
|
4567
|
-
const probesHitGithubModels = this.router?.getModelForTier("T3")?.provider === "github-models";
|
|
4568
4325
|
const openRouterModels = await fetchOpenRouterModels();
|
|
4569
4326
|
const orByNormalizedId = /* @__PURE__ */ new Map();
|
|
4570
4327
|
for (const m of openRouterModels) {
|
|
@@ -4579,7 +4336,7 @@ var ModelProfiler = class {
|
|
|
4579
4336
|
if (orMatch?.description) {
|
|
4580
4337
|
specializations = extractSpecializations(orMatch.description);
|
|
4581
4338
|
}
|
|
4582
|
-
if (specializations.length === 0 && this.router
|
|
4339
|
+
if (specializations.length === 0 && this.router) {
|
|
4583
4340
|
specializations = await queryModelDirectly(this.router);
|
|
4584
4341
|
}
|
|
4585
4342
|
this.store.saveModelProfile(model.id, model.provider, specializations);
|
|
@@ -4799,7 +4556,6 @@ var LiveDataProvider = class {
|
|
|
4799
4556
|
*/
|
|
4800
4557
|
applyLivePricing(models) {
|
|
4801
4558
|
return models.map((m) => {
|
|
4802
|
-
if (m.provider === "github-models") return m;
|
|
4803
4559
|
const p = this.getLivePrice(m.baseModelId ?? m.id);
|
|
4804
4560
|
if (!p) return m;
|
|
4805
4561
|
const reconciled = reconcilePrice(m, p);
|
|
@@ -4838,7 +4594,6 @@ var LiveDataProvider = class {
|
|
|
4838
4594
|
*/
|
|
4839
4595
|
applyLiveCapabilities(models) {
|
|
4840
4596
|
return models.map((m) => {
|
|
4841
|
-
if (m.provider === "github-models") return m;
|
|
4842
4597
|
const c = this.getCapability(m.baseModelId ?? m.id);
|
|
4843
4598
|
if (!c) return m;
|
|
4844
4599
|
const next = { ...m };
|
|
@@ -5142,16 +4897,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5142
4897
|
for (const model of azureDeployments) this.selector.addDynamicModel(model);
|
|
5143
4898
|
availableProviders.add("azure");
|
|
5144
4899
|
}
|
|
5145
|
-
if (availableProviders.has("github-models")) {
|
|
5146
|
-
await this.discoverGitHubModelsCatalog(config);
|
|
5147
|
-
}
|
|
5148
4900
|
await this.validateCloudProviderModels(config);
|
|
5149
4901
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
5150
4902
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
5151
4903
|
if (!override || override === "auto") continue;
|
|
5152
4904
|
const model = this.selector.selectForTier(tier, override);
|
|
5153
4905
|
if (!model) {
|
|
5154
|
-
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
4906
|
+
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
5155
4907
|
const hasProviderPrefix = override.includes(":") && knownProviders.some((p) => override.startsWith(p + ":"));
|
|
5156
4908
|
if (hasProviderPrefix) {
|
|
5157
4909
|
const provider = override.split(":")[0];
|
|
@@ -5306,7 +5058,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5306
5058
|
* models without a package upgrade. Mirrors discoverOllamaModels.
|
|
5307
5059
|
*/
|
|
5308
5060
|
async discoverProviderModels() {
|
|
5309
|
-
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"
|
|
5061
|
+
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"];
|
|
5310
5062
|
const tasks = cloud.map(async (type) => {
|
|
5311
5063
|
if (!this.selector.isProviderAvailable(type)) return;
|
|
5312
5064
|
const seed = this.getAnyModelForProvider(type);
|
|
@@ -5821,26 +5573,6 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5821
5573
|
return false;
|
|
5822
5574
|
}
|
|
5823
5575
|
}
|
|
5824
|
-
/**
|
|
5825
|
-
* Registers GitHub Models' real catalog ids via addDynamicModel so init()'s
|
|
5826
|
-
* tier-fill loop can pick one. Registration alone does not create a bound
|
|
5827
|
-
* BaseProvider instance (see ensureProvider) — the tier-fill loop's own
|
|
5828
|
-
* ensureProvider call does that for whichever model it actually selects,
|
|
5829
|
-
* which is why this runs BEFORE that loop rather than only in the
|
|
5830
|
-
* background discoverProviderModels() refresh.
|
|
5831
|
-
*/
|
|
5832
|
-
async discoverGitHubModelsCatalog(config) {
|
|
5833
|
-
const cfg = config.providers.find((p) => p.type === "github-models") ?? { type: "github-models" };
|
|
5834
|
-
const seed = this.getAnyModelForProvider("github-models");
|
|
5835
|
-
if (!seed) return;
|
|
5836
|
-
try {
|
|
5837
|
-
const provider = new GitHubModelsProvider(cfg, seed);
|
|
5838
|
-
const models = await provider.listModels();
|
|
5839
|
-
for (const m of models) this.selector.addDynamicModel(m);
|
|
5840
|
-
} catch (err) {
|
|
5841
|
-
console.warn("[router] GitHub Models catalog discovery failed:", err instanceof Error ? err.message : err);
|
|
5842
|
-
}
|
|
5843
|
-
}
|
|
5844
5576
|
ensureProvider(model, configs) {
|
|
5845
5577
|
const key = `${model.provider}:${model.id}`;
|
|
5846
5578
|
if (this.providers.has(key)) return;
|
|
@@ -5865,8 +5597,6 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5865
5597
|
return new OllamaProvider(cfg, model);
|
|
5866
5598
|
case "openai-compatible":
|
|
5867
5599
|
return new OpenAICompatibleProvider(cfg, model);
|
|
5868
|
-
case "github-models":
|
|
5869
|
-
return new GitHubModelsProvider(cfg, model);
|
|
5870
5600
|
default:
|
|
5871
5601
|
throw new Error(`Unsupported provider type: ${String(cfg.type)}`);
|
|
5872
5602
|
}
|
|
@@ -5874,7 +5604,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
5874
5604
|
getAnyModelForProvider(type) {
|
|
5875
5605
|
const fromCatalog = Object.values(MODELS).find((m) => m.provider === type);
|
|
5876
5606
|
if (fromCatalog) return fromCatalog;
|
|
5877
|
-
if (type === "openai-compatible" || type === "azure"
|
|
5607
|
+
if (type === "openai-compatible" || type === "azure") {
|
|
5878
5608
|
return {
|
|
5879
5609
|
id: type,
|
|
5880
5610
|
name: type,
|
|
@@ -7522,6 +7252,22 @@ ${assignment.expectedOutput}`;
|
|
|
7522
7252
|
requiresArtifact() {
|
|
7523
7253
|
return shouldRequireArtifact(this.assignment, this.tools.map((t) => t.name));
|
|
7524
7254
|
}
|
|
7255
|
+
/**
|
|
7256
|
+
* Where a file a tool wrote actually lives.
|
|
7257
|
+
*
|
|
7258
|
+
* This used to be `process.cwd()`, which is only the workspace in a plain
|
|
7259
|
+
* CLI run. The hosted server runs with cwd at the app directory while the
|
|
7260
|
+
* run's workspace is the tenant's scratch dir, and Electron's cwd is the app
|
|
7261
|
+
* bundle rather than the folder the user picked. So the tool wrote
|
|
7262
|
+
* `<workspace>/report.docx`, verification stat'd `<cwd>/report.docx`, found
|
|
7263
|
+
* nothing, and the worker looped and then threw WorkerStallError — surfacing
|
|
7264
|
+
* as "no file was created" on a file that had in fact been created, followed
|
|
7265
|
+
* by a failed node. Asking the registry is exact: it is the same root every
|
|
7266
|
+
* registered tool was configured with (registry.registerDefaults).
|
|
7267
|
+
*/
|
|
7268
|
+
artifactRoot() {
|
|
7269
|
+
return this.toolRegistry.getWorkspaceRoot?.() ?? process.cwd();
|
|
7270
|
+
}
|
|
7525
7271
|
extractArtifactPaths(assignment) {
|
|
7526
7272
|
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
7527
7273
|
const haystack = `${assignment.description}
|
|
@@ -7533,11 +7279,11 @@ ${assignment.expectedOutput}`;
|
|
|
7533
7279
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
7534
7280
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
7535
7281
|
const issues = [];
|
|
7536
|
-
const {
|
|
7282
|
+
const { execFile: execFile4 } = await import('child_process');
|
|
7537
7283
|
const { promisify: promisify4 } = await import('util');
|
|
7538
|
-
const
|
|
7284
|
+
const execFileAsync3 = promisify4(execFile4);
|
|
7539
7285
|
for (const artifactPath of artifactPaths) {
|
|
7540
|
-
const absolutePath = path26.resolve(
|
|
7286
|
+
const absolutePath = path26.resolve(this.artifactRoot(), artifactPath);
|
|
7541
7287
|
try {
|
|
7542
7288
|
const stat = await fs5.stat(absolutePath);
|
|
7543
7289
|
if (!stat.isFile()) {
|
|
@@ -7573,20 +7319,18 @@ ${assignment.expectedOutput}`;
|
|
|
7573
7319
|
continue;
|
|
7574
7320
|
}
|
|
7575
7321
|
const ext = path26.extname(absolutePath).toLowerCase();
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7322
|
+
const check = ext === ".ts" || ext === ".tsx" ? { cmd: "npx", args: ["tsc", "--noEmit", absolutePath] } : ext === ".js" || ext === ".jsx" ? { cmd: process.execPath, args: ["--check", absolutePath] } : ext === ".py" ? { cmd: "python", args: ["-m", "py_compile", absolutePath] } : null;
|
|
7323
|
+
if (check) {
|
|
7324
|
+
try {
|
|
7325
|
+
await execFileAsync3(check.cmd, check.args, { timeout: 1e4 });
|
|
7326
|
+
} catch (err) {
|
|
7327
|
+
const code = err.code;
|
|
7328
|
+
if (code === "ENOENT" || code === "EINVAL") continue;
|
|
7329
|
+
const e = err;
|
|
7330
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
7331
|
+
${e.stderr || String(err)}
|
|
7332
|
+
${e.stdout || ""}`);
|
|
7583
7333
|
}
|
|
7584
|
-
} catch (err) {
|
|
7585
|
-
const stderr = err?.stderr || String(err);
|
|
7586
|
-
const stdout = err?.stdout || "";
|
|
7587
|
-
issues.push(`Semantic error in ${artifactPath}:
|
|
7588
|
-
${stderr}
|
|
7589
|
-
${stdout}`);
|
|
7590
7334
|
}
|
|
7591
7335
|
} catch {
|
|
7592
7336
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -7665,7 +7409,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
7665
7409
|
async checkAcceptance(assignment) {
|
|
7666
7410
|
const criteria = assignment.acceptance ?? [];
|
|
7667
7411
|
if (!criteria.length) return [];
|
|
7668
|
-
const resolve2 = (target) => path26.resolve(
|
|
7412
|
+
const resolve2 = (target) => path26.resolve(this.artifactRoot(), target);
|
|
7669
7413
|
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
7670
7414
|
stat: async (target) => {
|
|
7671
7415
|
try {
|
|
@@ -13253,6 +12997,18 @@ var ToolRegistry = class extends EventEmitter {
|
|
|
13253
12997
|
this.tools.set(tool.name, tool);
|
|
13254
12998
|
this.emit("tool:added", tool.name);
|
|
13255
12999
|
}
|
|
13000
|
+
/**
|
|
13001
|
+
* The directory every registered tool resolves relative paths against.
|
|
13002
|
+
*
|
|
13003
|
+
* Exposed so a caller that has to reason about a file a tool WROTE can look
|
|
13004
|
+
* in the same place the tool wrote it. `process.cwd()` is not that place
|
|
13005
|
+
* outside a plain CLI run: the hosted server's cwd is the app directory
|
|
13006
|
+
* while the run's workspace is the tenant's scratch dir, and Electron's cwd
|
|
13007
|
+
* is the app bundle rather than the user's chosen folder.
|
|
13008
|
+
*/
|
|
13009
|
+
getWorkspaceRoot() {
|
|
13010
|
+
return this.workspaceRoot;
|
|
13011
|
+
}
|
|
13256
13012
|
/**
|
|
13257
13013
|
* Wait until a named tool is registered, resolving immediately if it already exists.
|
|
13258
13014
|
* T3 workers can call this after encountering a missing-tool error to resume
|
|
@@ -14007,7 +13763,7 @@ var PermissionEscalator = class extends EventEmitter {
|
|
|
14007
13763
|
}
|
|
14008
13764
|
};
|
|
14009
13765
|
var ProviderConfigSchema = z.object({
|
|
14010
|
-
type: z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
13766
|
+
type: z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"]),
|
|
14011
13767
|
label: z.string().optional(),
|
|
14012
13768
|
apiKey: z.string().optional(),
|
|
14013
13769
|
baseUrl: z.string().url().optional(),
|
|
@@ -19283,6 +19039,19 @@ Original error: ${err.message}`
|
|
|
19283
19039
|
const rows = provider ? this.db.prepare("SELECT metadata FROM model_cache WHERE provider = ?").all(provider) : this.db.prepare("SELECT metadata FROM model_cache").all();
|
|
19284
19040
|
return rows.map((r) => JSON.parse(r.metadata));
|
|
19285
19041
|
}
|
|
19042
|
+
/**
|
|
19043
|
+
* Deletes cached rows for a provider type that no longer exists in
|
|
19044
|
+
* `ProviderType`, so it takes a raw string rather than the union.
|
|
19045
|
+
*
|
|
19046
|
+
* Needed because the cache outlives the code: the REPL's loadCache() only
|
|
19047
|
+
* re-discovers when the cache is EMPTY or older than 24h, so rows left
|
|
19048
|
+
* behind by a removed provider read as "cache populated" and suppress
|
|
19049
|
+
* discovery for the providers that replaced it — `/model` then shows a
|
|
19050
|
+
* configured provider with zero models until the rows age out.
|
|
19051
|
+
*/
|
|
19052
|
+
purgeCachedModelsForRetiredProvider(providerType) {
|
|
19053
|
+
return this.db.prepare("DELETE FROM model_cache WHERE provider = ?").run(providerType).changes;
|
|
19054
|
+
}
|
|
19286
19055
|
clearModelCache(provider) {
|
|
19287
19056
|
if (provider) {
|
|
19288
19057
|
this.db.prepare("DELETE FROM model_cache WHERE provider = ?").run(provider);
|
|
@@ -19630,7 +19399,73 @@ function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
|
19630
19399
|
return merged;
|
|
19631
19400
|
}
|
|
19632
19401
|
|
|
19402
|
+
// src/config/retired-providers.ts
|
|
19403
|
+
var RETIRED_PROVIDER_TYPES = {
|
|
19404
|
+
"github-models": "GitHub retired GitHub Models on 30 July 2026; models.github.ai no longer responds."
|
|
19405
|
+
};
|
|
19406
|
+
function isRetiredProviderType(type) {
|
|
19407
|
+
return typeof type === "string" && Object.hasOwn(RETIRED_PROVIDER_TYPES, type);
|
|
19408
|
+
}
|
|
19409
|
+
function didCleanupChangeAnything(c) {
|
|
19410
|
+
return c.removed.length > 0 || c.clearedPins.length > 0;
|
|
19411
|
+
}
|
|
19412
|
+
function stripRetiredProviders(raw) {
|
|
19413
|
+
const cleanup = { removed: [], clearedPins: [] };
|
|
19414
|
+
if (typeof raw !== "object" || raw === null) return cleanup;
|
|
19415
|
+
const cfg = raw;
|
|
19416
|
+
if (Array.isArray(cfg["providers"])) {
|
|
19417
|
+
const kept = cfg["providers"].filter((p) => {
|
|
19418
|
+
const type = p?.type;
|
|
19419
|
+
if (!isRetiredProviderType(type)) return true;
|
|
19420
|
+
if (!cleanup.removed.includes(type)) cleanup.removed.push(type);
|
|
19421
|
+
return false;
|
|
19422
|
+
});
|
|
19423
|
+
cfg["providers"] = kept;
|
|
19424
|
+
}
|
|
19425
|
+
cleanup.clearedPins = clearRetiredPins(cfg["models"]);
|
|
19426
|
+
return cleanup;
|
|
19427
|
+
}
|
|
19428
|
+
function clearRetiredPins(models) {
|
|
19429
|
+
const cleared = [];
|
|
19430
|
+
if (typeof models !== "object" || models === null) return cleared;
|
|
19431
|
+
const tiers = models;
|
|
19432
|
+
for (const tier of ["t1", "t2", "t3"]) {
|
|
19433
|
+
const pin = tiers[tier];
|
|
19434
|
+
if (typeof pin !== "string") continue;
|
|
19435
|
+
const prefix = pin.slice(0, pin.indexOf(":")).toLowerCase();
|
|
19436
|
+
if (pin.includes(":") && isRetiredProviderType(prefix)) {
|
|
19437
|
+
delete tiers[tier];
|
|
19438
|
+
cleared.push(tier);
|
|
19439
|
+
}
|
|
19440
|
+
}
|
|
19441
|
+
return cleared;
|
|
19442
|
+
}
|
|
19443
|
+
function filterRetiredCredentials(providers) {
|
|
19444
|
+
const removed = [];
|
|
19445
|
+
const kept = providers.filter((p) => {
|
|
19446
|
+
if (!isRetiredProviderType(p.type)) return true;
|
|
19447
|
+
if (!removed.includes(p.type)) removed.push(p.type);
|
|
19448
|
+
return false;
|
|
19449
|
+
});
|
|
19450
|
+
return { kept, removed };
|
|
19451
|
+
}
|
|
19452
|
+
function describeCleanup(c) {
|
|
19453
|
+
const parts = [];
|
|
19454
|
+
for (const type of c.removed) {
|
|
19455
|
+
parts.push(`removed the retired "${type}" provider (${RETIRED_PROVIDER_TYPES[type]})`);
|
|
19456
|
+
}
|
|
19457
|
+
if (c.clearedPins.length > 0) {
|
|
19458
|
+
parts.push(`reset ${c.clearedPins.map((t) => t.toUpperCase()).join("/")} to Auto, since the pin named it`);
|
|
19459
|
+
}
|
|
19460
|
+
return `Cascade config migration: ${parts.join("; ")}.`;
|
|
19461
|
+
}
|
|
19462
|
+
|
|
19633
19463
|
// src/config/index.ts
|
|
19464
|
+
var KEY_OPTIONAL_PROVIDER_TYPES = /* @__PURE__ */ new Set(["ollama", "openai-compatible"]);
|
|
19465
|
+
function hasUsableProvider(providers) {
|
|
19466
|
+
if (!providers?.length) return false;
|
|
19467
|
+
return providers.some((p) => KEY_OPTIONAL_PROVIDER_TYPES.has(p.type) || !!p.apiKey || !!p.authToken);
|
|
19468
|
+
}
|
|
19634
19469
|
var ConfigManager = class {
|
|
19635
19470
|
config;
|
|
19636
19471
|
keystore;
|
|
@@ -19639,6 +19474,29 @@ var ConfigManager = class {
|
|
|
19639
19474
|
cascadeMd = null;
|
|
19640
19475
|
workspacePath;
|
|
19641
19476
|
globalDir;
|
|
19477
|
+
/**
|
|
19478
|
+
* What THIS load migrated out, if anything. Strictly per-load state: reset
|
|
19479
|
+
* at the top of every `load()`, because two places read it as a statement
|
|
19480
|
+
* about the load in progress — `injectEnvKeys()` asks whether an empty
|
|
19481
|
+
* provider list was emptied by a retirement, and the end of `load()` warns
|
|
19482
|
+
* and builds the user-facing notice from it. Left set, a second `load()` on
|
|
19483
|
+
* the same instance (`startRepl()` does one after its setup wizard) would
|
|
19484
|
+
* re-announce a migration that already happened and suppress the Ollama
|
|
19485
|
+
* fallback for a list that is empty for some unrelated reason.
|
|
19486
|
+
*/
|
|
19487
|
+
retiredCleanup;
|
|
19488
|
+
/**
|
|
19489
|
+
* Human-readable migration notice held for a UI to display. `console.warn`
|
|
19490
|
+
* is not sufficient: the REPL clears the TTY immediately after load(), and
|
|
19491
|
+
* the desktop emits from the main process where nothing is rendered.
|
|
19492
|
+
*/
|
|
19493
|
+
pendingRetiredNotice;
|
|
19494
|
+
/** Returns the pending migration notice once, then forgets it. */
|
|
19495
|
+
takeRetiredNotice() {
|
|
19496
|
+
const n = this.pendingRetiredNotice;
|
|
19497
|
+
this.pendingRetiredNotice = void 0;
|
|
19498
|
+
return n;
|
|
19499
|
+
}
|
|
19642
19500
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
19643
19501
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
19644
19502
|
this.workspacePath = workspacePath;
|
|
@@ -19658,10 +19516,32 @@ var ConfigManager = class {
|
|
|
19658
19516
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
19659
19517
|
this.keystore = new Keystore(path26.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
19660
19518
|
this.store = new MemoryStore(path26.join(this.workspacePath, CASCADE_DB_FILE));
|
|
19519
|
+
const globalCreds = filterRetiredCredentials(loadGlobalCredentials(this.globalDir));
|
|
19520
|
+
if (globalCreds.removed.length > 0) {
|
|
19521
|
+
try {
|
|
19522
|
+
saveGlobalCredentials(this.globalDir, globalCreds.kept);
|
|
19523
|
+
} catch (err) {
|
|
19524
|
+
console.warn(`Could not rewrite the global credential store: ${err instanceof Error ? err.message : String(err)}`);
|
|
19525
|
+
}
|
|
19526
|
+
this.retiredCleanup = {
|
|
19527
|
+
removed: [.../* @__PURE__ */ new Set([...this.retiredCleanup?.removed ?? [], ...globalCreds.removed])],
|
|
19528
|
+
clearedPins: this.retiredCleanup?.clearedPins ?? []
|
|
19529
|
+
};
|
|
19530
|
+
}
|
|
19661
19531
|
await this.injectEnvKeys();
|
|
19662
|
-
this.config.providers = mergeGlobalCredentials(this.config.providers,
|
|
19532
|
+
this.config.providers = mergeGlobalCredentials(this.config.providers, globalCreds.kept);
|
|
19533
|
+
for (const type of Object.keys(RETIRED_PROVIDER_TYPES)) {
|
|
19534
|
+
try {
|
|
19535
|
+
this.store.purgeCachedModelsForRetiredProvider(type);
|
|
19536
|
+
} catch {
|
|
19537
|
+
}
|
|
19538
|
+
}
|
|
19663
19539
|
await this.ensureDefaultIdentity();
|
|
19664
19540
|
if (mcpNamesChanged) await this.save();
|
|
19541
|
+
if (this.retiredCleanup) {
|
|
19542
|
+
console.warn(describeCleanup(this.retiredCleanup));
|
|
19543
|
+
this.pendingRetiredNotice = describeCleanup(this.retiredCleanup);
|
|
19544
|
+
}
|
|
19665
19545
|
}
|
|
19666
19546
|
getConfig() {
|
|
19667
19547
|
return this.config;
|
|
@@ -19700,14 +19580,13 @@ var ConfigManager = class {
|
|
|
19700
19580
|
anthropic: "ANTHROPIC_API_KEY",
|
|
19701
19581
|
openai: "OPENAI_API_KEY",
|
|
19702
19582
|
gemini: "GOOGLE_API_KEY",
|
|
19703
|
-
azure: "AZURE_OPENAI_KEY"
|
|
19583
|
+
azure: "AZURE_OPENAI_KEY"
|
|
19704
19584
|
// Deliberately NOT `GITHUB_TOKEN`: that one is injected automatically by
|
|
19705
19585
|
// GitHub Actions and is commonly exported in developer shells for `gh`
|
|
19706
19586
|
// and git auth, and it will essentially never carry the fine-grained
|
|
19707
19587
|
// `models: read` scope this provider needs. Adopting it would wire up a
|
|
19708
19588
|
// provider that 403s on its first real call — a confusing failure for
|
|
19709
19589
|
// something that is meant to be explicit opt-in BYOK.
|
|
19710
|
-
"github-models": "GITHUB_MODELS_TOKEN"
|
|
19711
19590
|
};
|
|
19712
19591
|
const envKey = envMap[provider];
|
|
19713
19592
|
if (envKey && process.env[envKey]) return process.env[envKey];
|
|
@@ -19719,10 +19598,21 @@ var ConfigManager = class {
|
|
|
19719
19598
|
return configProvider?.apiKey;
|
|
19720
19599
|
}
|
|
19721
19600
|
async loadConfig() {
|
|
19601
|
+
this.retiredCleanup = void 0;
|
|
19722
19602
|
const configPath = path26.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
19723
19603
|
try {
|
|
19724
19604
|
const raw = await fs5.readFile(configPath, "utf-8");
|
|
19725
|
-
|
|
19605
|
+
const parsed = JSON.parse(raw);
|
|
19606
|
+
const cleanup = stripRetiredProviders(parsed);
|
|
19607
|
+
if (didCleanupChangeAnything(cleanup)) {
|
|
19608
|
+
this.retiredCleanup = cleanup;
|
|
19609
|
+
try {
|
|
19610
|
+
await fs5.writeFile(configPath, JSON.stringify(parsed, null, 2), "utf-8");
|
|
19611
|
+
} catch (err) {
|
|
19612
|
+
console.warn(`Could not persist the retired-provider migration: ${err instanceof Error ? err.message : String(err)}`);
|
|
19613
|
+
}
|
|
19614
|
+
}
|
|
19615
|
+
return validateConfig(parsed);
|
|
19726
19616
|
} catch (err) {
|
|
19727
19617
|
if (err.code === "ENOENT") {
|
|
19728
19618
|
return validateConfig({});
|
|
@@ -19731,27 +19621,25 @@ var ConfigManager = class {
|
|
|
19731
19621
|
}
|
|
19732
19622
|
}
|
|
19733
19623
|
async injectEnvKeys() {
|
|
19734
|
-
const
|
|
19624
|
+
const wasEmpty = this.config.providers.length === 0;
|
|
19625
|
+
const emptiedByRetirement = wasEmpty && !!this.retiredCleanup;
|
|
19735
19626
|
const envProviders = [
|
|
19736
19627
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
19737
19628
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
19738
19629
|
{ env: "GOOGLE_API_KEY", type: "gemini" },
|
|
19739
|
-
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
19740
|
-
// See getApiKey() above for why this is GITHUB_MODELS_TOKEN and never
|
|
19741
|
-
// the ambient GITHUB_TOKEN.
|
|
19742
|
-
{ env: "GITHUB_MODELS_TOKEN", type: "github-models" }
|
|
19630
|
+
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
19743
19631
|
];
|
|
19744
19632
|
for (const { env, type } of envProviders) {
|
|
19745
19633
|
const key = process.env[env];
|
|
19746
19634
|
if (!key) continue;
|
|
19747
19635
|
const existing = this.config.providers.find((p) => p.type === type);
|
|
19748
|
-
if (!existing &&
|
|
19636
|
+
if (!existing && wasEmpty) {
|
|
19749
19637
|
this.config.providers.push({ type, apiKey: key });
|
|
19750
19638
|
} else if (existing && !existing.apiKey) {
|
|
19751
19639
|
existing.apiKey = key;
|
|
19752
19640
|
}
|
|
19753
19641
|
}
|
|
19754
|
-
if (
|
|
19642
|
+
if (wasEmpty && !emptiedByRetirement && !this.config.providers.find((p) => p.type === "ollama")) {
|
|
19755
19643
|
this.config.providers.push({ type: "ollama" });
|
|
19756
19644
|
}
|
|
19757
19645
|
}
|
|
@@ -22388,7 +22276,9 @@ function gatherSyncBundle(config) {
|
|
|
22388
22276
|
if (Object.keys(prefs).length) bundle.prefs = prefs;
|
|
22389
22277
|
return bundle;
|
|
22390
22278
|
}
|
|
22391
|
-
function applySyncBundle(bundle, config) {
|
|
22279
|
+
function applySyncBundle(bundle, config, cleanup) {
|
|
22280
|
+
const dropped = stripRetiredProviders(bundle);
|
|
22281
|
+
if (cleanup) cleanup.removed = dropped.removed;
|
|
22392
22282
|
const next = { ...config, tools: { ...config.tools } };
|
|
22393
22283
|
if (bundle.providers) next.providers = mergeByKey(config.providers, bundle.providers, providerSig) ?? [];
|
|
22394
22284
|
if (bundle.webSearch) next.tools.webSearch = { ...config.tools?.webSearch, ...bundle.webSearch };
|
|
@@ -22404,6 +22294,14 @@ function applySyncBundle(bundle, config) {
|
|
|
22404
22294
|
}
|
|
22405
22295
|
if (bundle.models) next.models = { ...config.models, ...bundle.models };
|
|
22406
22296
|
if (bundle.budget) next.budget = { ...config.budget, ...bundle.budget };
|
|
22297
|
+
const merged = { ...next.models };
|
|
22298
|
+
const clearedFromMerge = clearRetiredPins(merged);
|
|
22299
|
+
if (clearedFromMerge.length > 0) next.models = merged;
|
|
22300
|
+
if (cleanup) {
|
|
22301
|
+
cleanup.clearedPins = ["t1", "t2", "t3"].filter(
|
|
22302
|
+
(t) => (dropped.clearedPins.includes(t) || clearedFromMerge.includes(t)) && next.models?.[t] === void 0
|
|
22303
|
+
);
|
|
22304
|
+
}
|
|
22407
22305
|
if (bundle.prefs) {
|
|
22408
22306
|
const p = bundle.prefs;
|
|
22409
22307
|
if (p.autoBias !== void 0) next.autoBias = p.autoBias;
|
|
@@ -22414,6 +22312,6 @@ function applySyncBundle(bundle, config) {
|
|
|
22414
22312
|
return next;
|
|
22415
22313
|
}
|
|
22416
22314
|
|
|
22417
|
-
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore,
|
|
22315
|
+
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, RESUME_SCHEMA_VERSION, ResumeStore, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskGraphValidationError, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, evaluateAcceptance, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hasUsableProvider, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sessionWorthRemembering, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
|
|
22418
22316
|
//# sourceMappingURL=index.js.map
|
|
22419
22317
|
//# sourceMappingURL=index.js.map
|