diff --git a/src/main/database/migrations/056-kimi-providers.ts b/src/main/database/migrations/056-kimi-providers.ts new file mode 100644 index 00000000..13374112 --- /dev/null +++ b/src/main/database/migrations/056-kimi-providers.ts @@ -0,0 +1,12 @@ +export const kimiProvidersSchema = ` + insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at) + select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'kimi', 'Kimi', 'api_key', 'kimi-k2.7-code', null, 0, datetime('now') + from organizations; + + insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at) + select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'kimi-cn', 'Kimi CN', 'api_key', 'kimi-k2.7-code', null, 0, datetime('now') + from organizations; + + insert into schema_migrations(version, name, applied_at) + values (56, 'kimi_providers', datetime('now')); +`; diff --git a/src/main/database/schema.ts b/src/main/database/schema.ts index feaede16..ac9ba3b3 100644 --- a/src/main/database/schema.ts +++ b/src/main/database/schema.ts @@ -54,6 +54,7 @@ import { avatarImagesSchema } from "./migrations/052-avatar-images"; import { autonomousPauseSchema } from "./migrations/053-autonomous-pause"; import { closeStaleWakeTasksSchema } from "./migrations/054-close-stale-wake-tasks"; import { taskOriginSchema } from "./migrations/055-task-origin"; +import { kimiProvidersSchema } from "./migrations/056-kimi-providers"; interface Migration { version: number; @@ -117,6 +118,7 @@ const MIGRATIONS: Migration[] = [ { version: 53, name: "autonomous_pause", sql: autonomousPauseSchema }, { version: 54, name: "close_stale_wake_tasks", sql: closeStaleWakeTasksSchema }, { version: 55, name: "task_origin", sql: taskOriginSchema }, + { version: 56, name: "kimi_providers", sql: kimiProvidersSchema }, ]; const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version; diff --git a/src/main/services/provider-adapters.ts b/src/main/services/provider-adapters.ts index a24608fc..872668dc 100644 --- a/src/main/services/provider-adapters.ts +++ b/src/main/services/provider-adapters.ts @@ -210,6 +210,18 @@ export const providerAdapters: ProviderAdapterRegistry = { endpoint: "https://openrouter.ai/api/v1/chat/completions", healthUrl: "https://openrouter.ai/api/v1/models", }), + kimi: openAiCompatibleAdapter({ + providerKey: "kimi", + label: "Kimi", + endpoint: "https://api.moonshot.ai/v1/chat/completions", + healthUrl: "https://api.moonshot.ai/v1/models", + }), + "kimi-cn": openAiCompatibleAdapter({ + providerKey: "kimi-cn", + label: "Kimi CN", + endpoint: "https://api.moonshot.cn/v1/chat/completions", + healthUrl: "https://api.moonshot.cn/v1/models", + }), "github-copilot": browserTokenAdapter("github-copilot", PROVIDER_AUTH_TYPE.OAUTH_PKCE), "google-antigravity": browserTokenAdapter("google-antigravity", PROVIDER_AUTH_TYPE.OAUTH_PKCE), "gpt-account": browserTokenAdapter("gpt-account", PROVIDER_AUTH_TYPE.ACCOUNT_SUBSCRIPTION), diff --git a/src/main/services/provider-tool-turns/router.ts b/src/main/services/provider-tool-turns/router.ts index 426d774b..e3d1a5e3 100644 --- a/src/main/services/provider-tool-turns/router.ts +++ b/src/main/services/provider-tool-turns/router.ts @@ -23,6 +23,8 @@ const TURNS: Record = { mistral: makeChatCompletionsTurn("https://api.mistral.ai/v1/chat/completions", "Mistral"), cerebras: makeChatCompletionsTurn("https://api.cerebras.ai/v1/chat/completions", "Cerebras"), openrouter: makeChatCompletionsTurn("https://openrouter.ai/api/v1/chat/completions", "OpenRouter"), + kimi: makeChatCompletionsTurn("https://api.moonshot.ai/v1/chat/completions", "Kimi"), + "kimi-cn": makeChatCompletionsTurn("https://api.moonshot.cn/v1/chat/completions", "Kimi CN"), "gpt-account": runCodexTurn, "github-copilot": runCopilotTurn, "google-antigravity": runAntigravityTurn, diff --git a/src/shared/provider-catalog.ts b/src/shared/provider-catalog.ts index dfa6d613..649bcb18 100644 --- a/src/shared/provider-catalog.ts +++ b/src/shared/provider-catalog.ts @@ -74,6 +74,24 @@ const RAW_CATALOG: Omit[] signInUrl: null, credentialHelp: "Generate an API key at openrouter.ai → Keys.", }, + { + key: "kimi", + label: "Kimi", + authType: PROVIDER_AUTH_TYPE.API_KEY, + defaultModel: "kimi-k2.7-code", + capabilityTags: ["chat", "coding"], + signInUrl: null, + credentialHelp: "Generate an API key at platform.kimi.ai → API keys.", + }, + { + key: "kimi-cn", + label: "Kimi CN", + authType: PROVIDER_AUTH_TYPE.API_KEY, + defaultModel: "kimi-k2.7-code", + capabilityTags: ["chat", "coding"], + signInUrl: null, + credentialHelp: "Generate an API key at platform.kimi.com → API keys.", + }, { key: "github-copilot", label: "GitHub Copilot", diff --git a/src/shared/provider-models.ts b/src/shared/provider-models.ts index d4f0e264..7eeec100 100644 --- a/src/shared/provider-models.ts +++ b/src/shared/provider-models.ts @@ -3,6 +3,14 @@ export interface ProviderModelEntry { name: string; } +// Shared by the international (api.moonshot.ai) and CN (api.moonshot.cn) +// platforms, which publish the same model line-up under different endpoints. +const KIMI_MODELS: ProviderModelEntry[] = [ + { id: "kimi-k2.7-code", name: "Kimi K2.7 Code" }, + { id: "kimi-k2.7-code-highspeed", name: "Kimi K2.7 Code Highspeed" }, + { id: "kimi-k2.6", name: "Kimi K2.6" }, +]; + const ANTHROPIC_MODELS: ProviderModelEntry[] = [ { id: "claude-opus-4-7", name: "Claude Opus 4.7" }, { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, @@ -216,6 +224,8 @@ export const PROVIDER_MODELS: Record = { { id: "zai-glm-4.7", name: "Z.AI GLM-4.7" }, { id: "llama3.1-8b", name: "Llama 3.1 8B" }, ], + kimi: KIMI_MODELS, + "kimi-cn": KIMI_MODELS, openrouter: [ { id: "anthropic/claude-sonnet-4.5", name: "Anthropic: Claude Sonnet 4.5" }, { id: "anthropic/claude-opus-4.5", name: "Anthropic: Claude Opus 4.5" }, diff --git a/tests/contract/provider-contracts.test.ts b/tests/contract/provider-contracts.test.ts index dc01398d..db8797d1 100644 --- a/tests/contract/provider-contracts.test.ts +++ b/tests/contract/provider-contracts.test.ts @@ -18,6 +18,8 @@ const PROVIDER_SECRET_SAMPLES: Record = { mistral: "sk-live-provider", cerebras: "csk-live-provider", openrouter: "sk-or-live-provider", + kimi: "sk-live-provider", + "kimi-cn": "sk-live-provider", "github-copilot": "ghu_live_provider", "google-antigravity": "antigravity-session", "gpt-account": "chatgpt-session", diff --git a/tests/unit/domain-services.test.ts b/tests/unit/domain-services.test.ts index de299dca..749a791a 100644 --- a/tests/unit/domain-services.test.ts +++ b/tests/unit/domain-services.test.ts @@ -24,7 +24,7 @@ describe("domain services", () => { const connection = createConnection(); const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>; - expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55]); + expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56]); connection.close(); }); diff --git a/tests/unit/provider-tool-turns.test.ts b/tests/unit/provider-tool-turns.test.ts index 06ddc1f3..2ef9e193 100644 --- a/tests/unit/provider-tool-turns.test.ts +++ b/tests/unit/provider-tool-turns.test.ts @@ -186,7 +186,7 @@ describe("aws sigv4", () => { describe("tool-turn router", () => { test("supports every agent-capable provider (incl. cloud and custom) and rejects unknown ones", () => { - for (const key of ["openai", "anthropic", "xai", "groq", "mistral", "cerebras", "openrouter", "gpt-account", "github-copilot", "google-antigravity", "claude-pro", "custom-openai", "custom-anthropic", "azure-openai", "aws-bedrock", "vertex-ai"]) { + for (const key of ["openai", "anthropic", "xai", "groq", "mistral", "cerebras", "openrouter", "kimi", "kimi-cn", "gpt-account", "github-copilot", "google-antigravity", "claude-pro", "custom-openai", "custom-anthropic", "azure-openai", "aws-bedrock", "vertex-ai"]) { expect(supportsToolTurn(key)).toBe(true); expect(typeof selectToolTurn(key)).toBe("function"); }