Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/acp-model-catalog-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Return an empty ACP model catalog when `config.models` is not a model map.
23 changes: 15 additions & 8 deletions packages/acp-adapter/src/model-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,26 +69,33 @@ export function deriveAlwaysThinking(alias: ModelAlias): boolean {

/**
* Project `harness.getConfig().models` into a flat catalog. Returns an
* empty array when the harness has no models configured, when
* `getConfig` is missing on the harness (partial test stubs), or when
* `getConfig` throws — letting the caller decide how to surface a
* degenerate config without forcing every test stub to provide every
* field.
* empty array when the harness has no models configured, when the
* `models` field is malformed, when `getConfig` is missing on the
* harness (partial test stubs), or when `getConfig` throws — letting
* the caller decide how to surface a degenerate config without forcing
* every test stub to provide every field.
*/
export async function listModelsFromHarness(
harness: KimiHarness,
): Promise<readonly AcpModelEntry[]> {
if (typeof harness.getConfig !== 'function') return [];
let models: Record<string, ModelAlias> | undefined;
let models: unknown;
try {
const config = await harness.getConfig();
models = config.models;
} catch {
return [];
}
if (models === undefined) return [];
if (
models === undefined ||
models === null ||
typeof models !== 'object' ||
Array.isArray(models)
) {
return [];
}
const out: AcpModelEntry[] = [];
for (const [id, alias] of Object.entries(models)) {
for (const [id, alias] of Object.entries(models as Record<string, ModelAlias>)) {
out.push({
id,
name: alias.displayName ?? alias.model ?? id,
Expand Down
38 changes: 38 additions & 0 deletions packages/acp-adapter/test/model-catalog-list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest';

import type { KimiHarness, ModelAlias } from '@moonshot-ai/kimi-code-sdk';

import { listModelsFromHarness } from '../src/model-catalog';

function harnessWithModels(models: unknown): KimiHarness {
return {
getConfig: async () => ({ models }),
} as unknown as KimiHarness;
}

describe('listModelsFromHarness', () => {
it('projects configured model aliases into ACP model entries', async () => {
const models: Record<string, ModelAlias> = {
coder: {
model: 'kimi-code',
displayName: 'Kimi Code',
capabilities: ['thinking'],
} as ModelAlias,
};

await expect(listModelsFromHarness(harnessWithModels(models))).resolves.toEqual([
{
id: 'coder',
name: 'Kimi Code',
thinkingSupported: true,
alwaysThinking: false,
},
]);
});

it('returns an empty catalog for malformed models values', async () => {
await expect(listModelsFromHarness(harnessWithModels(null))).resolves.toEqual([]);
await expect(listModelsFromHarness(harnessWithModels('bad-models'))).resolves.toEqual([]);
await expect(listModelsFromHarness(harnessWithModels(['coder']))).resolves.toEqual([]);
});
});