cascade-ai 0.70.0 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +248 -357
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +248 -357
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +207 -311
- package/dist/index.cjs +207 -311
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -15
- package/dist/index.d.ts +69 -15
- package/dist/index.js +207 -309
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/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.72.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,
|
|
@@ -10034,6 +9874,22 @@ ${assignment.expectedOutput}`;
|
|
|
10034
9874
|
requiresArtifact() {
|
|
10035
9875
|
return shouldRequireArtifact(this.assignment, this.tools.map((t) => t.name));
|
|
10036
9876
|
}
|
|
9877
|
+
/**
|
|
9878
|
+
* Where a file a tool wrote actually lives.
|
|
9879
|
+
*
|
|
9880
|
+
* This used to be `process.cwd()`, which is only the workspace in a plain
|
|
9881
|
+
* CLI run. The hosted server runs with cwd at the app directory while the
|
|
9882
|
+
* run's workspace is the tenant's scratch dir, and Electron's cwd is the app
|
|
9883
|
+
* bundle rather than the folder the user picked. So the tool wrote
|
|
9884
|
+
* `<workspace>/report.docx`, verification stat'd `<cwd>/report.docx`, found
|
|
9885
|
+
* nothing, and the worker looped and then threw WorkerStallError — surfacing
|
|
9886
|
+
* as "no file was created" on a file that had in fact been created, followed
|
|
9887
|
+
* by a failed node. Asking the registry is exact: it is the same root every
|
|
9888
|
+
* registered tool was configured with (registry.registerDefaults).
|
|
9889
|
+
*/
|
|
9890
|
+
artifactRoot() {
|
|
9891
|
+
return this.toolRegistry.getWorkspaceRoot?.() ?? process.cwd();
|
|
9892
|
+
}
|
|
10037
9893
|
extractArtifactPaths(assignment) {
|
|
10038
9894
|
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
10039
9895
|
const haystack = `${assignment.description}
|
|
@@ -10045,11 +9901,11 @@ ${assignment.expectedOutput}`;
|
|
|
10045
9901
|
const artifactPaths = this.extractArtifactPaths(assignment);
|
|
10046
9902
|
if (!artifactPaths.length) return { ok: true, issues: [] };
|
|
10047
9903
|
const issues = [];
|
|
10048
|
-
const {
|
|
9904
|
+
const { execFile: execFile3 } = await import('child_process');
|
|
10049
9905
|
const { promisify: promisify4 } = await import('util');
|
|
10050
|
-
const
|
|
9906
|
+
const execFileAsync2 = promisify4(execFile3);
|
|
10051
9907
|
for (const artifactPath of artifactPaths) {
|
|
10052
|
-
const absolutePath = path34__default.default.resolve(
|
|
9908
|
+
const absolutePath = path34__default.default.resolve(this.artifactRoot(), artifactPath);
|
|
10053
9909
|
try {
|
|
10054
9910
|
const stat = await fs11__default.default.stat(absolutePath);
|
|
10055
9911
|
if (!stat.isFile()) {
|
|
@@ -10085,20 +9941,18 @@ ${assignment.expectedOutput}`;
|
|
|
10085
9941
|
continue;
|
|
10086
9942
|
}
|
|
10087
9943
|
const ext = path34__default.default.extname(absolutePath).toLowerCase();
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
9944
|
+
const check = ext === ".ts" || ext === ".tsx" ? { cmd: "npx", args: ["tsc", "--noEmit", absolutePath] } : ext === ".js" || ext === ".jsx" ? { cmd: process.execPath, args: ["--check", absolutePath] } : ext === ".py" ? { cmd: "python", args: ["-m", "py_compile", absolutePath] } : null;
|
|
9945
|
+
if (check) {
|
|
9946
|
+
try {
|
|
9947
|
+
await execFileAsync2(check.cmd, check.args, { timeout: 1e4 });
|
|
9948
|
+
} catch (err) {
|
|
9949
|
+
const code = err.code;
|
|
9950
|
+
if (code === "ENOENT" || code === "EINVAL") continue;
|
|
9951
|
+
const e = err;
|
|
9952
|
+
issues.push(`Semantic error in ${artifactPath}:
|
|
9953
|
+
${e.stderr || String(err)}
|
|
9954
|
+
${e.stdout || ""}`);
|
|
10095
9955
|
}
|
|
10096
|
-
} catch (err) {
|
|
10097
|
-
const stderr = err?.stderr || String(err);
|
|
10098
|
-
const stdout = err?.stdout || "";
|
|
10099
|
-
issues.push(`Semantic error in ${artifactPath}:
|
|
10100
|
-
${stderr}
|
|
10101
|
-
${stdout}`);
|
|
10102
9956
|
}
|
|
10103
9957
|
} catch {
|
|
10104
9958
|
issues.push(`Required artifact was not created: ${artifactPath}`);
|
|
@@ -10177,7 +10031,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
10177
10031
|
async checkAcceptance(assignment) {
|
|
10178
10032
|
const criteria = assignment.acceptance ?? [];
|
|
10179
10033
|
if (!criteria.length) return [];
|
|
10180
|
-
const resolve2 = (target) => path34__default.default.resolve(
|
|
10034
|
+
const resolve2 = (target) => path34__default.default.resolve(this.artifactRoot(), target);
|
|
10181
10035
|
return evaluateAcceptance(criteria, assignment.files ?? [], {
|
|
10182
10036
|
stat: async (target) => {
|
|
10183
10037
|
try {
|
|
@@ -15593,6 +15447,18 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
15593
15447
|
this.tools.set(tool.name, tool);
|
|
15594
15448
|
this.emit("tool:added", tool.name);
|
|
15595
15449
|
}
|
|
15450
|
+
/**
|
|
15451
|
+
* The directory every registered tool resolves relative paths against.
|
|
15452
|
+
*
|
|
15453
|
+
* Exposed so a caller that has to reason about a file a tool WROTE can look
|
|
15454
|
+
* in the same place the tool wrote it. `process.cwd()` is not that place
|
|
15455
|
+
* outside a plain CLI run: the hosted server's cwd is the app directory
|
|
15456
|
+
* while the run's workspace is the tenant's scratch dir, and Electron's cwd
|
|
15457
|
+
* is the app bundle rather than the user's chosen folder.
|
|
15458
|
+
*/
|
|
15459
|
+
getWorkspaceRoot() {
|
|
15460
|
+
return this.workspaceRoot;
|
|
15461
|
+
}
|
|
15596
15462
|
/**
|
|
15597
15463
|
* Wait until a named tool is registered, resolving immediately if it already exists.
|
|
15598
15464
|
* T3 workers can call this after encountering a missing-tool error to resume
|
|
@@ -22263,10 +22129,7 @@ async function refreshModelCache(store, providers) {
|
|
|
22263
22129
|
else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
|
|
22264
22130
|
else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
|
|
22265
22131
|
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") {
|
|
22132
|
+
else if (provider.type === "azure") {
|
|
22270
22133
|
const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
|
|
22271
22134
|
instance = new AzureOpenAIProvider2(provider, dummyModel);
|
|
22272
22135
|
}
|
|
@@ -22279,7 +22142,7 @@ async function refreshModelCache(store, providers) {
|
|
|
22279
22142
|
}
|
|
22280
22143
|
}
|
|
22281
22144
|
}
|
|
22282
|
-
function Repl({ config, workspacePath, themeName, initialPrompt, identityName, altScreen = false }) {
|
|
22145
|
+
function Repl({ config, workspacePath, themeName, initialPrompt, identityName, startupNotice, altScreen = false }) {
|
|
22283
22146
|
const { exit } = ink.useApp();
|
|
22284
22147
|
const { stdout } = ink.useStdout();
|
|
22285
22148
|
const [theme, setTheme] = React2.useState(() => getTheme(themeName));
|
|
@@ -22450,17 +22313,29 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
|
|
|
22450
22313
|
}
|
|
22451
22314
|
setCurrentIdentityId(initialIdentityId);
|
|
22452
22315
|
const loadCache = async () => {
|
|
22316
|
+
const toMap = (models2) => {
|
|
22317
|
+
const map = /* @__PURE__ */ new Map();
|
|
22318
|
+
for (const m of models2) {
|
|
22319
|
+
const list = map.get(m.provider) ?? [];
|
|
22320
|
+
list.push(m);
|
|
22321
|
+
map.set(m.provider, list);
|
|
22322
|
+
}
|
|
22323
|
+
return map;
|
|
22324
|
+
};
|
|
22453
22325
|
const models = store.getCachedModels();
|
|
22454
|
-
|
|
22455
|
-
|
|
22456
|
-
|
|
22457
|
-
|
|
22458
|
-
map.set(m.provider, list);
|
|
22326
|
+
setCachedModels(toMap(models));
|
|
22327
|
+
if (models.length === 0 || store.getCacheAge() > 24 * 60 * 60 * 1e3) {
|
|
22328
|
+
await refreshModelCache(store, config.providers);
|
|
22329
|
+
setCachedModels(toMap(store.getCachedModels()));
|
|
22459
22330
|
}
|
|
22460
|
-
setCachedModels(map);
|
|
22461
|
-
if (models.length === 0 || store.getCacheAge() > 24 * 60 * 60 * 1e3) await refreshModelCache(store, config.providers);
|
|
22462
22331
|
};
|
|
22463
22332
|
loadCache();
|
|
22333
|
+
if (startupNotice) {
|
|
22334
|
+
dispatch({
|
|
22335
|
+
type: "ADD_MESSAGE",
|
|
22336
|
+
message: { id: crypto.randomUUID(), role: "system", content: `\u26A0 ${startupNotice}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
22337
|
+
});
|
|
22338
|
+
}
|
|
22464
22339
|
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
22340
|
persistRuntimeSession("ACTIVE");
|
|
22466
22341
|
try {
|
|
@@ -23383,7 +23258,7 @@ async function validateConfiguredModels(config) {
|
|
|
23383
23258
|
function inferProviderFromModelId(id, providers) {
|
|
23384
23259
|
if (id.includes(":")) {
|
|
23385
23260
|
const prefix = id.split(":")[0].toLowerCase();
|
|
23386
|
-
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"
|
|
23261
|
+
const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
|
|
23387
23262
|
if (validProviders.includes(prefix)) {
|
|
23388
23263
|
return prefix;
|
|
23389
23264
|
}
|
|
@@ -23680,10 +23555,9 @@ var PROVIDER_LABELS = {
|
|
|
23680
23555
|
gemini: "Google Gemini",
|
|
23681
23556
|
azure: "Azure OpenAI",
|
|
23682
23557
|
ollama: "Ollama (local)",
|
|
23683
|
-
"openai-compatible": "OpenAI-Compatible"
|
|
23684
|
-
"github-models": "GitHub Models"
|
|
23558
|
+
"openai-compatible": "OpenAI-Compatible"
|
|
23685
23559
|
};
|
|
23686
|
-
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible"
|
|
23560
|
+
var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible"];
|
|
23687
23561
|
function buildInitialEntries(types) {
|
|
23688
23562
|
return [...types].map((type) => ({
|
|
23689
23563
|
id: crypto.randomUUID(),
|
|
@@ -23855,13 +23729,6 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
23855
23729
|
const fetched = await p.listModels();
|
|
23856
23730
|
fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
|
|
23857
23731
|
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
23732
|
} else if (type === "gemini") {
|
|
23866
23733
|
const { GeminiProvider: GeminiProvider2 } = await Promise.resolve().then(() => (init_gemini(), gemini_exports));
|
|
23867
23734
|
const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
|
|
@@ -24040,8 +23907,7 @@ function SetupWizard({ workspacePath, onComplete }) {
|
|
|
24040
23907
|
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: focused ? theme.colors.primary : selected ? theme.colors.foreground : theme.colors.muted, children: PROVIDER_LABELS[p] }),
|
|
24041
23908
|
p === "azure" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 multiple deployments supported" }),
|
|
24042
23909
|
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`" })
|
|
23910
|
+
p === "ollama" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" })
|
|
24045
23911
|
] }, p);
|
|
24046
23912
|
}) }),
|
|
24047
23913
|
/* @__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 +24314,7 @@ async function doctorCommand() {
|
|
|
24448
24314
|
{ type: "anthropic", name: "Anthropic" },
|
|
24449
24315
|
{ type: "openai", name: "OpenAI" },
|
|
24450
24316
|
{ type: "gemini", name: "Google Gemini" },
|
|
24451
|
-
{ type: "azure", name: "Azure OpenAI" }
|
|
24452
|
-
{ type: "github-models", name: "GitHub Models" }
|
|
24317
|
+
{ type: "azure", name: "Azure OpenAI" }
|
|
24453
24318
|
];
|
|
24454
24319
|
for (const { type, name } of providers) {
|
|
24455
24320
|
const key = cm.getApiKey(type);
|
|
@@ -24710,7 +24575,9 @@ function gatherSyncBundle(config) {
|
|
|
24710
24575
|
if (Object.keys(prefs).length) bundle.prefs = prefs;
|
|
24711
24576
|
return bundle;
|
|
24712
24577
|
}
|
|
24713
|
-
function applySyncBundle(bundle, config) {
|
|
24578
|
+
function applySyncBundle(bundle, config, cleanup) {
|
|
24579
|
+
const dropped = stripRetiredProviders(bundle);
|
|
24580
|
+
if (cleanup) cleanup.removed = dropped.removed;
|
|
24714
24581
|
const next = { ...config, tools: { ...config.tools } };
|
|
24715
24582
|
if (bundle.providers) next.providers = mergeByKey(config.providers, bundle.providers, providerSig) ?? [];
|
|
24716
24583
|
if (bundle.webSearch) next.tools.webSearch = { ...config.tools?.webSearch, ...bundle.webSearch };
|
|
@@ -24726,6 +24593,14 @@ function applySyncBundle(bundle, config) {
|
|
|
24726
24593
|
}
|
|
24727
24594
|
if (bundle.models) next.models = { ...config.models, ...bundle.models };
|
|
24728
24595
|
if (bundle.budget) next.budget = { ...config.budget, ...bundle.budget };
|
|
24596
|
+
const merged = { ...next.models };
|
|
24597
|
+
const clearedFromMerge = clearRetiredPins(merged);
|
|
24598
|
+
if (clearedFromMerge.length > 0) next.models = merged;
|
|
24599
|
+
if (cleanup) {
|
|
24600
|
+
cleanup.clearedPins = ["t1", "t2", "t3"].filter(
|
|
24601
|
+
(t) => (dropped.clearedPins.includes(t) || clearedFromMerge.includes(t)) && next.models?.[t] === void 0
|
|
24602
|
+
);
|
|
24603
|
+
}
|
|
24729
24604
|
if (bundle.prefs) {
|
|
24730
24605
|
const p = bundle.prefs;
|
|
24731
24606
|
if (p.autoBias !== void 0) next.autoBias = p.autoBias;
|
|
@@ -25033,14 +24908,28 @@ async function syncPullCommand() {
|
|
|
25033
24908
|
console.log(chalk7__default.default.dim("\n Cancelled.\n"));
|
|
25034
24909
|
return;
|
|
25035
24910
|
}
|
|
24911
|
+
let bundle;
|
|
24912
|
+
try {
|
|
24913
|
+
bundle = await decryptJSON(blob, pass);
|
|
24914
|
+
} catch {
|
|
24915
|
+
console.log(chalk7__default.default.red("\n Could not decrypt \u2014 check your passphrase and try again.\n"));
|
|
24916
|
+
process.exitCode = 1;
|
|
24917
|
+
return;
|
|
24918
|
+
}
|
|
25036
24919
|
try {
|
|
25037
|
-
const bundle = await decryptJSON(blob, pass);
|
|
25038
24920
|
const cm = new ConfigManager(process.cwd());
|
|
25039
24921
|
await cm.load();
|
|
25040
|
-
|
|
24922
|
+
const cleanup = { removed: [], clearedPins: [] };
|
|
24923
|
+
await cm.updateConfig(applySyncBundle(bundle, cm.getConfig(), cleanup));
|
|
25041
24924
|
console.log(chalk7__default.default.green("\n \u2713 Applied your synced settings. Your keys are ready here.\n"));
|
|
25042
|
-
|
|
25043
|
-
|
|
24925
|
+
if (didCleanupChangeAnything(cleanup)) {
|
|
24926
|
+
console.log(chalk7__default.default.yellow(` ${describeCleanup(cleanup)}
|
|
24927
|
+
`));
|
|
24928
|
+
}
|
|
24929
|
+
} catch (err) {
|
|
24930
|
+
console.log(chalk7__default.default.red(`
|
|
24931
|
+
Could not apply your synced settings: ${err instanceof Error ? err.message : String(err)}
|
|
24932
|
+
`));
|
|
25044
24933
|
process.exitCode = 1;
|
|
25045
24934
|
}
|
|
25046
24935
|
}
|
|
@@ -27667,6 +27556,7 @@ async function startRepl(options) {
|
|
|
27667
27556
|
process.exit(1);
|
|
27668
27557
|
}
|
|
27669
27558
|
let config = cm.getConfig();
|
|
27559
|
+
const startupNotice = cm.takeRetiredNotice();
|
|
27670
27560
|
const needsSetup = !hasUsableProvider(config.providers);
|
|
27671
27561
|
if (needsSetup) {
|
|
27672
27562
|
console.log(chalk7__default.default.magenta(" \u25C8 No providers configured \u2014 launching setup wizard\u2026"));
|
|
@@ -27689,6 +27579,7 @@ async function startRepl(options) {
|
|
|
27689
27579
|
themeName: options.theme ?? config.theme ?? DEFAULT_THEME,
|
|
27690
27580
|
initialPrompt: options.prompt,
|
|
27691
27581
|
identityName: options.identity,
|
|
27582
|
+
startupNotice,
|
|
27692
27583
|
altScreen: useAltScreen
|
|
27693
27584
|
}),
|
|
27694
27585
|
{ exitOnCtrlC: false }
|