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