cascade-ai 0.45.0 → 0.46.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.
@@ -222595,7 +222595,7 @@ Anthropic.Models = Models2;
222595
222595
  Anthropic.Beta = Beta;
222596
222596
 
222597
222597
  // src/constants.ts
222598
- var CASCADE_VERSION = "0.45.0";
222598
+ var CASCADE_VERSION = "0.46.0";
222599
222599
  var CASCADE_CONFIG_DIR = ".cascade";
222600
222600
  var CASCADE_MD_FILE = "CASCADE.md";
222601
222601
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -250104,12 +250104,21 @@ var ModelSelector = class {
250104
250104
  * Absent for a provider ⇒ no validation ran ⇒ behave exactly as before.
250105
250105
  */
250106
250106
  validatedIds = /* @__PURE__ */ new Map();
250107
+ /**
250108
+ * Normalized ids that were added via discovery/registration but aren't in the
250109
+ * bundled catalog — i.e. genuinely new models the provider reported (a Gemini
250110
+ * flash released after the catalog was last hand-updated, an Ollama tag, etc.).
250111
+ * getCandidatesForTier lets these compete in AUTO ranking so a newer,
250112
+ * better-value model isn't invisible until someone edits constants.ts.
250113
+ */
250114
+ discovered = /* @__PURE__ */ new Set();
250107
250115
  constructor(availableProviders) {
250108
250116
  this.availableProviders = availableProviders;
250109
250117
  this.availableModels = new Map(Object.entries(MODELS));
250110
250118
  }
250111
250119
  addDynamicModel(model) {
250112
250120
  this.availableModels.set(model.id, model);
250121
+ if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
250113
250122
  }
250114
250123
  /** Record the ids a provider's API actually serves (discovery). Empty ⇒ ignored. */
250115
250124
  setValidatedModels(provider, ids) {
@@ -250217,6 +250226,19 @@ var ModelSelector = class {
250217
250226
  candidates.push(model);
250218
250227
  }
250219
250228
  }
250229
+ if (this.discovered.size) {
250230
+ const tierProviders = /* @__PURE__ */ new Set();
250231
+ for (const key of priority) {
250232
+ const m3 = MODELS[key];
250233
+ if (m3) tierProviders.add(m3.provider);
250234
+ }
250235
+ const have = new Set(candidates.map((c4) => c4.id));
250236
+ for (const model of this.availableModels.values()) {
250237
+ if (have.has(model.id)) continue;
250238
+ if (!this.discovered.has(normalizeModelId(model.id))) continue;
250239
+ if (tierProviders.has(model.provider) && this.isUsable(model)) candidates.push(model);
250240
+ }
250241
+ }
250220
250242
  const localOnly = this.availableProviders.size > 0 && Array.from(this.availableProviders).every((p3) => p3 === "ollama");
250221
250243
  if (localOnly) {
250222
250244
  for (const model of this.availableModels.values()) {
@@ -250933,6 +250955,14 @@ var FAMILY_MATCHERS = [
250933
250955
  [/gemini-?1\.5-pro/i, "gemini-1.5-pro"],
250934
250956
  [/gemini-?2\.0-flash-lite/i, "gemini-flash-lite"],
250935
250957
  [/gemini-?2\.0-flash/i, "gemini-2.0-flash"],
250958
+ // Generic Gemini fallbacks — a model released after this table was written
250959
+ // (e.g. a Gemini 3.x flash discovered live from the provider) still gets a
250960
+ // sensible score by its class instead of a neutral 0.5, so it can compete in
250961
+ // AUTO ranking. Ordered after the specific rows above; most-specific first.
250962
+ [/gemini.*flash.?lite/i, "gemini-flash-lite"],
250963
+ [/gemini.*pro/i, "gemini-2.5-pro"],
250964
+ [/gemini.*flash/i, "gemini-2.5-flash"],
250965
+ [/gemini/i, "gemini-2.5-flash"],
250936
250966
  [/codellama|code-llama|starcoder|stable-code/i, "codellama"],
250937
250967
  [/deepseek/i, "deepseek"],
250938
250968
  [/qwen/i, "qwen"],
@@ -251024,6 +251054,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
251024
251054
  costByFeature: {}
251025
251055
  };
251026
251056
  tierModels = /* @__PURE__ */ new Map();
251057
+ /**
251058
+ * Tiers the user pinned to a specific model in config (`models.tX` !== 'auto').
251059
+ * An explicit pin always wins over Cascade Auto — selectModelForSubtask never
251060
+ * re-selects a pinned tier per subtask. (tierModels is also filled with
251061
+ * auto-picked defaults, so it alone can't distinguish "pinned" from "default".)
251062
+ */
251063
+ explicitTierModels = /* @__PURE__ */ new Set();
251027
251064
  config;
251028
251065
  sessionCostUsd = 0;
251029
251066
  // Per-run accounting for the hard per-task cap. Reset by beginRun() at the
@@ -251101,6 +251138,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
251101
251138
  );
251102
251139
  }
251103
251140
  this.tierModels.set(tier, model);
251141
+ this.explicitTierModels.add(tier);
251104
251142
  this.ensureProvider(model, config2.providers);
251105
251143
  }
