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 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, GITHUB_MODELS_INFERENCE_URL, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_API_VERSION, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
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.70.0";
1813
+ CASCADE_VERSION = "0.71.0";
1814
1814
  CASCADE_CONFIG_FILE = ".cascade/config.json";
1815
1815
  CASCADE_DB_FILE = ".cascade/memory.db";
1816
1816
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
@@ -2209,9 +2209,6 @@ var init_constants = __esm({
2209
2209
  OLLAMA_BASE_URL = "http://localhost:11434";
2210
2210
  LM_STUDIO_BASE_URL = "http://localhost:1234";
2211
2211
  AZURE_BASE_URL_TEMPLATE = "https://{resource}.openai.azure.com";
2212
- GITHUB_MODELS_INFERENCE_URL = "https://models.github.ai/inference";
2213
- GITHUB_MODELS_CATALOG_URL = "https://models.github.ai/catalog/models";
2214
- GITHUB_MODELS_API_VERSION = "2026-03-10";
2215
2212
  TOOL_NAMES = {
2216
2213
  SHELL: "shell",
2217
2214
  FILE_READ: "file_read",
@@ -3369,222 +3366,6 @@ var init_net = __esm({
3369
3366
  }
3370
3367
  });
3371
3368
 
3372
- // src/providers/github-models.ts
3373
- var github_models_exports = {};
3374
- __export(github_models_exports, {
3375
- GITHUB_MODELS_MAX_INPUT_TOKENS: () => GITHUB_MODELS_MAX_INPUT_TOKENS,
3376
- GITHUB_MODELS_MAX_OUTPUT_TOKENS: () => GITHUB_MODELS_MAX_OUTPUT_TOKENS,
3377
- GitHubModelsProvider: () => GitHubModelsProvider,
3378
- stripModelOwnerPrefix: () => stripModelOwnerPrefix
3379
- });
3380
- function stripModelOwnerPrefix(id) {
3381
- const slash = (id || "").indexOf("/");
3382
- return slash === -1 ? id : id.slice(slash + 1);
3383
- }
3384
- function pickString(o, keys) {
3385
- for (const k of keys) {
3386
- const v = o[k];
3387
- if (typeof v === "string" && v.length > 0) return v;
3388
- }
3389
- return void 0;
3390
- }
3391
- function pickNumber(o, keys) {
3392
- for (const k of keys) {
3393
- const v = k.includes(".") ? o[k.split(".")[0]]?.[k.split(".")[1]] : o[k];
3394
- if (typeof v === "number" && Number.isFinite(v) && v > 0) return v;
3395
- }
3396
- return void 0;
3397
- }
3398
- function looksVisionCapable(o) {
3399
- for (const k of ["supported_input_modalities", "input_modalities", "modalities", "capabilities"]) {
3400
- const v = o[k];
3401
- if (Array.isArray(v) && v.some((x) => typeof x === "string" && /image|vision/i.test(x))) return true;
3402
- }
3403
- return false;
3404
- }
3405
- function looksToolCapable(o) {
3406
- const params = o["supported_parameters"];
3407
- if (Array.isArray(params) && params.some((x) => typeof x === "string" && /^tools?$|function.?call/i.test(x))) {
3408
- return true;
3409
- }
3410
- for (const k of ["capabilities", "supported_output_modalities"]) {
3411
- const v = o[k];
3412
- if (Array.isArray(v) && v.some((x) => typeof x === "string" && /tool|function.?call/i.test(x))) return true;
3413
- }
3414
- const caps = o["capabilities"];
3415
- if (caps && typeof caps === "object" && !Array.isArray(caps)) {
3416
- const c = caps;
3417
- if (c["function_calling"] === true || c["tool_calling"] === true || c["tools"] === true) return true;
3418
- }
3419
- return false;
3420
- }
3421
- function parseNextLink(linkHeader) {
3422
- if (!linkHeader) return null;
3423
- for (const part of linkHeader.split(",")) {
3424
- const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
3425
- if (match) return match[1];
3426
- }
3427
- return null;
3428
- }
3429
- var GITHUB_MODELS_MAX_OUTPUT_TOKENS, GITHUB_MODELS_MAX_INPUT_TOKENS, DEFAULT_CONTEXT_WINDOW, GITHUB_MODELS_MAX_CATALOG_PAGES, GITHUB_MODELS_CATALOG_ORIGIN, GitHubModelsProvider;
3430
- var init_github_models = __esm({
3431
- "src/providers/github-models.ts"() {
3432
- init_constants();
3433
- init_openai();
3434
- init_model_filter();
3435
- init_net();
3436
- GITHUB_MODELS_MAX_OUTPUT_TOKENS = 4e3;
3437
- GITHUB_MODELS_MAX_INPUT_TOKENS = 8e3;
3438
- DEFAULT_CONTEXT_WINDOW = 128e3;
3439
- GITHUB_MODELS_MAX_CATALOG_PAGES = 20;
3440
- GITHUB_MODELS_CATALOG_ORIGIN = new URL(GITHUB_MODELS_CATALOG_URL).origin;
3441
- GitHubModelsProvider = class extends OpenAIProvider {
3442
- constructor(config, model) {
3443
- super({ ...config, apiKey: config.apiKey ?? "" }, model);
3444
- this.useMaxCompletionTokens = isReasoningModel(stripModelOwnerPrefix(model.id));
3445
- this.client = new OpenAI__default.default({
3446
- apiKey: config.apiKey ?? "",
3447
- baseURL: GITHUB_MODELS_INFERENCE_URL,
3448
- defaultHeaders: { "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION, "User-Agent": "Cascade-AI" }
3449
- });
3450
- }
3451
- /**
3452
- * `OpenAIProvider.generateStream()` builds its request budget as
3453
- * `options.maxTokens ?? this.model.maxOutputTokens` — an explicit per-call
3454
- * `maxTokens` (T1Administrator's final compilation asks for 8,000, well
3455
- * above GitHub's real ~4K hard cap) wins outright and is sent unclamped.
3456
- * GitHub then rejects the request instead of answering, so a run routed to
3457
- * this provider for that step fails outright rather than getting a
3458
- * (perfectly serviceable, just shorter) completion. Clamp here, once, right
3459
- * before delegating to the inherited implementation, rather than teaching
3460
- * every caller GitHub's specific cap.
3461
- */
3462
- async generateStream(options, onChunk) {
3463
- const clamped = options.maxTokens !== void 0 && options.maxTokens > GITHUB_MODELS_MAX_OUTPUT_TOKENS ? { ...options, maxTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS } : options;
3464
- return super.generateStream(clamped, onChunk);
3465
- }
3466
- /**
3467
- * The catalog is a GitHub REST resource, not an OpenAI `/models` list, and
3468
- * wants GitHub's own header trio — the plain `Accept: application/json` the
3469
- * openai-compatible provider sends is not the same request.
3470
- */
3471
- catalogHeaders() {
3472
- return {
3473
- Accept: "application/vnd.github+json",
3474
- Authorization: `Bearer ${this.config.apiKey ?? ""}`,
3475
- "X-GitHub-Api-Version": GITHUB_MODELS_API_VERSION,
3476
- // GitHub's REST API rejects any request with no User-Agent — a plain 403
3477
- // regardless of how valid the PAT is. nodeHttpFetch is a thin wrapper
3478
- // over node:http/https, neither of which sets one the way a browser's
3479
- // fetch or a full HTTP client library would, so this has to be explicit.
3480
- "User-Agent": "Cascade-AI"
3481
- };
3482
- }
3483
- async listModels() {
3484
- const raw = [];
3485
- let url = GITHUB_MODELS_CATALOG_URL;
3486
- let pages = 0;
3487
- const visited = /* @__PURE__ */ new Set();
3488
- while (url && pages < GITHUB_MODELS_MAX_CATALOG_PAGES) {
3489
- if (visited.has(url)) break;
3490
- visited.add(url);
3491
- const res = await nodeHttpFetch(url, { headers: this.catalogHeaders() }, 0, {
3492
- allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
3493
- });
3494
- if (!res.ok) throw new Error(`GitHub Models catalog ${url} returned HTTP ${res.status}`);
3495
- const body = await res.json();
3496
- const pageItems = Array.isArray(body) ? body : Array.isArray(body?.models) ? body.models : Array.isArray(body?.data) ? body.data : [];
3497
- raw.push(...pageItems);
3498
- const next = parseNextLink(res.headers.get("link"));
3499
- if (next) {
3500
- const resolved = new URL(next, url);
3501
- if (resolved.origin !== GITHUB_MODELS_CATALOG_ORIGIN) {
3502
- throw new Error(`Refusing cross-origin GitHub Models pagination URL: ${resolved.origin}`);
3503
- }
3504
- url = resolved.href;
3505
- } else {
3506
- url = null;
3507
- }
3508
- pages++;
3509
- }
3510
- const entries = raw.map((m) => {
3511
- if (typeof m === "string") {
3512
- return { id: m, name: m, contextWindow: DEFAULT_CONTEXT_WINDOW, vision: false, tools: false };
3513
- }
3514
- if (!m || typeof m !== "object") return void 0;
3515
- const o = m;
3516
- const id = pickString(o, ["id", "name", "model"]);
3517
- if (!id) return void 0;
3518
- return {
3519
- id,
3520
- name: pickString(o, ["friendly_name", "display_name", "name"]) ?? id,
3521
- contextWindow: pickNumber(o, [
3522
- "context_window",
3523
- "context_length",
3524
- "max_input_tokens",
3525
- "limits.max_input_tokens",
3526
- "limits.max_context_tokens"
3527
- ]) ?? DEFAULT_CONTEXT_WINDOW,
3528
- vision: looksVisionCapable(o),
3529
- tools: looksToolCapable(o)
3530
- };
3531
- }).filter((e) => e !== void 0);
3532
- if (entries.length === 0) return [];
3533
- const chat = entries.filter((e) => isChatModel(e.id));
3534
- return (chat.length ? chat : entries).map((e) => ({
3535
- id: e.id,
3536
- // full `owner/model` — what the API is addressed by
3537
- name: e.name,
3538
- provider: "github-models",
3539
- // GitHub's real per-request input cap, not the base model's larger one
3540
- // (see GITHUB_MODELS_MAX_INPUT_TOKENS above) — the same reasoning
3541
- // already applied to maxOutputTokens below, for the input side.
3542
- contextWindow: Math.min(e.contextWindow, GITHUB_MODELS_MAX_INPUT_TOKENS),
3543
- isVisionCapable: e.vision,
3544
- // A REAL $0, not a missing number — usage is bundled into the quota of
3545
- // the plan the PAT belongs to and is never billed per token, so
3546
- // `pricingUnknown: false` is the honest value (see the flag's contract in
3547
- // core/router/pricing.ts: 0 alone is ambiguous). Set here rather than in
3548
- // pricing-data.json because resolvePricing() matches exactly on
3549
- // provider|model|region with no wildcard: every catalog model would need
3550
- // a hand-curated row, and the next one GitHub adds would read as "cost
3551
- // unknown" — an inconsistent picture for a billing reality that is
3552
- // identical across the entire catalog.
3553
- //
3554
- // `isLocal` is the OTHER way to reach a real $0, and it is the wrong one:
3555
- // it means "runs on this machine" and carries consequences that are
3556
- // simply false for a hosted API — the shared concurrency-1
3557
- // LocalRequestQueue (serialising these calls behind actual Ollama GPU
3558
- // inference), the 300s local rather than 120s cloud inference timeout,
3559
- // and a literal "[local]" tag in `cascade models`.
3560
- inputCostPer1kTokens: 0,
3561
- outputCostPer1kTokens: 0,
3562
- pricingUnknown: false,
3563
- maxOutputTokens: GITHUB_MODELS_MAX_OUTPUT_TOKENS,
3564
- supportsStreaming: true,
3565
- // Not every catalog model supports function/tool calling — a hardcoded
3566
- // `true` sent `tools` to models that reject the parameter outright and
3567
- // never engaged t3-worker.ts's existing text-tool fallback (only an
3568
- // explicit `false` does). See looksToolCapable's own comment for why
3569
- // "not positively advertised" defaults to false rather than true here.
3570
- supportsToolUse: e.tools,
3571
- isLocal: false
3572
- }));
3573
- }
3574
- async isAvailable() {
3575
- try {
3576
- const res = await nodeHttpFetch(GITHUB_MODELS_CATALOG_URL, { headers: this.catalogHeaders() }, 0, {
3577
- allowedRedirectOrigin: GITHUB_MODELS_CATALOG_ORIGIN
3578
- });
3579
- return res.ok;
3580
- } catch {
3581
- return false;
3582
- }
3583
- }
3584
- };
3585
- }
3586
- });
3587
-
3588
3369
  // src/providers/ollama.ts
3589
3370
  var ollama_exports = {};
3590
3371
  __export(ollama_exports, {
@@ -4926,6 +4707,19 @@ Original error: ${err.message}`
4926
4707
  const rows = provider ? this.db.prepare("SELECT metadata FROM model_cache WHERE provider = ?").all(provider) : this.db.prepare("SELECT metadata FROM model_cache").all();
4927
4708
  return rows.map((r) => JSON.parse(r.metadata));
4928
4709
  }
4710
+ /**
4711
+ * Deletes cached rows for a provider type that no longer exists in
4712
+ * `ProviderType`, so it takes a raw string rather than the union.
4713
+ *
4714
+ * Needed because the cache outlives the code: the REPL's loadCache() only
4715
+ * re-discovers when the cache is EMPTY or older than 24h, so rows left
4716
+ * behind by a removed provider read as "cache populated" and suppress
4717
+ * discovery for the providers that replaced it — `/model` then shows a
4718
+ * configured provider with zero models until the rows age out.
4719
+ */
4720
+ purgeCachedModelsForRetiredProvider(providerType) {
4721
+ return this.db.prepare("DELETE FROM model_cache WHERE provider = ?").run(providerType).changes;
4722
+ }
4929
4723
  clearModelCache(provider) {
4930
4724
  if (provider) {
4931
4725
  this.db.prepare("DELETE FROM model_cache WHERE provider = ?").run(provider);
@@ -5219,7 +5013,7 @@ Original error: ${err.message}`
5219
5013
  }
5220
5014
  };
5221
5015
  var ProviderConfigSchema = zod.z.object({
5222
- type: zod.z.enum(["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"]),
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, loadGlobalCredentials(this.globalDir));
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
- return validateConfig(JSON.parse(raw));
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 isFirstRun = this.config.providers.length === 0;
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 && isFirstRun) {
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 (isFirstRun && !this.config.providers.find((p) => p.type === "ollama")) {
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", "github-models"];
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 && !probesHitGithubModels) {
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", "github-models"];
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", "github-models"];
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" || type === "github-models") {
8228
+ if (type === "openai-compatible" || type === "azure") {
8389
8229
  return {
8390
8230
  id: type,
8391
8231
  name: type,
@@ -22263,10 +22103,7 @@ async function refreshModelCache(store, providers) {
22263
22103
  else if (provider.type === "anthropic") instance = new AnthropicProvider(provider, dummyModel);
22264
22104
  else if (provider.type === "ollama") instance = new OllamaProvider(provider, dummyModel);
22265
22105
  else if (provider.type === "openai-compatible") instance = new OpenAICompatibleProvider(provider, dummyModel);
22266
- else if (provider.type === "github-models") {
22267
- const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
22268
- instance = new GitHubModelsProvider2(provider, dummyModel);
22269
- } else if (provider.type === "azure") {
22106
+ else if (provider.type === "azure") {
22270
22107
  const { AzureOpenAIProvider: AzureOpenAIProvider2 } = await Promise.resolve().then(() => (init_azure(), azure_exports));
22271
22108
  instance = new AzureOpenAIProvider2(provider, dummyModel);
22272
22109
  }
@@ -22279,7 +22116,7 @@ async function refreshModelCache(store, providers) {
22279
22116
  }
22280
22117
  }
22281
22118
  }
22282
- function Repl({ config, workspacePath, themeName, initialPrompt, identityName, altScreen = false }) {
22119
+ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, startupNotice, altScreen = false }) {
22283
22120
  const { exit } = ink.useApp();
22284
22121
  const { stdout } = ink.useStdout();
22285
22122
  const [theme, setTheme] = React2.useState(() => getTheme(themeName));
@@ -22450,17 +22287,29 @@ function Repl({ config, workspacePath, themeName, initialPrompt, identityName, a
22450
22287
  }
22451
22288
  setCurrentIdentityId(initialIdentityId);
22452
22289
  const loadCache = async () => {
22290
+ const toMap = (models2) => {
22291
+ const map = /* @__PURE__ */ new Map();
22292
+ for (const m of models2) {
22293
+ const list = map.get(m.provider) ?? [];
22294
+ list.push(m);
22295
+ map.set(m.provider, list);
22296
+ }
22297
+ return map;
22298
+ };
22453
22299
  const models = store.getCachedModels();
22454
- const map = /* @__PURE__ */ new Map();
22455
- for (const m of models) {
22456
- const list = map.get(m.provider) ?? [];
22457
- list.push(m);
22458
- map.set(m.provider, list);
22300
+ setCachedModels(toMap(models));
22301
+ if (models.length === 0 || store.getCacheAge() > 24 * 60 * 60 * 1e3) {
22302
+ await refreshModelCache(store, config.providers);
22303
+ setCachedModels(toMap(store.getCachedModels()));
22459
22304
  }
22460
- setCachedModels(map);
22461
- if (models.length === 0 || store.getCacheAge() > 24 * 60 * 60 * 1e3) await refreshModelCache(store, config.providers);
22462
22305
  };
22463
22306
  loadCache();
22307
+ if (startupNotice) {
22308
+ dispatch({
22309
+ type: "ADD_MESSAGE",
22310
+ message: { id: crypto.randomUUID(), role: "system", content: `\u26A0 ${startupNotice}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
22311
+ });
22312
+ }
22464
22313
  store.createSession({ id: sessionIdRef.current, title: "Cascade Session", createdAt: startedAtRef.current, updatedAt: startedAtRef.current, identityId: config.defaultIdentityId ?? "default", workspacePath, messages: [], metadata: { totalTokens: 0, totalCostUsd: 0, modelsUsed: [], toolsUsed: [], taskCount: 0 } });
22465
22314
  persistRuntimeSession("ACTIVE");
22466
22315
  try {
@@ -23383,7 +23232,7 @@ async function validateConfiguredModels(config) {
23383
23232
  function inferProviderFromModelId(id, providers) {
23384
23233
  if (id.includes(":")) {
23385
23234
  const prefix = id.split(":")[0].toLowerCase();
23386
- const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama", "github-models"];
23235
+ const validProviders = ["anthropic", "openai", "gemini", "azure", "openai-compatible", "ollama"];
23387
23236
  if (validProviders.includes(prefix)) {
23388
23237
  return prefix;
23389
23238
  }
@@ -23680,10 +23529,9 @@ var PROVIDER_LABELS = {
23680
23529
  gemini: "Google Gemini",
23681
23530
  azure: "Azure OpenAI",
23682
23531
  ollama: "Ollama (local)",
23683
- "openai-compatible": "OpenAI-Compatible",
23684
- "github-models": "GitHub Models"
23532
+ "openai-compatible": "OpenAI-Compatible"
23685
23533
  };
23686
- var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible", "github-models"];
23534
+ var providerOrder = ["anthropic", "openai", "gemini", "azure", "ollama", "openai-compatible"];
23687
23535
  function buildInitialEntries(types) {
23688
23536
  return [...types].map((type) => ({
23689
23537
  id: crypto.randomUUID(),
@@ -23855,13 +23703,6 @@ function SetupWizard({ workspacePath, onComplete }) {
23855
23703
  const fetched = await p.listModels();
23856
23704
  fetched.forEach((m) => models.push({ id: m.id, name: m.name, providerLabel: entry.label }));
23857
23705
  dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
23858
- } else if (type === "github-models") {
23859
- const { GitHubModelsProvider: GitHubModelsProvider2 } = await Promise.resolve().then(() => (init_github_models(), github_models_exports));
23860
- const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
23861
- const p = new GitHubModelsProvider2({ type, apiKey }, dummyModel);
23862
- const fetched = await p.listModels();
23863
- fetched.forEach((m) => models.push({ id: `${type}:${m.id}`, name: m.name, providerLabel: entry.label }));
23864
- dispatchRef.current({ type: "SET_FETCH_LOG", line: ` \u2714 ${entry.label} \u2014 ${fetched.length} models` });
23865
23706
  } else if (type === "gemini") {
23866
23707
  const { GeminiProvider: GeminiProvider2 } = await Promise.resolve().then(() => (init_gemini(), gemini_exports));
23867
23708
  const dummyModel = { id: "dummy", name: "dummy", provider: type, contextWindow: 0, isVisionCapable: false, inputCostPer1kTokens: 0, outputCostPer1kTokens: 0, maxOutputTokens: 0, supportsStreaming: false, isLocal: false };
@@ -24040,8 +23881,7 @@ function SetupWizard({ workspacePath, onComplete }) {
24040
23881
  /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: focused ? theme.colors.primary : selected ? theme.colors.foreground : theme.colors.muted, children: PROVIDER_LABELS[p] }),
24041
23882
  p === "azure" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 multiple deployments supported" }),
24042
23883
  p === "openai-compatible" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 Groq, Together, custom" }),
24043
- p === "ollama" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" }),
24044
- p === "github-models" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 GitHub PAT with `models: read`" })
23884
+ p === "ollama" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: " \u2014 no API key needed" })
24045
23885
  ] }, p);
24046
23886
  }) }),
24047
23887
  /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: theme.colors.muted, dimColor: true, children: "space select \xB7 a all \xB7 i invert \xB7 enter continue" }) }),
@@ -24448,8 +24288,7 @@ async function doctorCommand() {
24448
24288
  { type: "anthropic", name: "Anthropic" },
24449
24289
  { type: "openai", name: "OpenAI" },
24450
24290
  { type: "gemini", name: "Google Gemini" },
24451
- { type: "azure", name: "Azure OpenAI" },
24452
- { type: "github-models", name: "GitHub Models" }
24291
+ { type: "azure", name: "Azure OpenAI" }
24453
24292
  ];
24454
24293
  for (const { type, name } of providers) {
24455
24294
  const key = cm.getApiKey(type);
@@ -24710,7 +24549,9 @@ function gatherSyncBundle(config) {
24710
24549
  if (Object.keys(prefs).length) bundle.prefs = prefs;
24711
24550
  return bundle;
24712
24551
  }
24713
- function applySyncBundle(bundle, config) {
24552
+ function applySyncBundle(bundle, config, cleanup) {
24553
+ const dropped = stripRetiredProviders(bundle);
24554
+ if (cleanup) cleanup.removed = dropped.removed;
24714
24555
  const next = { ...config, tools: { ...config.tools } };
24715
24556
  if (bundle.providers) next.providers = mergeByKey(config.providers, bundle.providers, providerSig) ?? [];
24716
24557
  if (bundle.webSearch) next.tools.webSearch = { ...config.tools?.webSearch, ...bundle.webSearch };
@@ -24726,6 +24567,14 @@ function applySyncBundle(bundle, config) {
24726
24567
  }
24727
24568
  if (bundle.models) next.models = { ...config.models, ...bundle.models };
24728
24569
  if (bundle.budget) next.budget = { ...config.budget, ...bundle.budget };
24570
+ const merged = { ...next.models };
24571
+ const clearedFromMerge = clearRetiredPins(merged);
24572
+ if (clearedFromMerge.length > 0) next.models = merged;
24573
+ if (cleanup) {
24574
+ cleanup.clearedPins = ["t1", "t2", "t3"].filter(
24575
+ (t) => (dropped.clearedPins.includes(t) || clearedFromMerge.includes(t)) && next.models?.[t] === void 0
24576
+ );
24577
+ }
24729
24578
  if (bundle.prefs) {
24730
24579
  const p = bundle.prefs;
24731
24580
  if (p.autoBias !== void 0) next.autoBias = p.autoBias;
@@ -25033,14 +24882,28 @@ async function syncPullCommand() {
25033
24882
  console.log(chalk7__default.default.dim("\n Cancelled.\n"));
25034
24883
  return;
25035
24884
  }
24885
+ let bundle;
24886
+ try {
24887
+ bundle = await decryptJSON(blob, pass);
24888
+ } catch {
24889
+ console.log(chalk7__default.default.red("\n Could not decrypt \u2014 check your passphrase and try again.\n"));
24890
+ process.exitCode = 1;
24891
+ return;
24892
+ }
25036
24893
  try {
25037
- const bundle = await decryptJSON(blob, pass);
25038
24894
  const cm = new ConfigManager(process.cwd());
25039
24895
  await cm.load();
25040
- await cm.updateConfig(applySyncBundle(bundle, cm.getConfig()));
24896
+ const cleanup = { removed: [], clearedPins: [] };
24897
+ await cm.updateConfig(applySyncBundle(bundle, cm.getConfig(), cleanup));
25041
24898
  console.log(chalk7__default.default.green("\n \u2713 Applied your synced settings. Your keys are ready here.\n"));
25042
- } catch {
25043
- console.log(chalk7__default.default.red("\n Could not decrypt \u2014 check your passphrase and try again.\n"));
24899
+ if (didCleanupChangeAnything(cleanup)) {
24900
+ console.log(chalk7__default.default.yellow(` ${describeCleanup(cleanup)}
24901
+ `));
24902
+ }
24903
+ } catch (err) {
24904
+ console.log(chalk7__default.default.red(`
24905
+ Could not apply your synced settings: ${err instanceof Error ? err.message : String(err)}
24906
+ `));
25044
24907
  process.exitCode = 1;
25045
24908
  }
25046
24909
  }
@@ -27667,6 +27530,7 @@ async function startRepl(options) {
27667
27530
  process.exit(1);
27668
27531
  }
27669
27532
  let config = cm.getConfig();
27533
+ const startupNotice = cm.takeRetiredNotice();
27670
27534
  const needsSetup = !hasUsableProvider(config.providers);
27671
27535
  if (needsSetup) {
27672
27536
  console.log(chalk7__default.default.magenta(" \u25C8 No providers configured \u2014 launching setup wizard\u2026"));
@@ -27689,6 +27553,7 @@ async function startRepl(options) {
27689
27553
  themeName: options.theme ?? config.theme ?? DEFAULT_THEME,
27690
27554
  initialPrompt: options.prompt,
27691
27555
  identityName: options.identity,
27556
+ startupNotice,
27692
27557
  altScreen: useAltScreen
27693
27558
  }),
27694
27559
  { exitOnCtrlC: false }