From 39b005af77e1692268c32495004469e353f4ec21 Mon Sep 17 00:00:00 2001 From: Bin-Home Date: Tue, 28 Apr 2026 14:28:26 +0800 Subject: [PATCH] feat: add LM Studio as OpenAI-compatible local provider --- src/locales/en.json | 1 + src/locales/zh-CN.json | 1 + src/providers/defaults.ts | 1 + src/providers/lmstudio.ts | 4 ++++ src/providers/model-caps.ts | 1 + src/providers/registry.ts | 4 +++- src/types.ts | 2 +- tests/providers/registry.test.ts | 4 ++-- 8 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 src/providers/lmstudio.ts diff --git a/src/locales/en.json b/src/locales/en.json index dfaf117..a51986e 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -52,6 +52,7 @@ "provider.anthropic": "Anthropic", "provider.openrouter": "OpenRouter", "provider.ollama": "Ollama", + "provider.lmstudio": "LM Studio", "provider.custom": "Custom", "settings.apiKey": "API key", "settings.baseUrl": "Base URL", diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index fc85a9f..5f4226e 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -52,6 +52,7 @@ "provider.anthropic": "Anthropic", "provider.openrouter": "OpenRouter", "provider.ollama": "Ollama", + "provider.lmstudio": "LM Studio", "provider.custom": "自定义", "settings.apiKey": "API 密钥", "settings.baseUrl": "Base URL", diff --git a/src/providers/defaults.ts b/src/providers/defaults.ts index 29ab453..5b42394 100644 --- a/src/providers/defaults.ts +++ b/src/providers/defaults.ts @@ -16,6 +16,7 @@ export const PROVIDER_DEFAULTS: Record = { anthropic: { baseUrl: "https://api.anthropic.com", model: "claude-sonnet-4-6" }, openrouter: { baseUrl: "https://openrouter.ai/api/v1", model: "openai/gpt-4o-mini" }, ollama: { baseUrl: "http://localhost:11434", model: "llama3.1" }, + lmstudio: { baseUrl: "http://localhost:1234/v1", model: "local-model" }, custom: { baseUrl: "", model: "" }, }; diff --git a/src/providers/lmstudio.ts b/src/providers/lmstudio.ts new file mode 100644 index 0000000..b2492d6 --- /dev/null +++ b/src/providers/lmstudio.ts @@ -0,0 +1,4 @@ +import { createOpenAICompatible } from "./openai-compat"; +export const createLMStudio = (apiKey: string, baseUrl?: string) => + createOpenAICompatible({ id: "lmstudio", apiKey, + defaultBaseUrl: "http://localhost:1234/v1", baseUrl }); diff --git a/src/providers/model-caps.ts b/src/providers/model-caps.ts index 82c5a34..38aa530 100644 --- a/src/providers/model-caps.ts +++ b/src/providers/model-caps.ts @@ -19,6 +19,7 @@ const TABLE: Array<{ provider: ProviderId; match: RegExp; caps: ModelCaps }> = [ { provider: "minimax", match: /./, caps: { contextWindow: 245_000, supportsPromptCache: false } }, { provider: "openrouter",match: /./, caps: { contextWindow: 128_000, supportsPromptCache: false } }, { provider: "ollama", match: /./, caps: { contextWindow: 32_000, supportsPromptCache: false } }, + { provider: "lmstudio", match: /./, caps: { contextWindow: 32_000, supportsPromptCache: false } }, ]; const DEFAULT: ModelCaps = { contextWindow: 32_000, supportsPromptCache: false }; diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 0aa0954..d06004b 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -3,6 +3,7 @@ import type { ProviderId } from "../types"; import { OpenAIProvider } from "./openai"; import { AnthropicProvider } from "./anthropic"; import { OllamaProvider } from "./ollama"; +import { createLMStudio } from "./lmstudio"; import { createDeepSeek } from "./deepseek"; import { createQwen } from "./qwen"; import { createKimi } from "./kimi"; @@ -18,6 +19,7 @@ export function createProvider(id: ProviderId, cfg: ProviderConfig): LLMProvider case "openai": return new OpenAIProvider({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl }); case "anthropic": return new AnthropicProvider({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl }); case "ollama": return new OllamaProvider({ baseUrl: cfg.baseUrl || "http://localhost:11434" }); + case "lmstudio": return createLMStudio(cfg.apiKey, cfg.baseUrl); case "openrouter": return createOpenRouter(cfg.apiKey, cfg.baseUrl); case "deepseek": return createDeepSeek(cfg.apiKey, cfg.baseUrl); case "qwen": return createQwen(cfg.apiKey, cfg.baseUrl); @@ -34,5 +36,5 @@ export function createProvider(id: ProviderId, cfg: ProviderConfig): LLMProvider } export function listProviderIds(): ProviderId[] { - return ["deepseek","qwen","kimi","zhipu","zai","minimax","openai","anthropic","openrouter","ollama","custom"]; + return ["deepseek","qwen","kimi","zhipu","zai","minimax","openai","anthropic","openrouter","ollama","lmstudio","custom"]; } diff --git a/src/types.ts b/src/types.ts index c1c2513..1fef4b6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,7 @@ export type Mode = "ask" | "edit" | "scheduled"; export type ProviderId = | "deepseek" | "qwen" | "kimi" | "zhipu" | "zai" | "minimax" - | "openai" | "anthropic" | "openrouter" | "ollama" + | "openai" | "anthropic" | "openrouter" | "ollama" | "lmstudio" | "custom"; export type Locale = "auto" | "en" | "zh-CN"; diff --git a/tests/providers/registry.test.ts b/tests/providers/registry.test.ts index 1de79cc..9eec2ba 100644 --- a/tests/providers/registry.test.ts +++ b/tests/providers/registry.test.ts @@ -2,9 +2,9 @@ import { describe, it, expect } from "vitest"; import { createProvider, listProviderIds } from "../../src/providers/registry"; describe("provider registry", () => { - it("lists all eleven ids", () => { + it("lists all twelve ids", () => { expect(listProviderIds().sort()).toEqual( - ["anthropic","custom","deepseek","kimi","minimax","ollama","openai","openrouter","qwen","zai","zhipu"] + ["anthropic","custom","deepseek","kimi","lmstudio","minimax","ollama","openai","openrouter","qwen","zai","zhipu"] ); }); it("creates openai", () => {