cascade-ai 0.12.6 → 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/cli.js CHANGED
@@ -54,7 +54,7 @@ var __export = (target, all) => {
54
54
  var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_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;
55
55
  var init_constants = __esm({
56
56
  "src/constants.ts"() {
57
- CASCADE_VERSION = "0.12.6";
57
+ CASCADE_VERSION = "0.12.8";
58
58
  CASCADE_CONFIG_FILE = ".cascade/config.json";
59
59
  CASCADE_DB_FILE = ".cascade/memory.db";
60
60
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
@@ -3322,6 +3322,7 @@ var ModelSelector = class {
3322
3322
  if (lower.includes("claude")) providerStr = "anthropic";
3323
3323
  else if (lower.startsWith("gpt") || lower.startsWith("o1") || lower.startsWith("o3")) providerStr = "openai";
3324
3324
  else if (lower.includes("gemini")) providerStr = "gemini";
3325
+ else if ((lower.endsWith(".gguf") || actualId.includes("/") || actualId.includes("\\")) && this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
3325
3326
  else if (this.availableProviders.has("ollama")) providerStr = "ollama";
3326
3327
  else if (this.availableProviders.has("openai-compatible")) providerStr = "openai-compatible";
3327
3328
  else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
@@ -4004,6 +4005,11 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
4004
4005
  if (availableProviders.has("ollama")) {
4005
4006
  await this.discoverOllamaModels(ollamaCfg);
4006
4007
  }
4008
+ if (availableProviders.has("openai-compatible")) {
4009
+ await Promise.all(
4010
+ config.providers.filter((p) => p.type === "openai-compatible").map((cfg) => this.discoverOpenAICompatibleModels(cfg))
4011
+ );
4012
+ }
4007
4013
  for (const tier of ["T1", "T2", "T3"]) {
4008
4014
  const override = tier === "T1" ? config.models.t1 : tier === "T2" ? config.models.t2 : config.models.t3;
4009
4015
  if (!override || override === "auto") continue;
@@ -4435,6 +4441,14 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
4435
4441
  getModelsForProvider(provider) {
4436
4442
  return this.selector.getAvailableModelsForProvider(provider);
4437
4443
  }
4444
+ /**
4445
+ * Every model available across the configured + reachable providers, after
4446
+ * discovery (Ollama tags, OpenAI-compatible/llama.cpp models, cloud catalog).
4447
+ * Used to populate the desktop model pickers with the user's real models.
4448
+ */
4449
+ getAvailableModels() {
4450
+ return this.selector?.getAllAvailableModels() ?? [];
4451
+ }
4438
4452
  // ── Private ──────────────────────────────────
4439
4453
  async detectAvailableProviders(configs) {
4440
4454
  const available = /* @__PURE__ */ new Set();
@@ -4465,6 +4479,28 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
4465
4479
  } catch {
4466
4480
  }
4467
4481
  }
4482
+ async discoverOpenAICompatibleModels(cfg) {
4483
+ try {
4484
+ const seed = {
4485
+ id: "openai-compatible",
4486
+ name: "openai-compatible",
4487
+ provider: "openai-compatible",
4488
+ contextWindow: 32e3,
4489
+ isVisionCapable: false,
4490
+ inputCostPer1kTokens: 0,
4491
+ outputCostPer1kTokens: 0,
4492
+ maxOutputTokens: 4e3,
4493
+ supportsStreaming: true,
4494
+ isLocal: false
4495
+ };
4496
+ const provider = new OpenAICompatibleProvider(cfg, seed);
4497
+ const models = await provider.listModels();
4498
+ for (const m of models) {
4499
+ this.selector.addDynamicModel(m);
4500
+ }
4501
+ } catch {
4502
+ }
4503
+ }
4468
4504
  ensureProvider(model, configs) {
4469
4505
  const key = `${model.provider}:${model.id}`;
4470
4506
  if (this.providers.has(key)) return;
@@ -4494,7 +4530,23 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
4494
4530
  }
4495
4531
  }
4496
4532
  getAnyModelForProvider(type) {
4497
- return Object.values(MODELS).find((m) => m.provider === type);
4533
+ const fromCatalog = Object.values(MODELS).find((m) => m.provider === type);
4534
+ if (fromCatalog) return fromCatalog;
4535
+ if (type === "openai-compatible" || type === "azure") {
4536
+ return {
4537
+ id: type,
4538
+ name: type,
4539
+ provider: type,
4540
+ contextWindow: 32e3,
4541
+ isVisionCapable: false,
4542
+ inputCostPer1kTokens: 0,
4543
+ outputCostPer1kTokens: 0,
4544
+ maxOutputTokens: 4e3,
4545
+ supportsStreaming: true,
4546
+ isLocal: false
4547
+ };
4548
+ }
4549
+ return void 0;
4498
4550
  }
4499
4551
  recordStats(tier, model, usage) {
4500
4552
  this.stats.totalTokens += usage.totalTokens;
@@ -10363,7 +10415,11 @@ ${last.partialOutput}` : "");
10363
10415
  looksLikeConversational(prompt) {
10364
10416
  const LOW_COMPLEXITY = [
10365
10417
  /^(?:hi|hello|hey|thanks|thank you|ok|okay|yes|no|sure|got it|sounds good)\b/i,
10366
- /^(?:what is|what are|list|show me|tell me|who is|where is|when is|how do i)\b/i,
10418
+ /^(?: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,
10419
+ // Self-identity / capability questions ("who are you", "what can you do",
10420
+ // "who made you") are pure conversation — never a multi-agent build.
10421
+ /^(?:who|what)\b.*\byou\b/i,
10422
+ /^what can you\b/i,
10367
10423
  /\b(?:simple|quick|brief|small|single|one-line|typo|rename)\b/i
10368
10424
  ];
10369
10425
  const wordCount = prompt.trim().split(/\s+/).length;
@@ -10461,10 +10517,16 @@ ${prompt}` : prompt;
10461
10517
  temperature: 0
10462
10518
  });
10463
10519
  const content = result.content.trim();
10464
- const firstWord = (content.split(/[\s—–-]+/)[0] ?? "").toLowerCase();
10520
+ const match = content.toLowerCase().match(/\b(simple|moderate|complex)\b/);
10465
10521
  const reason = content.replace(/^\S+\s*[—–-]*\s*/, "").trim();
10466
- const verdict = firstWord.includes("simple") ? "Simple" : firstWord.includes("moderate") ? "Moderate" : "Complex";
10467
- this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
10522
+ let verdict;
10523
+ if (match) {
10524
+ verdict = match[1] === "simple" ? "Simple" : match[1] === "moderate" ? "Moderate" : "Complex";
10525
+ this.recordDecision("complexity", `${verdict} \u2014 classifier: ${reason || "no reason given"}`);
10526
+ } else {
10527
+ verdict = prompt.trim().split(/\s+/).length <= 12 ? "Simple" : "Moderate";
10528
+ this.recordDecision("complexity", `${verdict} \u2014 classifier output unparseable; defaulted by length`);
10529
+ }
10468
10530
  return verdict;
10469
10531
  } catch {
10470
10532
  const followUpPrompt = /^(proceed|continue|go ahead|do it|yes|yep|ok|okay|carry on)$/i.test(prompt.trim());
@@ -13873,7 +13935,8 @@ function SetupWizard({ workspacePath, onComplete }) {
13873
13935
  ) })
13874
13936
  ] });
