Download packages/kap-server/test/modelCatalog.test.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 13.5 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/kap-server/test/modelCatalog.test.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/packages/kap-server/test/modelCatalog.test.ts
-
curl -L -o modelCatalog.test.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/kap-server/test/modelCatalog.test.ts
13.5 kB
| import { mkdtemp, rm, writeFile } from 'node:fs/promises'; | |
| import { tmpdir } from 'node:os'; | |
| import { join } from 'node:path'; | |
| import { | |
| IConfigService, | |
| IModelCatalog, | |
| IOAuthService, | |
| IProviderDiscoveryService, | |
| type IModelCatalog as IModelCatalogType, | |
| type IOAuthService as IOAuthServiceType, | |
| type IProviderDiscoveryService as IProviderDiscoveryServiceType, | |
| type ModelCatalogConfig, | |
| type ScopeSeed, | |
| } from '@moonshot-ai/agent-core-v2'; | |
| import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; | |
| import { type RunningServer, startServer } from '../src/start'; | |
| import { TEST_HOST_IDENTITY } from './helpers/hostIdentity'; | |
| import { authHeaders } from './helpers/auth'; | |
| interface Envelope<T> { | |
| code: number; | |
| msg: string; | |
| data: T; | |
| request_id: string; | |
| } | |
| const CATALOG_TOML = [ | |
| 'default_model = "k2"', | |
| '', | |
| '[providers.kimi]', | |
| 'type = "kimi"', | |
| 'api_key = "sk-test"', | |
| 'base_url = "https://api.example.test/v1"', | |
| '', | |
| '[providers.openai]', | |
| 'type = "openai"', | |
| '', | |
| '[models.k2]', | |
| 'provider = "kimi"', | |
| 'model = "kimi-k2"', | |
| 'max_context_size = 131072', | |
| 'display_name = "Kimi K2"', | |
| 'capabilities = ["thinking"]', | |
| '', | |
| '[models.turbo]', | |
| 'provider = "kimi"', | |
| 'model = "kimi-turbo"', | |
| 'max_context_size = 32768', | |
| 'display_name = "Kimi Turbo"', | |
| '', | |
| '[models.gpt4o]', | |
| 'provider = "openai"', | |
| 'model = "gpt-4o"', | |
| 'max_context_size = 128000', | |
| '', | |
| ].join('\n'); | |
| describe('server-v2 /api/v1 model/provider catalog', () => { | |
| let server: RunningServer | undefined; | |
| let active: RunningServer | undefined; | |
| const alts: RunningServer[] = []; | |
| let home: string | undefined; | |
| let base: string; | |
| beforeAll(async () => { | |
| home = await mkdtemp(join(tmpdir(), 'kimi-server-v2-model-catalog-')); | |
| process.env['KIMI_CODE_MODEL_CATALOG_REFRESH_ON_START'] = '0'; | |
| process.env['KIMI_CODE_MODEL_CATALOG_REFRESH_INTERVAL_MS'] = '0'; | |
| server = await startServer({ | |
| hostIdentity: TEST_HOST_IDENTITY, | |
| host: '127.0.0.1', | |
| port: 0, | |
| homeDir: home, | |
| logLevel: 'silent', | |
| }); | |
| active = server; | |
| base = `http://127.0.0.1:${server.port}`; | |
| }); | |
| afterEach(async () => { | |
| for (const alt of alts.splice(0)) { | |
| await alt.close(); | |
| } | |
| active = server; | |
| base = `http://127.0.0.1:${(server as RunningServer).port}`; | |
| }); | |
| afterAll(async () => { | |
| if (server !== undefined) { | |
| await server.close(); | |
| server = undefined; | |
| active = undefined; | |
| } | |
| if (home !== undefined) { | |
| await rm(home, { recursive: true, force: true }); | |
| home = undefined; | |
| } | |
| delete process.env['KIMI_CODE_MODEL_CATALOG_REFRESH_ON_START']; | |
| delete process.env['KIMI_CODE_MODEL_CATALOG_REFRESH_INTERVAL_MS']; | |
| }); | |
| async function boot(toml?: string, seeds?: ScopeSeed): Promise<void> { | |
| if (toml !== undefined) { | |
| await writeFile(join(home as string, 'config.toml'), toml, 'utf-8'); | |
| } | |
| if (seeds !== undefined) { | |
| const alt = await startServer({ | |
| hostIdentity: TEST_HOST_IDENTITY, | |
| host: '127.0.0.1', | |
| port: 0, | |
| homeDir: home, | |
| logLevel: 'silent', | |
| seeds, | |
| }); | |
| alts.push(alt); | |
| active = alt; | |
| } else { | |
| await (server as RunningServer).core.accessor.get(IConfigService).reload(); | |
| active = server; | |
| } | |
| base = `http://127.0.0.1:${(active as RunningServer).port}`; | |
| } | |
| async function getJson<T>(path: string): Promise<{ status: number; body: Envelope<T> }> { | |
| const res = await fetch(`${base}${path}`, { | |
| headers: authHeaders(active as RunningServer), | |
| } as never); | |
| return { status: res.status, body: (await res.json()) as Envelope<T> }; | |
| } | |
| async function postJson<T>( | |
| path: string, | |
| body?: unknown, | |
| ): Promise<{ status: number; body: Envelope<T> }> { | |
| const res = await fetch(`${base}${path}`, { | |
| method: 'POST', | |
| headers: authHeaders( | |
| active as RunningServer, | |
| body === undefined ? {} : { 'content-type': 'application/json' }, | |
| ), | |
| body: body === undefined ? undefined : JSON.stringify(body), | |
| } as never); | |
| return { status: res.status, body: (await res.json()) as Envelope<T> }; | |
| } | |
| it('lists configured models as selectable aliases', async () => { | |
| await boot(CATALOG_TOML); | |
| const { status, body } = await getJson<{ items: unknown[] }>('/api/v1/models'); | |
| expect(status).toBe(200); | |
| expect(body.code).toBe(0); | |
| expect(body.data.items).toEqual([ | |
| { | |
| provider: 'kimi', | |
| model: 'k2', | |
| display_name: 'Kimi K2', | |
| max_context_size: 131072, | |
| capabilities: ['thinking'], | |
| }, | |
| { | |
| provider: 'kimi', | |
| model: 'turbo', | |
| display_name: 'Kimi Turbo', | |
| max_context_size: 32768, | |
| }, | |
| { | |
| provider: 'openai', | |
| model: 'gpt4o', | |
| display_name: 'gpt-4o', | |
| max_context_size: 128000, | |
| }, | |
| ]); | |
| }); | |
| it('lists models without refreshing providers', async () => { | |
| const refreshProviderModels = vi.fn(async () => ({ | |
| changed: [], | |
| unchanged: [], | |
| failed: [], | |
| })); | |
| const seeds = [ | |
| [IModelCatalog, catalogStub()], | |
| [IProviderDiscoveryService, discoveryStub(refreshProviderModels)], | |
| ] as unknown as ScopeSeed; | |
| await boot(CATALOG_TOML, seeds); | |
| const { status, body } = await getJson<{ items: unknown[] }>('/api/v1/models'); | |
| expect(status).toBe(200); | |
| expect(body.code).toBe(0); | |
| expect(body.data.items).toEqual([]); | |
| expect(refreshProviderModels).not.toHaveBeenCalled(); | |
| }); | |
| it('lists providers and returns a single provider by id', async () => { | |
| await boot(CATALOG_TOML); | |
| const list = await getJson<{ items: unknown[] }>('/api/v1/providers'); | |
| expect(list.body.code).toBe(0); | |
| expect(list.body.data.items).toEqual([ | |
| { | |
| id: 'kimi', | |
| type: 'kimi', | |
| base_url: 'https://api.example.test/v1', | |
| default_model: 'k2', | |
| has_api_key: true, | |
| status: 'connected', | |
| models: ['k2', 'turbo'], | |
| }, | |
| { | |
| id: 'openai', | |
| type: 'openai', | |
| has_api_key: false, | |
| status: 'unconfigured', | |
| models: ['gpt4o'], | |
| }, | |
| ]); | |
| const single = await getJson<unknown>('/api/v1/providers/kimi'); | |
| expect(single.body.code).toBe(0); | |
| expect(single.body.data).toEqual({ | |
| id: 'kimi', | |
| type: 'kimi', | |
| base_url: 'https://api.example.test/v1', | |
| default_model: 'k2', | |
| has_api_key: true, | |
| status: 'connected', | |
| models: ['k2', 'turbo'], | |
| api_key: 'sk-test', | |
| }); | |
| const noKey = await getJson<Record<string, unknown>>('/api/v1/providers/openai'); | |
| expect(noKey.body.code).toBe(0); | |
| expect(noKey.body.data).not.toHaveProperty('api_key'); | |
| }); | |
| it('sets the global default model and reflects it in /config', async () => { | |
| await boot(CATALOG_TOML); | |
| const { body } = await postJson<unknown>('/api/v1/models/turbo:set_default', {}); | |
| expect(body.code).toBe(0); | |
| expect(body.data).toEqual({ | |
| default_model: 'turbo', | |
| model: { | |
| provider: 'kimi', | |
| model: 'turbo', | |
| display_name: 'Kimi Turbo', | |
| max_context_size: 32768, | |
| }, | |
| }); | |
| const config = await getJson<{ default_model: string | null }>('/api/v1/config'); | |
| expect(config.body.code).toBe(0); | |
| expect(config.body.data.default_model).toBe('turbo'); | |
| }); | |
| it('maps unknown provider and model ids to catalog not-found codes', async () => { | |
| await boot(CATALOG_TOML); | |
| const provider = await getJson<unknown>('/api/v1/providers/missing'); | |
| expect(provider.body.code).toBe(40412); | |
| const model = await postJson<unknown>('/api/v1/models/missing:set_default', {}); | |
| expect(model.body.code).toBe(40413); | |
| }); | |
| it('returns an empty refresh result through the catalog route', async () => { | |
| await boot(CATALOG_TOML); | |
| const { status, body } = await postJson<{ | |
| changed: unknown[]; | |
| unchanged: unknown[]; | |
| failed: unknown[]; | |
| }>('/api/v1/providers:refresh_oauth', {}); | |
| expect(status).toBe(200); | |
| expect(body.code).toBe(0); | |
| expect(body.data).toEqual({ changed: [], unchanged: [], failed: [] }); | |
| }); | |
| it('returns an empty refresh result through the providers:refresh route', async () => { | |
| await boot(CATALOG_TOML); | |
| const { status, body } = await postJson<{ | |
| changed: unknown[]; | |
| unchanged: unknown[]; | |
| failed: unknown[]; | |
| }>('/api/v1/providers:refresh', {}); | |
| expect(status).toBe(200); | |
| expect(body.code).toBe(0); | |
| expect(body.data).toEqual({ changed: [], unchanged: [], failed: [] }); | |
| }); | |
| function catalogStub(): IModelCatalogType { | |
| return { | |
| _serviceBrand: undefined, | |
| get: () => { | |
| throw new Error('unused'); | |
| }, | |
| getRequester: () => { | |
| throw new Error('unused'); | |
| }, | |
| generate: () => { | |
| throw new Error('unused'); | |
| }, | |
| ping: async () => { | |
| throw new Error('unused'); | |
| }, | |
| findByName: () => [], | |
| listModels: async () => [], | |
| listProviders: async () => [], | |
| getProvider: async () => { | |
| throw new Error('unused'); | |
| }, | |
| setDefaultModel: async () => { | |
| throw new Error('unused'); | |
| }, | |
| }; | |
| } | |
| function discoveryStub( | |
| refreshProviderModels: IProviderDiscoveryServiceType['refreshProviderModels'], | |
| ): IProviderDiscoveryServiceType { | |
| return { _serviceBrand: undefined, refreshProviderModels }; | |
| } | |
| function oauthStub( | |
| refreshOAuthProviderModels: IOAuthServiceType['refreshOAuthProviderModels'], | |
| ): IOAuthServiceType { | |
| return { | |
| _serviceBrand: undefined, | |
| startLogin: async () => { | |
| throw new Error('unused'); | |
| }, | |
| getFlow: () => undefined, | |
| cancelLogin: async () => { | |
| throw new Error('unused'); | |
| }, | |
| logout: async () => { | |
| throw new Error('unused'); | |
| }, | |
| status: async () => ({ loggedIn: false }), | |
| refreshOAuthProviderModels, | |
| getManagedUsage: async () => ({ kind: 'error' as const, message: 'unused' }), | |
| getManagedUserInfo: async () => ({ kind: 'error' as const, message: 'unused' }), | |
| resolveTokenProvider: () => undefined, | |
| getCachedAccessToken: async () => undefined, | |
| getRegion: () => 'mainland-cn', | |
| }; | |
| } | |
| it('refreshes OAuth provider models through POST /providers:refresh_oauth', async () => { | |
| const refreshOAuthProviderModels = vi.fn(async () => ({ | |
| changed: [ | |
| { provider_id: 'managed:kimi-code', provider_name: 'Kimi Code', added: 1, removed: 0 }, | |
| ], | |
| unchanged: [], | |
| failed: [], | |
| })); | |
| const seeds = [[IOAuthService, oauthStub(refreshOAuthProviderModels)]] as unknown as ScopeSeed; | |
| await boot(CATALOG_TOML, seeds); | |
| const { status, body } = await postJson<{ | |
| changed: unknown[]; | |
| unchanged: unknown[]; | |
| failed: unknown[]; | |
| }>('/api/v1/providers:refresh_oauth', {}); | |
| expect(status).toBe(200); | |
| expect(body.code).toBe(0); | |
| expect(body.data).toEqual({ | |
| changed: [ | |
| { provider_id: 'managed:kimi-code', provider_name: 'Kimi Code', added: 1, removed: 0 }, | |
| ], | |
| unchanged: [], | |
| failed: [], | |
| }); | |
| expect(refreshOAuthProviderModels).toHaveBeenCalledTimes(1); | |
| }); | |
| it('refreshes all provider models through POST /providers:refresh', async () => { | |
| const refreshProviderModels = vi.fn(async () => ({ | |
| changed: [ | |
| { provider_id: 'managed:kimi-code', provider_name: 'Kimi Code', added: 2, removed: 1 }, | |
| ], | |
| unchanged: ['moonshot-cn'], | |
| failed: [], | |
| })); | |
| const seeds = [[IProviderDiscoveryService, discoveryStub(refreshProviderModels)]] as unknown as ScopeSeed; | |
| await boot(CATALOG_TOML, seeds); | |
| const { status, body } = await postJson('/api/v1/providers:refresh', {}); | |
| expect(status).toBe(200); | |
| expect(body.code).toBe(0); | |
| expect(refreshProviderModels).toHaveBeenCalledWith({ scope: 'all' }); | |
| }); | |
| it('refreshes a single provider through POST /providers/{id}:refresh', async () => { | |
| const refreshProviderModels = vi.fn(async () => ({ | |
| changed: [], | |
| unchanged: [], | |
| failed: [], | |
| })); | |
| const seeds = [[IProviderDiscoveryService, discoveryStub(refreshProviderModels)]] as unknown as ScopeSeed; | |
| await boot(CATALOG_TOML, seeds); | |
| const { status, body } = await postJson('/api/v1/providers/managed%3Akimi-code:refresh', {}); | |
| expect(status).toBe(200); | |
| expect(body.code).toBe(0); | |
| expect(refreshProviderModels).toHaveBeenCalledWith({ providerId: 'managed:kimi-code' }); | |
| }); | |
| it('rejects unsupported provider actions with 40001', async () => { | |
| const refreshProviderModels = vi.fn(async () => ({ | |
| changed: [], | |
| unchanged: [], | |
| failed: [], | |
| })); | |
| const seeds = [[IProviderDiscoveryService, discoveryStub(refreshProviderModels)]] as unknown as ScopeSeed; | |
| await boot(CATALOG_TOML, seeds); | |
| const { body } = await postJson('/api/v1/providers/foo:bogus', {}); | |
| expect(body.code).toBe(40001); | |
| expect(refreshProviderModels).not.toHaveBeenCalled(); | |
| }); | |
| it('loads the [model_catalog] config section from TOML', async () => { | |
| await boot( | |
| ['[model_catalog]', 'refresh_interval_ms = 1000', 'refresh_on_start = false', ''].join('\n'), | |
| ); | |
| const cfg = server!.core.accessor.get(IConfigService); | |
| await cfg.ready; | |
| const value = cfg.get<ModelCatalogConfig | undefined>('modelCatalog'); | |
| expect(value).toEqual({ refreshIntervalMs: 1000, refreshOnStart: false }); | |
| }); | |
| }); | |