mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-23 23:56:41 +00:00
* feat(managed-kimi-code): route anthropic protocol via beta api - kosong: add betaApi option to use client.beta.messages.create - agent-core: thread alias betaApi into the anthropic provider config - oauth: route managed models on the anthropic protocol through the beta Messages API * feat(providers): add KIMI_CODE_CUSTOM_HEADERS support - Add KIMI_CODE_CUSTOM_HEADERS env var for custom outbound LLM headers - Send User-Agent to non-Kimi providers - Forward Kimi identity headers to model catalog fetches - Support defaultHeaders in Google GenAI provider * feat(agent-core): add protocol attrs to turn and api error telemetry - Add type/protocol/alias to api_error for per-protocol error attribution - Add turn_ended event with reason/duration/mode/type/protocol - Add type/protocol to turn_interrupted * chore(oauth): remove hardcoded internal dev endpoint from shared OAuth base URLs --------- Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai>
186 lines
5.3 KiB
TypeScript
186 lines
5.3 KiB
TypeScript
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
|
|
import type { KimiConfig } from '@moonshot-ai/agent-core';
|
|
import { createKimiDefaultHeaders, KIMI_CODE_PLATFORM } from '@moonshot-ai/kimi-code-oauth';
|
|
|
|
import { ProviderManager } from '../../agent-core/src/session/provider-manager';
|
|
import { SDKRpcClient } from '#/index';
|
|
import { TEST_IDENTITY } from './test-identity';
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
function resolveRuntimeProvider(options: {
|
|
readonly config: KimiConfig;
|
|
readonly model?: string;
|
|
readonly kimiRequestHeaders?: Record<string, string>;
|
|
}) {
|
|
const manager = new ProviderManager({
|
|
config: options.config,
|
|
kimiRequestHeaders: options.kimiRequestHeaders,
|
|
});
|
|
const model = options.model ?? options.config.defaultModel;
|
|
if (model === undefined) {
|
|
throw new Error('No model selected');
|
|
}
|
|
return manager.resolveProviderConfig(model);
|
|
}
|
|
|
|
async function makeTempDir(): Promise<string> {
|
|
const dir = await mkdtemp(join(tmpdir(), 'kimi-sdk-provider-identity-'));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
for (const dir of tempDirs.splice(0)) {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe('runtime provider identity headers', () => {
|
|
it('preserves the host user agent suffix in SDK RPC headers', async () => {
|
|
const homeDir = await makeTempDir();
|
|
const client = new SDKRpcClient({
|
|
homeDir,
|
|
identity: {
|
|
...TEST_IDENTITY,
|
|
userAgentSuffix: 'web-runtime',
|
|
},
|
|
});
|
|
const core = client.core as unknown as {
|
|
readonly kimiRequestHeaders?: Record<string, string>;
|
|
};
|
|
|
|
try {
|
|
expect(core.kimiRequestHeaders).toMatchObject({
|
|
'User-Agent': 'kimi-code-cli/0.0.0-test (web-runtime)',
|
|
'X-Msh-Version': '0.0.0-test',
|
|
});
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
});
|
|
|
|
it('adds kimi-code-cli User-Agent and complete X-Msh headers to the default Kimi provider', async () => {
|
|
const homeDir = await makeTempDir();
|
|
const kimiRequestHeaders = createKimiDefaultHeaders({ homeDir, ...TEST_IDENTITY });
|
|
const resolved = resolveRuntimeProvider({
|
|
config: {
|
|
defaultModel: 'kimi-model',
|
|
providers: {
|
|
kimi: {
|
|
type: 'kimi',
|
|
apiKey: 'test-key',
|
|
},
|
|
},
|
|
models: {
|
|
'kimi-model': {
|
|
provider: 'kimi',
|
|
model: 'kimi-model',
|
|
maxContextSize: 1000,
|
|
},
|
|
},
|
|
},
|
|
kimiRequestHeaders,
|
|
});
|
|
|
|
expect(resolved.provider).toMatchObject({
|
|
type: 'kimi',
|
|
defaultHeaders: expect.objectContaining({
|
|
'User-Agent': 'kimi-code-cli/0.0.0-test',
|
|
'X-Msh-Platform': KIMI_CODE_PLATFORM,
|
|
'X-Msh-Version': '0.0.0-test',
|
|
'X-Msh-Device-Name': expect.any(String),
|
|
'X-Msh-Device-Model': expect.any(String),
|
|
'X-Msh-Os-Version': expect.any(String),
|
|
'X-Msh-Device-Id': expect.stringMatching(/^[0-9a-f-]+$/),
|
|
}),
|
|
});
|
|
});
|
|
|
|
it('lets Kimi provider customHeaders override default identity headers', async () => {
|
|
const homeDir = await makeTempDir();
|
|
const kimiRequestHeaders = createKimiDefaultHeaders({ homeDir, ...TEST_IDENTITY });
|
|
const config: KimiConfig = {
|
|
providers: {
|
|
kimi: {
|
|
type: 'kimi',
|
|
apiKey: 'test-key',
|
|
customHeaders: {
|
|
'User-Agent': 'Custom/1',
|
|
'X-Msh-Version': 'override-version',
|
|
},
|
|
},
|
|
},
|
|
defaultProvider: 'kimi',
|
|
defaultModel: 'kimi-model',
|
|
models: {
|
|
'kimi-model': {
|
|
provider: 'kimi',
|
|
model: 'kimi-model',
|
|
maxContextSize: 1000,
|
|
},
|
|
},
|
|
};
|
|
|
|
const resolved = resolveRuntimeProvider({
|
|
config,
|
|
kimiRequestHeaders,
|
|
});
|
|
|
|
expect(resolved.provider).toMatchObject({
|
|
type: 'kimi',
|
|
defaultHeaders: expect.objectContaining({
|
|
'User-Agent': 'Custom/1',
|
|
'X-Msh-Version': 'override-version',
|
|
'X-Msh-Platform': KIMI_CODE_PLATFORM,
|
|
}),
|
|
});
|
|
});
|
|
|
|
it('applies only the User-Agent (no device identity headers) to non-Kimi providers', async () => {
|
|
const homeDir = await makeTempDir();
|
|
const kimiRequestHeaders = createKimiDefaultHeaders({ homeDir, ...TEST_IDENTITY });
|
|
const config: KimiConfig = {
|
|
providers: {
|
|
openai: {
|
|
type: 'openai',
|
|
baseUrl: 'https://example.test/v1',
|
|
apiKey: 'sk-test',
|
|
},
|
|
},
|
|
defaultProvider: 'openai',
|
|
defaultModel: 'gpt-test',
|
|
models: {
|
|
'gpt-test': {
|
|
provider: 'openai',
|
|
model: 'gpt-test',
|
|
maxContextSize: 1000,
|
|
},
|
|
},
|
|
};
|
|
|
|
const resolved = resolveRuntimeProvider({
|
|
config,
|
|
kimiRequestHeaders,
|
|
});
|
|
|
|
expect(resolved.provider).toMatchObject({
|
|
type: 'openai',
|
|
model: 'gpt-test',
|
|
defaultHeaders: {
|
|
'User-Agent': `kimi-code-cli/${TEST_IDENTITY.version}`,
|
|
},
|
|
});
|
|
// Device identity headers (`X-Msh-*`) stay Kimi-only — must not leak to
|
|
// third-party providers.
|
|
const headers = (resolved.provider as { defaultHeaders?: Record<string, string> })
|
|
.defaultHeaders;
|
|
expect(headers).toBeDefined();
|
|
expect(headers).not.toHaveProperty('X-Msh-Platform');
|
|
});
|
|
});
|