cascade-ai 0.70.0 → 0.71.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 +205 -340
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +205 -340
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +164 -294
- package/dist/index.cjs +164 -294
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -15
- package/dist/index.d.ts +45 -15
- package/dist/index.js +164 -292
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -1806,11 +1806,11 @@ var init_pricing = __esm({
|
|
|
1806
1806
|
});
|
|
1807
1807
|
|
|
1808
1808
|
// src/constants.ts
|
|
1809
|
-
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_CREDENTIALS_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE,
|
|
1809
|
+
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_CREDENTIALS_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
|
|
1810
1810
|
var init_constants = __esm({
|
|
1811
1811
|
"src/constants.ts"() {
|
|
1812
1812
|
init_pricing();
|
|
1813
|
-
CASCADE_VERSION = "0.
|
|
1813
|
+
CASCADE_VERSION = "0.71.0";
|
|
1814
1814
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1815
1815
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1816
1816
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -2209,9 +2209,6 @@ var init_constants = __esm({
|
|
|
2209
2209
|
OLLAMA_BASE_URL = "http://localhost:11434";
|
|
2210
2210
|
LM_STUDIO_BASE_URL = "http://localhost:1234";
|
|
2211
2211
|
AZURE_BASE_URL_TEMPLATE = "https://{resource}.openai.azure.com";
|
|
2212
|
-
GITHUB_MODELS_INFERENCE_URL = "https://models.github.ai/inference";
|
|
2213
|
-
GITHUB_MODELS_CATALOG_URL = "https://models.github.ai/catalog/models";
|
|
2214
|
-
GITHUB_MODELS_API_VERSION = "2026-03-10";
|
|
2215
2212
|
TOOL_NAMES = {
|
|
2216
2213
|
SHELL: "shell",
|
|
2217
2214
|
FILE_READ: "file_read",
|
|
@@ -3369,222 +3366,6 @@ var init_net = __esm({
|
|
|
3369
3366
|
}
|
|
3370
3367
|
});
|
|
3371
3368
|
|
|
3372
|
-
// src/providers/github-models.ts
|
|
3373
|
-
var github_models_exports = {};
|
|
3374
|
-
__export(github_models_exports, {
|
|
3375
|
-
GITHUB_MODELS_MAX_INPUT_TOKENS: () => GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
3376
|
-
GITHUB_MODELS_MAX_OUTPUT_TOKENS: () => GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3377
|
-
GitHubModelsProvider: () => GitHubModelsProvider,
|
|
3378
|
-
stripModelOwnerPrefix: () => stripModelOwnerPrefix
|
|
3379
|
-
});
|
|
3380
|
-
function stripModelOwnerPrefix(id) {
|
|
3381
|
-
const slash = (id || "").indexOf("/");
|
|
3382
|
-
return slash === -1 ? id : id.slice(slash + 1);
|
|
3383
|
-
}
|
|
3384
|
-
function pickString(o, keys) {
|
|
3385
|
-
for (const k of keys) {
|
|
3386
|
-
const v = o[k];
|
|
3387
|
-
if (typeof v === "string" && v.length > 0) return v;
|
|
3388
|
-
}
|
|
3389
|
-
return void 0;
|
|
3390
|
-
}
|
|
3391
|
-
function pickNumber(o, keys) {
|
|
3392
|
-
for (const k of keys) {
|
|
3393
|
-
const v = k.includes(".") ? o[k.split(".")[0]]?.[k.split(".")[1]] : o[k];
|
|
3394
|
-
if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
|
|
3395
|
-
}
|
|
3396
|
-
return void 0;
|
|
3397
|
-
}
|
|
3398
|
-
function looksVisionCapable(o) {
|
|
3399
|
-
for (const k of ["supported_input_modalities", "input_modalities", "modalities", "capabilities"]) {
|
|
3400
|
-
const v = o[k];
|
|
3401
|
-
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /image|vision/i.test(x))) return true;
|
|
3402
|
-
}
|
|
3403
|
-
return false;
|
|
3404
|
-
}
|
|
3405
|
-
function looksToolCapable(o) {
|
|
3406
|
-
const params = o["supported_parameters"];
|
|
3407
|
-
if (Array.isArray(params) && params.some((x) => typeof x === "string" && /^tools?$|function.?call/i.test(x))) {
|
|
3408
|
-
return true;
|
|
3409
|
-
}
|
|
3410
|
-
for (const k of ["capabilities", "supported_output_modalities"]) {
|
|
3411
|
-
const v = o[k];
|
|
3412
|
-
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /tool|function.?call/i.test(x))) return true;
|
|
3413
|
-
}
|
|
3414
|
-
const caps = o["capabilities"];
|
|
3415
|
-
if (caps && typeof caps === "object" && !Array.isArray(caps)) {
|
|
3416
|
-
const c = caps;
|
|
3417
|
-
if (c["function_calling"] === true || c["tool_calling"] === true || c["tools"] === true) return true;
|
|
3418
|
-
}
|
|
3419
|
-
return false;
|
|
3420
|
-
}
|
|
3421
|
-
function parseNextLink(linkHeader) {
|
|
3422
|
-
if (!linkHeader) return null;
|
|
3423
|
-
for (const part of linkHeader.split(",")) {
|
|
3424
|
-
const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
|
|
3425
|
-
if (match) return match[1];
|
|
3426
|
-
}
|
|
3427
|
-
return null;
|
|
3428
|
-
}
|
|
3429
|
-
var GITHUB_MODELS_MAX_OUTPUT_TOKENS, GITHUB_MODELS_MAX_INPUT_TOKENS, DEFAULT_CONTEXT_WINDOW, GITHUB_MODELS_MAX_CATALOG_PAGES, GITHUB_MODELS_CATALOG_ORIGIN, GitHubModelsProvider;
|
|
3430
|
-
var init_github_models = __esm({
|
|
3431
|
-
"src/providers/github-models.ts"() {
|
|
3432
|
-
init_constants();
|
|
3433
|
-
init_openai();
|
|
3434
|
-
init_model_filter();
|
|
3435
|
-
init_net();
|
|
3436
|
-
GITHUB_MODELS_MAX_OUTPUT_TOKENS = 4e3;
|
|
3437
|
-
GITHUB_MODELS_MAX_INPUT_TOKENS = 8e3;
|
|
3438
|
-
DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
3439
|
-
GITHUB_MODELS_MAX_CATALOG_PAGES = 20;
|
|
3440
|
-
GITHUB_MODELS_CATALOG_ORIGIN = new URL(GITHUB_MODELS_CATALOG_URL).origin;
|
|
3441
|
-
GitHubModelsProvider = class extends OpenAIProvider {
|
|
3442
|
-
constructor(config, model) {
|
|
3443
|
-
super({ ...config, apiKey: config.apiKey ?? "" }, model);
|
|
3444
|
-
this.useMaxCompletionTokens = isReasoningModel(stripModelOwnerPrefix(model.id));
|
|
3445
|
-
this.client = new OpenAI__default.default({
|
|
3446
|
-
apiKey: config.apiKey ?? "",
|
|
3447
|
-
baseURL: GITHUB_MODELS_INFERENCE_URL,
|
|
3448
|
-
defaultHeaders: { "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION, "User-Agent": "Cascade-AI" }
|
|
3449
|
-
});
|
|
3450
|
-
}
|
|
3451
|
-
/**
|
|
3452
|
-
* `OpenAIProvider.generateStream()` builds its request budget as
|
|
3453
|
-
* `options.maxTokens ?? this.model.maxOutputTokens` — an explicit per-call
|
|
3454
|
-
* `maxTokens` (T1Administrator's final compilation asks for 8,000, well
|
|
3455
|
-
* above GitHub's real ~4K hard cap) wins outright and is sent unclamped.
|
|
3456
|
-
* GitHub then rejects the request instead of answering, so a run routed to
|
|
3457
|
-
* this provider for that step fails outright rather than getting a
|
|
3458
|
-
* (perfectly serviceable, just shorter) completion. Clamp here, once, right
|
|
3459
|
-
* before delegating to the inherited implementation, rather than teaching
|
|
3460
|
-
* every caller GitHub's specific cap.
|
|
3461
|
-
*/
|
|
3462
|
-
async generateStream(options, onChunk) {
|
|
3463
|
-
const clamped = options.maxTokens !== void 0 && options.maxTokens > GITHUB_MODELS_MAX_OUTPUT_TOKENS ? { ...options, maxTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS } : options;
|
|
3464
|
-
return super.generateStream(clamped, onChunk);
|
|
3465
|
-
}
|
|
3466
|
-
/**
|
|
3467
|
-
* The catalog is a GitHub REST resource, not an OpenAI `/models` list, and
|
|
3468
|
-
* wants GitHub's own header trio — the plain `Accept: application/json` the
|
|
3469
|
-
* openai-compatible provider sends is not the same request.
|
|
3470
|
-
*/
|
|
3471
|
-
catalogHeaders() {
|
|
3472
|
-
return {
|
|
3473
|
-
Accept: "application/vnd.github+json",
|
|
3474
|
-
Authorization: `Bearer ${this.config.apiKey ?? ""}`,
|
|
3475
|
-
"X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION,
|
|
3476
|
-
// GitHub's REST API rejects any request with no User-Agent — a plain 403
|
|
3477
|
-
// regardless of how valid the PAT is. nodeHttpFetch is a thin wrapper
|
|
3478
|
-
// over node:http/https, neither of which sets one the way a browser's
|
|
3479
|
-
// fetch or a full HTTP client library would, so this has to be explicit.
|
|
3480
|
-
"User-Agent": "Cascade-AI"
|
|
3481
|
-
};
|
|
3482
|
-
}
|
|
3483
|
-
async listModels() {
|
|
3484
|
-
const raw = [];
|
|
3485
|
-
let url = GITHUB_MODELS_CATALOG_URL;
|
|
3486
|
-
let pages = 0;
|
|
3487
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3488
|
-
while (url && pages < GITHUB_MODELS_MAX_CATALOG_PAGES) {
|
|
3489
|
-
if (visited.has(url)) break;
|
|
3490
|
-
visited.add(url);
|
|
3491
|
-
const res = await nodeHttpFetch(url, { headers: this.catalogHeaders() }, 0, {
|
|
3492
|
-
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3493
|
-
});
|
|
3494
|
-
if (!res.ok) throw new Error(`GitHub Models catalog ${url} returned HTTP ${res.status}`);
|
|
3495
|
-
const body = await res.json();
|
|
3496
|
-
const pageItems = Array.isArray(body) ? body : Array.isArray(body?.models) ? body.models : Array.isArray(body?.data) ? body.data : [];
|
|
3497
|
-
raw.push(...pageItems);
|
|
3498
|
-
const next = parseNextLink(res.headers.get("link"));
|
|
3499
|
-
if (next) {
|
|
3500
|
-
const resolved = new URL(next, url);
|
|
3501
|
-
if (resolved.origin !== GITHUB_MODELS_CATALOG_ORIGIN) {
|
|
3502
|
-
throw new Error(`Refusing cross-origin GitHub Models pagination URL: ${resolved.origin}`);
|
|
3503
|
-
}
|
|
3504
|
-
url = resolved.href;
|
|
3505
|
-
} else {
|
|
3506
|
-
url = null;
|
|
3507
|
-
}
|
|
3508
|
-
pages++;
|
|
3509
|
-
}
|
|
3510
|
-
const entries = raw.map((m) => {
|
|
3511
|
-
if (typeof m === "string") {
|
|
3512
|
-
return { id: m, name: m, contextWindow: DEFAULT_CONTEXT_WINDOW, vision: false, tools: false };
|
|
3513
|
-
}
|
|
3514
|
-
if (!m || typeof m !== "object") return void 0;
|
|
3515
|
-
const o = m;
|
|
3516
|
-
const id = pickString(o, ["id", "name", "model"]);
|
|
3517
|
-
if (!id) return void 0;
|
|
3518
|
-
return {
|
|
3519
|
-
id,
|
|
3520
|
-
name: pickString(o, ["friendly_name", "display_name", "name"]) ?? id,
|
|
3521
|
-
contextWindow: pickNumber(o, [
|
|
3522
|
-
"context_window",
|
|
3523
|
-
"context_length",
|
|
3524
|
-
"max_input_tokens",
|
|
3525
|
-
"limits.max_input_tokens",
|
|
3526
|
-
"limits.max_context_tokens"
|
|
3527
|
-
]) ?? DEFAULT_CONTEXT_WINDOW,
|
|
3528
|
-
vision: looksVisionCapable(o),
|
|
3529
|
-
tools: looksToolCapable(o)
|
|
3530
|
-
};
|
|
3531
|
-
}).filter((e) => e !== void 0);
|
|
3532
|
-
if (entries.length === 0) return [];
|
|
3533
|
-
const chat = entries.filter((e) => isChatModel(e.id));
|
|
3534
|
-
return (chat.length ? chat : entries).map((e) => ({
|
|
3535
|
-
id: e.id,
|
|
3536
|
-
// full `owner/model` — what the API is addressed by
|
|
3537
|
-
name: e.name,
|
|
3538
|
-
provider: "github-models",
|
|
3539
|
-
// GitHub's real per-request input cap, not the base model's larger one
|
|
3540
|
-
// (see GITHUB_MODELS_MAX_INPUT_TOKENS above) — the same reasoning
|
|
3541
|
-
// already applied to maxOutputTokens below, for the input side.
|
|
3542
|
-
contextWindow: Math.min(e.contextWindow, GITHUB_MODELS_MAX_INPUT_TOKENS),
|
|
3543
|
-
isVisionCapable: e.vision,
|
|
3544
|
-
// A REAL $0, not a missing number — usage is bundled into the quota of
|
|
3545
|
-
// the plan the PAT belongs to and is never billed per token, so
|
|
3546
|
-
// `pricingUnknown: false` is the honest value (see the flag's contract in
|
|
3547
|
-
// core/router/pricing.ts: 0 alone is ambiguous). Set here rather than in
|
|
3548
|
-
// pricing-data.json because resolvePricing() matches exactly on
|
|
3549
|
-
// provider|model|region with no wildcard: every catalog model would need
|
|
3550
|
-
// a hand-curated row, and the next one GitHub adds would read as "cost
|
|
3551
|
-
// unknown" — an inconsistent picture for a billing reality that is
|
|
3552
|
-
// identical across the entire catalog.
|
|
3553
|
-
//
|
|
3554
|
-
// `isLocal` is the OTHER way to reach a real $0, and it is the wrong one:
|
|
3555
|
-
// it means "runs on this machine" and carries consequences that are
|
|
3556
|
-
// simply false for a hosted API — the shared concurrency-1
|
|
3557
|
-
// LocalRequestQueue (serialising these calls behind actual Ollama GPU
|
|
3558
|
-
// inference), the 300s local rather than 120s cloud inference timeout,
|
|
3559
|
-
// and a literal "[local]" tag in `cascade models`.
|
|
3560
|
-
inputCostPer1kTokens: 0,
|
|
3561
|
-
outputCostPer1kTokens: 0,
|
|
3562
|
-
pricingUnknown: false,
|
|
3563
|
-
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3564
|
-
supportsStreaming: true,
|
|
3565
|
-
// Not every catalog model supports function/tool calling — a hardcoded
|
|
3566
|
-
// `true` sent `tools` to models that reject the parameter outright and
|
|
3567
|
-
// never engaged t3-worker.ts's existing text-tool fallback (only an
|
|
3568
|
-
// explicit `false` does). See looksToolCapable's own comment for why
|
|
3569
|
-
// "not positively advertised" defaults to false rather than true here.
|
|
3570
|
-
supportsToolUse: e.tools,
|
|
3571
|
-
isLocal: false
|
|
3572
|
-
}));
|
|
3573
|
-
}
|
|
3574
|
-
async isAvailable() {
|
|
3575
|
-
try {
|
|
3576
|
-
const res = await nodeHttpFetch(GITHUB_MODELS_CATALOG_URL, { headers: this.catalogHeaders() }, 0, {
|
|
3577
|
-
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3578
|
-
});
|
|
3579
|
-
return res.ok;
|
|
3580
|
-
} catch {
|
|
3581
|
-
return false;
|
|
3582
|
-
}
|
|
3583
|
-
}
|
|
3584
|
-
};
|
|
3585
|
-
}
|
|
3586
|
-
});
|
|
3587
|
-
|
|
3588
3369
|
// src/providers/ollama.ts
|
|
3589
3370
|
var ollama_exports = {};
|
|
3590
3371
|
__export(ollama_exports, {
|
|
@@ -4926,6 +4707,19 @@ Original error: ${err.message}`
|
|
|
4926
4707
|
const rows = provider ? this.db.prepare("SELECT metadata FROM model_cache WHERE provider = ?").all(provider) : this.db.prepare("SELECT metadata FROM model_cache").all();
|
|
4927
4708
|
return rows.map((r) => JSON.parse(r.metadata));
|
|
4928
4709
|
}
|
|
4710
|
+
/**
|
|
4711
|
+
* Deletes cached rows for a provider type that no longer exists in
|
|
4712
|
+
* `ProviderType`, so it takes a raw string rather than the union.
|
|
4713
|
+
*
|
|
4714
|
+
* Needed because the cache outlives the code: the REPL's loadCache() only
|
|
4715
|
+
* re-discovers when the cache is EMPTY or older than 24h, so rows left
|
|
4716
|
+
* behind by a removed provider read as "cache populated" and suppress
|
|
4717
|
+
* discovery for the providers that replaced it — `/model` then shows a
|
|
4718
|
+
* configured provider with zero models until the rows age out.
|
|
4719
|
+
*/
|
|
4720
|
+
purgeCachedModelsForRetiredProvider(providerType) {
|
|
4721
|
+
return this.db.prepare("DELETE FROM model_cache WHERE provider = ?").run(providerType).changes;
|
|
4722
|
+
}
|
|
4929
4723
|
clearModelCache(provider) {
|
|
4930
4724
|
if (provider) {
|
|
4931
4725
|
this.db.prepare("DELETE FROM model_cache WHERE provider = ?").run(provider);
|
|
@@ -5219,7 +5013,7 @@ Original error: ${err.message}`
|
|
|
5219
5013
|
}
|
|
5220
5014
|
};
|
|
5221
5015
|
var ProviderConfigSchema = zod.z.object({
|
|
5222
|
-
type: zod.z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
5016
|
+
type: zod.z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"]),
|
|
5223
5017
|
label: zod.z.string().optional(),
|
|
5224
5018
|
apiKey: zod.z.string().optional(),
|
|
5225
5019
|
baseUrl: zod.z.string().url().optional(),
|
|
@@ -5688,6 +5482,67 @@ function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
|
5688
5482
|
}
|
|
5689
5483
|
return merged;
|
|
5690
5484
|
}
|
|
5485
|
+
|
|
5486
|
+
// src/config/retired-providers.ts
|
|
5487
|
+
var RETIRED_PROVIDER_TYPES = {
|
|
5488
|
+
"github-models": "GitHub retired GitHub Models on 30 July 2026; models.github.ai no longer responds."
|
|
5489
|
+
};
|
|
5490
|
+
function isRetiredProviderType(type) {
|
|
5491
|
+
return typeof type === "string" && Object.hasOwn(RETIRED_PROVIDER_TYPES, type);
|
|
5492
|
+
}
|
|
5493
|
+
function didCleanupChangeAnything(c) {
|
|
5494
|
+
return c.removed.length > 0 || c.clearedPins.length > 0;
|
|
5495
|
+
}
|
|
5496
|
+
function stripRetiredProviders(raw) {
|
|
5497
|
+
const cleanup = { removed: [], clearedPins: [] };
|
|
5498
|
+
if (typeof raw !== "object" || raw === null) return cleanup;
|
|
5499
|
+
const cfg = raw;
|
|
5500
|
+
if (Array.isArray(cfg["providers"])) {
|
|
5501
|
+
const kept = cfg["providers"].filter((p) => {
|
|
5502
|
+
const type = p?.type;
|
|
5503
|
+
if (!isRetiredProviderType(type)) return true;
|
|
5504
|
+
if (!cleanup.removed.includes(type)) cleanup.removed.push(type);
|
|
5505
|
+
return false;
|
|
5506
|
+
});
|
|
5507
|
+
cfg["providers"] = kept;
|
|
5508
|
+
}
|
|
5509
|
+
cleanup.clearedPins = clearRetiredPins(cfg["models"]);
|
|
5510
|
+
return cleanup;
|
|
5511
|
+
}
|
|
5512
|
+
function clearRetiredPins(models) {
|
|
5513
|
+
const cleared = [];
|
|
5514
|
+
if (typeof models !== "object" || models === null) return cleared;
|
|
5515
|
+
const tiers = models;
|
|
5516
|
+
for (const tier of ["t1", "t2", "t3"]) {
|
|
5517
|
+
const pin = tiers[tier];
|
|
5518
|
+
if (typeof pin !== "string") continue;
|
|
5519
|
+
const prefix = pin.slice(0, pin.indexOf(":")).toLowerCase();
|
|
5520
|
+
if (pin.includes(":") && isRetiredProviderType(prefix)) {
|
|
5521
|
+
delete tiers[tier];
|
|
5522
|
+
cleared.push(tier);
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
return cleared;
|
|
5526
|
+
}
|
|
5527
|
+
function filterRetiredCredentials(providers) {
|
|
5528
|
+
const removed = [];
|
|
5529
|
+
const kept = providers.filter((p) => {
|
|
5530
|
+
if (!isRetiredProviderType(p.type)) return true;
|
|
5531
|
+
if (!removed.includes(p.type)) removed.push(p.type);
|
|
5532
|
+
return false;
|
|
5533
|
+
});
|
|
5534
|
+
return { kept, removed };
|
|
5535
|
+
}
|
|
5536
|
+
function describeCleanup(c) {
|
|
5537
|
+
const parts = [];
|
|
5538
|
+
for (const type of c.removed) {
|
|
5539
|
+
parts.push(`removed the retired "${type}" provider (${RETIRED_PROVIDER_TYPES[type]})`);
|
|
5540
|
+
}
|
|
5541
|
+
if (c.clearedPins.length > 0) {
|
|
5542
|
+
parts.push(`reset ${c.clearedPins.map((t) => t.toUpperCase()).join("/")} to Auto, since the pin named it`);
|
|
5543
|
+
}
|
|
5544
|
+
return `Cascade config migration: ${parts.join("; ")}.`;
|
|
5545
|
+
}
|
|
5691
5546
|
var MCP_TOOL_PREFIX = "mcp__";
|
|
5692
5547
|
var MAX_SEGMENT_LENGTH = 24;
|
|
5693
5548
|
var SEGMENT_HASH_LENGTH = 8;
|
|
@@ -5871,7 +5726,7 @@ init_constants();
|
|
|
5871
5726
|
var KEY_OPTIONAL_PROVIDER_TYPES = /* @__PURE__ */ new Set(["ollama", "openai-compatible"]);
|
|
5872
5727
|
function hasUsableProvider(providers) {
|
|
5873
5728
|
if (!providers?.length) return false;
|
|
5874
|
-
return providers.some((p) => KEY_OPTIONAL_PROVIDER_TYPES.has(p.type) || !!p.apiKey);
|
|
5729
|
+
return providers.some((p) => KEY_OPTIONAL_PROVIDER_TYPES.has(p.type) || !!p.apiKey || !!p.authToken);
|
|
5875
5730
|
}
|
|
5876
5731
|
var ConfigManager = class {
|
|
5877
5732
|
config;
|
|
@@ -5881,6 +5736,29 @@ var ConfigManager = class {
|
|
|
5881
5736
|
cascadeMd = null;
|
|
5882
5737
|
workspacePath;
|
|
5883
5738
|
globalDir;
|
|
5739
|
+
/**
|
|
5740
|
+
* What THIS load migrated out, if anything. Strictly per-load state: reset
|
|
5741
|
+
* at the top of every `load()`, because two places read it as a statement
|
|
5742
|
+
* about the load in progress — `injectEnvKeys()` asks whether an empty
|
|
5743
|
+
* provider list was emptied by a retirement, and the end of `load()` warns
|
|
5744
|
+
* and builds the user-facing notice from it. Left set, a second `load()` on
|
|
5745
|
+
* the same instance (`startRepl()` does one after its setup wizard) would
|
|
5746
|
+
* re-announce a migration that already happened and suppress the Ollama
|
|
5747
|
+
* fallback for a list that is empty for some unrelated reason.
|
|
5748
|
+
*/
|
|
5749
|
+
retiredCleanup;
|
|
5750
|
+
/**
|
|
5751
|
+
* Human-readable migration notice held for a UI to display. `console.warn`
|
|
5752
|
+
* is not sufficient: the REPL clears the TTY immediately after load(), and
|
|
5753
|
+
* the desktop emits from the main process where nothing is rendered.
|
|
5754
|
+
*/
|
|
5755
|
+
pendingRetiredNotice;
|
|
5756
|
+
/** Returns the pending migration notice once, then forgets it. */
|
|
5757
|
+
takeRetiredNotice() {
|
|
5758
|
+
const n = this.pendingRetiredNotice;
|
|
5759
|
+
this.pendingRetiredNotice = void 0;
|
|
5760
|
+
return n;
|
|
5761
|
+
}
|
|
5884
5762
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
5885
5763
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
5886
5764
|
this.workspacePath = workspacePath;
|
|
@@ -5900,10 +5778,32 @@ var ConfigManager = class {
|
|
|
5900
5778
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
5901
5779
|
this.keystore = new Keystore(path34__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
5902
5780
|
this.store = new MemoryStore(path34__default.default.join(this.workspacePath, CASCADE_DB_FILE));
|
|
5781
|
+
const globalCreds = filterRetiredCredentials(loadGlobalCredentials(this.globalDir));
|
|
5782
|
+
if (globalCreds.removed.length > 0) {
|
|
5783
|
+
try {
|
|
5784
|
+
saveGlobalCredentials(this.globalDir, globalCreds.kept);
|
|
5785
|
+
} catch (err) {
|
|
5786
|
+
console.warn(`Could not rewrite the global credential store: ${err instanceof Error ? err.message : String(err)}`);
|
|
5787
|
+
}
|
|
5788
|
+
this.retiredCleanup = {
|
|
5789
|
+
removed: [.../* @__PURE__ */ new Set([...this.retiredCleanup?.removed ?? [], ...globalCreds.removed])],
|
|
5790
|
+
clearedPins: this.retiredCleanup?.clearedPins ?? []
|
|
5791
|
+
};
|
|
5792
|
+
}
|
|
5903
5793
|
await this.injectEnvKeys();
|
|
5904
|
-
this.config.providers = mergeGlobalCredentials(this.config.providers,
|
|
5794
|
+
this.config.providers = mergeGlobalCredentials(this.config.providers, globalCreds.kept);
|
|
5795
|
+
for (const type of Object.keys(RETIRED_PROVIDER_TYPES)) {
|
|
5796
|
+
try {
|
|
5797
|
+
this.store.purgeCachedModelsForRetiredProvider(type);
|
|
5798
|
+
} catch {
|
|
5799
|
+
}
|
|
5800
|
+
}
|
|
5905
5801
|
await this.ensureDefaultIdentity();
|
|
5906
5802
|
if (mcpNamesChanged) await this.save();
|
|
5803
|
+
if (this.retiredCleanup) {
|
|
5804
|
+
console.warn(describeCleanup(this.retiredCleanup));
|
|
5805
|
+
this.pendingRetiredNotice = describeCleanup(this.retiredCleanup);
|
|
5806
|
+
}
|
|
5907
5807
|
}
|
|
5908
5808
|
getConfig() {
|
|
5909
5809
|
return this.config;
|
|
@@ -5942,14 +5842,13 @@ var ConfigManager = class {
|
|
|
5942
5842
|
anthropic: "ANTHROPIC_API_KEY",
|
|
5943
5843
|
openai: "OPENAI_API_KEY",
|
|
5944
5844
|
gemini: "GOOGLE_API_KEY",
|
|
5945
|
-
azure: "AZURE_OPENAI_KEY"
|
|
5845
|
+
azure: "AZURE_OPENAI_KEY"
|
|
5946
5846
|
// Deliberately NOT `GITHUB_TOKEN`: that one is injected automatically by
|
|
5947
5847
|
// GitHub Actions and is commonly exported in developer shells for `gh`
|
|
5948
5848
|
// and git auth, and it will essentially never carry the fine-grained
|
|
5949
5849
|
// `models: read` scope this provider needs. Adopting it would wire up a
|
|
5950
5850
|
// provider that 403s on its first real call — a confusing failure for
|
|
5951
5851
|
// something that is meant to be explicit opt-in BYOK.
|
|
5952
|
-
"github-models": "GITHUB_MODELS_TOKEN"
|
|
5953
5852
|
};
|
|
5954
5853
|
const envKey = envMap[provider];
|
|
5955
5854
|
if (envKey && process.env[envKey]) return process.env[envKey];
|
|
@@ -5961,10 +5860,21 @@ var ConfigManager = class {
|
|
|
5961
5860
|
return configProvider?.apiKey;
|
|
5962
5861
|
}
|
|
5963
5862
|
async loadConfig() {
|
|
5863
|
+
this.retiredCleanup = void 0;
|
|
5964
5864
|
const configPath = path34__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5965
5865
|
try {
|
|
5966
5866
|
const raw = await fs11__default.default.readFile(configPath, "utf-8");
|
|
5967
|
-
|
|
5867
|
+
const parsed = JSON.parse(raw);
|
|
5868
|
+
const cleanup = stripRetiredProviders(parsed);
|
|
5869
|
+
if (didCleanupChangeAnything(cleanup)) {
|
|
5870
|
+
this.retiredCleanup = cleanup;
|
|
5871
|
+
try {
|
|
5872
|
+
await fs11__default.default.writeFile(configPath, JSON.stringify(parsed, null, 2), "utf-8");
|
|
5873
|
+
} catch (err) {
|
|
5874
|
+
console.warn(`Could not persist the retired-provider migration: ${err instanceof Error ? err.message : String(err)}`);
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
return validateConfig(parsed);
|
|
5968
5878
|
} catch (err) {
|
|
5969
5879
|
if (err.code === "ENOENT") {
|
|
5970
5880
|
return validateConfig({});
|
|
@@ -5973,27 +5883,25 @@ var ConfigManager = class {
|
|
|
5973
5883
|
}
|
|
5974
5884
|
}
|
|
5975
5885
|
async injectEnvKeys() {
|
|
5976
|
-
const
|
|
5886
|
+
const wasEmpty = this.config.providers.length === 0;
|
|
5887
|
+
const emptiedByRetirement = wasEmpty && !!this.retiredCleanup;
|
|
5977
5888
|
const envProviders = [
|
|
5978
5889
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
5979
5890
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
5980
5891
|
{ env: "GOOGLE_API_KEY", type: "gemini" },
|
|
5981
|
-
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
5982
|
-
// See getApiKey() above for why this is GITHUB_MODELS_TOKEN and never
|
|
5983
|
-
// the ambient GITHUB_TOKEN.
|
|
5984
|
-
{ env: "GITHUB_MODELS_TOKEN", type: "github-models" }
|
|
5892
|
+
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
5985
5893
|
];
|
|
5986
5894
|
for (const { env, type } of envProviders) {
|
|
5987
5895
|
const key = process.env[env];
|
|
5988
5896
|
if (!key) continue;
|
|
5989
5897
|
const existing = this.config.providers.find((p) => p.type === type);
|
|
5990
|
-
if (!existing &&
|
|
5898
|
+
if (!existing && wasEmpty) {
|
|
5991
5899
|
this.config.providers.push({ type, apiKey: key });
|
|
5992
5900
|
} else if (existing && !existing.apiKey) {
|
|
5993
5901
|
existing.apiKey = key;
|
|
5994
5902
|
}
|
|
5995
5903
|
}
|
|
5996
|
-
if (
|
|
5904
|
+
if (wasEmpty && !emptiedByRetirement && !this.config.providers.find((p) => p.type === "ollama")) {
|
|
5997
5905
|
this.config.providers.push({ type: "ollama" });
|
|
5998
5906
|
}
|
|
5999
5907
|
}
|
|
@@ -6363,7 +6271,6 @@ init_constants();
|
|
|
6363
6271
|
init_anthropic();
|
|
6364
6272
|
init_azure();
|
|
6365
6273
|
init_gemini();
|
|
6366
|
-
init_github_models();
|
|
6367
6274
|
init_ollama();
|
|
6368
6275
|
init_openai_compatible();
|
|
6369
6276
|
init_openai();
|
|
@@ -6371,7 +6278,6 @@ init_openai();
|
|
|
6371
6278
|
// src/core/router/selector.ts
|
|
6372
6279
|
init_constants();
|
|
6373
6280
|
init_pricing();
|
|
6374
|
-
init_github_models();
|
|
6375
6281
|
function normalizeModelId(id) {
|
|
6376
6282
|
return id.replace(/^models\//, "").toLowerCase();
|
|
6377
6283
|
}
|
|
@@ -6573,7 +6479,7 @@ var ModelSelector = class {
|
|
|
6573
6479
|
if (overrideModelId.includes(":")) {
|
|
6574
6480
|
const parts = overrideModelId.split(":");
|
|
6575
6481
|
const prefix = parts[0].toLowerCase();
|
|
6576
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
6482
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
6577
6483
|
if (validProviders.includes(prefix)) {
|
|
6578
6484
|
providerStr = prefix;
|
|
6579
6485
|
actualId = parts.slice(1).join(":");
|
|
@@ -6593,31 +6499,6 @@ var ModelSelector = class {
|
|
|
6593
6499
|
else if (this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
|
|
6594
6500
|
else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
|
|
6595
6501
|
}
|
|
6596
|
-
if (providerStr === "github-models" && this.availableProviders.has("github-models")) {
|
|
6597
|
-
const dynamicModel = {
|
|
6598
|
-
id: actualId,
|
|
6599
|
-
name: actualId,
|
|
6600
|
-
provider: "github-models",
|
|
6601
|
-
contextWindow: GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
6602
|
-
isVisionCapable: false,
|
|
6603
|
-
inputCostPer1kTokens: 0,
|
|
6604
|
-
outputCostPer1kTokens: 0,
|
|
6605
|
-
pricingUnknown: false,
|
|
6606
|
-
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
6607
|
-
supportsStreaming: true,
|
|
6608
|
-
// Unconfirmed (no live catalog entry to read a real capability from)
|
|
6609
|
-
// must default to false, not left undefined: t3-worker.ts's text-tool
|
|
6610
|
-
// fallback only engages on a strict `=== false`, so an undefined
|
|
6611
|
-
// value here sends native `tools` to an unverified multi-vendor
|
|
6612
|
-
// model and any that don't support function calling reject the
|
|
6613
|
-
// request outright. Same default-to-false-when-unconfirmed policy
|
|
6614
|
-
// listModels() already applies for a real catalog entry.
|
|
6615
|
-
supportsToolUse: false,
|
|
6616
|
-
isLocal: false
|
|
6617
|
-
};
|
|
6618
|
-
this.addDynamicModel(dynamicModel);
|
|
6619
|
-
return dynamicModel;
|
|
6620
|
-
}
|
|
6621
6502
|
if (providerStr && this.availableProviders.has(providerStr)) {
|
|
6622
6503
|
const dynamicModel = withResolvedPricing({
|
|
6623
6504
|
id: actualId,
|
|
@@ -6710,20 +6591,7 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
6710
6591
|
gemini: 6e4,
|
|
6711
6592
|
azure: 3e4,
|
|
6712
6593
|
"openai-compatible": 3e4,
|
|
6713
|
-
ollama: Number.POSITIVE_INFINITY
|
|
6714
|
-
// An order of magnitude below the others, on purpose. GitHub Models is
|
|
6715
|
-
// rate-limited by REQUESTS (Free ≈10 RPM, Pro ≈20, plus low daily caps), and
|
|
6716
|
-
// this bucket meters TOKENS — there is no request-count limiter in this
|
|
6717
|
-
// codebase, and building one is not in scope. The approximation works because
|
|
6718
|
-
// the router reserves `model.maxOutputTokens + 512` per call, and the
|
|
6719
|
-
// provider reports GitHub's real ~4K per-request cap rather than the
|
|
6720
|
-
// underlying model's larger one — so every call withdraws a near-constant
|
|
6721
|
-
// ~4.5K regardless of how short the reply actually turns out to be. At 8K/min
|
|
6722
|
-
// that lands around 1–2 calls per minute of headroom: comfortably inside even
|
|
6723
|
-
// the Free tier, with room for the daily cap to matter more than this does.
|
|
6724
|
-
// Users on a higher tier can raise it with no code change through the generic
|
|
6725
|
-
// `config.rateLimits.providerTpm['github-models']` override.
|
|
6726
|
-
"github-models": 8e3
|
|
6594
|
+
ollama: Number.POSITIVE_INFINITY
|
|
6727
6595
|
};
|
|
6728
6596
|
var TpmLimiter = class {
|
|
6729
6597
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -7070,7 +6938,6 @@ var ModelProfiler = class {
|
|
|
7070
6938
|
(m) => !alreadyProfiled.has(m.id) && !SKIP_PATTERN.test(m.id) && !SKIP_PATTERN.test(m.name)
|
|
7071
6939
|
);
|
|
7072
6940
|
if (toProfile.length === 0) return;
|
|
7073
|
-
const probesHitGithubModels = this.router?.getModelForTier("T3")?.provider === "github-models";
|
|
7074
6941
|
const openRouterModels = await fetchOpenRouterModels();
|
|
7075
6942
|
const orByNormalizedId = /* @__PURE__ */ new Map();
|
|
7076
6943
|
for (const m of openRouterModels) {
|
|
@@ -7085,7 +6952,7 @@ var ModelProfiler = class {
|
|
|
7085
6952
|
if (orMatch?.description) {
|
|
7086
6953
|
specializations = extractSpecializations(orMatch.description);
|
|
7087
6954
|
}
|
|
7088
|
-
if (specializations.length === 0 && this.router
|
|
6955
|
+
if (specializations.length === 0 && this.router) {
|
|
7089
6956
|
specializations = await queryModelDirectly(this.router);
|
|
7090
6957
|
}
|
|
7091
6958
|
this.store.saveModelProfile(model.id, model.provider, specializations);
|
|
@@ -7310,7 +7177,6 @@ var LiveDataProvider = class {
|
|
|
7310
7177
|
*/
|
|
7311
7178
|
applyLivePricing(models) {
|
|
7312
7179
|
return models.map((m) => {
|
|
7313
|
-
if (m.provider === "github-models") return m;
|
|
7314
7180
|
const p = this.getLivePrice(m.baseModelId ?? m.id);
|
|
7315
7181
|
if (!p) return m;
|
|
7316
7182
|
const reconciled = reconcilePrice(m, p);
|
|
@@ -7349,7 +7215,6 @@ var LiveDataProvider = class {
|
|
|
7349
7215
|
*/
|
|
7350
7216
|
applyLiveCapabilities(models) {
|
|
7351
7217
|
return models.map((m) => {
|
|
7352
|
-
if (m.provider === "github-models") return m;
|
|
7353
7218
|
const c = this.getCapability(m.baseModelId ?? m.id);
|
|
7354
7219
|
if (!c) return m;
|
|
7355
7220
|
const next = { ...m };
|
|
@@ -7653,16 +7518,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7653
7518
|
for (const model of azureDeployments) this.selector.addDynamicModel(model);
|
|
7654
7519
|
availableProviders.add("azure");
|
|
7655
7520
|
}
|
|
7656
|
-
if (availableProviders.has("github-models")) {
|
|
7657
|
-
await this.discoverGitHubModelsCatalog(config);
|
|
7658
|
-
}
|
|
7659
7521
|
await this.validateCloudProviderModels(config);
|
|
7660
7522
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
7661
7523
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
7662
7524
|
if (!override || override === "auto") continue;
|
|
7663
7525
|
const model = this.selector.selectForTier(tier, override);
|
|
7664
7526
|
if (!model) {
|
|
7665
|
-
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
7527
|
+
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
7666
7528
|
const hasProviderPrefix = override.includes(":") && knownProviders.some((p) => override.startsWith(p + ":"));
|
|
7667
7529
|
if (hasProviderPrefix) {
|
|
7668
7530
|
const provider = override.split(":")[0];
|
|
@@ -7817,7 +7679,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
7817
7679
|
* models without a package upgrade. Mirrors discoverOllamaModels.
|
|
7818
7680
|
*/
|
|
7819
7681
|
async discoverProviderModels() {
|
|
7820
|
-
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"
|
|
7682
|
+
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"];
|
|
7821
7683
|
const tasks = cloud.map(async (type) => {
|
|
7822
7684
|
if (!this.selector.isProviderAvailable(type)) return;
|
|
7823
7685
|
const seed = this.getAnyModelForProvider(type);
|
|
@@ -8332,26 +8194,6 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8332
8194
|
return false;
|
|
8333
8195
|
}
|
|
8334
8196
|
}
|
|
8335
|
-
/**
|
|
8336
|
-
* Registers GitHub Models' real catalog ids via addDynamicModel so init()'s
|
|
8337
|
-
* tier-fill loop can pick one. Registration alone does not create a bound
|
|
8338
|
-
* BaseProvider instance (see ensureProvider) — the tier-fill loop's own
|
|
8339
|
-
* ensureProvider call does that for whichever model it actually selects,
|
|
8340
|
-
* which is why this runs BEFORE that loop rather than only in the
|
|
8341
|
-
* background discoverProviderModels() refresh.
|
|
8342
|
-
*/
|
|
8343
|
-
async discoverGitHubModelsCatalog(config) {
|
|
8344
|
-
const cfg = config.providers.find((p) => p.type === "github-models") ?? { type: "github-models" };
|
|
8345
|
-
const seed = this.getAnyModelForProvider("github-models");
|
|
8346
|
-
if (!seed) return;
|
|
8347
|
-
try {
|
|
8348
|
-
const provider = new GitHubModelsProvider(cfg, seed);
|
|
8349
|
-
const models = await provider.listModels();
|
|
8350
|
-
for (const m of models) this.selector.addDynamicModel(m);
|
|
8351
|
-
} catch (err) {
|
|
8352
|
-
console.warn("[router] GitHub Models catalog discovery failed:", err instanceof Error ? err.message : err);
|
|
8353
|
-
}
|
|
8354
|
-
}
|
|
8355
8197
|
ensureProvider(model, configs) {
|
|
8356
8198
|
const key = `${model.provider}:${model.id}`;
|
|
8357
8199
|
if (this.providers.has(key)) return;
|
|
@@ -8376,8 +8218,6 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8376
8218
|
return new OllamaProvider(cfg, model);
|
|
8377
8219
|
case "openai-compatible":
|
|
8378
8220
|
return new OpenAICompatibleProvider(cfg, model);
|
|
8379
|
-
case "github-models":
|
|
8380
|
-
return new GitHubModelsProvider(cfg, model);
|
|
8381
8221
|
default:
|
|
8382
8222
|
throw new Error(`Unsupported provider type: ${String(cfg.type)}`);
|
|
8383
8223
|
}
|
|
@@ -8385,7 +8225,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
8385
8225
|
getAnyModelForProvider(type) {
|
|
8386
8226
|
const fromCatalog = Object.values(MODELS).find((m) => m.provider === type);
|
|
8387
8227
|
if (fromCatalog) return fromCatalog;
|
|
8388
|
-
if (type === "openai-compatible" || type === "azure"
|
|
8228
|
+
if (type === "openai-compatible" || type === "azure") {
|
|
8389
8229
|
return {
|
|
8390
8230
|
id: type,
|
|
8391
8231
|
name: type,
|
|
@@ -22263,10 +22103,7 @@ async function refreshModelCache(store, providers) {
|
|
|
22263
22103
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
22264
22104
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
22265
22105
|
else if (provider.type === "openai-compatible") instance = new OpenAICompatibleProvider(provider, dummyModel);
|
|
22266
|
-
else if (provider.type === "
|
|
22267
|
-
const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
|
|
22268
|
-
instance = new GitHubModelsProvider2(provider, dummyModel);
|
|
22269
|
-
} else if (provider.type === "azure") {
|
|
22106
|
+
else if (provider.type === "azure") {
|
|
22270
22107
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
22271
22108
|
instance = new AzureOpenAIProvider2(provider, dummyModel);
|
|
22272
22109
|
}
|
|
@@ -22279,7 +22116,7 @@ async function refreshModelCache(store, providers) {
|
|
|
22279
22116
|
}
|
|
22280
22117
|
}
|
|
22281
22118
|
}
|
|
22282
|
-
function Repl({ config, workspacePath, themeName, initialPrompt, identityName, altScreen = false }) {
|
|
22119
|
+
function Repl({ config, workspacePath, themeName, initialPrompt, identityName, startupNotice, altScreen = false }) {
|
|
22283
22120
|
const { exit } = ink.useApp();
|
|
22284
22121
|
const { stdout } = ink.useStdout();
|
|
22285
22122
|
const [theme, setTheme] = React2.useState(() => getTheme(themeName));
|
|
@@ -22450,17 +22287,29 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
22450
22287
|
}
|
|
22451
22288
|
setCurrentIdentityId(initialIdentityId);
|
|
22452
22289
|
const loadCache = async () => {
|
|
22290
|
+
const toMap = (models2) => {
|
|
22291
|
+
const map = /* @__PURE__ */ new Map();
|
|
22292
|
+
for (const m of models2) {
|
|
22293
|
+
const list = map.get(m.provider) ?? [];
|
|
22294
|
+
list.push(m);
|
|
22295
|
+
map.set(m.provider, list);
|
|
22296
|
+
}
|
|
22297
|
+
return map;
|
|
22298
|
+
};
|
|
22453
22299
|
const models = store.getCachedModels();
|
|
22454
|
-
|
|
22455
|
-
|
|
22456
|
-
|
|
22457
|
-
|
|
22458
|
-
map.set(m.provider, list);
|
|
22300
|
+
setCachedModels(toMap(models));
|
|
22301
|
+
if (models.length === 0 || store.getCacheAge() > 24 * 60 * 60 * 1e3) {
|
|
22302
|
+
await refreshModelCache(store, config.providers);
|
|
22303
|
+
setCachedModels(toMap(store.getCachedModels()));
|
|
22459
22304
|
}
|
|
22460
|
-
setCachedModels(map);
|
|
22461
|
-
if (models.length === 0 || store.getCacheAge() > 24 * 60 * 60 * 1e3) await refreshModelCache(store, config.providers);
|
|
22462
22305
|
};
|
|
22463
22306
|
loadCache();
|
|
22307
|
+
if (startupNotice) {
|
|
22308
|
+
dispatch({
|
|
22309
|
+
type: "ADD_MESSAGE",
|
|
22310
|
+
message: { id: crypto.randomUUID(), role: "system", content: `\u26A0 ${startupNotice}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
22311
|
+
});
|
|
22312
|
+
}
|
|
22464
22313
|
store.createSession({ id: sessionIdRef.current, title: "Cascade Session", createdAt: startedAtRef.current, updatedAt: startedAtRef.current, identityId: config.defaultIdentityId ?? "default", workspacePath, messages: [], metadata: { totalTokens: 0, totalCostUsd: 0, modelsUsed: [], toolsUsed: [], taskCount: 0 } });
|
|
22465
22314
|
persistRuntimeSession("ACTIVE");
|
|
22466
22315
|
try {
|
|
@@ -23383,7 +23232,7 @@ async function validateConfiguredModels(config) {
|
|
|
23383
23232
|
function inferProviderFromModelId(id, providers) {
|
|
23384
23233
|
if (id.includes(":")) {
|
|
23385
23234
|
const prefix = id.split(":")[0].toLowerCase();
|
|
23386
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
23235
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
23387
23236
|
if (validProviders.includes(prefix)) {
|
|
23388
23237
|
return prefix;
|
|
23389
23238
|
}
|
|
@@ -23680,10 +23529,9 @@ var PROVIDER_LABELS = {
|
|
|
23680
23529
|
gemini: "Google Gemini",
|
|
23681
23530
|
azure: "Azure OpenAI",
|
|
23682
23531
|
ollama: "Ollama (local)",
|
|
23683
|
-
"openai-compatible": "OpenAI-Compatible"
|
|
23684
|
-
"github-models": "GitHub Models"
|
|
23532
|
+
"openai-compatible": "OpenAI-Compatible"
|
|
23685
23533
|
};
|
|
23686
|
-
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible"
|
|
23534
|
+
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible"];
|
|
23687
23535
|
function buildInitialEntries(types) {
|
|
23688
23536
|
return [...types].map((type) => ({
|
|
23689
23537
|
id: crypto.randomUUID(),
|
|
@@ -23855,13 +23703,6 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
23855
23703
|
const fetched = await p.listModels();
|
|
23856
23704
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
23857
23705
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
23858
|
-
} else if (type === "github-models") {
|
|
23859
|
-
const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
|
|
23860
|
-
const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
23861
|
-
const p = new GitHubModelsProvider2({ type, apiKey }, dummyModel);
|
|
23862
|
-
const fetched = await p.listModels();
|
|
23863
|
-
fetched.forEach((m) => models.push({ id: `${type}:${m.id}`, name: m.name, providerLabel: entry.label }));
|
|
23864
|
-
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
23865
23706
|
} else if (type === "gemini") {
|
|
23866
23707
|
const { GeminiProvider: GeminiProvider2 } = await Promise.resolve().then(() => (init_gemini(), gemini_exports));
|
|
23867
23708
|
const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
@@ -24040,8 +23881,7 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
24040
23881
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: focused ? theme.colors.primary : selected ? theme.colors.foreground : theme.colors.muted, children: PROVIDER_LABELS[p] }),
|
|
24041
23882
|
p === "azure" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 multiple deployments supported" }),
|
|
24042
23883
|
p === "openai-compatible" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 Groq, Together, custom" }),
|
|
24043
|
-
p === "ollama" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" })
|
|
24044
|
-
p === "github-models" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 GitHub PAT with `models: read`" })
|
|
23884
|
+
p === "ollama" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" })
|
|
24045
23885
|
] }, p);
|
|
24046
23886
|
}) }),
|
|
24047
23887
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: "space select \xB7 a all \xB7 i invert \xB7 enter continue" }) }),
|
|
@@ -24448,8 +24288,7 @@ async function doctorCommand() {
|
|
|
24448
24288
|
{ type: "anthropic", name: "Anthropic" },
|
|
24449
24289
|
{ type: "openai", name: "OpenAI" },
|
|
24450
24290
|
{ type: "gemini", name: "Google Gemini" },
|
|
24451
|
-
{ type: "azure", name: "Azure OpenAI" }
|
|
24452
|
-
{ type: "github-models", name: "GitHub Models" }
|
|
24291
|
+
{ type: "azure", name: "Azure OpenAI" }
|
|
24453
24292
|
];
|
|
24454
24293
|
for (const { type, name } of providers) {
|
|
24455
24294
|
const key = cm.getApiKey(type);
|
|
@@ -24710,7 +24549,9 @@ function gatherSyncBundle(config) {
|
|
|
24710
24549
|
if (Object.keys(prefs).length) bundle.prefs = prefs;
|
|
24711
24550
|
return bundle;
|
|
24712
24551
|
}
|
|
24713
|
-
function applySyncBundle(bundle, config) {
|
|
24552
|
+
function applySyncBundle(bundle, config, cleanup) {
|
|
24553
|
+
const dropped = stripRetiredProviders(bundle);
|
|
24554
|
+
if (cleanup) cleanup.removed = dropped.removed;
|
|
24714
24555
|
const next = { ...config, tools: { ...config.tools } };
|
|
24715
24556
|
if (bundle.providers) next.providers = mergeByKey(config.providers, bundle.providers, providerSig) ?? [];
|
|
24716
24557
|
if (bundle.webSearch) next.tools.webSearch = { ...config.tools?.webSearch, ...bundle.webSearch };
|
|
@@ -24726,6 +24567,14 @@ function applySyncBundle(bundle, config) {
|
|
|
24726
24567
|
}
|
|
24727
24568
|
if (bundle.models) next.models = { ...config.models, ...bundle.models };
|
|
24728
24569
|
if (bundle.budget) next.budget = { ...config.budget, ...bundle.budget };
|
|
24570
|
+
const merged = { ...next.models };
|
|
24571
|
+
const clearedFromMerge = clearRetiredPins(merged);
|
|
24572
|
+
if (clearedFromMerge.length > 0) next.models = merged;
|
|
24573
|
+
if (cleanup) {
|
|
24574
|
+
cleanup.clearedPins = ["t1", "t2", "t3"].filter(
|
|
24575
|
+
(t) => (dropped.clearedPins.includes(t) || clearedFromMerge.includes(t)) && next.models?.[t] === void 0
|
|
24576
|
+
);
|
|
24577
|
+
}
|
|
24729
24578
|
if (bundle.prefs) {
|
|
24730
24579
|
const p = bundle.prefs;
|
|
24731
24580
|
if (p.autoBias !== void 0) next.autoBias = p.autoBias;
|
|
@@ -25033,14 +24882,28 @@ async function syncPullCommand() {
|
|
|
25033
24882
|
console.log(chalk7__default.default.dim("\n Cancelled.\n"));
|
|
25034
24883
|
return;
|
|
25035
24884
|
}
|
|
24885
|
+
let bundle;
|
|
24886
|
+
try {
|
|
24887
|
+
bundle = await decryptJSON(blob, pass);
|
|
24888
|
+
} catch {
|
|
24889
|
+
console.log(chalk7__default.default.red("\n Could not decrypt \u2014 check your passphrase and try again.\n"));
|
|
24890
|
+
process.exitCode = 1;
|
|
24891
|
+
return;
|
|
24892
|
+
}
|
|
25036
24893
|
try {
|
|
25037
|
-
const bundle = await decryptJSON(blob, pass);
|
|
25038
24894
|
const cm = new ConfigManager(process.cwd());
|
|
25039
24895
|
await cm.load();
|
|
25040
|
-
|
|
24896
|
+
const cleanup = { removed: [], clearedPins: [] };
|
|
24897
|
+
await cm.updateConfig(applySyncBundle(bundle, cm.getConfig(), cleanup));
|
|
25041
24898
|
console.log(chalk7__default.default.green("\n \u2713 Applied your synced settings. Your keys are ready here.\n"));
|
|
25042
|
-
|
|
25043
|
-
|
|
24899
|
+
if (didCleanupChangeAnything(cleanup)) {
|
|
24900
|
+
console.log(chalk7__default.default.yellow(` ${describeCleanup(cleanup)}
|
|
24901
|
+
`));
|
|
24902
|
+
}
|
|
24903
|
+
} catch (err) {
|
|
24904
|
+
console.log(chalk7__default.default.red(`
|
|
24905
|
+
Could not apply your synced settings: ${err instanceof Error ? err.message : String(err)}
|
|
24906
|
+
`));
|
|
25044
24907
|
process.exitCode = 1;
|
|
25045
24908
|
}
|
|
25046
24909
|
}
|
|
@@ -27667,6 +27530,7 @@ async function startRepl(options) {
|
|
|
27667
27530
|
process.exit(1);
|
|
27668
27531
|
}
|
|
27669
27532
|
let config = cm.getConfig();
|
|
27533
|
+
const startupNotice = cm.takeRetiredNotice();
|
|
27670
27534
|
const needsSetup = !hasUsableProvider(config.providers);
|
|
27671
27535
|
if (needsSetup) {
|
|
27672
27536
|
console.log(chalk7__default.default.magenta(" \u25C8 No providers configured \u2014 launching setup wizard\u2026"));
|
|
@@ -27689,6 +27553,7 @@ async function startRepl(options) {
|
|
|
27689
27553
|
themeName: options.theme ?? config.theme ?? DEFAULT_THEME,
|
|
27690
27554
|
initialPrompt: options.prompt,
|
|
27691
27555
|
identityName: options.identity,
|
|
27556
|
+
startupNotice,
|
|
27692
27557
|
altScreen: useAltScreen
|
|
27693
27558
|
}),
|
|
27694
27559
|
{ exitOnCtrlC: false }
|