13875
13937
  }
13876
- const prompt = isAzure && fieldStage === "deploymentName" ? `Azure deployment name (${currentEntry.label})` : isAzure && fieldStage === "baseUrl" ? `Azure endpoint URL` : isAzure && fieldStage === "apiKey" ? `${currentEntry.label} API Key` : isAzure && fieldStage === "apiVersion" ? `Azure API version (e.g. 2024-08-01-preview)` : isCompat && fieldStage === "label" ? `Name for this endpoint (e.g. Groq)` : isCompat && fieldStage === "baseUrl" ? `Base URL (e.g. https://api.groq.com/openai/v1)` : isOllama ? `Ollama URL` : `${currentEntry.label} API Key`;
13938
+ const prompt = isAzure && fieldStage === "deploymentName" ? `Azure deployment name (${currentEntry.label})` : isAzure && fieldStage === "baseUrl" ? `Azure endpoint URL` : isAzure && fieldStage === "apiKey" ? `${currentEntry.label} API Key` : isAzure && fieldStage === "apiVersion" ? `Azure API version (e.g. 2024-08-01-preview)` : isCompat && fieldStage === "label" ? `Name for this endpoint (e.g. Groq)` : isCompat && fieldStage === "baseUrl" ? `Base URL (e.g. https://api.groq.com/openai/v1)` : isCompat && fieldStage === "apiKey" ? `${currentEntry.label} API Key (optional)` : isOllama ? `Ollama URL` : `${currentEntry.label} API Key`;
13939
+ const keyOptional = isCompat && fieldStage === "apiKey";
13877
13940
  const isMasked = fieldStage === "apiKey" && !isOllama;
13878
13941
  return /* @__PURE__ */ jsxs(Frame, { theme, phase: "keys", children: [
13879
13942
  doneEntries.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, children: doneEntries.map((e) => /* @__PURE__ */ jsxs(Box, { children: [
@@ -13891,8 +13954,8 @@ function SetupWizard({ workspacePath, onComplete }) {
13891
13954
  {
13892
13955
  theme,
13893
13956
  label: prompt,
13894
- tag: isOllama ? "optional \u2014 Enter for default" : "required",
13895
- tagColor: isOllama ? theme.colors.muted : theme.colors.error,
13957
+ tag: isOllama ? "optional \u2014 Enter for default" : keyOptional ? "optional \u2014 Enter to skip" : "required",
13958
+ tagColor: isOllama || keyOptional ? theme.colors.muted : theme.colors.error,
13896
13959
  active: true,
13897
13960
  children: /* @__PURE__ */ jsx(
13898
13961
  SafeTextInput,