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.
package/dist/index.d.cts CHANGED
@@ -831,6 +831,14 @@ declare class ModelSelector {
831
831
  * Absent for a provider ⇒ no validation ran ⇒ behave exactly as before.
832
832
  */
833
833
  private validatedIds;
834
+ /**
835
+ * Normalized ids that were added via discovery/registration but aren't in the
836
+ * bundled catalog — i.e. genuinely new models the provider reported (a Gemini
837
+ * flash released after the catalog was last hand-updated, an Ollama tag, etc.).
838
+ * getCandidatesForTier lets these compete in AUTO ranking so a newer,
839
+ * better-value model isn't invisible until someone edits constants.ts.
840
+ */
841
+ private discovered;
834
842
  constructor(availableProviders: Set<ProviderType>);
835
843
  addDynamicModel(model: ModelInfo): void;
836
844
  /** Record the ids a provider's API actually serves (discovery). Empty ⇒ ignored. */
@@ -1344,6 +1352,13 @@ declare class CascadeRouter extends EventEmitter {
1344
1352
  private providers;
1345
1353
  private stats;
1346
1354
  private tierModels;
1355
+ /**
1356
+ * Tiers the user pinned to a specific model in config (`models.tX` !== 'auto').
1357
+ * An explicit pin always wins over Cascade Auto — selectModelForSubtask never
1358
+ * re-selects a pinned tier per subtask. (tierModels is also filled with
1359
+ * auto-picked defaults, so it alone can't distinguish "pinned" from "default".)
1360
+ */
1361
+ private explicitTierModels;
1347
1362
  private config;
1348
1363
  private sessionCostUsd;
1349
1364
  private runTokens;
package/dist/index.d.ts CHANGED
@@ -831,6 +831,14 @@ declare class ModelSelector {
831
831
  * Absent for a provider ⇒ no validation ran ⇒ behave exactly as before.
832
832
  */
833
833
  private validatedIds;
834
+ /**
835
+ * Normalized ids that were added via discovery/registration but aren't in the
836
+ * bundled catalog — i.e. genuinely new models the provider reported (a Gemini
837
+ * flash released after the catalog was last hand-updated, an Ollama tag, etc.).
838
+ * getCandidatesForTier lets these compete in AUTO ranking so a newer,
839
+ * better-value model isn't invisible until someone edits constants.ts.
840
+ */
841
+ private discovered;
834
842
  constructor(availableProviders: Set<ProviderType>);
835
843
  addDynamicModel(model: ModelInfo): void;
836
844
  /** Record the ids a provider's API actually serves (discovery). Empty ⇒ ignored. */
@@ -1344,6 +1352,13 @@ declare class CascadeRouter extends EventEmitter {
1344
1352
  private providers;
1345
1353
  private stats;
1346
1354
  private tierModels;
1355
+ /**
1356
+ * Tiers the user pinned to a specific model in config (`models.tX` !== 'auto').
1357
+ * An explicit pin always wins over Cascade Auto — selectModelForSubtask never
1358
+ * re-selects a pinned tier per subtask. (tierModels is also filled with
1359
+ * auto-picked defaults, so it alone can't distinguish "pinned" from "default".)
1360
+ */
1361
+ private explicitTierModels;
1347
1362
  private config;
1348
1363
  private sessionCostUsd;
1349
1364
  private runTokens;
package/dist/index.js CHANGED
@@ -193,7 +193,7 @@ var init_audit_logger = __esm({
193
193
  });
194
194
 
195
195
  // src/constants.ts
196
- var CASCADE_VERSION = "0.45.0";
196
+ var CASCADE_VERSION = "0.46.0";
197
197
  var CASCADE_CONFIG_DIR = ".cascade";
198
198
  var CASCADE_MD_FILE = "CASCADE.md";
199
199
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -1797,12 +1797,21 @@ var ModelSelector = class {
1797
1797
  * Absent for a provider ⇒ no validation ran ⇒ behave exactly as before.
1798
1798
  */
1799
1799
  validatedIds = /* @__PURE__ */ new Map();
1800
+ /**
1801
+ * Normalized ids that were added via discovery/registration but aren't in the
1802
+ * bundled catalog — i.e. genuinely new models the provider reported (a Gemini
1803
+ * flash released after the catalog was last hand-updated, an Ollama tag, etc.).
1804
+ * getCandidatesForTier lets these compete in AUTO ranking so a newer,
1805
+ * better-value model isn't invisible until someone edits constants.ts.
1806
+ */
1807
+ discovered = /* @__PURE__ */ new Set();
1800
1808
  constructor(availableProviders) {
1801
1809
  this.availableProviders = availableProviders;
1802
1810
  this.availableModels = new Map(Object.entries(MODELS));
1803
1811
  }
1804
1812
  addDynamicModel(model) {
1805
1813
  this.availableModels.set(model.id, model);
1814
+ if (!(model.id in MODELS)) this.discovered.add(normalizeModelId(model.id));
1806
1815
  }
1807
1816
  /** Record the ids a provider's API actually serves (discovery). Empty ⇒ ignored. */
1808
1817
  setValidatedModels(provider, ids) {
@@ -1910,6 +1919,19 @@ var ModelSelector = class {
1910
1919
  candidates.push(model);
1911
1920
  }
1912
1921
  }
1922
+ if (this.discovered.size) {
1923
+ const tierProviders = /* @__PURE__ */ new Set();
1924
+ for (const key of priority) {
1925
+ const m = MODELS[key];
1926
+ if (m) tierProviders.add(m.provider);
1927
+ }
1928
+ const have = new Set(candidates.map((c) => c.id));
1929
+ for (const model of this.availableModels.values()) {
1930
+ if (have.has(model.id)) continue;
1931
+ if (!this.discovered.has(normalizeModelId(model.id))) continue;
1932
+ if (tierProviders.has(model.provider) && this.isUsable(model)) candidates.push(model);
1933
+ }
1934
+ }
1913
1935
  const localOnly = this.availableProviders.size > 0 && Array.from(this.availableProviders).every((p) => p === "ollama");
1914
1936
  if (localOnly) {
1915
1937
  for (const model of this.availableModels.values()) {
@@ -2626,6 +2648,14 @@ var FAMILY_MATCHERS = [
2626
2648
  [/gemini-?1\.5-pro/i, "gemini-1.5-pro"],
2627
2649
  [/gemini-?2\.0-flash-lite/i, "gemini-flash-lite"],
2628
2650
  [/gemini-?2\.0-flash/i, "gemini-2.0-flash"],
2651
+ // Generic Gemini fallbacks — a model released after this table was written
2652
+ // (e.g. a Gemini 3.x flash discovered live from the provider) still gets a
2653
+ // sensible score by its class instead of a neutral 0.5, so it can compete in
2654
+ // AUTO ranking. Ordered after the specific rows above; most-specific first.
2655
+ [/gemini.*flash.?lite/i, "gemini-flash-lite"],
2656
+ [/gemini.*pro/i, "gemini-2.5-pro"],
2657
+ [/gemini.*flash/i, "gemini-2.5-flash"],
2658
+ [/gemini/i, "gemini-2.5-flash"],
2629
2659
  [/codellama|code-llama|starcoder|stable-code/i, "codellama"],
2630
2660
  [/deepseek/i, "deepseek"],
2631
2661
  [/qwen/i, "qwen"],
@@ -2717,6 +2747,13 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
2717
2747
  costByFeature: {}
2718
2748
  };
2719
2749
  tierModels = /* @__PURE__ */ new Map();
2750
+ /**
2751
+ * Tiers the user pinned to a specific model in config (`models.tX` !== 'auto').
2752
+ * An explicit pin always wins over Cascade Auto — selectModelForSubtask never
2753
+ * re-selects a pinned tier per subtask. (tierModels is also filled with
2754
+ * auto-picked defaults, so it alone can't distinguish "pinned" from "default".)
2755
+ */
2756
+ explicitTierModels = /* @__PURE__ */ new Set();
2720
2757
  config;
2721
2758
  sessionCostUsd = 0;
2722
2759
  // Per-run accounting for the hard per-task cap. Reset by beginRun() at the
@@ -2794,6 +2831,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
2794
2831
  );
2795
2832
  }
2796
2833
  this.tierModels.set(tier, model);
2834
+ this.explicitTierModels.add(tier);
2797
2835
  this.ensureProvider(model, config.providers);
2798
2836
  }
2799
2837
  const azureRanked = [...azureDeployments].sort((a, b) => inferModelCapability(b.id) - inferModelCapability(a.id));
@@ -3234,6 +3272,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
3234
3272
  * Pure heuristic — no extra LLM call.
3235
3273
  */
3236
3274
  async selectModelForSubtask(tier, text, opts) {
3275
+ if (this.explicitTierModels.has(tier)) return this.tierModels.get(tier) ?? null;
3237
3276
  if (!this.config?.cascadeAuto || !this.taskAnalyzer || !text.trim()) return null;
3238
3277
  try {
3239
3278
  return await this.taskAnalyzer.selectModel(text, tier, this.selector, opts);