From a8e6b1d1777c24692227cc500902bd71a4b1323f Mon Sep 17 00:00:00 2001 From: Konradopenclaw Date: Fri, 17 Jul 2026 16:31:59 -0500 Subject: [PATCH] feat(ai): add Moonshot Kimi provider recipe (#2378) --- src/core/ai/recipes/index.ts | 2 ++ src/core/ai/recipes/moonshot.ts | 42 ++++++++++++++++++++++++++ test/ai/recipe-moonshot.test.ts | 53 +++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/core/ai/recipes/moonshot.ts create mode 100644 test/ai/recipe-moonshot.test.ts diff --git a/src/core/ai/recipes/index.ts b/src/core/ai/recipes/index.ts index 383350fff..098691b46 100644 --- a/src/core/ai/recipes/index.ts +++ b/src/core/ai/recipes/index.ts @@ -23,6 +23,7 @@ import { zhipu } from './zhipu.ts'; import { azureOpenAI } from './azure-openai.ts'; import { zeroentropyai } from './zeroentropyai.ts'; import { llamaServerReranker } from './llama-server-reranker.ts'; +import { moonshot } from './moonshot.ts'; const ALL: Recipe[] = [ openai, @@ -42,6 +43,7 @@ const ALL: Recipe[] = [ zhipu, azureOpenAI, zeroentropyai, + moonshot, ]; /** Map from `provider:id` key to recipe. */ diff --git a/src/core/ai/recipes/moonshot.ts b/src/core/ai/recipes/moonshot.ts new file mode 100644 index 000000000..859588c76 --- /dev/null +++ b/src/core/ai/recipes/moonshot.ts @@ -0,0 +1,42 @@ +import type { Recipe } from '../types.ts'; + +/** + * Moonshot AI / Kimi Open Platform. Kimi exposes an OpenAI-compatible + * /v1/chat/completions API at https://api.moonshot.ai/v1. + * + * Verified against Kimi API docs and live /v1/models on 2026-06-23. + * The recipe is local-production glue until upstream GBrain carries a native + * Moonshot recipe; keep it registered in the local patch registry. + */ +export const moonshot: Recipe = { + id: 'moonshot', + name: 'Moonshot AI / Kimi', + tier: 'openai-compat', + implementation: 'openai-compatible', + base_url_default: 'https://api.moonshot.ai/v1', + auth_env: { + required: ['MOONSHOT_API_KEY'], + setup_url: 'https://platform.kimi.ai/console/api-keys', + }, + touchpoints: { + expansion: { + models: ['kimi-k2.7-code', 'kimi-k2.7-code-highspeed', 'kimi-k2.6', 'kimi-k2.5'], + // Kimi pricing varies by current promotional/account terms; do not use + // this advisory field for budget enforcement. Canonical budget pricing + // belongs in src/core/model-pricing.ts when verified for the account. + price_last_verified: '2026-06-23', + }, + chat: { + models: ['kimi-k2.7-code', 'kimi-k2.7-code-highspeed', 'kimi-k2.6', 'kimi-k2.5'], + supports_tools: true, + // Kimi tool calling is enough for ordinary chat/tool calls. GBrain's + // subagent loop remains Anthropic-pinned because upstream requires stable + // Anthropic-style tool_use_id behavior across crashes/replays. + supports_subagent_loop: false, + supports_prompt_cache: false, + max_context_tokens: 256000, + price_last_verified: '2026-06-23', + }, + }, + setup_hint: 'Get an API key at https://platform.kimi.ai/console/api-keys, then `export MOONSHOT_API_KEY=...` and use `moonshot:kimi-k2.7-code`.', +}; diff --git a/test/ai/recipe-moonshot.test.ts b/test/ai/recipe-moonshot.test.ts new file mode 100644 index 000000000..0af8844d3 --- /dev/null +++ b/test/ai/recipe-moonshot.test.ts @@ -0,0 +1,53 @@ +/** + * Moonshot/Kimi local recipe smoke. + * + * This pins the governed production exception GBrain-Local-003: GBrain can + * route configured Kimi chat/expansion IDs through Moonshot's OpenAI-compatible + * endpoint without treating `moonshot` as an unknown provider. + */ + +import { describe, expect, test } from 'bun:test'; +import { getRecipe } from '../../src/core/ai/recipes/index.ts'; +import { defaultResolveAuth } from '../../src/core/ai/gateway.ts'; +import { assertTouchpoint } from '../../src/core/ai/model-resolver.ts'; +import { AIConfigError } from '../../src/core/ai/errors.ts'; + +describe('recipe: moonshot', () => { + test('registered with expected OpenAI-compatible shape', () => { + const r = getRecipe('moonshot'); + expect(r).toBeDefined(); + expect(r!.id).toBe('moonshot'); + expect(r!.tier).toBe('openai-compat'); + expect(r!.implementation).toBe('openai-compatible'); + expect(r!.base_url_default).toBe('https://api.moonshot.ai/v1'); + expect(r!.auth_env?.required).toEqual(['MOONSHOT_API_KEY']); + }); + + test('chat and expansion touchpoints include Kimi K2.7 Code', () => { + const r = getRecipe('moonshot')!; + expect(r.touchpoints.chat).toBeDefined(); + expect(r.touchpoints.expansion).toBeDefined(); + expect(r.touchpoints.chat!.models).toContain('kimi-k2.7-code'); + expect(r.touchpoints.expansion!.models).toContain('kimi-k2.7-code'); + expect(r.touchpoints.chat!.supports_tools).toBe(true); + expect(r.touchpoints.chat!.supports_subagent_loop).toBe(false); + }); + + test('configured Kimi model is accepted for chat and expansion', () => { + const r = getRecipe('moonshot')!; + expect(() => assertTouchpoint(r, 'chat', 'kimi-k2.7-code')).not.toThrow(); + expect(() => assertTouchpoint(r, 'expansion', 'kimi-k2.7-code')).not.toThrow(); + }); + + test('default auth: MOONSHOT_API_KEY set -> Bearer token', () => { + const r = getRecipe('moonshot')!; + const auth = defaultResolveAuth(r, { MOONSHOT_API_KEY: 'fake-moonshot-key' }, 'chat'); + expect(auth.headerName).toBe('Authorization'); + expect(auth.token).toBe('Bearer fake-moonshot-key'); + }); + + test('default auth: missing MOONSHOT_API_KEY -> AIConfigError', () => { + const r = getRecipe('moonshot')!; + expect(() => defaultResolveAuth(r, {}, 'chat')).toThrow(AIConfigError); + }); +});