251106
251144
  const azureRanked = [...azureDeployments].sort((a2, b3) => inferModelCapability(b3.id) - inferModelCapability(a2.id));
@@ -251541,6 +251579,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
251541
251579
  * Pure heuristic — no extra LLM call.
251542
251580
  */
251543
251581
  async selectModelForSubtask(tier, text, opts) {
251582
+ if (this.explicitTierModels.has(tier)) return this.tierModels.get(tier) ?? null;
251544
251583
  if (!this.config?.cascadeAuto || !this.taskAnalyzer || !text.trim()) return null;
251545
251584
  try {
251546
251585
  return await this.taskAnalyzer.selectModel(text, tier, this.selector, opts);
package/dist/index.cjs CHANGED
@@ -239,7 +239,7 @@ var init_audit_logger = __esm({
239
239
  });
240
240
 
241
241
  // src/constants.ts
242
- var CASCADE_VERSION = "0.45.0";
242
+ var CASCADE_VERSION = "0.46.0";
243
243
  var CASCADE_CONFIG_DIR = ".cascade";
244
244
  var CASCADE_MD_FILE = "CASCADE.md";
245
245
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -1843,12 +1843,21 @@ var ModelSelector = class {
1843
1843
  * Absent for a provider ⇒ no validation ran ⇒ behave exactly as before.
1844
1844
  */
1845
1845
  validatedIds = /* @__PURE__ */ new Map();
1846
+ /**
1847
+ * Normalized ids that were added via discovery/registration but aren't in the
1848
+ * bundled catalog — i.e. genuinely new models the provider reported (a Gemini
1849
+ * flash released after the catalog was last hand-updated, an Ollama tag, etc.).
1850
+ * getCandidatesForTier lets these compete in AUTO ranking so a newer,
1851
+ * better-value model isn't invisible until someone edits constants.ts.
1852
+ */
1853
+ discovered = /* @__PURE__ */ new Set();
1846
1854
  constructor(availableProviders) {
1847
1855
  this.availableProviders = availableProviders;
1848
1856
  this.availableModels = new Map(Object.entries(MODELS));
1849
1857
  }
1850
1858
  addDynamicModel(model) {
1851
1859
  this.availableModels.set(model.id, model);
1860
+ if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
1852
1861
  }
1853
1862
  /** Record the ids a provider's API actually serves (discovery). Empty ⇒ ignored. */
1854
1863
  setValidatedModels(provider, ids) {
@@ -1956,6 +1965,19 @@ var ModelSelector = class {
1956
1965
  candidates.push(model);
1957
1966
  }
1958
1967
  }
1968
+ if (this.discovered.size) {
1969
+ const tierProviders = /* @__PURE__ */ new Set();
1970
+ for (const key of priority) {
1971
+ const m = MODELS[key];
1972
+ if (m) tierProviders.add(m.provider);
1973
+ }
1974
+ const have = new Set(candidates.map((c) => c.id));
1975
+ for (const model of this.availableModels.values()) {
1976
+ if (have.has(model.id)) continue;
1977
+ if (!this.discovered.has(normalizeModelId(model.id))) continue;
1978
+ if (tierProviders.has(model.provider) && this.isUsable(model)) candidates.push(model);
1979
+ }
1980
+ }
1959
1981
  const localOnly = this.availableProviders.size > 0 && Array.from(this.availableProviders).every((p) => p === "ollama");
1960
1982
  if (localOnly) {
1961
1983
  for (const model of this.availableModels.values()) {
@@ -2672,6 +2694,14 @@ var FAMILY_MATCHERS = [
2672
2694
  [/gemini-?1\.5-pro/i, "gemini-1.5-pro"],
2673
2695
  [/gemini-?2\.0-flash-lite/i, "gemini-flash-lite"],
2674
2696
  [/gemini-?2\.0-flash/i, "gemini-2.0-flash"],
2697
+ // Generic Gemini fallbacks — a model released after this table was written
2698
+ // (e.g. a Gemini 3.x flash discovered live from the provider) still gets a
2699
+ // sensible score by its class instead of a neutral 0.5, so it can compete in
2700
+ // AUTO ranking. Ordered after the specific rows above; most-specific first.
2701
+ [/gemini.*flash.?lite/i, "gemini-flash-lite"],
2702
+ [/gemini.*pro/i, "gemini-2.5-pro"],
2703
+ [/gemini.*flash/i, "gemini-2.5-flash"],
2704
+ [/gemini/i, "gemini-2.5-flash"],
2675
2705
  [/codellama|code-llama|starcoder|stable-code/i, "codellama"],
2676
2706
  [/deepseek/i, "deepseek"],
2677
2707
  [/qwen/i, "qwen"],
@@ -2763,6 +2793,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
2763
2793
  costByFeature: {}
2764
2794
  };
2765
2795
  tierModels = /* @__PURE__ */ new Map();
2796
+ /**
2797
+ * Tiers the user pinned to a specific model in config (`models.tX` !== 'auto').
2798
+ * An explicit pin always wins over Cascade Auto — selectModelForSubtask never
2799
+ * re-selects a pinned tier per subtask. (tierModels is also filled with
2800
+ * auto-picked defaults, so it alone can't distinguish "pinned" from "default".)
2801
+ */
2802
+ explicitTierModels = /* @__PURE__ */ new Set();
2766
2803
  config;
2767
2804
  sessionCostUsd = 0;
2768
2805
  // Per-run accounting for the hard per-task cap. Reset by beginRun() at the
@@ -2840,6 +2877,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
2840
2877
  );
2841
2878
  }
2842
2879
  this.tierModels.set(tier, model);
2880
+ this.explicitTierModels.add(tier);
2843
2881
  this.ensureProvider(model, config.providers);
2844
2882
  }
2845
2883
  const azureRanked = [...azureDeployments].sort((a, b) => inferModelCapability(b.id) - inferModelCapability(a.id));
@@ -3280,6 +3318,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
3280
3318
  * Pure heuristic — no extra LLM call.
3281
3319
  */
3282
3320
  async selectModelForSubtask(tier, text, opts) {
3321
+ if (this.explicitTierModels.has(tier)) return this.tierModels.get(tier) ?? null;
3283
3322
  if (!this.config?.cascadeAuto || !this.taskAnalyzer || !text.trim()) return null;
3284
3323
  try {
3285
3324
  return await this.taskAnalyzer.selectModel(text, tier, this.selector, opts);