Browse Source

test: add provider enabled checks to hasUsableLlm

Mochocyang 3 months ago
parent
commit
6813e8896b
2 changed files with 119 additions and 3 deletions
  1. 75 0
      src/lib/has-usable-llm.test.ts
  2. 44 3
      src/lib/has-usable-llm.ts

+ 75 - 0
src/lib/has-usable-llm.test.ts

@@ -0,0 +1,75 @@
+import { describe, expect, it } from "vitest"
+import { hasUsableLlm } from "./has-usable-llm"
+import type { LlmConfig, ProviderConfigs } from "@/stores/wiki-store"
+
+const baseCfg: LlmConfig = {
+  provider: "openai",
+  apiKey: "sk-test",
+  model: "gpt-4o",
+  ollamaUrl: "http://localhost:11434",
+  customEndpoint: "",
+  maxContextSize: 204800,
+  apiMode: "chat_completions",
+  reasoning: { mode: "auto" },
+}
+
+describe("hasUsableLlm", () => {
+  it("accepts a hosted provider with apiKey and model", () => {
+    const providers: ProviderConfigs = {}
+    expect(hasUsableLlm(baseCfg, providers)).toBe(true)
+  })
+
+  it("rejects hosted provider without apiKey", () => {
+    const providers: ProviderConfigs = {}
+    expect(hasUsableLlm({ ...baseCfg, apiKey: "" }, providers)).toBe(false)
+  })
+
+  it("rejects hosted provider without model", () => {
+    const providers: ProviderConfigs = {}
+    expect(hasUsableLlm({ ...baseCfg, model: "" }, providers)).toBe(false)
+  })
+
+  it("accepts claude-code when the preset is enabled", () => {
+    const providers: ProviderConfigs = {
+      "claude-code-cli": { enabled: true },
+    }
+    const cfg: LlmConfig = { ...baseCfg, provider: "claude-code", apiKey: "", model: "" }
+    expect(hasUsableLlm(cfg, providers)).toBe(true)
+  })
+
+  it("rejects claude-code when the preset is explicitly disabled", () => {
+    const providers: ProviderConfigs = {
+      "claude-code-cli": { enabled: false },
+    }
+    const cfg: LlmConfig = { ...baseCfg, provider: "claude-code", apiKey: "", model: "" }
+    expect(hasUsableLlm(cfg, providers)).toBe(false)
+  })
+
+  it("rejects claude-code when there is no provider config entry at all (never enabled by user)", () => {
+    const providers: ProviderConfigs = {}
+    const cfg: LlmConfig = { ...baseCfg, provider: "claude-code", apiKey: "", model: "" }
+    expect(hasUsableLlm(cfg, providers)).toBe(false)
+  })
+
+  it("rejects codex-cli when disabled, even if binary exists on disk", () => {
+    const providers: ProviderConfigs = {
+      "codex-cli": { enabled: false },
+    }
+    const cfg: LlmConfig = { ...baseCfg, provider: "codex-cli", apiKey: "", model: "gpt-5" }
+    expect(hasUsableLlm(cfg, providers)).toBe(false)
+  })
+
+  it("accepts ollama without apiKey when enabled", () => {
+    const providers: ProviderConfigs = {
+      "ollama-local": { enabled: true, model: "qwen2.5" },
+    }
+    const cfg: LlmConfig = { ...baseCfg, provider: "ollama", apiKey: "", model: "qwen2.5", ollamaUrl: "http://localhost:11434" }
+    expect(hasUsableLlm(cfg, providers)).toBe(true)
+  })
+
+  it("accepts custom endpoint with apiKey", () => {
+    const providers: ProviderConfigs = {}
+    const cfg: LlmConfig = { ...baseCfg, provider: "custom", apiKey: "sk-test", model: "qwen-plus", customEndpoint: "https://dashscope.aliyuncs.com/compatible-mode/v1" }
+    expect(hasUsableLlm(cfg, providers)).toBe(true)
+  })
+})

+ 44 - 3
src/lib/has-usable-llm.ts

@@ -1,4 +1,5 @@
-import type { LlmConfig } from "@/stores/wiki-store"
+import type { LlmConfig, ProviderConfigs } from "@/stores/wiki-store"
+import { LLM_PRESETS } from "@/components/settings/llm-presets"
 
 export type LlmProvider = LlmConfig["provider"]
 
@@ -25,6 +26,29 @@ export const PROVIDERS_WITHOUT_KEY: ReadonlySet<LlmProvider> = new Set<LlmProvid
   "codex-cli",
 ])
 
+/**
+ * Maps LlmConfig.provider values to their corresponding LLM_PRESETS id,
+ * for providers that are gated by a single well-known preset toggle.
+ */
+const PRESET_ID_BY_PROVIDER: Partial<Record<LlmProvider, string>> = {
+  "claude-code": "claude-code-cli",
+  "codex-cli": "codex-cli",
+  "ollama": "ollama-local",
+}
+
+function isPresetEnabled(providerConfigs: ProviderConfigs, presetId: string): boolean {
+  const entry = providerConfigs[presetId]
+  return entry?.enabled === true
+}
+
+function hasEnabledCustomPreset(providerConfigs: ProviderConfigs): boolean {
+  for (const preset of LLM_PRESETS) {
+    if (preset.provider !== "custom") continue
+    if (isPresetEnabled(providerConfigs, preset.id)) return true
+  }
+  return false
+}
+
 /**
  * Single source of truth for "is the user's LLM configuration good
  * enough to make calls?" Replaces ad-hoc `apiKey || provider ===
@@ -41,7 +65,24 @@ export const PROVIDERS_WITHOUT_KEY: ReadonlySet<LlmProvider> = new Set<LlmProvid
  */
 export function hasUsableLlm(
   cfg: Pick<LlmConfig, "provider" | "apiKey" | "model">,
+  providerConfigs: ProviderConfigs = {},
 ): boolean {
-  if (PROVIDERS_WITHOUT_KEY.has(cfg.provider)) return true
-  return cfg.apiKey.trim().length > 0 && cfg.model.trim().length > 0
+  const hasKey = cfg.apiKey.trim().length > 0
+  const hasModel = cfg.model.trim().length > 0
+
+  if (cfg.provider === "claude-code" || cfg.provider === "codex-cli") {
+    const presetId = PRESET_ID_BY_PROVIDER[cfg.provider]
+    return presetId !== undefined && isPresetEnabled(providerConfigs, presetId)
+  }
+
+  if (cfg.provider === "ollama") {
+    return isPresetEnabled(providerConfigs, "ollama-local") && hasModel
+  }
+
+  if (cfg.provider === "custom") {
+    if (hasKey && hasModel) return true
+    return hasEnabledCustomPreset(providerConfigs) && hasModel
+  }
+
+  return hasKey && hasModel
 }