mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
refactor(cli): drop v1 sdk and telemetry deps from v2 print
- run-v2-print: use core ITelemetryService + CloudAppender instead of kimi-telemetry; remove kimi-code-sdk import (auth via IOAuthToolkit, config path from bootstrap, hook result via structural type) - prompt-render: replace SDK HookResultEvent with a structural type so the shared renderer does not depend on the v1 SDK event shape - telemetry: revert initializeCliTelemetry to its original signature now that v2 no longer calls it; keep v1 callers and assertions untouched - update run-prompt and v2-run-print tests for the new wiring
This commit is contained in:
parent
c278cbbf78
commit
ec50813f7e
9 changed files with 99 additions and 99 deletions
|
|
@ -9,10 +9,20 @@
|
|||
* event-filtering / completion flow intact.
|
||||
*/
|
||||
|
||||
import type { HookResultEvent } from '@moonshot-ai/kimi-code-sdk';
|
||||
|
||||
import type { PromptOutputFormat } from './options';
|
||||
|
||||
/**
|
||||
* Structural hook-result shape the renderer reads. Both the v1 SDK
|
||||
* `HookResultEvent` and the v2 native `hook.result` `DomainEvent` satisfy it,
|
||||
* so the renderer stays engine-agnostic without depending on either event
|
||||
* definition.
|
||||
*/
|
||||
interface HookResultEventLike {
|
||||
readonly hookEvent: string;
|
||||
readonly content: string;
|
||||
readonly blocked?: boolean;
|
||||
}
|
||||
|
||||
export interface PromptOutput {
|
||||
readonly columns?: number | undefined;
|
||||
write(chunk: string): boolean;
|
||||
|
|
@ -23,7 +33,7 @@ const PROMPT_BLOCK_INDENT = ' ';
|
|||
|
||||
export interface PromptTurnWriter {
|
||||
writeAssistantDelta(delta: string): void;
|
||||
writeHookResult(event: HookResultEvent): void;
|
||||
writeHookResult(event: HookResultEventLike): void;
|
||||
writeThinkingDelta(delta: string): void;
|
||||
writeToolCall(toolCallId: string, name: string, args: unknown): void;
|
||||
writeToolCallDelta(
|
||||
|
|
@ -72,7 +82,7 @@ export class PromptTranscriptWriter implements PromptTurnWriter {
|
|||
this.assistantWriter.write(delta);
|
||||
}
|
||||
|
||||
writeHookResult(event: HookResultEvent): void {
|
||||
writeHookResult(event: HookResultEventLike): void {
|
||||
this.thinkingWriter.finish();
|
||||
this.assistantWriter.finish();
|
||||
this.assistantWriter.write(formatHookResultPlain(event));
|
||||
|
|
@ -109,7 +119,7 @@ export class PromptJsonWriter implements PromptTurnWriter {
|
|||
this.assistantText += delta;
|
||||
}
|
||||
|
||||
writeHookResult(event: HookResultEvent): void {
|
||||
writeHookResult(event: HookResultEventLike): void {
|
||||
this.flushAssistant();
|
||||
this.writeJsonLine({
|
||||
role: 'assistant',
|
||||
|
|
@ -263,15 +273,15 @@ function visibleCharWidth(char: string): number {
|
|||
return char === '\t' ? 4 : 1;
|
||||
}
|
||||
|
||||
function formatHookResultPlain(event: HookResultEvent): string {
|
||||
function formatHookResultPlain(event: HookResultEventLike): string {
|
||||
return `${formatHookResultTitle(event)}\n\n${formatHookResultBody(event)}`;
|
||||
}
|
||||
|
||||
function formatHookResultTitle(event: HookResultEvent): string {
|
||||
function formatHookResultTitle(event: HookResultEventLike): string {
|
||||
return `${event.hookEvent} hook${event.blocked === true ? ' blocked' : ''}`;
|
||||
}
|
||||
|
||||
function formatHookResultBody(event: HookResultEvent): string {
|
||||
function formatHookResultBody(event: HookResultEventLike): string {
|
||||
const content = event.content.trim();
|
||||
return content.length === 0 ? '(empty)' : content;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,10 +185,7 @@ export async function runPrompt(
|
|||
restorePromptSessionPermission = restorePermission;
|
||||
|
||||
initializeCliTelemetry({
|
||||
homeDir: harness.homeDir,
|
||||
auth: harness.auth,
|
||||
track: (event, properties) =>
|
||||
properties === undefined ? harness.track(event) : harness.track(event, properties),
|
||||
harness,
|
||||
bootstrap: telemetryBootstrap,
|
||||
config,
|
||||
version,
|
||||
|
|
|
|||
|
|
@ -112,10 +112,7 @@ export async function runShell(
|
|||
});
|
||||
|
||||
initializeCliTelemetry({
|
||||
homeDir: harness.homeDir,
|
||||
auth: harness.auth,
|
||||
track: (event, properties) =>
|
||||
properties === undefined ? harness.track(event) : harness.track(event, properties),
|
||||
harness,
|
||||
bootstrap: telemetryBootstrap,
|
||||
config,
|
||||
version,
|
||||
|
|
|
|||
|
|
@ -159,12 +159,7 @@ function createDefaultExportDeps(overrides: Partial<ExportDeps> = {}): ExportDep
|
|||
await currentHarness.ensureConfigFile();
|
||||
const config = await currentHarness.getConfig();
|
||||
initializeCliTelemetry({
|
||||
homeDir: currentHarness.homeDir,
|
||||
auth: currentHarness.auth,
|
||||
track: (event, properties) =>
|
||||
properties === undefined
|
||||
? currentHarness.track(event)
|
||||
: currentHarness.track(event, properties),
|
||||
harness: currentHarness,
|
||||
bootstrap: currentTelemetryBootstrap,
|
||||
config,
|
||||
version: identity.version,
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import {
|
|||
resolveKimiHome,
|
||||
type KimiConfig,
|
||||
type TelemetryClient,
|
||||
type TelemetryProperties,
|
||||
} from '@moonshot-ai/kimi-code-sdk';
|
||||
|
||||
import type { PromptHarness } from './prompt-session';
|
||||
import {
|
||||
initializeTelemetry,
|
||||
setTelemetryContext,
|
||||
|
|
@ -27,11 +27,7 @@ export interface CliTelemetryBootstrap {
|
|||
}
|
||||
|
||||
export interface InitializeCliTelemetryOptions {
|
||||
readonly homeDir: string;
|
||||
readonly auth: {
|
||||
getCachedAccessToken(name: string): Promise<string | null | undefined>;
|
||||
};
|
||||
readonly track: (event: string, properties?: TelemetryProperties) => void;
|
||||
readonly harness: PromptHarness;
|
||||
readonly bootstrap: CliTelemetryBootstrap;
|
||||
readonly config: Pick<KimiConfig, 'defaultModel' | 'telemetry'>;
|
||||
readonly version: string;
|
||||
|
|
@ -53,7 +49,7 @@ export function createCliTelemetryBootstrap(): CliTelemetryBootstrap {
|
|||
|
||||
export function initializeCliTelemetry(options: InitializeCliTelemetryOptions): void {
|
||||
initializeTelemetry({
|
||||
homeDir: options.homeDir,
|
||||
homeDir: options.harness.homeDir,
|
||||
deviceId: options.bootstrap.deviceId,
|
||||
enabled: options.config.telemetry !== false,
|
||||
appName: CLI_USER_AGENT_PRODUCT,
|
||||
|
|
@ -62,10 +58,10 @@ export function initializeCliTelemetry(options: InitializeCliTelemetryOptions):
|
|||
model: options.model ?? options.config.defaultModel,
|
||||
sessionId: options.sessionId,
|
||||
getAccessToken: async () =>
|
||||
(await options.auth.getCachedAccessToken(KIMI_CODE_PROVIDER_NAME)) ?? null,
|
||||
(await options.harness.auth.getCachedAccessToken(KIMI_CODE_PROVIDER_NAME)) ?? null,
|
||||
});
|
||||
if (options.bootstrap.firstLaunch) {
|
||||
options.track('first_launch');
|
||||
options.harness.track('first_launch');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
CloudAppender,
|
||||
IAgentGoalService,
|
||||
IAgentLifecycleService,
|
||||
IAgentPermissionModeService,
|
||||
|
|
@ -25,35 +26,30 @@ import {
|
|||
IAuthSummaryService,
|
||||
IConfigService,
|
||||
IEventBus,
|
||||
IFileSystemStorageService,
|
||||
IOAuthToolkit,
|
||||
ISessionIndex,
|
||||
ISessionLifecycleService,
|
||||
ITelemetryService,
|
||||
bootstrap,
|
||||
ensureMainAgent,
|
||||
hostRequestHeadersSeed,
|
||||
logSeed,
|
||||
resolveKimiHome,
|
||||
resolveLoggingConfig,
|
||||
type DomainEvent,
|
||||
type IAgentScopeHandle,
|
||||
type ISessionScopeHandle,
|
||||
type Scope,
|
||||
} from '@moonshot-ai/agent-core-v2';
|
||||
import { createKimiDefaultHeaders } from '@moonshot-ai/kimi-code-oauth';
|
||||
import {
|
||||
KimiAuthFacade,
|
||||
log,
|
||||
resolveConfigPath,
|
||||
type HookResultEvent,
|
||||
type KimiConfig,
|
||||
} from '@moonshot-ai/kimi-code-sdk';
|
||||
import {
|
||||
setCrashPhase,
|
||||
shutdownTelemetry,
|
||||
track as telemetryTrack,
|
||||
withTelemetryContext,
|
||||
} from '@moonshot-ai/kimi-telemetry';
|
||||
import { createKimiDefaultHeaders, createKimiDeviceId } from '@moonshot-ai/kimi-code-oauth';
|
||||
import { resolve } from 'pathe';
|
||||
|
||||
import { CLI_SHUTDOWN_TIMEOUT_MS, PROMPT_CLEANUP_TIMEOUT_MS } from '#/constant/app';
|
||||
import {
|
||||
CLI_SHUTDOWN_TIMEOUT_MS,
|
||||
CLI_USER_AGENT_PRODUCT,
|
||||
PROMPT_CLEANUP_TIMEOUT_MS,
|
||||
} from '#/constant/app';
|
||||
|
||||
import {
|
||||
formatGoalSummaryText,
|
||||
|
|
@ -70,7 +66,6 @@ import {
|
|||
raceWithTimeout,
|
||||
requireConfiguredModel,
|
||||
} from '../run-prompt';
|
||||
import { createCliTelemetryBootstrap, initializeCliTelemetry } from '../telemetry';
|
||||
import { createKimiCodeHostIdentity } from '../version';
|
||||
|
||||
import type { CLIOptions, PromptOutputFormat } from '../options';
|
||||
|
|
@ -106,65 +101,51 @@ export async function runV2Print(
|
|||
|
||||
writeExperimentalVersion(version, outputFormat, stdout, stderr);
|
||||
|
||||
const telemetryBootstrap = createCliTelemetryBootstrap();
|
||||
const homeDir = telemetryBootstrap.homeDir;
|
||||
const configPath = resolveConfigPath({ homeDir });
|
||||
const homeDir = resolveKimiHome();
|
||||
let firstLaunch = false;
|
||||
const deviceId = createKimiDeviceId(homeDir, {
|
||||
onFirstLaunch: () => {
|
||||
firstLaunch = true;
|
||||
},
|
||||
});
|
||||
const logging = resolveLoggingConfig({ homeDir, env: process.env });
|
||||
const identity = createKimiCodeHostIdentity(version);
|
||||
const hostHeaders = createKimiDefaultHeaders({ homeDir, ...identity });
|
||||
|
||||
const { app } = bootstrap({ homeDir, configPath }, [
|
||||
const { app } = bootstrap({ homeDir }, [
|
||||
...logSeed(logging),
|
||||
...hostRequestHeadersSeed(hostHeaders),
|
||||
]);
|
||||
|
||||
const auth = new KimiAuthFacade({
|
||||
homeDir,
|
||||
configPath,
|
||||
identity,
|
||||
onRefresh: (outcome) => {
|
||||
telemetryTrack('oauth_refresh', {
|
||||
outcome: outcome.success ? 'success' : 'error',
|
||||
...(outcome.success ? {} : { reason: outcome.reason }),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
log.info('kimi-code starting', {
|
||||
version,
|
||||
uiMode: PROMPT_UI_MODE,
|
||||
nodeVersion: process.version,
|
||||
platform: `${process.platform}/${process.arch}`,
|
||||
workDir,
|
||||
});
|
||||
const auth = app.accessor.get(IOAuthToolkit);
|
||||
|
||||
const configService = app.accessor.get(IConfigService);
|
||||
await configService.ready;
|
||||
const defaultModel = configService.get<string>('defaultModel') ?? undefined;
|
||||
let telemetryConfig: KimiConfig['telemetry'];
|
||||
let telemetryEnabled = true;
|
||||
try {
|
||||
telemetryConfig = configService.get<KimiConfig['telemetry']>('telemetry');
|
||||
telemetryEnabled = configService.get('telemetry') !== false;
|
||||
} catch {
|
||||
telemetryConfig = undefined;
|
||||
telemetryEnabled = true;
|
||||
}
|
||||
for (const diagnostic of configService.diagnostics()) {
|
||||
if (diagnostic.severity === 'warning') {
|
||||
stderr.write(`Warning: ${diagnostic.message}\n`);
|
||||
}
|
||||
}
|
||||
const config = { defaultModel, telemetry: telemetryConfig };
|
||||
|
||||
let restorePermission = async (): Promise<void> => {};
|
||||
let removeTerminationCleanup: (() => void) | undefined;
|
||||
let cleanupPromise: Promise<void> | undefined;
|
||||
let telemetryService: ITelemetryService | undefined;
|
||||
const cleanup = async (): Promise<void> => {
|
||||
const pending = (cleanupPromise ??= (async () => {
|
||||
removeTerminationCleanup?.();
|
||||
setCrashPhase('shutdown');
|
||||
try {
|
||||
await restorePermission();
|
||||
} finally {
|
||||
await shutdownTelemetry({ timeoutMs: CLI_SHUTDOWN_TIMEOUT_MS });
|
||||
if (telemetryService !== undefined) {
|
||||
await raceWithTimeout(telemetryService.shutdown(), CLI_SHUTDOWN_TIMEOUT_MS);
|
||||
}
|
||||
app.dispose();
|
||||
}
|
||||
})());
|
||||
|
|
@ -176,18 +157,25 @@ export async function runV2Print(
|
|||
const resolved = await resolveNativeSession(app, opts, workDir, defaultModel, stderr);
|
||||
restorePermission = resolved.restorePermission;
|
||||
|
||||
initializeCliTelemetry({
|
||||
homeDir,
|
||||
auth,
|
||||
track: (event, properties) => telemetryTrack(event, properties),
|
||||
bootstrap: telemetryBootstrap,
|
||||
config,
|
||||
version,
|
||||
uiMode: PROMPT_UI_MODE,
|
||||
model: resolved.telemetryModel,
|
||||
sessionId: resolved.session.id,
|
||||
});
|
||||
setCrashPhase('runtime');
|
||||
telemetryService = app.accessor.get(ITelemetryService);
|
||||
if (telemetryEnabled) {
|
||||
telemetryService.setAppender(
|
||||
new CloudAppender({
|
||||
storage: app.accessor.get(IFileSystemStorageService),
|
||||
deviceId,
|
||||
appName: CLI_USER_AGENT_PRODUCT,
|
||||
version,
|
||||
uiMode: PROMPT_UI_MODE,
|
||||
model: resolved.telemetryModel,
|
||||
getAccessToken: async () => (await auth.getCachedAccessToken()) ?? null,
|
||||
env: process.env,
|
||||
}),
|
||||
);
|
||||
}
|
||||
telemetryService.setContext({ sessionId: resolved.session.id });
|
||||
if (firstLaunch) {
|
||||
telemetryService.track('first_launch');
|
||||
}
|
||||
|
||||
const goalCreate = parseHeadlessGoalCreate(opts.prompt!);
|
||||
if (goalCreate !== undefined) {
|
||||
|
|
@ -214,7 +202,7 @@ export async function runV2Print(
|
|||
}
|
||||
writeResumeHint(resolved.session.id, outputFormat, stdout, stderr);
|
||||
|
||||
withTelemetryContext({ sessionId: resolved.session.id }).track('exit', {
|
||||
telemetryService.withContext({ sessionId: resolved.session.id }).track('exit', {
|
||||
duration_ms: Date.now() - startedAt,
|
||||
});
|
||||
} finally {
|
||||
|
|
@ -369,8 +357,9 @@ async function runNativeTurn(
|
|||
if (result.reason === 'completed') {
|
||||
try {
|
||||
await drainBackgroundTasks(app, session);
|
||||
} catch (error) {
|
||||
log.warn('drainBackgroundTasks failed', { error });
|
||||
} catch {
|
||||
// Draining is best-effort; a wedged background task must not fail the
|
||||
// (already completed) turn. Swallow and proceed to finish.
|
||||
}
|
||||
writer.finish();
|
||||
return;
|
||||
|
|
@ -444,7 +433,7 @@ function dispatchNativeEvent(
|
|||
writer.writeAssistantDelta(event.delta);
|
||||
return;
|
||||
case 'hook.result':
|
||||
writer.writeHookResult(event as unknown as HookResultEvent);
|
||||
writer.writeHookResult(event);
|
||||
return;
|
||||
case 'thinking.delta':
|
||||
writer.writeThinkingDelta(event.delta);
|
||||
|
|
|
|||
|
|
@ -105,10 +105,7 @@ export async function handleUpgradeCommand(version: string): Promise<void> {
|
|||
await harness.ensureConfigFile();
|
||||
const config = await harness.getConfig();
|
||||
initializeCliTelemetry({
|
||||
homeDir: harness.homeDir,
|
||||
auth: harness.auth,
|
||||
track: (event, properties) =>
|
||||
properties === undefined ? harness.track(event) : harness.track(event, properties),
|
||||
harness,
|
||||
bootstrap: telemetryBootstrap,
|
||||
config,
|
||||
version,
|
||||
|
|
|
|||
|
|
@ -396,8 +396,9 @@ describe('main entry command handling', () => {
|
|||
}));
|
||||
expect(mocks.harness.ensureConfigFile).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.initializeCliTelemetry).toHaveBeenCalledWith(expect.objectContaining({
|
||||
homeDir: '/tmp/kimi-home',
|
||||
track: expect.any(Function),
|
||||
harness: expect.objectContaining({
|
||||
homeDir: '/tmp/kimi-home',
|
||||
}),
|
||||
bootstrap: {
|
||||
homeDir: '/tmp/kimi-home',
|
||||
deviceId: 'device-id',
|
||||
|
|
|
|||
|
|
@ -10,8 +10,11 @@ import {
|
|||
IAuthSummaryService,
|
||||
IConfigService,
|
||||
IEventBus,
|
||||
IFileSystemStorageService,
|
||||
IOAuthToolkit,
|
||||
ISessionIndex,
|
||||
ISessionLifecycleService,
|
||||
ITelemetryService,
|
||||
type DomainEvent,
|
||||
} from '@moonshot-ai/agent-core-v2';
|
||||
|
||||
|
|
@ -182,6 +185,21 @@ describe('runV2Print', () => {
|
|||
},
|
||||
],
|
||||
[ISessionIndex, { list: vi.fn(async () => ({ items: [] })) }],
|
||||
[IOAuthToolkit, { getCachedAccessToken: vi.fn(async () => undefined) }],
|
||||
[IFileSystemStorageService, {}],
|
||||
[
|
||||
ITelemetryService,
|
||||
(() => {
|
||||
const svc = {
|
||||
setAppender: vi.fn(),
|
||||
setContext: vi.fn(),
|
||||
track: vi.fn(),
|
||||
shutdown: vi.fn(async () => {}),
|
||||
withContext: vi.fn(() => svc),
|
||||
};
|
||||
return svc;
|
||||
})(),
|
||||
],
|
||||
]);
|
||||
const app = fakeScope('app', appServices);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue