has-usable-llm.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. import type { LlmConfig, ProviderConfigs } from "@/stores/wiki-store"
  2. export type LlmProvider = LlmConfig["provider"]
  3. /**
  4. * Maps LlmConfig.provider values to their corresponding LLM_PRESETS id,
  5. * for providers that are gated by a single well-known preset toggle.
  6. */
  7. const PRESET_ID_BY_PROVIDER: Partial<Record<LlmProvider, string>> = {
  8. "claude-code": "claude-code-cli",
  9. "codex-cli": "codex-cli",
  10. "ollama": "ollama-local",
  11. }
  12. function isPresetEnabled(providerConfigs: ProviderConfigs, presetId: string): boolean {
  13. const entry = providerConfigs[presetId]
  14. return entry?.enabled === true
  15. }
  16. function hasEnabledCustomPreset(providerConfigs: ProviderConfigs): boolean {
  17. for (const key of Object.keys(providerConfigs)) {
  18. if (!key.startsWith("custom-")) continue
  19. if (isPresetEnabled(providerConfigs, key)) return true
  20. }
  21. return false
  22. }
  23. /**
  24. * Single source of truth for "is the user's LLM configuration good
  25. * enough to make calls?" Replaces ad-hoc `apiKey || provider ===
  26. * "ollama" || …` checks scattered across ingest, sweep, lint,
  27. * chat, and clip-watcher — every one of which had to be edited
  28. * by hand whenever a new no-key provider was added, and at least
  29. * three of which were silently out of date when the
  30. * Claude Code CLI provider shipped.
  31. *
  32. * Use this everywhere a guard like "do we have an LLM?" is
  33. * needed; the type-level union plus the exhaustiveness test in
  34. * `has-usable-llm.test.ts` ensures future provider additions
  35. * land in exactly one bucket and don't slip through.
  36. */
  37. export function hasUsableLlm(
  38. cfg: Pick<LlmConfig, "provider" | "apiKey" | "model">,
  39. providerConfigs: ProviderConfigs = {},
  40. ): boolean {
  41. const hasKey = cfg.apiKey.trim().length > 0
  42. const hasModel = cfg.model.trim().length > 0
  43. if (cfg.provider === "claude-code" || cfg.provider === "codex-cli") {
  44. const presetId = PRESET_ID_BY_PROVIDER[cfg.provider]!
  45. return isPresetEnabled(providerConfigs, presetId)
  46. }
  47. if (cfg.provider === "ollama") {
  48. return isPresetEnabled(providerConfigs, "ollama-local") && hasModel
  49. }
  50. if (cfg.provider === "custom") {
  51. if (hasKey && hasModel) return true
  52. return hasEnabledCustomPreset(providerConfigs) && hasModel
  53. }
  54. return hasKey && hasModel
  55. }