mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
feat(cli): emit version first in experimental v2 prompt mode
- write `kimi version <version>` to stderr first in text mode - emit a `system.version` meta line first in stream-json mode - gate behind isKimiV2Enabled() so the default v1 path is unchanged
This commit is contained in:
parent
4658f235f5
commit
2ebaf7221e
2 changed files with 109 additions and 2 deletions
|
|
@ -101,6 +101,14 @@ export async function runPrompt(
|
|||
const stdout = io.stdout ?? process.stdout;
|
||||
const stderr = io.stderr ?? process.stderr;
|
||||
const promptProcess = io.process ?? process;
|
||||
const outputFormat = opts.outputFormat ?? 'text';
|
||||
// The experimental agent-core-v2 engine (selected by KIMI_CODE_EXPERIMENTAL_FLAG)
|
||||
// announces the running version as the very first output so headless consumers
|
||||
// can identify which build produced the stream, in both `text` and `stream-json`
|
||||
// formats. The default v1 path keeps its established output unchanged.
|
||||
if (isKimiV2Enabled()) {
|
||||
writeExperimentalVersion(version, outputFormat, stdout, stderr);
|
||||
}
|
||||
const workDir = process.cwd();
|
||||
const telemetryBootstrap = createCliTelemetryBootstrap();
|
||||
const telemetryClient: TelemetryClient = {
|
||||
|
|
@ -183,7 +191,6 @@ export async function runPrompt(
|
|||
});
|
||||
setCrashPhase('runtime');
|
||||
|
||||
const outputFormat = opts.outputFormat ?? 'text';
|
||||
// Headless goal mode: `kimi -p "/goal <objective>"`. The goal driver keeps
|
||||
// the turn-run alive across continuation turns, so the normal prompt-turn
|
||||
// waiter blocks until the goal is terminal; we then emit a summary and set a
|
||||
|
|
@ -663,6 +670,30 @@ interface PromptJsonResumeMetaMessage {
|
|||
content: string;
|
||||
}
|
||||
|
||||
interface PromptJsonVersionMetaMessage {
|
||||
role: 'meta';
|
||||
type: 'system.version';
|
||||
version: string;
|
||||
}
|
||||
|
||||
function writeExperimentalVersion(
|
||||
version: string,
|
||||
outputFormat: PromptOutputFormat,
|
||||
stdout: PromptOutput,
|
||||
stderr: PromptOutput,
|
||||
): void {
|
||||
if (outputFormat === 'stream-json') {
|
||||
const message: PromptJsonVersionMetaMessage = {
|
||||
role: 'meta',
|
||||
type: 'system.version',
|
||||
version,
|
||||
};
|
||||
stdout.write(`${JSON.stringify(message)}\n`);
|
||||
return;
|
||||
}
|
||||
stderr.write(`kimi version ${version}\n`);
|
||||
}
|
||||
|
||||
function writeResumeHint(
|
||||
sessionId: string,
|
||||
outputFormat: PromptOutputFormat,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { createKimiDeviceId as createKimiDeviceIdFn } from '@moonshot-ai/kimi-code-oauth';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { runPrompt } from '#/cli/run-prompt';
|
||||
import { PROMPT_CLEANUP_TIMEOUT_MS } from '#/constant/app';
|
||||
|
|
@ -64,6 +64,19 @@ const mocks = vi.hoisted(() => {
|
|||
harnessClose: vi.fn(),
|
||||
harnessTrack: vi.fn(),
|
||||
harnessGetCachedAccessToken: vi.fn(),
|
||||
createV2Harness: vi.fn(async () => ({
|
||||
homeDir: '/tmp/kimi-code-test-home',
|
||||
auth: { getCachedAccessToken: mocks.harnessGetCachedAccessToken },
|
||||
ensureConfigFile: mocks.harnessEnsureConfigFile,
|
||||
getConfig: mocks.harnessGetConfig,
|
||||
getConfigDiagnostics: mocks.harnessGetConfigDiagnostics,
|
||||
getExperimentalFeatures: mocks.harnessGetExperimentalFeatures,
|
||||
createSession: mocks.harnessCreateSession,
|
||||
resumeSession: mocks.harnessResumeSession,
|
||||
listSessions: mocks.harnessListSessions,
|
||||
close: mocks.harnessClose,
|
||||
track: mocks.harnessTrack,
|
||||
})),
|
||||
initializeTelemetry: vi.fn(),
|
||||
setCrashPhase: vi.fn(),
|
||||
shutdownTelemetry: vi.fn(),
|
||||
|
|
@ -126,6 +139,14 @@ vi.mock('@moonshot-ai/kimi-telemetry', () => ({
|
|||
withTelemetryContext: mocks.withTelemetryContext,
|
||||
}));
|
||||
|
||||
// The experimental v2 engine is loaded via a dynamic import from run-prompt.ts
|
||||
// when KIMI_CODE_EXPERIMENTAL_FLAG is set. Mock it so tests that flip that flag
|
||||
// can exercise the experimental path without pulling in the real agent-core-v2
|
||||
// graph. The returned harness mirrors the v1 mock shape above.
|
||||
vi.mock('../../src/cli/v2/create-v2-harness', () => ({
|
||||
createV2Harness: mocks.createV2Harness,
|
||||
}));
|
||||
|
||||
function opts(overrides: Partial<Parameters<typeof runPrompt>[0]> = {}) {
|
||||
return {
|
||||
session: undefined,
|
||||
|
|
@ -185,8 +206,16 @@ async function waitForAssertion(assertion: () => void): Promise<void> {
|
|||
}
|
||||
|
||||
describe('runPrompt', () => {
|
||||
beforeEach(() => {
|
||||
// Pin the experimental engine flag off so the default v1 path is
|
||||
// deterministic regardless of the host environment. Tests that exercise the
|
||||
// experimental path opt back in explicitly with `vi.stubEnv(..., '1')`.
|
||||
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.unstubAllEnvs();
|
||||
mocks.eventHandlers.clear();
|
||||
mocks.createKimiDeviceId.mockImplementation(() => 'device-1');
|
||||
mocks.resolveKimiHome.mockImplementation(
|
||||
|
|
@ -1030,4 +1059,51 @@ describe('runPrompt', () => {
|
|||
const handler = mocks.session.setQuestionHandler.mock.calls[0]![0] as () => unknown;
|
||||
expect(handler()).toBeNull();
|
||||
});
|
||||
|
||||
it('emits the version first in text mode when the experimental flag is enabled', async () => {
|
||||
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '1');
|
||||
const stdout = writer();
|
||||
const stderr = writer();
|
||||
|
||||
await runPrompt(opts(), '1.2.3-test', { stdout, stderr });
|
||||
|
||||
// The experimental engine is selected and the version banner is the very
|
||||
// first write, ahead of any assistant output or the resume hint.
|
||||
expect(mocks.createV2Harness).toHaveBeenCalled();
|
||||
expect(mocks.kimiHarnessConstructor).not.toHaveBeenCalled();
|
||||
expect(stderr.write).toHaveBeenNthCalledWith(1, 'kimi version 1.2.3-test\n');
|
||||
expect(stderr.text().startsWith('kimi version 1.2.3-test\n')).toBe(true);
|
||||
expect(stdout.text()).toBe('• hello world\n\n');
|
||||
});
|
||||
|
||||
it('emits the version first in stream-json mode when the experimental flag is enabled', async () => {
|
||||
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '1');
|
||||
const stdout = writer();
|
||||
const stderr = writer();
|
||||
|
||||
await runPrompt(opts({ outputFormat: 'stream-json' }), '1.2.3-test', {
|
||||
stdout,
|
||||
stderr,
|
||||
});
|
||||
|
||||
expect(mocks.createV2Harness).toHaveBeenCalled();
|
||||
expect(mocks.kimiHarnessConstructor).not.toHaveBeenCalled();
|
||||
const lines = stdout.text().split('\n');
|
||||
expect(lines[0]).toBe(
|
||||
'{"role":"meta","type":"system.version","version":"1.2.3-test"}',
|
||||
);
|
||||
expect(stderr.text()).toBe('');
|
||||
});
|
||||
|
||||
it('does not emit the version when the experimental flag is disabled', async () => {
|
||||
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '0');
|
||||
const stdout = writer();
|
||||
const stderr = writer();
|
||||
|
||||
await runPrompt(opts(), '1.2.3-test', { stdout, stderr });
|
||||
|
||||
expect(mocks.createV2Harness).not.toHaveBeenCalled();
|
||||
expect(mocks.kimiHarnessConstructor).toHaveBeenCalled();
|
||||
expect(stderr.text()).not.toContain('kimi version');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue