Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/commands/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ async function runExplain(args: string[]): Promise<void> {
DEEPSEEK_API_KEY: !!process.env.DEEPSEEK_API_KEY,
GROQ_API_KEY: !!process.env.GROQ_API_KEY,
TOGETHER_API_KEY: !!process.env.TOGETHER_API_KEY,
MOONSHOT_API_KEY: !!process.env.MOONSHOT_API_KEY,
};

// Parallel probes for local providers (1s timeout each)
Expand Down
2 changes: 2 additions & 0 deletions src/core/ai/recipes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { dashscope } from './dashscope.ts';
import { zhipu } from './zhipu.ts';
import { azureOpenAI } from './azure-openai.ts';
import { zeroentropyai } from './zeroentropyai.ts';
import { kimi } from './kimi.ts';

const ALL: Recipe[] = [
openai,
Expand All @@ -38,6 +39,7 @@ const ALL: Recipe[] = [
zhipu,
azureOpenAI,
zeroentropyai,
kimi,
];

/** Map from `provider:id` key to recipe. */
Expand Down
37 changes: 37 additions & 0 deletions src/core/ai/recipes/kimi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Recipe } from '../types.ts';

/**
* Kimi / Moonshot AI. OpenAI-compatible chat completions endpoint.
*
* Docs: https://platform.moonshot.ai/docs/overview
* API base: https://api.moonshot.ai/v1
*/
export const kimi: Recipe = {
id: 'kimi',
name: 'Kimi (Moonshot AI)',
tier: 'openai-compat',
implementation: 'openai-compatible',
base_url_default: 'https://api.moonshot.ai/v1',
auth_env: {
required: ['MOONSHOT_API_KEY'],
setup_url: 'https://platform.moonshot.ai/',
},
touchpoints: {
expansion: {
models: ['kimi-k2.6', 'kimi-k2.5', 'kimi-k2-turbo-preview', 'kimi-k2-thinking', 'kimi-k2-thinking-turbo'],
cost_per_1m_tokens_usd: undefined,
price_last_verified: '2026-05-20',
},
chat: {
models: ['kimi-k2.6', 'kimi-k2.5', 'kimi-k2-turbo-preview', 'kimi-k2-thinking', 'kimi-k2-thinking-turbo'],
supports_tools: true,
supports_subagent_loop: false,
supports_prompt_cache: false,
max_context_tokens: 256000,
cost_per_1m_input_usd: undefined,
cost_per_1m_output_usd: undefined,
price_last_verified: '2026-05-20',
},
},
setup_hint: 'Get an API key at https://platform.moonshot.ai/, then `export MOONSHOT_API_KEY=...`',
};
58 changes: 58 additions & 0 deletions test/ai/recipe-kimi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Kimi / Moonshot AI recipe smoke.
*
* Coverage:
* - Recipe registered with expected OpenAI-compatible shape
* - Chat + expansion touchpoints expose current Kimi model IDs
* - default auth: MOONSHOT_API_KEY -> Authorization Bearer
*/

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 { AIConfigError } from '../../src/core/ai/errors.ts';

describe('recipe: kimi', () => {
test('registered with expected provider shape', () => {
const r = getRecipe('kimi');
expect(r).toBeDefined();
expect(r!.id).toBe('kimi');
expect(r!.name).toBe('Kimi (Moonshot AI)');
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('expansion touchpoint declares Kimi models', () => {
const r = getRecipe('kimi')!;
expect(r.touchpoints.expansion).toBeDefined();
expect(r.touchpoints.expansion!.models[0]).toBe('kimi-k2.6');
expect(r.touchpoints.expansion!.models).toContain('kimi-k2.5');
expect(r.touchpoints.expansion!.models).toContain('kimi-k2-turbo-preview');
expect(r.touchpoints.expansion!.models).toContain('kimi-k2-thinking');
});

test('chat touchpoint declares Kimi models and conservative capabilities', () => {
const r = getRecipe('kimi')!;
expect(r.touchpoints.chat).toBeDefined();
expect(r.touchpoints.chat!.models[0]).toBe('kimi-k2.6');
expect(r.touchpoints.chat!.models).toContain('kimi-k2.5');
expect(r.touchpoints.chat!.supports_tools).toBe(true);
expect(r.touchpoints.chat!.supports_subagent_loop).toBe(false);
expect(r.touchpoints.chat!.supports_prompt_cache).toBe(false);
expect(r.touchpoints.chat!.max_context_tokens).toBe(256000);
});

test('default auth: MOONSHOT_API_KEY set -> Bearer token', () => {
const r = getRecipe('kimi')!;
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('kimi')!;
expect(() => defaultResolveAuth(r, {}, 'chat')).toThrow(AIConfigError);
});
});