Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/kimi-code/src/cli/experimental-v2.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Agent engine routing gates for the CLI surfaces.
*
* `kimi -p`, the interactive TUI, and `kimi doctor` use the native
* `kimi -p` and the interactive TUI use the native
* agent-core-v2 path by default. A truthy `KIMI_CODE_LEGACY_FLAG` selects the
* legacy agent-core-backed path instead. `KIMI_CODE_EXPERIMENTAL_FLAG` remains
* the master switch for experimental features within either engine; it does
Expand Down
27 changes: 4 additions & 23 deletions apps/kimi-code/src/cli/sub/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { isAbsolute, resolve } from 'node:path';

import {
createKimiConfigRpc,
type KimiConfigRpc,
type KimiConfigValidationIssue,
} from '@moonshot-ai/kimi-code-sdk';
import { resolveConfigPath, type KimiConfigValidationIssue } from '@moonshot-ai/kimi-code-sdk';
import type { Command } from 'commander';
import { z } from 'zod';

import { isKimiV2Enabled } from '#/cli/experimental-v2';
import { getTuiConfigPath, parseTuiConfig } from '#/tui/config';

interface WritableLike {
Expand All @@ -26,7 +21,6 @@ export interface DoctorDeps {
readonly stdout: WritableLike;
readonly stderr: WritableLike;
readonly exit: (code: number) => never;
readonly configRpc?: KimiConfigRpc;
readonly fileExists?: (path: string) => boolean;
readonly readTextFile?: (path: string) => Promise<string>;
readonly validateConfigToml?: (text: string, path: string) => MaybePromise<string | void>;
Expand Down Expand Up @@ -115,15 +109,9 @@ async function runDoctorCommand(
}

function resolveDeps(deps: Partial<DoctorDeps> | DoctorDeps | undefined): ResolvedDoctorDeps {
let configRpc = deps?.configRpc;
const getConfigRpc = (): KimiConfigRpc => {
configRpc ??= createKimiConfigRpc();
return configRpc;
};

return {
cwd: deps?.cwd ?? (() => process.cwd()),
defaultConfigPath: deps?.defaultConfigPath ?? (() => getConfigRpc().resolveConfigPath()),
defaultConfigPath: deps?.defaultConfigPath ?? (() => resolveConfigPath({})),
defaultTuiConfigPath: deps?.defaultTuiConfigPath ?? getTuiConfigPath,
stdout: deps?.stdout ?? process.stdout,
stderr: deps?.stderr ?? process.stderr,
Expand All @@ -133,15 +121,8 @@ function resolveDeps(deps: Partial<DoctorDeps> | DoctorDeps | undefined): Resolv
validateConfigToml:
deps?.validateConfigToml ??
(async (text, filePath) => {
if (isKimiV2Enabled()) {
// Default v2 route (same engine gate as `kimi -p`): validate with
// the agent-core-v2 section registry instead of the legacy schema.
// Loaded lazily so the v2 module graph stays off the legacy path.
const { validateConfigTomlV2 } = await import('../v2/validate-config');
return validateConfigTomlV2(text, filePath);
}
await getConfigRpc().validateConfigToml({ text, filePath });
return undefined;
const { validateConfigTomlV2 } = await import('../v2/validate-config');
return validateConfigTomlV2(text, filePath);
}),
};
}
Expand Down
60 changes: 45 additions & 15 deletions apps/kimi-code/test/cli/doctor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
let dir: string;

beforeEach(async () => {
vi.stubEnv('KIMI_CODE_LEGACY_FLAG', '');
dir = join(tmpdir(), `kimi-doctor-${Date.now()}-${Math.random().toString(36).slice(2)}`);
await mkdir(dir, { recursive: true });
});
Expand Down Expand Up @@ -103,25 +102,56 @@ describe('kimi doctor', () => {
expect(out).toContain('built-in defaults will apply');
});

it('uses the legacy validator when legacy wins over the experimental flag', async () => {
it('keeps v2 validation when the legacy flag is set', async () => {
const configPath = join(dir, 'config.toml');
const text = '[providers.kimi]\ntype = "kimi"\n';
await writeFile(configPath, text, 'utf-8');
vi.stubEnv('KIMI_CODE_LEGACY_FLAG', '1');
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '1');
const validateConfigToml = vi.fn(async () => undefined);
const { deps } = makeDeps();
await writeFile(
configPath,
`
default_model = "kimi"

const code = await handleDoctor(
{
...deps,
configRpc: { validateConfigToml } as unknown as NonNullable<DoctorDeps['configRpc']>,
},
{ target: 'config' },
[providers.kimi]
type = "kimi"
base_url = "https://api.example.com/v1"
api_key = "YOUR_API_KEY"

[models.kimi]
provider = "kimi"
model = "kimi"
protocol = "openai"
max_context_size = 262144
`,
'utf-8',
);
vi.stubEnv('KIMI_CODE_LEGACY_FLAG', '1');
const { deps, stdout, stderr } = makeDeps();

const code = await handleDoctor(deps, { target: 'config' });

expect(code).toBe(0);
expect(validateConfigToml).toHaveBeenCalledWith({ text, filePath: configPath });
expect(stderr.join('')).toBe('');
expect(stdout.join('')).toContain(`OK config.toml ${configPath}`);
});

it('reports schema-invalid sections with the v2 engine when the legacy flag is set', async () => {
await writeFile(
join(dir, 'config.toml'),
`
[models.kimi]
provider = "kimi"
model = "kimi"
max_context_size = "large"
`,
'utf-8',
);
vi.stubEnv('KIMI_CODE_LEGACY_FLAG', '1');
const { deps, stderr } = makeDeps();

const code = await handleDoctor(deps, { target: 'config' });

expect(code).toBe(1);
const err = stderr.join('');
expect(err).toContain('Validation issues:');
expect(err).toContain('models.kimi.max_context_size:');
});

it('checks only config.toml when the config target is selected', async () => {
Expand Down
Loading