cascade-ai 0.12.7 → 0.12.8

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.cjs CHANGED
@@ -77,7 +77,7 @@ var cron__default = /*#__PURE__*/_interopDefault(cron);
77
77
 
78
78
 
79
79
  // src/constants.ts
80
- var CASCADE_VERSION = "0.12.7";
80
+ var CASCADE_VERSION = "0.12.8";
81
81
  var CASCADE_CONFIG_DIR = ".cascade";
82
82
  var CASCADE_MD_FILE = "CASCADE.md";
83
83
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -1516,6 +1516,7 @@ var ModelSelector = class {
1516
1516
  if (lower.includes("claude")) providerStr = "anthropic";
1517
1517
  else if (lower.startsWith("gpt") || lower.startsWith("o1") || lower.startsWith("o3")) providerStr = "openai";
1518
1518
  else if (lower.includes("gemini")) providerStr = "gemini";
1519
+ else if ((lower.endsWith(".gguf") || actualId.includes("/") || actualId.includes("\\")) && this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
1519
1520
  else if (this.availableProviders.has("ollama")) providerStr = "ollama";
1520
1521
  else if (this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
1521
1522
  else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
@@ -2203,6 +2204,11 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
2203
2204
  if (availableProviders.has("ollama")) {
2204
2205
  await this.discoverOllamaModels(ollamaCfg);
2205
2206
  }
2207
+ if (availableProviders.has("openai-compatible")) {
2208
+ await Promise.all(
2209
+ config.providers.filter((p) => p.type === "openai-compatible").map((cfg) => this.discoverOpenAICompatibleModels(cfg))
2210
+ );
2211
+ }
2206
2212
  for (const tier of ["T1", "T2", "T3"]) {
2207
2213
  const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
2208
2214
  if (!override || override === "auto") continue;
@@ -2634,6 +2640,14 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
2634
2640
  getModelsForProvider(provider) {
2635
2641
  return this.selector.getAvailableModelsForProvider(provider);
2636
2642
  }
2643
+ /**
2644
+ * Every model available across the configured + reachable providers, after
2645
+ * discovery (Ollama tags, OpenAI-compatible/llama.cpp models, cloud catalog).
2646
+ * Used to populate the desktop model pickers with the user's real models.
2647
+ */
2648
+ getAvailableModels() {
2649
+ return this.selector?.getAllAvailableModels() ?? [];
2650
+ }
2637
2651
  // ── Private ──────────────────────────────────
2638
2652
  async detectAvailableProviders(configs) {
2639
2653
  const available = /* @__PURE__ */ new Set();
@@ -2664,6 +2678,28 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
2664
2678
  } catch {
2665
2679
  }
2666
2680
  }
2681
+ async discoverOpenAICompatibleModels(cfg) {
2682
+ try {
2683
+ const seed = {
2684
+ id: "openai-compatible",
2685
+ name: "openai-compatible",
2686
+ provider: "openai-compatible",
2687
+ contextWindow: 32e3,
2688
+ isVisionCapable: false,
2689
+ inputCostPer1kTokens: 0,
2690
+ outputCostPer1kTokens: 0,
2691
+ maxOutputTokens: 4e3,
2692
+ supportsStreaming: true,
2693
+ isLocal: false
2694
+ };
2695
+ const provider = new OpenAICompatibleProvider(cfg, seed);
2696
+ const models = await provider.listModels();
2697
+ for (const m of models) {
2698
+ this.selector.addDynamicModel(m);
2699
+ }
2700
+ } catch {
2701
+ }
2702
+ }
2667
2703
  ensureProvider(model, configs) {
2668
2704
  const key = `${model.provider}:${model.id}`;
2669
2705
  if (this.providers.has(key)) return;
@@ -2693,7 +2729,23 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
2693
2729
  }
2694
2730
  }
2695
2731
  getAnyModelForProvider(type) {
2696
- return Object.values(MODELS).find((m) => m.provider === type);
2732
+ const fromCatalog = Object.values(MODELS).find((m) => m.provider === type);
2733
+ if (fromCatalog) return fromCatalog;
2734
+ if (type === "openai-compatible" || type === "azure") {
2735
+ return {
2736
+ id: type,
2737
+ name: type,
2738
+ provider: type,
2739
+ contextWindow: 32e3,
2740
+ isVisionCapable: false,
2741
+ inputCostPer1kTokens: 0,
2742
+ outputCostPer1kTokens: 0,
2743
+ maxOutputTokens: 4e3,
2744
+ supportsStreaming: true,
2745
+ isLocal: false
2746
+ };
2747
+ }
2748
+ return void 0;
2697
2749
  }
2698
2750
  recordStats(tier, model, usage) {
2699
2751
  this.stats.totalTokens += usage.totalTokens;
@@ -8834,7 +8886,11 @@ ${last.partialOutput}` : "");
8834
8886
  looksLikeConversational(prompt) {
8835
8887
  const LOW_COMPLEXITY = [
8836
8888
  /^(?:hi|hello|hey|thanks|thank you|ok|okay|yes|no|sure|got it|sounds good)\b/i,
8837
- /^(?:what is|what are|list|show me|tell me|who is|where is|when is|how do i)\b/i,
8889
+ /^(?:what is|what are|what'?s|list|show me|tell me|who is|who are|who'?re|where is|when is|how do i)\b/i,
8890
+ // Self-identity / capability questions ("who are you", "what can you do",
8891
+ // "who made you") are pure conversation — never a multi-agent build.
8892
+ /^(?:who|what)\b.*\byou\b/i,
8893
+ /^what can you\b/i,
8838
8894
  /\b(?:simple|quick|brief|small|single|one-line|typo|rename)\b/i
8839
8895
  ];
8840
8896
  const wordCount = prompt.trim().split(/\s+/).length;
@@ -8932,10 +8988,16 @@ ${prompt}` : prompt;
8932
8988
  temperature: 0
8933
8989
  });
8934
8990
  const content = result.content.trim();
8935
- const firstWord = (content.split(/[\s—–-]+/)[0] ?? "").toLowerCase();
8991
+ const match = content.toLowerCase().match(/\b(simple|moderate|complex)\b/);
8936
8992
  const reason = content.replace(/^\S+\s*[—–-]*\s*/, "").trim();
8937
- const verdict = firstWord.includes("simple") ? "Simple" : firstWord.includes("moderate") ? "Moderate" : "Complex";
8938
- this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
8993
+ let verdict;
8994
+ if (match) {
8995
+ verdict = match[1] === "simple" ? "Simple" : match[1] === "moderate" ? "Moderate" : "Complex";
8996
+ this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
8997
+ } else {
8998
+ verdict = prompt.trim().split(/\s+/).length <= 12 ? "Simple" : "Moderate";
8999
+ this.recordDecision("complexity", `${verdict} \u2014 classifier output unparseable; defaulted by length`);
9000
+ }
8939
9001
  return verdict;
8940
9002
  } catch {
8941
9003
  const followUpPrompt = /^(proceed|continue|go ahead|do it|yes|yep|ok|okay|carry on)$/i.test(prompt.trim());