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.js
CHANGED
|
@@ -1753,11 +1753,11 @@ var init_pricing = __esm({
|
|
|
1753
1753
|
});
|
|
1754
1754
|
|
|
1755
1755
|
// src/constants.ts
|
|
1756
|
-
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,
|
|
1756
|
+
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;
|
|
1757
1757
|
var init_constants = __esm({
|
|
1758
1758
|
"src/constants.ts"() {
|
|
1759
1759
|
init_pricing();
|
|
1760
|
-
CASCADE_VERSION = "0.
|
|
1760
|
+
CASCADE_VERSION = "0.71.0";
|
|
1761
1761
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
1762
1762
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
1763
1763
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -2156,9 +2156,6 @@ var init_constants = __esm({
|
|
|
2156
2156
|
OLLAMA_BASE_URL = "http://localhost:11434";
|
|
2157
2157
|
LM_STUDIO_BASE_URL = "http://localhost:1234";
|
|
2158
2158
|
AZURE_BASE_URL_TEMPLATE = "https://{resource}.openai.azure.com";
|
|
2159
|
-
GITHUB_MODELS_INFERENCE_URL = "https://models.github.ai/inference";
|
|
2160
|
-
GITHUB_MODELS_CATALOG_URL = "https://models.github.ai/catalog/models";
|
|
2161
|
-
GITHUB_MODELS_API_VERSION = "2026-03-10";
|
|
2162
2159
|
TOOL_NAMES = {
|
|
2163
2160
|
SHELL: "shell",
|
|
2164
2161
|
FILE_READ: "file_read",
|
|
@@ -3316,222 +3313,6 @@ var init_net = __esm({
|
|
|
3316
3313
|
}
|
|
3317
3314
|
});
|
|
3318
3315
|
|
|
3319
|
-
// src/providers/github-models.ts
|
|
3320
|
-
var github_models_exports = {};
|
|
3321
|
-
__export(github_models_exports, {
|
|
3322
|
-
GITHUB_MODELS_MAX_INPUT_TOKENS: () => GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
3323
|
-
GITHUB_MODELS_MAX_OUTPUT_TOKENS: () => GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3324
|
-
GitHubModelsProvider: () => GitHubModelsProvider,
|
|
3325
|
-
stripModelOwnerPrefix: () => stripModelOwnerPrefix
|
|
3326
|
-
});
|
|
3327
|
-
function stripModelOwnerPrefix(id) {
|
|
3328
|
-
const slash = (id || "").indexOf("/");
|
|
3329
|
-
return slash === -1 ? id : id.slice(slash + 1);
|
|
3330
|
-
}
|
|
3331
|
-
function pickString(o, keys) {
|
|
3332
|
-
for (const k of keys) {
|
|
3333
|
-
const v = o[k];
|
|
3334
|
-
if (typeof v === "string" && v.length > 0) return v;
|
|
3335
|
-
}
|
|
3336
|
-
return void 0;
|
|
3337
|
-
}
|
|
3338
|
-
function pickNumber(o, keys) {
|
|
3339
|
-
for (const k of keys) {
|
|
3340
|
-
const v = k.includes(".") ? o[k.split(".")[0]]?.[k.split(".")[1]] : o[k];
|
|
3341
|
-
if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
|
|
3342
|
-
}
|
|
3343
|
-
return void 0;
|
|
3344
|
-
}
|
|
3345
|
-
function looksVisionCapable(o) {
|
|
3346
|
-
for (const k of ["supported_input_modalities", "input_modalities", "modalities", "capabilities"]) {
|
|
3347
|
-
const v = o[k];
|
|
3348
|
-
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /image|vision/i.test(x))) return true;
|
|
3349
|
-
}
|
|
3350
|
-
return false;
|
|
3351
|
-
}
|
|
3352
|
-
function looksToolCapable(o) {
|
|
3353
|
-
const params = o["supported_parameters"];
|
|
3354
|
-
if (Array.isArray(params) && params.some((x) => typeof x === "string" && /^tools?$|function.?call/i.test(x))) {
|
|
3355
|
-
return true;
|
|
3356
|
-
}
|
|
3357
|
-
for (const k of ["capabilities", "supported_output_modalities"]) {
|
|
3358
|
-
const v = o[k];
|
|
3359
|
-
if (Array.isArray(v) && v.some((x) => typeof x === "string" && /tool|function.?call/i.test(x))) return true;
|
|
3360
|
-
}
|
|
3361
|
-
const caps = o["capabilities"];
|
|
3362
|
-
if (caps && typeof caps === "object" && !Array.isArray(caps)) {
|
|
3363
|
-
const c = caps;
|
|
3364
|
-
if (c["function_calling"] === true || c["tool_calling"] === true || c["tools"] === true) return true;
|
|
3365
|
-
}
|
|
3366
|
-
return false;
|
|
3367
|
-
}
|
|
3368
|
-
function parseNextLink(linkHeader) {
|
|
3369
|
-
if (!linkHeader) return null;
|
|
3370
|
-
for (const part of linkHeader.split(",")) {
|
|
3371
|
-
const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
|
|
3372
|
-
if (match) return match[1];
|
|
3373
|
-
}
|
|
3374
|
-
return null;
|
|
3375
|
-
}
|
|
3376
|
-
var GITHUB_MODELS_MAX_OUTPUT_TOKENS, GITHUB_MODELS_MAX_INPUT_TOKENS, DEFAULT_CONTEXT_WINDOW, GITHUB_MODELS_MAX_CATALOG_PAGES, GITHUB_MODELS_CATALOG_ORIGIN, GitHubModelsProvider;
|
|
3377
|
-
var init_github_models = __esm({
|
|
3378
|
-
"src/providers/github-models.ts"() {
|
|
3379
|
-
init_constants();
|
|
3380
|
-
init_openai();
|
|
3381
|
-
init_model_filter();
|
|
3382
|
-
init_net();
|
|
3383
|
-
GITHUB_MODELS_MAX_OUTPUT_TOKENS = 4e3;
|
|
3384
|
-
GITHUB_MODELS_MAX_INPUT_TOKENS = 8e3;
|
|
3385
|
-
DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
3386
|
-
GITHUB_MODELS_MAX_CATALOG_PAGES = 20;
|
|
3387
|
-
GITHUB_MODELS_CATALOG_ORIGIN = new URL(GITHUB_MODELS_CATALOG_URL).origin;
|
|
3388
|
-
GitHubModelsProvider = class extends OpenAIProvider {
|
|
3389
|
-
constructor(config, model) {
|
|
3390
|
-
super({ ...config, apiKey: config.apiKey ?? "" }, model);
|
|
3391
|
-
this.useMaxCompletionTokens = isReasoningModel(stripModelOwnerPrefix(model.id));
|
|
3392
|
-
this.client = new OpenAI({
|
|
3393
|
-
apiKey: config.apiKey ?? "",
|
|
3394
|
-
baseURL: GITHUB_MODELS_INFERENCE_URL,
|
|
3395
|
-
defaultHeaders: { "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION, "User-Agent": "Cascade-AI" }
|
|
3396
|
-
});
|
|
3397
|
-
}
|
|
3398
|
-
/**
|
|
3399
|
-
* `OpenAIProvider.generateStream()` builds its request budget as
|
|
3400
|
-
* `options.maxTokens ?? this.model.maxOutputTokens` — an explicit per-call
|
|
3401
|
-
* `maxTokens` (T1Administrator's final compilation asks for 8,000, well
|
|
3402
|
-
* above GitHub's real ~4K hard cap) wins outright and is sent unclamped.
|
|
3403
|
-
* GitHub then rejects the request instead of answering, so a run routed to
|
|
3404
|
-
* this provider for that step fails outright rather than getting a
|
|
3405
|
-
* (perfectly serviceable, just shorter) completion. Clamp here, once, right
|
|
3406
|
-
* before delegating to the inherited implementation, rather than teaching
|
|
3407
|
-
* every caller GitHub's specific cap.
|
|
3408
|
-
*/
|
|
3409
|
-
async generateStream(options, onChunk) {
|
|
3410
|
-
const clamped = options.maxTokens !== void 0 && options.maxTokens > GITHUB_MODELS_MAX_OUTPUT_TOKENS ? { ...options, maxTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS } : options;
|
|
3411
|
-
return super.generateStream(clamped, onChunk);
|
|
3412
|
-
}
|
|
3413
|
-
/**
|
|
3414
|
-
* The catalog is a GitHub REST resource, not an OpenAI `/models` list, and
|
|
3415
|
-
* wants GitHub's own header trio — the plain `Accept: application/json` the
|
|
3416
|
-
* openai-compatible provider sends is not the same request.
|
|
3417
|
-
*/
|
|
3418
|
-
catalogHeaders() {
|
|
3419
|
-
return {
|
|
3420
|
-
Accept: "application/vnd.github+json",
|
|
3421
|
-
Authorization: `Bearer ${this.config.apiKey ?? ""}`,
|
|
3422
|
-
"X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION,
|
|
3423
|
-
// GitHub's REST API rejects any request with no User-Agent — a plain 403
|
|
3424
|
-
// regardless of how valid the PAT is. nodeHttpFetch is a thin wrapper
|
|
3425
|
-
// over node:http/https, neither of which sets one the way a browser's
|
|
3426
|
-
// fetch or a full HTTP client library would, so this has to be explicit.
|
|
3427
|
-
"User-Agent": "Cascade-AI"
|
|
3428
|
-
};
|
|
3429
|
-
}
|
|
3430
|
-
async listModels() {
|
|
3431
|
-
const raw = [];
|
|
3432
|
-
let url = GITHUB_MODELS_CATALOG_URL;
|
|
3433
|
-
let pages = 0;
|
|
3434
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3435
|
-
while (url && pages < GITHUB_MODELS_MAX_CATALOG_PAGES) {
|
|
3436
|
-
if (visited.has(url)) break;
|
|
3437
|
-
visited.add(url);
|
|
3438
|
-
const res = await nodeHttpFetch(url, { headers: this.catalogHeaders() }, 0, {
|
|
3439
|
-
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3440
|
-
});
|
|
3441
|
-
if (!res.ok) throw new Error(`GitHub Models catalog ${url} returned HTTP ${res.status}`);
|
|
3442
|
-
const body = await res.json();
|
|
3443
|
-
const pageItems = Array.isArray(body) ? body : Array.isArray(body?.models) ? body.models : Array.isArray(body?.data) ? body.data : [];
|
|
3444
|
-
raw.push(...pageItems);
|
|
3445
|
-
const next = parseNextLink(res.headers.get("link"));
|
|
3446
|
-
if (next) {
|
|
3447
|
-
const resolved = new URL(next, url);
|
|
3448
|
-
if (resolved.origin !== GITHUB_MODELS_CATALOG_ORIGIN) {
|
|
3449
|
-
throw new Error(`Refusing cross-origin GitHub Models pagination URL: ${resolved.origin}`);
|
|
3450
|
-
}
|
|
3451
|
-
url = resolved.href;
|
|
3452
|
-
} else {
|
|
3453
|
-
url = null;
|
|
3454
|
-
}
|
|
3455
|
-
pages++;
|
|
3456
|
-
}
|
|
3457
|
-
const entries = raw.map((m) => {
|
|
3458
|
-
if (typeof m === "string") {
|
|
3459
|
-
return { id: m, name: m, contextWindow: DEFAULT_CONTEXT_WINDOW, vision: false, tools: false };
|
|
3460
|
-
}
|
|
3461
|
-
if (!m || typeof m !== "object") return void 0;
|
|
3462
|
-
const o = m;
|
|
3463
|
-
const id = pickString(o, ["id", "name", "model"]);
|
|
3464
|
-
if (!id) return void 0;
|
|
3465
|
-
return {
|
|
3466
|
-
id,
|
|
3467
|
-
name: pickString(o, ["friendly_name", "display_name", "name"]) ?? id,
|
|
3468
|
-
contextWindow: pickNumber(o, [
|
|
3469
|
-
"context_window",
|
|
3470
|
-
"context_length",
|
|
3471
|
-
"max_input_tokens",
|
|
3472
|
-
"limits.max_input_tokens",
|
|
3473
|
-
"limits.max_context_tokens"
|
|
3474
|
-
]) ?? DEFAULT_CONTEXT_WINDOW,
|
|
3475
|
-
vision: looksVisionCapable(o),
|
|
3476
|
-
tools: looksToolCapable(o)
|
|
3477
|
-
};
|
|
3478
|
-
}).filter((e) => e !== void 0);
|
|
3479
|
-
if (entries.length === 0) return [];
|
|
3480
|
-
const chat = entries.filter((e) => isChatModel(e.id));
|
|
3481
|
-
return (chat.length ? chat : entries).map((e) => ({
|
|
3482
|
-
id: e.id,
|
|
3483
|
-
// full `owner/model` — what the API is addressed by
|
|
3484
|
-
name: e.name,
|
|
3485
|
-
provider: "github-models",
|
|
3486
|
-
// GitHub's real per-request input cap, not the base model's larger one
|
|
3487
|
-
// (see GITHUB_MODELS_MAX_INPUT_TOKENS above) — the same reasoning
|
|
3488
|
-
// already applied to maxOutputTokens below, for the input side.
|
|
3489
|
-
contextWindow: Math.min(e.contextWindow, GITHUB_MODELS_MAX_INPUT_TOKENS),
|
|
3490
|
-
isVisionCapable: e.vision,
|
|
3491
|
-
// A REAL $0, not a missing number — usage is bundled into the quota of
|
|
3492
|
-
// the plan the PAT belongs to and is never billed per token, so
|
|
3493
|
-
// `pricingUnknown: false` is the honest value (see the flag's contract in
|
|
3494
|
-
// core/router/pricing.ts: 0 alone is ambiguous). Set here rather than in
|
|
3495
|
-
// pricing-data.json because resolvePricing() matches exactly on
|
|
3496
|
-
// provider|model|region with no wildcard: every catalog model would need
|
|
3497
|
-
// a hand-curated row, and the next one GitHub adds would read as "cost
|
|
3498
|
-
// unknown" — an inconsistent picture for a billing reality that is
|
|
3499
|
-
// identical across the entire catalog.
|
|
3500
|
-
//
|
|
3501
|
-
// `isLocal` is the OTHER way to reach a real $0, and it is the wrong one:
|
|
3502
|
-
// it means "runs on this machine" and carries consequences that are
|
|
3503
|
-
// simply false for a hosted API — the shared concurrency-1
|
|
3504
|
-
// LocalRequestQueue (serialising these calls behind actual Ollama GPU
|
|
3505
|
-
// inference), the 300s local rather than 120s cloud inference timeout,
|
|
3506
|
-
// and a literal "[local]" tag in `cascade models`.
|
|
3507
|
-
inputCostPer1kTokens: 0,
|
|
3508
|
-
outputCostPer1kTokens: 0,
|
|
3509
|
-
pricingUnknown: false,
|
|
3510
|
-
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
3511
|
-
supportsStreaming: true,
|
|
3512
|
-
// Not every catalog model supports function/tool calling — a hardcoded
|
|
3513
|
-
// `true` sent `tools` to models that reject the parameter outright and
|
|
3514
|
-
// never engaged t3-worker.ts's existing text-tool fallback (only an
|
|
3515
|
-
// explicit `false` does). See looksToolCapable's own comment for why
|
|
3516
|
-
// "not positively advertised" defaults to false rather than true here.
|
|
3517
|
-
supportsToolUse: e.tools,
|
|
3518
|
-
isLocal: false
|
|
3519
|
-
}));
|
|
3520
|
-
}
|
|
3521
|
-
async isAvailable() {
|
|
3522
|
-
try {
|
|
3523
|
-
const res = await nodeHttpFetch(GITHUB_MODELS_CATALOG_URL, { headers: this.catalogHeaders() }, 0, {
|
|
3524
|
-
allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
|
|
3525
|
-
});
|
|
3526
|
-
return res.ok;
|
|
3527
|
-
} catch {
|
|
3528
|
-
return false;
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
3531
|
-
};
|
|
3532
|
-
}
|
|
3533
|
-
});
|
|
3534
|
-
|
|
3535
3316
|
// src/providers/ollama.ts
|
|
3536
3317
|
var ollama_exports = {};
|
|
3537
3318
|
__export(ollama_exports, {
|
|
@@ -4873,6 +4654,19 @@ Original error: ${err.message}`
|
|
|
4873
4654
|
const rows = provider ? this.db.prepare("SELECT metadata FROM model_cache WHERE provider = ?").all(provider) : this.db.prepare("SELECT metadata FROM model_cache").all();
|
|
4874
4655
|
return rows.map((r) => JSON.parse(r.metadata));
|
|
4875
4656
|
}
|
|
4657
|
+
/**
|
|
4658
|
+
* Deletes cached rows for a provider type that no longer exists in
|
|
4659
|
+
* `ProviderType`, so it takes a raw string rather than the union.
|
|
4660
|
+
*
|
|
4661
|
+
* Needed because the cache outlives the code: the REPL's loadCache() only
|
|
4662
|
+
* re-discovers when the cache is EMPTY or older than 24h, so rows left
|
|
4663
|
+
* behind by a removed provider read as "cache populated" and suppress
|
|
4664
|
+
* discovery for the providers that replaced it — `/model` then shows a
|
|
4665
|
+
* configured provider with zero models until the rows age out.
|
|
4666
|
+
*/
|
|
4667
|
+
purgeCachedModelsForRetiredProvider(providerType) {
|
|
4668
|
+
return this.db.prepare("DELETE FROM model_cache WHERE provider = ?").run(providerType).changes;
|
|
4669
|
+
}
|
|
4876
4670
|
clearModelCache(provider) {
|
|
4877
4671
|
if (provider) {
|
|
4878
4672
|
this.db.prepare("DELETE FROM model_cache WHERE provider = ?").run(provider);
|
|
@@ -5166,7 +4960,7 @@ Original error: ${err.message}`
|
|
|
5166
4960
|
}
|
|
5167
4961
|
};
|
|
5168
4962
|
var ProviderConfigSchema = z.object({
|
|
5169
|
-
type: z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
4963
|
+
type: z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"]),
|
|
5170
4964
|
label: z.string().optional(),
|
|
5171
4965
|
apiKey: z.string().optional(),
|
|
5172
4966
|
baseUrl: z.string().url().optional(),
|
|
@@ -5635,6 +5429,67 @@ function mergeGlobalCredentials(workspaceProviders, globalProviders) {
|
|
|
5635
5429
|
}
|
|
5636
5430
|
return merged;
|
|
5637
5431
|
}
|
|
5432
|
+
|
|
5433
|
+
// src/config/retired-providers.ts
|
|
5434
|
+
var RETIRED_PROVIDER_TYPES = {
|
|
5435
|
+
"github-models": "GitHub retired GitHub Models on 30 July 2026; models.github.ai no longer responds."
|
|
5436
|
+
};
|
|
5437
|
+
function isRetiredProviderType(type) {
|
|
5438
|
+
return typeof type === "string" && Object.hasOwn(RETIRED_PROVIDER_TYPES, type);
|
|
5439
|
+
}
|
|
5440
|
+
function didCleanupChangeAnything(c) {
|
|
5441
|
+
return c.removed.length > 0 || c.clearedPins.length > 0;
|
|
5442
|
+
}
|
|
5443
|
+
function stripRetiredProviders(raw) {
|
|
5444
|
+
const cleanup = { removed: [], clearedPins: [] };
|
|
5445
|
+
if (typeof raw !== "object" || raw === null) return cleanup;
|
|
5446
|
+
const cfg = raw;
|
|
5447
|
+
if (Array.isArray(cfg["providers"])) {
|
|
5448
|
+
const kept = cfg["providers"].filter((p) => {
|
|
5449
|
+
const type = p?.type;
|
|
5450
|
+
if (!isRetiredProviderType(type)) return true;
|
|
5451
|
+
if (!cleanup.removed.includes(type)) cleanup.removed.push(type);
|
|
5452
|
+
return false;
|
|
5453
|
+
});
|
|
5454
|
+
cfg["providers"] = kept;
|
|
5455
|
+
}
|
|
5456
|
+
cleanup.clearedPins = clearRetiredPins(cfg["models"]);
|
|
5457
|
+
return cleanup;
|
|
5458
|
+
}
|
|
5459
|
+
function clearRetiredPins(models) {
|
|
5460
|
+
const cleared = [];
|
|
5461
|
+
if (typeof models !== "object" || models === null) return cleared;
|
|
5462
|
+
const tiers = models;
|
|
5463
|
+
for (const tier of ["t1", "t2", "t3"]) {
|
|
5464
|
+
const pin = tiers[tier];
|
|
5465
|
+
if (typeof pin !== "string") continue;
|
|
5466
|
+
const prefix = pin.slice(0, pin.indexOf(":")).toLowerCase();
|
|
5467
|
+
if (pin.includes(":") && isRetiredProviderType(prefix)) {
|
|
5468
|
+
delete tiers[tier];
|
|
5469
|
+
cleared.push(tier);
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
return cleared;
|
|
5473
|
+
}
|
|
5474
|
+
function filterRetiredCredentials(providers) {
|
|
5475
|
+
const removed = [];
|
|
5476
|
+
const kept = providers.filter((p) => {
|
|
5477
|
+
if (!isRetiredProviderType(p.type)) return true;
|
|
5478
|
+
if (!removed.includes(p.type)) removed.push(p.type);
|
|
5479
|
+
return false;
|
|
5480
|
+
});
|
|
5481
|
+
return { kept, removed };
|
|
5482
|
+
}
|
|
5483
|
+
function describeCleanup(c) {
|
|
5484
|
+
const parts = [];
|
|
5485
|
+
for (const type of c.removed) {
|
|
5486
|
+
parts.push(`removed the retired "${type}" provider (${RETIRED_PROVIDER_TYPES[type]})`);
|
|
5487
|
+
}
|
|
5488
|
+
if (c.clearedPins.length > 0) {
|
|
5489
|
+
parts.push(`reset ${c.clearedPins.map((t) => t.toUpperCase()).join("/")} to Auto, since the pin named it`);
|
|
5490
|
+
}
|
|
5491
|
+
return `Cascade config migration: ${parts.join("; ")}.`;
|
|
5492
|
+
}
|
|
5638
5493
|
var MCP_TOOL_PREFIX = "mcp__";
|
|
5639
5494
|
var MAX_SEGMENT_LENGTH = 24;
|
|
5640
5495
|
var SEGMENT_HASH_LENGTH = 8;
|
|
@@ -5818,7 +5673,7 @@ init_constants();
|
|
|
5818
5673
|
var KEY_OPTIONAL_PROVIDER_TYPES = /* @__PURE__ */ new Set(["ollama", "openai-compatible"]);
|
|
5819
5674
|
function hasUsableProvider(providers) {
|
|
5820
5675
|
if (!providers?.length) return false;
|
|
5821
|
-
return providers.some((p) => KEY_OPTIONAL_PROVIDER_TYPES.has(p.type) || !!p.apiKey);
|
|
5676
|
+
return providers.some((p) => KEY_OPTIONAL_PROVIDER_TYPES.has(p.type) || !!p.apiKey || !!p.authToken);
|
|
5822
5677
|
}
|
|
5823
5678
|
var ConfigManager = class {
|
|
5824
5679
|
config;
|
|
@@ -5828,6 +5683,29 @@ var ConfigManager = class {
|
|
|
5828
5683
|
cascadeMd = null;
|
|
5829
5684
|
workspacePath;
|
|
5830
5685
|
globalDir;
|
|
5686
|
+
/**
|
|
5687
|
+
* What THIS load migrated out, if anything. Strictly per-load state: reset
|
|
5688
|
+
* at the top of every `load()`, because two places read it as a statement
|
|
5689
|
+
* about the load in progress — `injectEnvKeys()` asks whether an empty
|
|
5690
|
+
* provider list was emptied by a retirement, and the end of `load()` warns
|
|
5691
|
+
* and builds the user-facing notice from it. Left set, a second `load()` on
|
|
5692
|
+
* the same instance (`startRepl()` does one after its setup wizard) would
|
|
5693
|
+
* re-announce a migration that already happened and suppress the Ollama
|
|
5694
|
+
* fallback for a list that is empty for some unrelated reason.
|
|
5695
|
+
*/
|
|
5696
|
+
retiredCleanup;
|
|
5697
|
+
/**
|
|
5698
|
+
* Human-readable migration notice held for a UI to display. `console.warn`
|
|
5699
|
+
* is not sufficient: the REPL clears the TTY immediately after load(), and
|
|
5700
|
+
* the desktop emits from the main process where nothing is rendered.
|
|
5701
|
+
*/
|
|
5702
|
+
pendingRetiredNotice;
|
|
5703
|
+
/** Returns the pending migration notice once, then forgets it. */
|
|
5704
|
+
takeRetiredNotice() {
|
|
5705
|
+
const n = this.pendingRetiredNotice;
|
|
5706
|
+
this.pendingRetiredNotice = void 0;
|
|
5707
|
+
return n;
|
|
5708
|
+
}
|
|
5831
5709
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
5832
5710
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
5833
5711
|
this.workspacePath = workspacePath;
|
|
@@ -5847,10 +5725,32 @@ var ConfigManager = class {
|
|
|
5847
5725
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
5848
5726
|
this.keystore = new Keystore(path34.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
5849
5727
|
this.store = new MemoryStore(path34.join(this.workspacePath, CASCADE_DB_FILE));
|
|
5728
|
+
const globalCreds = filterRetiredCredentials(loadGlobalCredentials(this.globalDir));
|
|
5729
|
+
if (globalCreds.removed.length > 0) {
|
|
5730
|
+
try {
|
|
5731
|
+
saveGlobalCredentials(this.globalDir, globalCreds.kept);
|
|
5732
|
+
} catch (err) {
|
|
5733
|
+
console.warn(`Could not rewrite the global credential store: ${err instanceof Error ? err.message : String(err)}`);
|
|
5734
|
+
}
|
|
5735
|
+
this.retiredCleanup = {
|
|
5736
|
+
removed: [.../* @__PURE__ */ new Set([...this.retiredCleanup?.removed ?? [], ...globalCreds.removed])],
|
|
5737
|
+
clearedPins: this.retiredCleanup?.clearedPins ?? []
|
|
5738
|
+
};
|
|
5739
|
+
}
|
|
5850
5740
|
await this.injectEnvKeys();
|
|
5851
|
-
this.config.providers = mergeGlobalCredentials(this.config.providers,
|
|
5741
|
+
this.config.providers = mergeGlobalCredentials(this.config.providers, globalCreds.kept);
|
|
5742
|
+
for (const type of Object.keys(RETIRED_PROVIDER_TYPES)) {
|
|
5743
|
+
try {
|
|
5744
|
+
this.store.purgeCachedModelsForRetiredProvider(type);
|
|
5745
|
+
} catch {
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5852
5748
|
await this.ensureDefaultIdentity();
|
|
5853
5749
|
if (mcpNamesChanged) await this.save();
|
|
5750
|
+
if (this.retiredCleanup) {
|
|
5751
|
+
console.warn(describeCleanup(this.retiredCleanup));
|
|
5752
|
+
this.pendingRetiredNotice = describeCleanup(this.retiredCleanup);
|
|
5753
|
+
}
|
|
5854
5754
|
}
|
|
5855
5755
|
getConfig() {
|
|
5856
5756
|
return this.config;
|
|
@@ -5889,14 +5789,13 @@ var ConfigManager = class {
|
|
|
5889
5789
|
anthropic: "ANTHROPIC_API_KEY",
|
|
5890
5790
|
openai: "OPENAI_API_KEY",
|
|
5891
5791
|
gemini: "GOOGLE_API_KEY",
|
|
5892
|
-
azure: "AZURE_OPENAI_KEY"
|
|
5792
|
+
azure: "AZURE_OPENAI_KEY"
|
|
5893
5793
|
// Deliberately NOT `GITHUB_TOKEN`: that one is injected automatically by
|
|
5894
5794
|
// GitHub Actions and is commonly exported in developer shells for `gh`
|
|
5895
5795
|
// and git auth, and it will essentially never carry the fine-grained
|
|
5896
5796
|
// `models: read` scope this provider needs. Adopting it would wire up a
|
|
5897
5797
|
// provider that 403s on its first real call — a confusing failure for
|
|
5898
5798
|
// something that is meant to be explicit opt-in BYOK.
|
|
5899
|
-
"github-models": "GITHUB_MODELS_TOKEN"
|
|
5900
5799
|
};
|
|
5901
5800
|
const envKey = envMap[provider];
|
|
5902
5801
|
if (envKey && process.env[envKey]) return process.env[envKey];
|
|
@@ -5908,10 +5807,21 @@ var ConfigManager = class {
|
|
|
5908
5807
|
return configProvider?.apiKey;
|
|
5909
5808
|
}
|
|
5910
5809
|
async loadConfig() {
|
|
5810
|
+
this.retiredCleanup = void 0;
|
|
5911
5811
|
const configPath = path34.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
5912
5812
|
try {
|
|
5913
5813
|
const raw = await fs11.readFile(configPath, "utf-8");
|
|
5914
|
-
|
|
5814
|
+
const parsed = JSON.parse(raw);
|
|
5815
|
+
const cleanup = stripRetiredProviders(parsed);
|
|
5816
|
+
if (didCleanupChangeAnything(cleanup)) {
|
|
5817
|
+
this.retiredCleanup = cleanup;
|
|
5818
|
+
try {
|
|
5819
|
+
await fs11.writeFile(configPath, JSON.stringify(parsed, null, 2), "utf-8");
|
|
5820
|
+
} catch (err) {
|
|
5821
|
+
console.warn(`Could not persist the retired-provider migration: ${err instanceof Error ? err.message : String(err)}`);
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
return validateConfig(parsed);
|
|
5915
5825
|
} catch (err) {
|
|
5916
5826
|
if (err.code === "ENOENT") {
|
|
5917
5827
|
return validateConfig({});
|
|
@@ -5920,27 +5830,25 @@ var ConfigManager = class {
|
|
|
5920
5830
|
}
|
|
5921
5831
|
}
|
|
5922
5832
|
async injectEnvKeys() {
|
|
5923
|
-
const
|
|
5833
|
+
const wasEmpty = this.config.providers.length === 0;
|
|
5834
|
+
const emptiedByRetirement = wasEmpty && !!this.retiredCleanup;
|
|
5924
5835
|
const envProviders = [
|
|
5925
5836
|
{ env: "ANTHROPIC_API_KEY", type: "anthropic" },
|
|
5926
5837
|
{ env: "OPENAI_API_KEY", type: "openai" },
|
|
5927
5838
|
{ env: "GOOGLE_API_KEY", type: "gemini" },
|
|
5928
|
-
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
5929
|
-
// See getApiKey() above for why this is GITHUB_MODELS_TOKEN and never
|
|
5930
|
-
// the ambient GITHUB_TOKEN.
|
|
5931
|
-
{ env: "GITHUB_MODELS_TOKEN", type: "github-models" }
|
|
5839
|
+
{ env: "AZURE_OPENAI_KEY", type: "azure" }
|
|
5932
5840
|
];
|
|
5933
5841
|
for (const { env, type } of envProviders) {
|
|
5934
5842
|
const key = process.env[env];
|
|
5935
5843
|
if (!key) continue;
|
|
5936
5844
|
const existing = this.config.providers.find((p) => p.type === type);
|
|
5937
|
-
if (!existing &&
|
|
5845
|
+
if (!existing && wasEmpty) {
|
|
5938
5846
|
this.config.providers.push({ type, apiKey: key });
|
|
5939
5847
|
} else if (existing && !existing.apiKey) {
|
|
5940
5848
|
existing.apiKey = key;
|
|
5941
5849
|
}
|
|
5942
5850
|
}
|
|
5943
|
-
if (
|
|
5851
|
+
if (wasEmpty && !emptiedByRetirement && !this.config.providers.find((p) => p.type === "ollama")) {
|
|
5944
5852
|
this.config.providers.push({ type: "ollama" });
|
|
5945
5853
|
}
|
|
5946
5854
|
}
|
|
@@ -6310,7 +6218,6 @@ init_constants();
|
|
|
6310
6218
|
init_anthropic();
|
|
6311
6219
|
init_azure();
|
|
6312
6220
|
init_gemini();
|
|
6313
|
-
init_github_models();
|
|
6314
6221
|
init_ollama();
|
|
6315
6222
|
init_openai_compatible();
|
|
6316
6223
|
init_openai();
|
|
@@ -6318,7 +6225,6 @@ init_openai();
|
|
|
6318
6225
|
// src/core/router/selector.ts
|
|
6319
6226
|
init_constants();
|
|
6320
6227
|
init_pricing();
|
|
6321
|
-
init_github_models();
|
|
6322
6228
|
function normalizeModelId(id) {
|
|
6323
6229
|
return id.replace(/^models\//, "").toLowerCase();
|
|
6324
6230
|
}
|
|
@@ -6520,7 +6426,7 @@ var ModelSelector = class {
|
|
|
6520
6426
|
if (overrideModelId.includes(":")) {
|
|
6521
6427
|
const parts = overrideModelId.split(":");
|
|
6522
6428
|
const prefix = parts[0].toLowerCase();
|
|
6523
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
6429
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
6524
6430
|
if (validProviders.includes(prefix)) {
|
|
6525
6431
|
providerStr = prefix;
|
|
6526
6432
|
actualId = parts.slice(1).join(":");
|
|
@@ -6540,31 +6446,6 @@ var ModelSelector = class {
|
|
|
6540
6446
|
else if (this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
|
|
6541
6447
|
else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
|
|
6542
6448
|
}
|
|
6543
|
-
if (providerStr === "github-models" && this.availableProviders.has("github-models")) {
|
|
6544
|
-
const dynamicModel = {
|
|
6545
|
-
id: actualId,
|
|
6546
|
-
name: actualId,
|
|
6547
|
-
provider: "github-models",
|
|
6548
|
-
contextWindow: GITHUB_MODELS_MAX_INPUT_TOKENS,
|
|
6549
|
-
isVisionCapable: false,
|
|
6550
|
-
inputCostPer1kTokens: 0,
|
|
6551
|
-
outputCostPer1kTokens: 0,
|
|
6552
|
-
pricingUnknown: false,
|
|
6553
|
-
maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
|
|
6554
|
-
supportsStreaming: true,
|
|
6555
|
-
// Unconfirmed (no live catalog entry to read a real capability from)
|
|
6556
|
-
// must default to false, not left undefined: t3-worker.ts's text-tool
|
|
6557
|
-
// fallback only engages on a strict `=== false`, so an undefined
|
|
6558
|
-
// value here sends native `tools` to an unverified multi-vendor
|
|
6559
|
-
// model and any that don't support function calling reject the
|
|
6560
|
-
// request outright. Same default-to-false-when-unconfirmed policy
|
|
6561
|
-
// listModels() already applies for a real catalog entry.
|
|
6562
|
-
supportsToolUse: false,
|
|
6563
|
-
isLocal: false
|
|
6564
|
-
};
|
|
6565
|
-
this.addDynamicModel(dynamicModel);
|
|
6566
|
-
return dynamicModel;
|
|
6567
|
-
}
|
|
6568
6449
|
if (providerStr && this.availableProviders.has(providerStr)) {
|
|
6569
6450
|
const dynamicModel = withResolvedPricing({
|
|
6570
6451
|
id: actualId,
|
|
@@ -6657,20 +6538,7 @@ var DEFAULT_PROVIDER_TPM = {
|
|
|
6657
6538
|
gemini: 6e4,
|
|
6658
6539
|
azure: 3e4,
|
|
6659
6540
|
"openai-compatible": 3e4,
|
|
6660
|
-
ollama: Number.POSITIVE_INFINITY
|
|
6661
|
-
// An order of magnitude below the others, on purpose. GitHub Models is
|
|
6662
|
-
// rate-limited by REQUESTS (Free ≈10 RPM, Pro ≈20, plus low daily caps), and
|
|
6663
|
-
// this bucket meters TOKENS — there is no request-count limiter in this
|
|
6664
|
-
// codebase, and building one is not in scope. The approximation works because
|
|
6665
|
-
// the router reserves `model.maxOutputTokens + 512` per call, and the
|
|
6666
|
-
// provider reports GitHub's real ~4K per-request cap rather than the
|
|
6667
|
-
// underlying model's larger one — so every call withdraws a near-constant
|
|
6668
|
-
// ~4.5K regardless of how short the reply actually turns out to be. At 8K/min
|
|
6669
|
-
// that lands around 1–2 calls per minute of headroom: comfortably inside even
|
|
6670
|
-
// the Free tier, with room for the daily cap to matter more than this does.
|
|
6671
|
-
// Users on a higher tier can raise it with no code change through the generic
|
|
6672
|
-
// `config.rateLimits.providerTpm['github-models']` override.
|
|
6673
|
-
"github-models": 8e3
|
|
6541
|
+
ollama: Number.POSITIVE_INFINITY
|
|
6674
6542
|
};
|
|
6675
6543
|
var TpmLimiter = class {
|
|
6676
6544
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -7017,7 +6885,6 @@ var ModelProfiler = class {
|
|
|
7017
6885
|
(m) => !alreadyProfiled.has(m.id) && !SKIP_PATTERN.test(m.id) && !SKIP_PATTERN.test(m.name)
|
|
7018
6886
|
);
|
|
7019
6887
|
if (toProfile.length === 0) return;
|
|
7020
|
-
const probesHitGithubModels = this.router?.getModelForTier("T3")?.provider === "github-models";
|
|
7021
6888
|
const openRouterModels = await fetchOpenRouterModels();
|
|
7022
6889
|
const orByNormalizedId = /* @__PURE__ */ new Map();
|
|
7023
6890
|
for (const m of openRouterModels) {
|
|
@@ -7032,7 +6899,7 @@ var ModelProfiler = class {
|
|
|
7032
6899
|
if (orMatch?.description) {
|
|
7033
6900
|
specializations = extractSpecializations(orMatch.description);
|
|
7034
6901
|
}
|
|
7035
|
-
if (specializations.length === 0 && this.router
|
|
6902
|
+
if (specializations.length === 0 && this.router) {
|
|
7036
6903
|
specializations = await queryModelDirectly(this.router);
|
|
7037
6904
|
}
|
|
7038
6905
|
this.store.saveModelProfile(model.id, model.provider, specializations);
|
|
@@ -7257,7 +7124,6 @@ var LiveDataProvider = class {
|
|
|
7257
7124
|
*/
|
|
7258
7125
|
applyLivePricing(models) {
|
|
7259
7126
|
return models.map((m) => {
|
|
7260
|
-
if (m.provider === "github-models") return m;
|
|
7261
7127
|
const p = this.getLivePrice(m.baseModelId ?? m.id);
|
|
7262
7128
|
if (!p) return m;
|
|
7263
7129
|
const reconciled = reconcilePrice(m, p);
|
|
@@ -7296,7 +7162,6 @@ var LiveDataProvider = class {
|
|
|
7296
7162
|
*/
|
|
7297
7163
|
applyLiveCapabilities(models) {
|
|
7298
7164
|
return models.map((m) => {
|
|
7299
|
-
if (m.provider === "github-models") return m;
|
|
7300
7165
|
const c = this.getCapability(m.baseModelId ?? m.id);
|
|
7301
7166
|
if (!c) return m;
|
|
7302
7167
|
const next = { ...m };
|
|
@@ -7600,16 +7465,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7600
7465
|
for (const model of azureDeployments) this.selector.addDynamicModel(model);
|
|
7601
7466
|
availableProviders.add("azure");
|
|
7602
7467
|
}
|
|
7603
|
-
if (availableProviders.has("github-models")) {
|
|
7604
|
-
await this.discoverGitHubModelsCatalog(config);
|
|
7605
|
-
}
|
|
7606
7468
|
await this.validateCloudProviderModels(config);
|
|
7607
7469
|
for (const tier of ["T1", "T2", "T3"]) {
|
|
7608
7470
|
const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
|
|
7609
7471
|
if (!override || override === "auto") continue;
|
|
7610
7472
|
const model = this.selector.selectForTier(tier, override);
|
|
7611
7473
|
if (!model) {
|
|
7612
|
-
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
7474
|
+
const knownProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
7613
7475
|
const hasProviderPrefix = override.includes(":") && knownProviders.some((p) => override.startsWith(p + ":"));
|
|
7614
7476
|
if (hasProviderPrefix) {
|
|
7615
7477
|
const provider = override.split(":")[0];
|
|
@@ -7764,7 +7626,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
7764
7626
|
* models without a package upgrade. Mirrors discoverOllamaModels.
|
|
7765
7627
|
*/
|
|
7766
7628
|
async discoverProviderModels() {
|
|
7767
|
-
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"
|
|
7629
|
+
const cloud = ["anthropic", "openai", "gemini", "azure", "openai-compatible"];
|
|
7768
7630
|
const tasks = cloud.map(async (type) => {
|
|
7769
7631
|
if (!this.selector.isProviderAvailable(type)) return;
|
|
7770
7632
|
const seed = this.getAnyModelForProvider(type);
|
|
@@ -8279,26 +8141,6 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
8279
8141
|
return false;
|
|
8280
8142
|
}
|
|
8281
8143
|
}
|
|
8282
|
-
/**
|
|
8283
|
-
* Registers GitHub Models' real catalog ids via addDynamicModel so init()'s
|
|
8284
|
-
* tier-fill loop can pick one. Registration alone does not create a bound
|
|
8285
|
-
* BaseProvider instance (see ensureProvider) — the tier-fill loop's own
|
|
8286
|
-
* ensureProvider call does that for whichever model it actually selects,
|
|
8287
|
-
* which is why this runs BEFORE that loop rather than only in the
|
|
8288
|
-
* background discoverProviderModels() refresh.
|
|
8289
|
-
*/
|
|
8290
|
-
async discoverGitHubModelsCatalog(config) {
|
|
8291
|
-
const cfg = config.providers.find((p) => p.type === "github-models") ?? { type: "github-models" };
|
|
8292
|
-
const seed = this.getAnyModelForProvider("github-models");
|
|
8293
|
-
if (!seed) return;
|
|
8294
|
-
try {
|
|
8295
|
-
const provider = new GitHubModelsProvider(cfg, seed);
|
|
8296
|
-
const models = await provider.listModels();
|
|
8297
|
-
for (const m of models) this.selector.addDynamicModel(m);
|
|
8298
|
-
} catch (err) {
|
|
8299
|
-
console.warn("[router] GitHub Models catalog discovery failed:", err instanceof Error ? err.message : err);
|
|
8300
|
-
}
|
|
8301
|
-
}
|
|
8302
8144
|
ensureProvider(model, configs) {
|
|
8303
8145
|
const key = `${model.provider}:${model.id}`;
|
|
8304
8146
|
if (this.providers.has(key)) return;
|
|
@@ -8323,8 +8165,6 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
8323
8165
|
return new OllamaProvider(cfg, model);
|
|
8324
8166
|
case "openai-compatible":
|
|
8325
8167
|
return new OpenAICompatibleProvider(cfg, model);
|
|
8326
|
-
case "github-models":
|
|
8327
|
-
return new GitHubModelsProvider(cfg, model);
|
|
8328
8168
|
default:
|
|
8329
8169
|
throw new Error(`Unsupported provider type: ${String(cfg.type)}`);
|
|
8330
8170
|
}
|
|
@@ -8332,7 +8172,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
|
|
|
8332
8172
|
getAnyModelForProvider(type) {
|
|
8333
8173
|
const fromCatalog = Object.values(MODELS).find((m) => m.provider === type);
|
|
8334
8174
|
if (fromCatalog) return fromCatalog;
|
|
8335
|
-
if (type === "openai-compatible" || type === "azure"
|
|
8175
|
+
if (type === "openai-compatible" || type === "azure") {
|
|
8336
8176
|
return {
|
|
8337
8177
|
id: type,
|
|
8338
8178
|
name: type,
|
|
@@ -22210,10 +22050,7 @@ async function refreshModelCache(store, providers) {
|
|
|
22210
22050
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
22211
22051
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
22212
22052
|
else if (provider.type === "openai-compatible") instance = new OpenAICompatibleProvider(provider, dummyModel);
|
|
22213
|
-
else if (provider.type === "
|
|
22214
|
-
const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
|
|
22215
|
-
instance = new GitHubModelsProvider2(provider, dummyModel);
|
|
22216
|
-
} else if (provider.type === "azure") {
|
|
22053
|
+
else if (provider.type === "azure") {
|
|
22217
22054
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
22218
22055
|
instance = new AzureOpenAIProvider2(provider, dummyModel);
|
|
22219
22056
|
}
|
|
@@ -22226,7 +22063,7 @@ async function refreshModelCache(store, providers) {
|
|
|
22226
22063
|
}
|
|
22227
22064
|
}
|
|
22228
22065
|
}
|
|
22229
|
-
function Repl({ config, workspacePath, themeName, initialPrompt, identityName, altScreen = false }) {
|
|
22066
|
+
function Repl({ config, workspacePath, themeName, initialPrompt, identityName, startupNotice, altScreen = false }) {
|
|
22230
22067
|
const { exit } = useApp();
|
|
22231
22068
|
const { stdout } = useStdout();
|
|
22232
22069
|
const [theme, setTheme] = useState(() => getTheme(themeName));
|
|
@@ -22397,17 +22234,29 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
22397
22234
|
}
|
|
22398
22235
|
setCurrentIdentityId(initialIdentityId);
|
|
22399
22236
|
const loadCache = async () => {
|
|
22237
|
+
const toMap = (models2) => {
|
|
22238
|
+
const map = /* @__PURE__ */ new Map();
|
|
22239
|
+
for (const m of models2) {
|
|
22240
|
+
const list = map.get(m.provider) ?? [];
|
|
22241
|
+
list.push(m);
|
|
22242
|
+
map.set(m.provider, list);
|
|
22243
|
+
}
|
|
22244
|
+
return map;
|
|
22245
|
+
};
|
|
22400
22246
|
const models = store.getCachedModels();
|
|
22401
|
-
|
|
22402
|
-
|
|
22403
|
-
|
|
22404
|
-
|
|
22405
|
-
map.set(m.provider, list);
|
|
22247
|
+
setCachedModels(toMap(models));
|
|
22248
|
+
if (models.length === 0 || store.getCacheAge() > 24 * 60 * 60 * 1e3) {
|
|
22249
|
+
await refreshModelCache(store, config.providers);
|
|
22250
|
+
setCachedModels(toMap(store.getCachedModels()));
|
|
22406
22251
|
}
|
|
22407
|
-
setCachedModels(map);
|
|
22408
|
-
if (models.length === 0 || store.getCacheAge() > 24 * 60 * 60 * 1e3) await refreshModelCache(store, config.providers);
|
|
22409
22252
|
};
|
|
22410
22253
|
loadCache();
|
|
22254
|
+
if (startupNotice) {
|
|
22255
|
+
dispatch({
|
|
22256
|
+
type: "ADD_MESSAGE",
|
|
22257
|
+
message: { id: randomUUID(), role: "system", content: `\u26A0 ${startupNotice}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
22258
|
+
});
|
|
22259
|
+
}
|
|
22411
22260
|
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 } });
|
|
22412
22261
|
persistRuntimeSession("ACTIVE");
|
|
22413
22262
|
try {
|
|
@@ -23330,7 +23179,7 @@ async function validateConfiguredModels(config) {
|
|
|
23330
23179
|
function inferProviderFromModelId(id, providers) {
|
|
23331
23180
|
if (id.includes(":")) {
|
|
23332
23181
|
const prefix = id.split(":")[0].toLowerCase();
|
|
23333
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
23182
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
23334
23183
|
if (validProviders.includes(prefix)) {
|
|
23335
23184
|
return prefix;
|
|
23336
23185
|
}
|
|
@@ -23627,10 +23476,9 @@ var PROVIDER_LABELS = {
|
|
|
23627
23476
|
gemini: "Google Gemini",
|
|
23628
23477
|
azure: "Azure OpenAI",
|
|
23629
23478
|
ollama: "Ollama (local)",
|
|
23630
|
-
"openai-compatible": "OpenAI-Compatible"
|
|
23631
|
-
"github-models": "GitHub Models"
|
|
23479
|
+
"openai-compatible": "OpenAI-Compatible"
|
|
23632
23480
|
};
|
|
23633
|
-
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible"
|
|
23481
|
+
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible"];
|
|
23634
23482
|
function buildInitialEntries(types) {
|
|
23635
23483
|
return [...types].map((type) => ({
|
|
23636
23484
|
id: randomUUID(),
|
|
@@ -23802,13 +23650,6 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
23802
23650
|
const fetched = await p.listModels();
|
|
23803
23651
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
23804
23652
|
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
23805
|
-
} else if (type === "github-models") {
|
|
23806
|
-
const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
|
|
23807
|
-
const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
23808
|
-
const p = new GitHubModelsProvider2({ type, apiKey }, dummyModel);
|
|
23809
|
-
const fetched = await p.listModels();
|
|
23810
|
-
fetched.forEach((m) => models.push({ id: `${type}:${m.id}`, name: m.name, providerLabel: entry.label }));
|
|
23811
|
-
dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
|
|
23812
23653
|
} else if (type === "gemini") {
|
|
23813
23654
|
const { GeminiProvider: GeminiProvider2 } = await Promise.resolve().then(() => (init_gemini(), gemini_exports));
|
|
23814
23655
|
const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
@@ -23987,8 +23828,7 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
23987
23828
|
/* @__PURE__ */ jsx(Text, { color: focused ? theme.colors.primary : selected ? theme.colors.foreground : theme.colors.muted, children: PROVIDER_LABELS[p] }),
|
|
23988
23829
|
p === "azure" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 multiple deployments supported" }),
|
|
23989
23830
|
p === "openai-compatible" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 Groq, Together, custom" }),
|
|
23990
|
-
p === "ollama" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" })
|
|
23991
|
-
p === "github-models" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 GitHub PAT with `models: read`" })
|
|
23831
|
+
p === "ollama" && /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" })
|
|
23992
23832
|
] }, p);
|
|
23993
23833
|
}) }),
|
|
23994
23834
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: "space select \xB7 a all \xB7 i invert \xB7 enter continue" }) }),
|
|
@@ -24395,8 +24235,7 @@ async function doctorCommand() {
|
|
|
24395
24235
|
{ type: "anthropic", name: "Anthropic" },
|
|
24396
24236
|
{ type: "openai", name: "OpenAI" },
|
|
24397
24237
|
{ type: "gemini", name: "Google Gemini" },
|
|
24398
|
-
{ type: "azure", name: "Azure OpenAI" }
|
|
24399
|
-
{ type: "github-models", name: "GitHub Models" }
|
|
24238
|
+
{ type: "azure", name: "Azure OpenAI" }
|
|
24400
24239
|
];
|
|
24401
24240
|
for (const { type, name } of providers) {
|
|
24402
24241
|
const key = cm.getApiKey(type);
|
|
@@ -24657,7 +24496,9 @@ function gatherSyncBundle(config) {
|
|
|
24657
24496
|
if (Object.keys(prefs).length) bundle.prefs = prefs;
|
|
24658
24497
|
return bundle;
|
|
24659
24498
|
}
|
|
24660
|
-
function applySyncBundle(bundle, config) {
|
|
24499
|
+
function applySyncBundle(bundle, config, cleanup) {
|
|
24500
|
+
const dropped = stripRetiredProviders(bundle);
|
|
24501
|
+
if (cleanup) cleanup.removed = dropped.removed;
|
|
24661
24502
|
const next = { ...config, tools: { ...config.tools } };
|
|
24662
24503
|
if (bundle.providers) next.providers = mergeByKey(config.providers, bundle.providers, providerSig) ?? [];
|
|
24663
24504
|
if (bundle.webSearch) next.tools.webSearch = { ...config.tools?.webSearch, ...bundle.webSearch };
|
|
@@ -24673,6 +24514,14 @@ function applySyncBundle(bundle, config) {
|
|
|
24673
24514
|
}
|
|
24674
24515
|
if (bundle.models) next.models = { ...config.models, ...bundle.models };
|
|
24675
24516
|
if (bundle.budget) next.budget = { ...config.budget, ...bundle.budget };
|
|
24517
|
+
const merged = { ...next.models };
|
|
24518
|
+
const clearedFromMerge = clearRetiredPins(merged);
|
|
24519
|
+
if (clearedFromMerge.length > 0) next.models = merged;
|
|
24520
|
+
if (cleanup) {
|
|
24521
|
+
cleanup.clearedPins = ["t1", "t2", "t3"].filter(
|
|
24522
|
+
(t) => (dropped.clearedPins.includes(t) || clearedFromMerge.includes(t)) && next.models?.[t] === void 0
|
|
24523
|
+
);
|
|
24524
|
+
}
|
|
24676
24525
|
if (bundle.prefs) {
|
|
24677
24526
|
const p = bundle.prefs;
|
|
24678
24527
|
if (p.autoBias !== void 0) next.autoBias = p.autoBias;
|
|
@@ -24980,14 +24829,28 @@ async function syncPullCommand() {
|
|
|
24980
24829
|
console.log(chalk7.dim("\n Cancelled.\n"));
|
|
24981
24830
|
return;
|
|
24982
24831
|
}
|
|
24832
|
+
let bundle;
|
|
24833
|
+
try {
|
|
24834
|
+
bundle = await decryptJSON(blob, pass);
|
|
24835
|
+
} catch {
|
|
24836
|
+
console.log(chalk7.red("\n Could not decrypt \u2014 check your passphrase and try again.\n"));
|
|
24837
|
+
process.exitCode = 1;
|
|
24838
|
+
return;
|
|
24839
|
+
}
|
|
24983
24840
|
try {
|
|
24984
|
-
const bundle = await decryptJSON(blob, pass);
|
|
24985
24841
|
const cm = new ConfigManager(process.cwd());
|
|
24986
24842
|
await cm.load();
|
|
24987
|
-
|
|
24843
|
+
const cleanup = { removed: [], clearedPins: [] };
|
|
24844
|
+
await cm.updateConfig(applySyncBundle(bundle, cm.getConfig(), cleanup));
|
|
24988
24845
|
console.log(chalk7.green("\n \u2713 Applied your synced settings. Your keys are ready here.\n"));
|
|
24989
|
-
|
|
24990
|
-
|
|
24846
|
+
if (didCleanupChangeAnything(cleanup)) {
|
|
24847
|
+
console.log(chalk7.yellow(` ${describeCleanup(cleanup)}
|
|
24848
|
+
`));
|
|
24849
|
+
}
|
|
24850
|
+
} catch (err) {
|
|
24851
|
+
console.log(chalk7.red(`
|
|
24852
|
+
Could not apply your synced settings: ${err instanceof Error ? err.message : String(err)}
|
|
24853
|
+
`));
|
|
24991
24854
|
process.exitCode = 1;
|
|
24992
24855
|
}
|
|
24993
24856
|
}
|
|
@@ -27614,6 +27477,7 @@ async function startRepl(options) {
|
|
|
27614
27477
|
process.exit(1);
|
|
27615
27478
|
}
|
|
27616
27479
|
let config = cm.getConfig();
|
|
27480
|
+
const startupNotice = cm.takeRetiredNotice();
|
|
27617
27481
|
const needsSetup = !hasUsableProvider(config.providers);
|
|
27618
27482
|
if (needsSetup) {
|
|
27619
27483
|
console.log(chalk7.magenta(" \u25C8 No providers configured \u2014 launching setup wizard\u2026"));
|
|
@@ -27636,6 +27500,7 @@ async function startRepl(options) {
|
|
|
27636
27500
|
themeName: options.theme ?? config.theme ?? DEFAULT_THEME,
|
|
27637
27501
|
initialPrompt: options.prompt,
|
|
27638
27502
|
identityName: options.identity,
|
|
27503
|
+
startupNotice,
|
|
27639
27504
|
altScreen: useAltScreen
|
|
27640
27505
|
}),
|
|
27641
27506
|
{ exitOnCtrlC: false }
|