227 lines
6.5 KiB
TypeScript
227 lines
6.5 KiB
TypeScript
import { describe, expect, it } from "vitest"
|
|
import { LLM_PRESETS } from "./llm-presets"
|
|
import { disabledLlmConfig, resolveConfig } from "./preset-resolver"
|
|
import { hasUsableLlm } from "@/lib/has-usable-llm"
|
|
import type { LlmConfig } from "@/stores/wiki-store"
|
|
import type { LlmPreset } from "./llm-presets"
|
|
|
|
function fallbackConfig(overrides: Partial<LlmConfig> = {}): LlmConfig {
|
|
return {
|
|
provider: "openai",
|
|
apiKey: "sk-old",
|
|
model: "gpt-4o",
|
|
ollamaUrl: "http://localhost:11434",
|
|
customEndpoint: "https://example.test/v1",
|
|
maxContextSize: 8192,
|
|
reasoning: { mode: "high" },
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
describe("resolveConfig", () => {
|
|
it("keeps DeepSeek presets aligned with the current V4 model list", () => {
|
|
const deepseek = LLM_PRESETS.find((preset) => preset.id === "deepseek")
|
|
|
|
expect(deepseek?.defaultModel).toBe("deepseek-v4-flash")
|
|
expect(deepseek?.suggestedModels).toEqual([
|
|
"deepseek-v4-flash",
|
|
"deepseek-v4-pro",
|
|
"deepseek-chat",
|
|
"deepseek-reasoner",
|
|
])
|
|
})
|
|
|
|
it("exposes Atlas Cloud as an OpenAI-compatible chat-completions preset", () => {
|
|
const atlas = LLM_PRESETS.find((preset) => preset.id === "atlascloud")
|
|
|
|
expect(atlas?.provider).toBe("custom")
|
|
expect(atlas?.baseUrl).toBe("https://api.atlascloud.ai/v1")
|
|
expect(atlas?.apiMode).toBe("chat_completions")
|
|
expect(atlas?.defaultModel).toBe("deepseek-ai/deepseek-v4-pro")
|
|
expect(atlas?.suggestedModels).toContain("deepseek-ai/deepseek-v4-pro")
|
|
})
|
|
|
|
it("keeps Xiaomi MiMo presets aligned with current official and Token Plan endpoints", () => {
|
|
const mimo = LLM_PRESETS.find((preset) => preset.id === "xiaomi-mimo")
|
|
|
|
expect(mimo?.defaultModel).toBe("mimo-v2.5-pro")
|
|
expect(mimo?.suggestedContextSize).toBe(1000000)
|
|
expect(mimo?.baseUrl).toBe("https://api.xiaomimimo.com/v1")
|
|
expect(mimo?.baseUrlByMode).toEqual({
|
|
chat_completions: "https://token-plan-cn.xiaomimimo.com/v1",
|
|
anthropic_messages: "https://token-plan-cn.xiaomimimo.com/anthropic",
|
|
})
|
|
expect(mimo?.suggestedModels).toEqual([
|
|
"mimo-v2.5-pro",
|
|
"mimo-v2.5",
|
|
"mimo-v2-flash",
|
|
"mimo-v2-pro",
|
|
"mimo-v2-omni",
|
|
])
|
|
})
|
|
|
|
it("defaults reasoning to auto instead of inheriting another preset's fallback", () => {
|
|
const preset: LlmPreset = {
|
|
id: "deepseek",
|
|
label: "DeepSeek",
|
|
provider: "custom",
|
|
baseUrl: "https://api.deepseek.com/v1",
|
|
defaultModel: "deepseek-chat",
|
|
apiMode: "chat_completions",
|
|
}
|
|
|
|
const resolved = resolveConfig(preset, undefined, fallbackConfig())
|
|
|
|
expect(resolved.reasoning).toEqual({ mode: "auto" })
|
|
})
|
|
|
|
it("keeps an explicit provider-level reasoning override", () => {
|
|
const preset: LlmPreset = {
|
|
id: "qwen",
|
|
label: "Qwen",
|
|
provider: "custom",
|
|
baseUrl: "http://localhost:8000/v1",
|
|
defaultModel: "Qwen3.5-122B",
|
|
apiMode: "chat_completions",
|
|
}
|
|
|
|
const resolved = resolveConfig(
|
|
preset,
|
|
{ reasoning: { mode: "off" } },
|
|
fallbackConfig(),
|
|
)
|
|
|
|
expect(resolved.reasoning).toEqual({ mode: "off" })
|
|
})
|
|
|
|
it("preserves an explicit non-streaming provider preference", () => {
|
|
const preset: LlmPreset = {
|
|
id: "openai",
|
|
label: "OpenAI",
|
|
provider: "openai",
|
|
defaultModel: "gpt-5",
|
|
}
|
|
|
|
expect(resolveConfig(
|
|
preset,
|
|
{ streamingEnabled: false },
|
|
fallbackConfig(),
|
|
).streamingEnabled).toBe(false)
|
|
expect(resolveConfig(
|
|
preset,
|
|
undefined,
|
|
fallbackConfig({ streamingEnabled: false }),
|
|
).streamingEnabled)
|
|
.toBeUndefined()
|
|
})
|
|
|
|
it("carries Azure API version and model family overrides", () => {
|
|
const preset: LlmPreset = {
|
|
id: "azure",
|
|
label: "Azure OpenAI",
|
|
provider: "azure",
|
|
baseUrl: "https://resource.openai.azure.com",
|
|
defaultModel: "wiki-main",
|
|
azureApiVersion: "2024-10-21",
|
|
}
|
|
|
|
const resolved = resolveConfig(
|
|
preset,
|
|
{ azureApiVersion: "2025-01-01-preview", azureModelFamily: "gpt5" },
|
|
fallbackConfig(),
|
|
)
|
|
|
|
expect(resolved.azureApiVersion).toBe("2025-01-01-preview")
|
|
expect(resolved.azureModelFamily).toBe("gpt5")
|
|
})
|
|
|
|
it("carries local CLI isolation for Claude Code and Codex CLI presets", () => {
|
|
const preset: LlmPreset = {
|
|
id: "codex-cli",
|
|
label: "Codex CLI",
|
|
provider: "codex-cli",
|
|
defaultModel: "gpt-5",
|
|
}
|
|
|
|
const resolved = resolveConfig(
|
|
preset,
|
|
{ localCliIsolation: true },
|
|
fallbackConfig(),
|
|
)
|
|
|
|
expect(resolved.localCliIsolation).toBe(true)
|
|
})
|
|
|
|
it("carries Codex CLI timeout only for the Codex CLI preset", () => {
|
|
const codexPreset: LlmPreset = {
|
|
id: "codex-cli",
|
|
label: "Codex CLI",
|
|
provider: "codex-cli",
|
|
defaultModel: "gpt-5",
|
|
}
|
|
const claudePreset: LlmPreset = {
|
|
id: "claude-code-cli",
|
|
label: "Claude Code CLI",
|
|
provider: "claude-code",
|
|
defaultModel: "sonnet",
|
|
}
|
|
|
|
expect(resolveConfig(
|
|
codexPreset,
|
|
{ codexCliTimeoutMinutes: 9999 },
|
|
fallbackConfig(),
|
|
).codexCliTimeoutMinutes).toBe(240)
|
|
expect(resolveConfig(
|
|
claudePreset,
|
|
{ codexCliTimeoutMinutes: 45 },
|
|
fallbackConfig(),
|
|
).codexCliTimeoutMinutes).toBeUndefined()
|
|
})
|
|
|
|
it("does not apply local CLI isolation to hosted providers", () => {
|
|
const preset: LlmPreset = {
|
|
id: "openai",
|
|
label: "OpenAI",
|
|
provider: "openai",
|
|
defaultModel: "gpt-5",
|
|
}
|
|
|
|
const resolved = resolveConfig(
|
|
preset,
|
|
{ localCliIsolation: true },
|
|
fallbackConfig({ localCliIsolation: true }),
|
|
)
|
|
|
|
expect(resolved.localCliIsolation).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("disabledLlmConfig", () => {
|
|
it("turns a keyless previous provider into an unusable LLM config", () => {
|
|
const cleared = disabledLlmConfig(fallbackConfig({
|
|
provider: "claude-code",
|
|
apiKey: "",
|
|
model: "sonnet",
|
|
}))
|
|
|
|
expect(cleared.provider).toBe("openai")
|
|
expect(cleared.apiKey).toBe("")
|
|
expect(hasUsableLlm(cleared)).toBe(false)
|
|
})
|
|
|
|
it("does not erase provider-specific saved details from the fallback object shape", () => {
|
|
const cleared = disabledLlmConfig(fallbackConfig({
|
|
provider: "codex-cli",
|
|
model: "gpt-5",
|
|
maxContextSize: 123456,
|
|
customEndpoint: "http://local.test/v1",
|
|
}))
|
|
|
|
expect(cleared.model).toBe("gpt-5")
|
|
expect(cleared.maxContextSize).toBe(123456)
|
|
expect(cleared.customEndpoint).toBe("http://local.test/v1")
|
|
expect(cleared.provider).toBe("openai")
|
|
expect(hasUsableLlm(cleared)).toBe(false)
|
|
})
|
|
})
|