diff --git a/.changeset/split-sdk-rpc-client.md b/.changeset/split-sdk-rpc-client.md new file mode 100644 index 0000000000..4cf3d8356a --- /dev/null +++ b/.changeset/split-sdk-rpc-client.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/kimi-code-sdk": minor +"@moonshot-ai/kimi-code": patch +--- + +Allow SDK runtime creation to use a separate RPC client while preserving local CLI startup. diff --git a/apps/kimi-code/src/cli/run-prompt.ts b/apps/kimi-code/src/cli/run-prompt.ts index 7bc720d61a..c01b0015b2 100644 --- a/apps/kimi-code/src/cli/run-prompt.ts +++ b/apps/kimi-code/src/cli/run-prompt.ts @@ -7,11 +7,12 @@ import { } from '@moonshot-ai/kimi-telemetry'; import chalk from 'chalk'; import { - KimiHarness, + createKimiHarness, log, type Event, type GoalSnapshot, type HookResultEvent, + type KimiHarness, type Session, type SessionStatus, type TelemetryClient, @@ -68,7 +69,7 @@ export async function runPrompt( withContext: withTelemetryContext, setContext: setTelemetryContext, }; - const harness = new KimiHarness({ + const harness = createKimiHarness({ homeDir: telemetryBootstrap.homeDir, identity: createKimiCodeHostIdentity(version), uiMode: PROMPT_UI_MODE, diff --git a/apps/kimi-code/src/cli/run-shell.ts b/apps/kimi-code/src/cli/run-shell.ts index a68d2f896d..4ec4330a57 100644 --- a/apps/kimi-code/src/cli/run-shell.ts +++ b/apps/kimi-code/src/cli/run-shell.ts @@ -9,7 +9,12 @@ import { track, withTelemetryContext, } from '@moonshot-ai/kimi-telemetry'; -import { KimiHarness, log, type TelemetryClient } from '@moonshot-ai/kimi-code-sdk'; +import { + createKimiHarness, + log, + type KimiHarness, + type TelemetryClient, +} from '@moonshot-ai/kimi-code-sdk'; import { CLI_SHUTDOWN_TIMEOUT_MS, CLI_UI_MODE } from '#/constant/app'; import { detectPendingMigration } from '#/migration/index'; @@ -51,7 +56,7 @@ export async function runShell( withContext: withTelemetryContext, setContext: setTelemetryContext, }; - const harness = new KimiHarness({ + const harness = createKimiHarness({ homeDir: telemetryBootstrap.homeDir, identity: createKimiCodeHostIdentity(version), telemetry: telemetryClient, diff --git a/apps/kimi-code/src/cli/sub/export.ts b/apps/kimi-code/src/cli/sub/export.ts index f779cef583..a93c3e2719 100644 --- a/apps/kimi-code/src/cli/sub/export.ts +++ b/apps/kimi-code/src/cli/sub/export.ts @@ -14,9 +14,10 @@ import { withTelemetryContext, } from '@moonshot-ai/kimi-telemetry'; import { - KimiHarness, + createKimiHarness, type ExportSessionInput, type ExportSessionResult, + type KimiHarness, type SessionSummary, type ShellEnvironment, type TelemetryClient, @@ -144,7 +145,7 @@ function createDefaultExportDeps(overrides: Partial = {}): ExportDep }; const getHarness = (): KimiHarness => { const currentTelemetryBootstrap = getTelemetryBootstrap(); - harness ??= new KimiHarness({ + harness ??= createKimiHarness({ homeDir: currentTelemetryBootstrap.homeDir, identity, telemetry: telemetryClient, diff --git a/apps/kimi-code/src/cli/sub/provider.ts b/apps/kimi-code/src/cli/sub/provider.ts index bac1964e80..38cb755219 100644 --- a/apps/kimi-code/src/cli/sub/provider.ts +++ b/apps/kimi-code/src/cli/sub/provider.ts @@ -23,13 +23,14 @@ import { catalogBaseUrl, catalogProviderModels, CatalogFetchError, + createKimiHarness, DEFAULT_CATALOG_URL, fetchCatalog, inferWireType, - KimiHarness, type Catalog, type CatalogProviderEntry, type KimiConfig, + type KimiHarness, } from '@moonshot-ai/kimi-code-sdk'; import type { Command } from 'commander'; @@ -487,7 +488,7 @@ function resolveDeps(overrides: Partial = {}): ProviderDeps { getHarness: overrides.getHarness ?? (() => { - harness ??= new KimiHarness({ identity }); + harness ??= createKimiHarness({ identity }); return harness; }), stdout: overrides.stdout ?? process.stdout, diff --git a/apps/kimi-code/src/main.ts b/apps/kimi-code/src/main.ts index 56d829447a..e1251be2fd 100644 --- a/apps/kimi-code/src/main.ts +++ b/apps/kimi-code/src/main.ts @@ -6,7 +6,7 @@ */ import { - KimiHarness, + createKimiHarness, flushDiagnosticLogs, log, resolveGlobalLogPath, @@ -78,7 +78,7 @@ export async function handleUpgradeCommand(version: string): Promise { withContext: withTelemetryContext, setContext: setTelemetryContext, }; - const harness = new KimiHarness({ + const harness = createKimiHarness({ homeDir: telemetryBootstrap.homeDir, identity: createKimiCodeHostIdentity(version), telemetry: telemetryClient, diff --git a/apps/kimi-code/test/cli/export.test.ts b/apps/kimi-code/test/cli/export.test.ts index 934c41ccd6..39963536e9 100644 --- a/apps/kimi-code/test/cli/export.test.ts +++ b/apps/kimi-code/test/cli/export.test.ts @@ -52,24 +52,23 @@ vi.mock('@moonshot-ai/kimi-code-sdk', async (importOriginal) => { return { ...actual, resolveKimiHome: mocks.resolveKimiHome, - KimiHarness: class { - homeDir: string; - auth = { - getCachedAccessToken: mocks.harnessGetCachedAccessToken, - }; - ensureConfigFile = mocks.harnessEnsureConfigFile; - getConfig = mocks.harnessGetConfig; - track = mocks.harnessTrack; - constructor(...args: unknown[]) { - const options = args[0] as { readonly homeDir?: string } | undefined; - this.homeDir = options?.homeDir ?? '/tmp/kimi-export-home'; - if (mocks.harnessCreatesDeviceIdOnConstruction) { - mocks.createKimiDeviceId(this.homeDir); - } - mocks.kimiHarnessConstructor(...args); + createKimiHarness: (...args: unknown[]) => { + const options = args[0] as { readonly homeDir?: string } | undefined; + const homeDir = options?.homeDir ?? '/tmp/kimi-export-home'; + if (mocks.harnessCreatesDeviceIdOnConstruction) { + mocks.createKimiDeviceId(homeDir); } - - exportSession = mocks.harnessExportSession; + mocks.kimiHarnessConstructor(...args); + return { + homeDir, + auth: { + getCachedAccessToken: mocks.harnessGetCachedAccessToken, + }, + ensureConfigFile: mocks.harnessEnsureConfigFile, + getConfig: mocks.harnessGetConfig, + track: mocks.harnessTrack, + exportSession: mocks.harnessExportSession, + }; }, }; }); diff --git a/apps/kimi-code/test/cli/goal-prompt.test.ts b/apps/kimi-code/test/cli/goal-prompt.test.ts index 3103e4d155..3a5bcf9a4c 100644 --- a/apps/kimi-code/test/cli/goal-prompt.test.ts +++ b/apps/kimi-code/test/cli/goal-prompt.test.ts @@ -119,19 +119,18 @@ vi.mock('@moonshot-ai/kimi-code-sdk', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - KimiHarness: class { - homeDir = '/tmp/kimi-goal-home'; - auth = { getCachedAccessToken: vi.fn() }; - ensureConfigFile = vi.fn(); - getConfig = vi.fn(async () => ({ providers: {}, defaultModel: 'k2', telemetry: true })); - getExperimentalFlags = vi.fn(async () => mocks.experimentalFlags); - createSession = vi.fn(async () => mocks.session); - resumeSession = vi.fn(async () => mocks.session); - listSessions = vi.fn(async () => mocks.sessions); - close = vi.fn(); - track = vi.fn(); - constructor() {} - }, + createKimiHarness: () => ({ + homeDir: '/tmp/kimi-goal-home', + auth: { getCachedAccessToken: vi.fn() }, + ensureConfigFile: vi.fn(), + getConfig: vi.fn(async () => ({ providers: {}, defaultModel: 'k2', telemetry: true })), + getExperimentalFlags: vi.fn(async () => mocks.experimentalFlags), + createSession: vi.fn(async () => mocks.session), + resumeSession: vi.fn(async () => mocks.session), + listSessions: vi.fn(async () => mocks.sessions), + close: vi.fn(), + track: vi.fn(), + }), }; }); diff --git a/apps/kimi-code/test/cli/main.test.ts b/apps/kimi-code/test/cli/main.test.ts index 8d674d4422..4a5ac62ad3 100644 --- a/apps/kimi-code/test/cli/main.test.ts +++ b/apps/kimi-code/test/cli/main.test.ts @@ -46,6 +46,7 @@ const mocks = vi.hoisted(() => { track: vi.fn(), }, KimiHarness: vi.fn(), + createKimiHarness: vi.fn(), }; }); @@ -74,6 +75,10 @@ vi.mock('@moonshot-ai/kimi-code-sdk', async () => { } return { ...actual, + createKimiHarness: (...args: unknown[]) => { + mocks.createKimiHarness(...args); + return mocks.harness; + }, KimiHarness: MockKimiHarness, log: mocks.log, }; @@ -272,7 +277,7 @@ describe('main entry command handling', () => { expect(exitCode).toBe(0); expect(mocks.createCliTelemetryBootstrap).toHaveBeenCalledTimes(1); - expect(mocks.KimiHarness).toHaveBeenCalledWith(expect.objectContaining({ + expect(mocks.createKimiHarness).toHaveBeenCalledWith(expect.objectContaining({ homeDir: '/tmp/kimi-home', telemetry: { track: mocks.track, diff --git a/apps/kimi-code/test/cli/run-prompt.test.ts b/apps/kimi-code/test/cli/run-prompt.test.ts index 004a3cac8d..123c9920aa 100644 --- a/apps/kimi-code/test/cli/run-prompt.test.ts +++ b/apps/kimi-code/test/cli/run-prompt.test.ts @@ -79,26 +79,25 @@ vi.mock('@moonshot-ai/kimi-code-sdk', async (importOriginal) => { return { ...actual, resolveKimiHome: mocks.resolveKimiHome, - KimiHarness: class { - homeDir: string; - auth = { getCachedAccessToken: mocks.harnessGetCachedAccessToken }; - ensureConfigFile = mocks.harnessEnsureConfigFile; - getConfig = mocks.harnessGetConfig; - getExperimentalFlags = mocks.harnessGetExperimentalFlags; - createSession = mocks.harnessCreateSession; - resumeSession = mocks.harnessResumeSession; - listSessions = mocks.harnessListSessions; - close = mocks.harnessClose; - track = mocks.harnessTrack; - - constructor(...args: unknown[]) { - const options = args[0] as { readonly homeDir?: string } | undefined; - this.homeDir = options?.homeDir ?? '/tmp/kimi-code-test-home'; - if (mocks.harnessCreatesDeviceIdOnConstruction) { - mocks.createKimiDeviceId(this.homeDir); - } - mocks.kimiHarnessConstructor(...args); + createKimiHarness: (...args: unknown[]) => { + const options = args[0] as { readonly homeDir?: string } | undefined; + const homeDir = options?.homeDir ?? '/tmp/kimi-code-test-home'; + if (mocks.harnessCreatesDeviceIdOnConstruction) { + mocks.createKimiDeviceId(homeDir); } + mocks.kimiHarnessConstructor(...args); + return { + homeDir, + auth: { getCachedAccessToken: mocks.harnessGetCachedAccessToken }, + ensureConfigFile: mocks.harnessEnsureConfigFile, + getConfig: mocks.harnessGetConfig, + getExperimentalFlags: mocks.harnessGetExperimentalFlags, + createSession: mocks.harnessCreateSession, + resumeSession: mocks.harnessResumeSession, + listSessions: mocks.harnessListSessions, + close: mocks.harnessClose, + track: mocks.harnessTrack, + }; }, }; }); diff --git a/apps/kimi-code/test/cli/run-shell.test.ts b/apps/kimi-code/test/cli/run-shell.test.ts index 8aa72d9455..c55a5e5904 100644 --- a/apps/kimi-code/test/cli/run-shell.test.ts +++ b/apps/kimi-code/test/cli/run-shell.test.ts @@ -68,24 +68,23 @@ vi.mock('@moonshot-ai/kimi-code-sdk', async (importOriginal) => { return { ...actual, resolveKimiHome: mocks.resolveKimiHome, - KimiHarness: class { - homeDir: string; - auth = { - getCachedAccessToken: mocks.harnessGetCachedAccessToken, - }; - ensureConfigFile = mocks.harnessEnsureConfigFile; - getConfig = mocks.harnessGetConfig; - close = mocks.harnessClose; - track = mocks.harnessTrack; - - constructor(...args: unknown[]) { - const options = args[0] as { readonly homeDir?: string } | undefined; - this.homeDir = options?.homeDir ?? '/tmp/kimi-code-test-home'; - if (mocks.harnessCreatesDeviceIdOnConstruction) { - mocks.createKimiDeviceId(this.homeDir); - } - mocks.kimiHarnessConstructor(...args); + createKimiHarness: (...args: unknown[]) => { + const options = args[0] as { readonly homeDir?: string } | undefined; + const homeDir = options?.homeDir ?? '/tmp/kimi-code-test-home'; + if (mocks.harnessCreatesDeviceIdOnConstruction) { + mocks.createKimiDeviceId(homeDir); } + mocks.kimiHarnessConstructor(...args); + return { + homeDir, + auth: { + getCachedAccessToken: mocks.harnessGetCachedAccessToken, + }, + ensureConfigFile: mocks.harnessEnsureConfigFile, + getConfig: mocks.harnessGetConfig, + close: mocks.harnessClose, + track: mocks.harnessTrack, + }; }, }; }); diff --git a/apps/kimi-code/test/e2e/local-logging-export.e2e.test.ts b/apps/kimi-code/test/e2e/local-logging-export.e2e.test.ts index 0487f702ba..1bb985f261 100644 --- a/apps/kimi-code/test/e2e/local-logging-export.e2e.test.ts +++ b/apps/kimi-code/test/e2e/local-logging-export.e2e.test.ts @@ -8,7 +8,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { registerExportCommand } from '#/cli/sub/export'; import { createKimiCodeHostIdentity } from '#/cli/version'; -import { KimiHarness, log } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness, log } from '@moonshot-ai/kimi-code-sdk'; import { __resetRootLoggerForTest } from '../../../../packages/agent-core/src/logging/logger'; const SESSION_LOG = 'logs/kimi-code.log'; @@ -49,7 +49,7 @@ afterEach(async () => { describe.skipIf(!ENABLED)('local logging export e2e', () => { it('exports session log and global log by default, and allows skipping global log', async () => { - const harness = new KimiHarness({ + const harness = createKimiHarness({ homeDir, identity: createKimiCodeHostIdentity('0.1.1'), }); diff --git a/apps/kimi-code/test/e2e/real-llm-smoke.e2e.test.ts b/apps/kimi-code/test/e2e/real-llm-smoke.e2e.test.ts index ffa93ba06c..b31faefd08 100644 --- a/apps/kimi-code/test/e2e/real-llm-smoke.e2e.test.ts +++ b/apps/kimi-code/test/e2e/real-llm-smoke.e2e.test.ts @@ -14,7 +14,7 @@ import { mkdirSync } from 'node:fs'; import process from 'node:process'; -import { KimiHarness, type Event } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness, type Event } from '@moonshot-ai/kimi-code-sdk'; import { describe, expect, test } from 'vitest'; import { createKimiCodeHostIdentity, getVersion } from '#/cli/version'; @@ -41,7 +41,7 @@ describe.skipIf(!ENABLED)('SDK e2e — real LLM smoke', () => { `[smoke] prompt=${JSON.stringify(prompt)}\n`, ); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: createKimiCodeHostIdentity(version), }); diff --git a/packages/node-sdk/examples/kimi-harness-auth-smoke.ts b/packages/node-sdk/examples/kimi-harness-auth-smoke.ts index 98d3db8e65..90ce644a8d 100644 --- a/packages/node-sdk/examples/kimi-harness-auth-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-auth-smoke.ts @@ -2,7 +2,7 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness, type KimiHarness } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv, runPromptToEnd } from './runtime-smoke-helpers'; @@ -17,7 +17,7 @@ async function main(): Promise { const forceLogin = process.env['KIMI_SDK_AUTH_SMOKE_FORCE_LOGIN'] === '1'; const prompt = process.env['KIMI_SDK_AUTH_SMOKE_PROMPT'] ?? 'Reply with exactly: Kimi SDK auth smoke ok'; - const harness = new KimiHarness({ homeDir, identity: smokeIdentityFromEnv() }); + const harness = createKimiHarness({ homeDir, identity: smokeIdentityFromEnv() }); process.stdout.write(`home: ${homeDir}\n`); process.stdout.write(`workDir: ${workDir}\n`); diff --git a/packages/node-sdk/examples/kimi-harness-cancel-smoke.ts b/packages/node-sdk/examples/kimi-harness-cancel-smoke.ts index 92bc11ad7d..5f6c87aea0 100644 --- a/packages/node-sdk/examples/kimi-harness-cancel-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-cancel-smoke.ts @@ -1,4 +1,4 @@ -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv, @@ -10,7 +10,7 @@ const PROMPT = 'Write a detailed multi-paragraph explanation of how cancellation should work in an SDK streaming session.'; async function main(): Promise { - const harness = new KimiHarness({ identity: smokeIdentityFromEnv() }); + const harness = createKimiHarness({ identity: smokeIdentityFromEnv() }); try { const session = await createConfiguredSession(harness); diff --git a/packages/node-sdk/examples/kimi-harness-config-smoke.ts b/packages/node-sdk/examples/kimi-harness-config-smoke.ts index 7c4f7d4d8a..0294b8ff9b 100644 --- a/packages/node-sdk/examples/kimi-harness-config-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-config-smoke.ts @@ -2,13 +2,13 @@ import { mkdtemp, readFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv } from './runtime-smoke-helpers'; async function main(): Promise { const homeDir = await mkdtemp(join(tmpdir(), 'kimi-harness-config-home-')); - const harness = new KimiHarness({ homeDir, identity: smokeIdentityFromEnv() }); + const harness = createKimiHarness({ homeDir, identity: smokeIdentityFromEnv() }); const initial = await harness.getConfig(); if (Object.keys(initial.providers).length > 0) { diff --git a/packages/node-sdk/examples/kimi-harness-export-smoke.ts b/packages/node-sdk/examples/kimi-harness-export-smoke.ts index 1244379183..86807cbc63 100644 --- a/packages/node-sdk/examples/kimi-harness-export-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-export-smoke.ts @@ -2,14 +2,14 @@ import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv } from './runtime-smoke-helpers'; async function main(): Promise { const homeDir = await mkdtemp(join(tmpdir(), 'kimi-harness-export-home-')); const workDir = await mkdtemp(join(tmpdir(), 'kimi-harness-export-work-')); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: smokeIdentityFromEnv(), homeDir, }); diff --git a/packages/node-sdk/examples/kimi-harness-list-smoke.ts b/packages/node-sdk/examples/kimi-harness-list-smoke.ts index 01a601e1c7..c51ec64442 100644 --- a/packages/node-sdk/examples/kimi-harness-list-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-list-smoke.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { setTimeout as delay } from 'node:timers/promises'; -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv } from './runtime-smoke-helpers'; @@ -11,7 +11,7 @@ async function main(): Promise { const homeDir = await mkdtemp(join(tmpdir(), 'kimi-harness-list-home-')); const workDirA = await mkdtemp(join(tmpdir(), 'kimi-harness-list-work-a-')); const workDirB = await mkdtemp(join(tmpdir(), 'kimi-harness-list-work-b-')); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: smokeIdentityFromEnv(), homeDir, }); diff --git a/packages/node-sdk/examples/kimi-harness-log-marker.ts b/packages/node-sdk/examples/kimi-harness-log-marker.ts index 4ffa28ec83..8062f5d528 100644 --- a/packages/node-sdk/examples/kimi-harness-log-marker.ts +++ b/packages/node-sdk/examples/kimi-harness-log-marker.ts @@ -3,8 +3,8 @@ import { join } from 'node:path'; import { parseArgs } from 'node:util'; import { + createKimiHarness, flushDiagnosticLogs, - KimiHarness, log, resolveGlobalLogPath, resolveKimiHome, @@ -41,7 +41,7 @@ Options: async function main(): Promise { const options = parseCliArgs(); const resolvedHome = resolveKimiHome(options.homeDir); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: { userAgentProduct: 'kimi-code-cli', version: 'log-marker' }, homeDir: options.homeDir, }); diff --git a/packages/node-sdk/examples/kimi-harness-logging-smoke.ts b/packages/node-sdk/examples/kimi-harness-logging-smoke.ts index 9dcde502b8..090cb50c4c 100644 --- a/packages/node-sdk/examples/kimi-harness-logging-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-logging-smoke.ts @@ -2,7 +2,7 @@ import { mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises' import { homedir } from 'node:os'; import { join } from 'node:path'; -import { KimiHarness, log } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness, log } from '@moonshot-ai/kimi-code-sdk'; const SESSION_LOG = 'logs/kimi-code.log'; const GLOBAL_LOG = 'logs/global/kimi-code.log'; @@ -72,7 +72,7 @@ async function main(): Promise { const globalOnly = `SMOKE_GLOBAL_ONLY_${runId}`; const longEntry = `SMOKE_LONG_TRUNCATED_${runId}`; const finalEntry = `SMOKE_FINAL_AFTER_ROTATION_SHOULD_APPEAR_${runId}`; - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: { userAgentProduct: 'kimi-code-cli', version: '0.1.1' }, homeDir: TEST_HOME, }); diff --git a/packages/node-sdk/examples/kimi-harness-prompt-demo.ts b/packages/node-sdk/examples/kimi-harness-prompt-demo.ts index 8ea4792e0a..eebbdccfbf 100644 --- a/packages/node-sdk/examples/kimi-harness-prompt-demo.ts +++ b/packages/node-sdk/examples/kimi-harness-prompt-demo.ts @@ -1,4 +1,4 @@ -import { KimiHarness, type Session, type Event } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness, type Event, type Session } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv } from './runtime-smoke-helpers'; @@ -8,7 +8,7 @@ const PROMPT = async function main(): Promise { const workDir = process.cwd(); - const harness = new KimiHarness({ identity: smokeIdentityFromEnv() }); + const harness = createKimiHarness({ identity: smokeIdentityFromEnv() }); try { const config = await harness.getConfig(); diff --git a/packages/node-sdk/examples/kimi-harness-rename-smoke.ts b/packages/node-sdk/examples/kimi-harness-rename-smoke.ts index 8312d1ab30..9e77080096 100644 --- a/packages/node-sdk/examples/kimi-harness-rename-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-rename-smoke.ts @@ -2,7 +2,7 @@ import { mkdtemp, readFile, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; import type { Event } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv } from './runtime-smoke-helpers'; @@ -10,7 +10,7 @@ import { smokeIdentityFromEnv } from './runtime-smoke-helpers'; async function main(): Promise { const homeDir = await mkdtemp(join(tmpdir(), 'kimi-harness-rename-home-')); const workDir = await mkdtemp(join(tmpdir(), 'kimi-harness-rename-work-')); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: smokeIdentityFromEnv(), homeDir, }); diff --git a/packages/node-sdk/examples/kimi-harness-set-model-smoke.ts b/packages/node-sdk/examples/kimi-harness-set-model-smoke.ts index 5af91a000d..40c9b09111 100644 --- a/packages/node-sdk/examples/kimi-harness-set-model-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-set-model-smoke.ts @@ -1,4 +1,4 @@ -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv, @@ -7,7 +7,7 @@ import { } from './runtime-smoke-helpers'; async function main(): Promise { - const harness = new KimiHarness({ identity: smokeIdentityFromEnv() }); + const harness = createKimiHarness({ identity: smokeIdentityFromEnv() }); try { const config = await harness.getConfig(); diff --git a/packages/node-sdk/examples/kimi-harness-set-permission-smoke.ts b/packages/node-sdk/examples/kimi-harness-set-permission-smoke.ts index e51ed3c277..4b161dbc2f 100644 --- a/packages/node-sdk/examples/kimi-harness-set-permission-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-set-permission-smoke.ts @@ -1,4 +1,4 @@ -import { KimiHarness, type PermissionMode } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness, type PermissionMode } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv, @@ -7,7 +7,7 @@ import { } from './runtime-smoke-helpers'; async function main(): Promise { - const harness = new KimiHarness({ identity: smokeIdentityFromEnv() }); + const harness = createKimiHarness({ identity: smokeIdentityFromEnv() }); const mode: PermissionMode = 'yolo'; try { diff --git a/packages/node-sdk/examples/kimi-harness-set-thinking-smoke.ts b/packages/node-sdk/examples/kimi-harness-set-thinking-smoke.ts index e5d440922a..a5d49ba5e0 100644 --- a/packages/node-sdk/examples/kimi-harness-set-thinking-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-set-thinking-smoke.ts @@ -1,4 +1,4 @@ -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv, @@ -7,7 +7,7 @@ import { } from './runtime-smoke-helpers'; async function main(): Promise { - const harness = new KimiHarness({ identity: smokeIdentityFromEnv() }); + const harness = createKimiHarness({ identity: smokeIdentityFromEnv() }); try { const session = await createConfiguredSession(harness); diff --git a/packages/node-sdk/examples/kimi-harness-smoke.ts b/packages/node-sdk/examples/kimi-harness-smoke.ts index 85220da72b..eb1375384b 100644 --- a/packages/node-sdk/examples/kimi-harness-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-smoke.ts @@ -2,7 +2,7 @@ import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv } from './runtime-smoke-helpers'; @@ -10,7 +10,7 @@ async function main(): Promise { const workDir = await mkdtemp(join(tmpdir(), 'kimi-harness-smoke-')); const homeDir = await mkdtemp(join(tmpdir(), 'kimi-harness-home-')); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: smokeIdentityFromEnv(), homeDir, }); diff --git a/packages/node-sdk/examples/kimi-harness-steer-smoke.ts b/packages/node-sdk/examples/kimi-harness-steer-smoke.ts index 704c2c0afd..776290320c 100644 --- a/packages/node-sdk/examples/kimi-harness-steer-smoke.ts +++ b/packages/node-sdk/examples/kimi-harness-steer-smoke.ts @@ -1,4 +1,4 @@ -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; import { smokeIdentityFromEnv, @@ -10,7 +10,7 @@ const PROMPT = 'Draft a long checklist for validating a TypeScript SDK runtime.' const STEER = 'Also include cancellation and permission-mode checks.'; async function main(): Promise { - const harness = new KimiHarness({ identity: smokeIdentityFromEnv() }); + const harness = createKimiHarness({ identity: smokeIdentityFromEnv() }); try { const session = await createConfiguredSession(harness); diff --git a/packages/node-sdk/examples/t8-race-create-single.ts b/packages/node-sdk/examples/t8-race-create-single.ts index d3b0c01600..a6974caee1 100644 --- a/packages/node-sdk/examples/t8-race-create-single.ts +++ b/packages/node-sdk/examples/t8-race-create-single.ts @@ -1,5 +1,5 @@ // One creator: meant to run twice as separate processes simultaneously. -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness } from '@moonshot-ai/kimi-code-sdk'; const workDir = process.argv[2]!; const homeDir = process.argv[3]!; @@ -7,7 +7,7 @@ const sessionId = process.argv[4]!; const label = process.argv[5] ?? 'P'; const identity: any = { userAgentProduct: 'kimi-code-cli', version: '0.0.1-test' }; -const h = new KimiHarness({ identity, homeDir }); +const h = createKimiHarness({ identity, homeDir }); try { const s = await h.createSession({ workDir, id: sessionId, model: 'kimi-code/kimi-for-coding' }); diff --git a/packages/node-sdk/examples/t8-race-create.ts b/packages/node-sdk/examples/t8-race-create.ts index fb8a7f9fab..0c6a74d2f4 100644 --- a/packages/node-sdk/examples/t8-race-create.ts +++ b/packages/node-sdk/examples/t8-race-create.ts @@ -1,13 +1,13 @@ // T8.4 driver: create session with explicit id, twice concurrently in same process. -import { KimiHarness } from '@moonshot-ai/kimi-code-sdk'; +import { createKimiHarness, type KimiHarness } from '@moonshot-ai/kimi-code-sdk'; const workDir = process.argv[2]!; const homeDir = process.argv[3]!; const sessionId = process.argv[4]!; const identity: any = { userAgentProduct: 'kimi-code-cli', version: '0.0.1-test' }; -const harnessA = new KimiHarness({ identity, homeDir }); -const harnessB = new KimiHarness({ identity, homeDir }); +const harnessA = createKimiHarness({ identity, homeDir }); +const harnessB = createKimiHarness({ identity, homeDir }); async function run(label: string, h: KimiHarness): Promise { try { diff --git a/packages/node-sdk/src/index.ts b/packages/node-sdk/src/index.ts index 289465148a..83823f9072 100644 --- a/packages/node-sdk/src/index.ts +++ b/packages/node-sdk/src/index.ts @@ -1,6 +1,13 @@ export { KimiHarness } from '#/kimi-harness'; +export type { KimiHarnessRuntimeOptions } from '#/kimi-harness'; export { Session } from '#/session'; export { KimiAuthFacade } from '#/auth'; +export { + createKimiHarness, + SDKRpcClient, + type SDKRpcClientOptions, +} from '#/sdk-rpc-client'; +export { SDKRpcClientBase } from '#/rpc'; export { KimiForCodingProvider } from '#/kimi-code-model-provider'; export type { KimiForCodingProviderOptions } from '#/kimi-code-model-provider'; diff --git a/packages/node-sdk/src/kimi-harness.ts b/packages/node-sdk/src/kimi-harness.ts index 769cd3a3df..499ce75a01 100644 --- a/packages/node-sdk/src/kimi-harness.ts +++ b/packages/node-sdk/src/kimi-harness.ts @@ -1,23 +1,13 @@ import { - ensureConfigFile, ErrorCodes, KimiError, - getRootLogger, - noopTelemetryClient, - resolveConfigPath, - resolveKimiHome, - resolveLoggingConfig, withTelemetryContext, type ExperimentalFlagMap, - type TelemetryClient, - type TelemetryContextPatch, - type TelemetryProperties, } from '@moonshot-ai/agent-core'; -import { assertKimiHostIdentity } from '@moonshot-ai/kimi-code-oauth'; -import { KimiAuthFacade } from '#/auth'; -import { SDKRpcClient } from '#/rpc'; import { Session } from '#/session'; +import type { KimiAuthFacade } from '#/auth'; +import type { SDKRpcClientBase } from '#/rpc'; import type { CreateSessionOptions, ExportSessionInput, @@ -26,14 +16,27 @@ import type { GetConfigOptions, KimiConfig, KimiConfigPatch, - KimiHarnessOptions, KimiHostIdentity, ListSessionsOptions, RenameSessionInput, ResumeSessionInput, SessionSummary, + TelemetryClient, + TelemetryContextPatch, + TelemetryProperties, } from '#/types'; +export interface KimiHarnessRuntimeOptions { + readonly identity?: KimiHostIdentity; + readonly uiMode?: string; + readonly homeDir: string; + readonly configPath: string; + readonly auth: KimiAuthFacade; + readonly telemetry: TelemetryClient; + readonly ensureConfigFile: () => Promise; + readonly onClose: () => void | Promise; +} + export class KimiHarness { readonly homeDir: string; readonly configPath: string; @@ -43,39 +46,21 @@ export class KimiHarness { private readonly uiMode: string; private readonly telemetry: TelemetryClient; private readonly activeSessions = new Map(); - private readonly rpc: SDKRpcClient; - - constructor(options: KimiHarnessOptions) { - this.identity = - options.identity === undefined ? undefined : assertKimiHostIdentity(options.identity); + private readonly ensureConfigFileImpl: () => Promise; + private readonly closeImpl: () => void | Promise; + + constructor( + private readonly rpc: SDKRpcClientBase, + options: KimiHarnessRuntimeOptions, + ) { + this.identity = options.identity; this.uiMode = options.uiMode ?? DEFAULT_SESSION_STARTED_UI_MODE; - this.homeDir = resolveKimiHome(options.homeDir); - this.configPath = resolveConfigPath({ - homeDir: this.homeDir, - configPath: options.configPath, - }); - this.configureLogging(); - this.telemetry = options.telemetry ?? noopTelemetryClient; - this.auth = new KimiAuthFacade({ - homeDir: this.homeDir, - configPath: this.configPath, - identity: this.identity, - onRefresh: options.onOAuthRefresh, - }); - this.rpc = new SDKRpcClient({ - homeDir: options.homeDir, - configPath: this.configPath, - identity: this.identity, - resolveOAuthTokenProvider: this.auth.resolveOAuthTokenProvider, - skillDirs: options.skillDirs, - telemetry: this.telemetry, - }); - } - - private configureLogging(): void { - // Fresh configure completes synchronously on the first-time path; pre-init - // noop covers any caller that races before this returns. - void getRootLogger().configure(resolveLoggingConfig({ homeDir: this.homeDir })); + this.homeDir = options.homeDir; + this.configPath = options.configPath; + this.telemetry = options.telemetry; + this.auth = options.auth; + this.ensureConfigFileImpl = options.ensureConfigFile; + this.closeImpl = options.onClose; } get sessions(): ReadonlyMap { @@ -198,7 +183,7 @@ export class KimiHarness { } async ensureConfigFile(): Promise { - await ensureConfigFile(this.configPath); + await this.ensureConfigFileImpl(); } async setConfig(patch: KimiConfigPatch): Promise { @@ -211,11 +196,7 @@ export class KimiHarness { async close(): Promise { await Promise.all(Array.from(this.activeSessions.values(), (session) => session.close())); - try { - await getRootLogger().flush(); - } catch { - // never let logger flush block process exit - } + await this.closeImpl(); } private trackSessionEvent(eventSessionId: string, event: string): void { diff --git a/packages/node-sdk/src/rpc.ts b/packages/node-sdk/src/rpc.ts index ed57965bb6..ef2563ab2e 100644 --- a/packages/node-sdk/src/rpc.ts +++ b/packages/node-sdk/src/rpc.ts @@ -1,25 +1,19 @@ import { - createRPC, ErrorCodes, - KimiCore, makeErrorPayload, - resolveKimiHome, type AgentContextData, type ApprovalRequest, type ApprovalResponse, type CoreAPI, type Event, type ExperimentalFlagMap, - type OAuthTokenProviderResolver, type QuestionRequest, type QuestionResult, + type RPCMethods, type SDKAPI, - type SDKRPCClient, - type TelemetryClient, type ToolCallRequest, type ToolCallResponse, } from '@moonshot-ai/agent-core'; -import { createKimiDefaultHeaders } from '@moonshot-ai/kimi-code-oauth'; import type { ApprovalHandler, QuestionHandler } from '#/events'; import type { @@ -52,20 +46,10 @@ import type { SessionSummary, SkillSummary, Unsubscribe, - KimiHostIdentity, } from '#/types'; const MAIN_AGENT_ID = 'main'; -export interface SDKRpcClientOptions { - readonly homeDir?: string | undefined; - readonly configPath?: string | undefined; - readonly identity?: KimiHostIdentity | undefined; - readonly resolveOAuthTokenProvider?: OAuthTokenProviderResolver | undefined; - readonly skillDirs?: readonly string[]; - readonly telemetry?: TelemetryClient | undefined; -} - export interface SessionPromptRpcInput { readonly sessionId: string; readonly input: PromptInput; @@ -105,45 +89,15 @@ export interface ReconnectMcpServerRpcInput extends SessionIdRpcInput { readonly name: string; } -type ResolvedCoreAPI = Awaited>; +type ResolvedCoreAPI = RPCMethods; -export class SDKRpcClient { - readonly core: KimiCore; +export abstract class SDKRpcClientBase { interactiveAgentId = MAIN_AGENT_ID; - private readonly ready: Promise; - private rpc: ResolvedCoreAPI | undefined; private readonly eventListeners = new Set<(event: Event) => void>(); private readonly approvalHandlers = new Map(); private readonly questionHandlers = new Map(); - constructor(options: SDKRpcClientOptions = {}) { - const [coreRpc, sdkRpc] = createRPC(); - const homeDir = resolveKimiHome(options.homeDir); - const kimiRequestHeaders = - options.identity === undefined - ? undefined - : createKimiDefaultHeaders({ homeDir, ...options.identity }); - this.core = new KimiCore(coreRpc, { - homeDir: options.homeDir, - configPath: options.configPath, - kimiRequestHeaders, - resolveOAuthTokenProvider: options.resolveOAuthTokenProvider, - skillDirs: options.skillDirs, - telemetry: options.telemetry, - appVersion: options.identity?.version, - }); - this.ready = sdkRpc(new ClientAPI(this)).then((rpc) => { - this.rpc = rpc; - }); - } - - get homeDir(): string { - return this.core.homeDir; - } - - get configPath(): string { - return this.core.configPath; - } + protected abstract getRpc(): Promise; async createSession(input: CreateSessionOptions): Promise { const rpc = await this.getRpc(); @@ -610,17 +564,10 @@ export class SDKRpcClient { }; } - private async getRpc(): Promise { - await this.ready; - if (this.rpc === undefined) { - throw new Error('SDK RPC client was not initialized.'); - } - return this.rpc; - } } export class ClientAPI implements SDKAPI { - constructor(readonly client: SDKRpcClient) {} + constructor(readonly client: SDKRpcClientBase) {} emitEvent(event: Event): void { this.client.receiveEvent(event); diff --git a/packages/node-sdk/src/sdk-rpc-client.ts b/packages/node-sdk/src/sdk-rpc-client.ts new file mode 100644 index 0000000000..010b29ecad --- /dev/null +++ b/packages/node-sdk/src/sdk-rpc-client.ts @@ -0,0 +1,113 @@ +import { + createRPC, + ensureConfigFile, + getRootLogger, + KimiCore, + noopTelemetryClient, + resolveConfigPath, + resolveKimiHome, + resolveLoggingConfig, + type CoreAPI, + type OAuthTokenProviderResolver, + type RPCMethods, + type SDKAPI, + type TelemetryClient, +} from '@moonshot-ai/agent-core'; +import { assertKimiHostIdentity, createKimiDefaultHeaders } from '@moonshot-ai/kimi-code-oauth'; + +import { KimiAuthFacade } from '#/auth'; +import { KimiHarness } from '#/kimi-harness'; +import { ClientAPI, SDKRpcClientBase } from '#/rpc'; +import type { KimiHarnessOptions, KimiHostIdentity, OAuthRefreshOutcome } from '#/types'; + +export interface SDKRpcClientOptions { + readonly homeDir?: string; + readonly configPath?: string; + readonly identity?: KimiHostIdentity; + readonly resolveOAuthTokenProvider?: OAuthTokenProviderResolver; + readonly skillDirs?: readonly string[]; + readonly telemetry?: TelemetryClient; + readonly onOAuthRefresh?: (outcome: OAuthRefreshOutcome) => void; +} + +export class SDKRpcClient extends SDKRpcClientBase { + readonly homeDir: string; + readonly configPath: string; + readonly identity: KimiHostIdentity | undefined; + readonly telemetry: TelemetryClient; + readonly auth: KimiAuthFacade; + readonly core: KimiCore; + + private readonly ready: Promise>; + + constructor(options: SDKRpcClientOptions = {}) { + super(); + this.identity = + options.identity === undefined ? undefined : assertKimiHostIdentity(options.identity); + this.homeDir = resolveKimiHome(options.homeDir); + this.configPath = resolveConfigPath({ + homeDir: this.homeDir, + configPath: options.configPath, + }); + this.telemetry = options.telemetry ?? noopTelemetryClient; + this.auth = new KimiAuthFacade({ + homeDir: this.homeDir, + configPath: this.configPath, + identity: this.identity, + onRefresh: options.onOAuthRefresh, + }); + + void getRootLogger().configure(resolveLoggingConfig({ homeDir: this.homeDir })); + + const [coreRpc, sdkRpc] = createRPC(); + this.core = new KimiCore(coreRpc, { + homeDir: options.homeDir, + configPath: this.configPath, + kimiRequestHeaders: this.createKimiRequestHeaders(), + resolveOAuthTokenProvider: + options.resolveOAuthTokenProvider ?? this.auth.resolveOAuthTokenProvider, + skillDirs: options.skillDirs, + telemetry: this.telemetry, + appVersion: this.identity?.version, + }); + this.ready = sdkRpc(new ClientAPI(this)); + } + + async ensureConfigFile(): Promise { + await ensureConfigFile(this.configPath); + } + + async close(): Promise { + try { + await getRootLogger().flush(); + } catch { + // never let logger flush block process exit + } + } + + protected async getRpc(): Promise> { + return this.ready; + } + + private createKimiRequestHeaders(): Record | undefined { + if (this.identity === undefined) return undefined; + return createKimiDefaultHeaders({ + homeDir: this.homeDir, + ...this.identity, + }); + } +} + +export function createKimiHarness(options: KimiHarnessOptions): KimiHarness { + const rpc = new SDKRpcClient(options); + return new KimiHarness(rpc, { + identity: rpc.identity, + uiMode: options.uiMode, + homeDir: rpc.homeDir, + configPath: rpc.configPath, + auth: rpc.auth, + telemetry: rpc.telemetry, + ensureConfigFile: () => rpc.ensureConfigFile(), + onClose: () => rpc.close(), + }); +} diff --git a/packages/node-sdk/src/session.ts b/packages/node-sdk/src/session.ts index faa6b940d4..be224af65e 100644 --- a/packages/node-sdk/src/session.ts +++ b/packages/node-sdk/src/session.ts @@ -1,6 +1,12 @@ -import { ErrorCodes, KimiError, type AgentContextData, type KimiErrorCode } from '@moonshot-ai/agent-core'; +import { + ErrorCodes, + KimiError, + type AgentContextData, + type KimiErrorCode, +} from '@moonshot-ai/agent-core'; + import { type ApprovalHandler, type Event, type QuestionHandler } from '#/events'; -import type { SDKRpcClient } from '#/rpc'; +import type { SDKRpcClientBase } from '#/rpc'; import type { BackgroundTaskInfo, CompactOptions, @@ -30,7 +36,7 @@ export interface SessionOptions { readonly workDir: string; readonly summary?: SessionSummary | undefined; readonly resumeState?: ResumedSessionState | undefined; - readonly rpc: SDKRpcClient; + readonly rpc: SDKRpcClientBase; readonly onClose?: (() => void | Promise) | undefined; } @@ -40,7 +46,7 @@ export class Session { readonly summary?: SessionSummary | undefined; private readonly resumeState: ResumedSessionState | undefined; - private readonly rpc: SDKRpcClient; + private readonly rpc: SDKRpcClientBase; private readonly onClose?: (() => void | Promise) | undefined; private closed = false; diff --git a/packages/node-sdk/test/auth-facade.test.ts b/packages/node-sdk/test/auth-facade.test.ts index 635d3e21bb..2b493a755a 100644 --- a/packages/node-sdk/test/auth-facade.test.ts +++ b/packages/node-sdk/test/auth-facade.test.ts @@ -5,7 +5,7 @@ import { join } from 'node:path'; import { FileTokenStorage, KIMI_CODE_PROVIDER_NAME, type TokenInfo } from '@moonshot-ai/kimi-code-oauth'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { KimiHarness } from '#/index'; +import { createKimiHarness } from '#/index'; import { ProviderManager } from '../../agent-core/src/session/provider-manager'; import { TEST_IDENTITY } from './test-identity'; @@ -39,12 +39,12 @@ afterEach(async () => { describe('KimiHarness.auth', () => { it('can construct auth facade without host identity', () => { - expect(() => new KimiHarness({ homeDir })).not.toThrow(); + expect(() => createKimiHarness({ homeDir })).not.toThrow(); }); it('exposes a cached access token without refreshing auth state', async () => { await new FileTokenStorage(join(homeDir, 'credentials')).save('kimi-code', freshToken()); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); await expect(harness.auth.getCachedAccessToken()).resolves.toBe('oauth-access-token'); }); @@ -71,7 +71,7 @@ describe('KimiHarness.auth', () => { ); vi.stubGlobal('fetch', fetchMock); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); const result = await harness.auth.login(); const config = await harness.getConfig({ reload: true }); @@ -147,7 +147,7 @@ model = "kimi-for-coding" ), ); - expect(() => new KimiHarness({ homeDir, identity: TEST_IDENTITY })).toThrow( + expect(() => createKimiHarness({ homeDir, identity: TEST_IDENTITY })).toThrow( /Model "kimi-code\/kimi-for-coding" must define a positive max_context_size/, ); }); @@ -190,7 +190,7 @@ oauth = { storage = "file", key = "oauth/kimi-code" } `, ); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); await expect(harness.auth.logout()).resolves.toMatchObject({ providerName: KIMI_CODE_PROVIDER_NAME, @@ -228,7 +228,7 @@ oauth = { storage = "file", key = "oauth/kimi-code" } ); vi.stubGlobal('fetch', fetchMock); - const harness = new KimiHarness({ homeDir }); + const harness = createKimiHarness({ homeDir }); const result = await harness.auth.getManagedUsage(); expect(result).toMatchObject({ @@ -248,7 +248,7 @@ oauth = { storage = "file", key = "oauth/kimi-code" } const fetchMock = vi.fn(async () => new Response('', { status: 200 })); vi.stubGlobal('fetch', fetchMock); - const harness = new KimiHarness({ homeDir }); + const harness = createKimiHarness({ homeDir }); const result = await harness.auth.submitFeedback({ content: 'great tool', sessionId: 'sess-42', @@ -290,7 +290,7 @@ oauth = { storage = "file", key = "oauth/kimi-code" } ), ); - const harness = new KimiHarness({ homeDir }); + const harness = createKimiHarness({ homeDir }); const result = await harness.auth.submitFeedback({ content: 'x', sessionId: 's', diff --git a/packages/node-sdk/test/config.test.ts b/packages/node-sdk/test/config.test.ts index 3518e848d5..3ec7986592 100644 --- a/packages/node-sdk/test/config.test.ts +++ b/packages/node-sdk/test/config.test.ts @@ -4,7 +4,7 @@ import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; -import { KimiError, KimiHarness } from '#/index'; +import { createKimiHarness, KimiError } from '#/index'; import { parseConfigString, @@ -223,7 +223,7 @@ describe('KimiHarness config API', () => { const configPath = join(homeDir, 'config.toml'); await writeFile(configPath, COMPLETE_TOML, 'utf-8'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); await harness.setConfig({ providers: { @@ -260,7 +260,7 @@ describe('KimiHarness config API', () => { await writeFile(configPath, COMPLETE_TOML, 'utf-8'); const before = await readFile(configPath, 'utf-8'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); const setInvalidConfig = harness.setConfig({ providers: { @@ -280,7 +280,7 @@ describe('KimiHarness config API', () => { it('uses default config when the config file is absent', async () => { const homeDir = await makeTempDir(); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); await expect(harness.getConfig()).resolves.toEqual({ providers: {} }); }); @@ -288,7 +288,7 @@ describe('KimiHarness config API', () => { it('can create the default config scaffold without selecting a model', async () => { const homeDir = await makeTempDir(); const configPath = join(homeDir, 'config.toml'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); await harness.ensureConfigFile(); diff --git a/packages/node-sdk/test/create-session-transport.test.ts b/packages/node-sdk/test/create-session-transport.test.ts index b46ff7f55a..eb35de8e8b 100644 --- a/packages/node-sdk/test/create-session-transport.test.ts +++ b/packages/node-sdk/test/create-session-transport.test.ts @@ -3,8 +3,8 @@ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { KimiHarness } from '#/index'; -import type { KimiError } from '#/index'; +import { createKimiHarness } from '#/index'; +import type { KimiError, KimiHarness } from '#/index'; import { afterEach, describe, expect, it } from 'vitest'; import { waitForAgentWireEvent } from './session-runtime-helpers'; @@ -48,7 +48,7 @@ describe('KimiHarness.createSession transport link', () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); const records: TelemetryRecord[] = []; - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, telemetry: recordingTelemetry(records), @@ -106,7 +106,7 @@ describe('KimiHarness.createSession transport link', () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); const records: TelemetryRecord[] = []; - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, telemetry: recordingTelemetry(records), @@ -138,7 +138,7 @@ describe('KimiHarness.createSession transport link', () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); const records: TelemetryRecord[] = []; - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, telemetry: recordingTelemetry(records), @@ -180,7 +180,7 @@ describe('KimiHarness.createSession transport link', () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); const records: TelemetryRecord[] = []; - const harness = new KimiHarness({ + const harness = createKimiHarness({ homeDir, telemetry: recordingTelemetry(records), }); @@ -210,7 +210,7 @@ describe('KimiHarness.createSession transport link', () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); await writeTestModelConfig(homeDir); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -280,7 +280,7 @@ effort = "medium" `, 'utf-8', ); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -309,7 +309,7 @@ effort = "medium" it('does not require provider config or API keys before prompt is implemented', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -326,7 +326,7 @@ effort = "medium" it('requires a non-empty workDir on createSession', async () => { const homeDir = await makeTempDir(); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { await expect( @@ -352,7 +352,7 @@ effort = "medium" // Project-local mcp.json is intentionally ignored, so plant the malformed // file under the user home dir where the loader actually reads from. await writeFile(join(homeDir, 'mcp.json'), '{not json}', 'utf-8'); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -375,7 +375,7 @@ effort = "medium" const homeDir = await makeTempDir(); const workDir = await makeTempDir(); await writeTestModelConfig(homeDir); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -408,7 +408,7 @@ effort = "medium" it('applies initial thinking and permission runtime options', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -452,7 +452,7 @@ effort = "medium" const homeDir = await makeTempDir(); const workDir = await makeTempDir(); await writeFile(join(homeDir, 'config.toml'), 'default_permission_mode = "auto"\n', 'utf-8'); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); diff --git a/packages/node-sdk/test/export-session.test.ts b/packages/node-sdk/test/export-session.test.ts index 58d65cd182..e1459ad06f 100644 --- a/packages/node-sdk/test/export-session.test.ts +++ b/packages/node-sdk/test/export-session.test.ts @@ -7,8 +7,8 @@ import * as zlib from 'node:zlib'; import { afterEach, describe, expect, it } from 'vitest'; import { + createKimiHarness, KimiError, - KimiHarness, type SessionSummary, } from '#/index'; import { resolveGlobalLogPath } from '../../agent-core/src/logging/logger'; @@ -266,7 +266,7 @@ describe('KimiHarness.exportSession', () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); const records: TelemetryRecord[] = []; - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, telemetry: recordingTelemetry(records), @@ -301,7 +301,7 @@ describe('KimiHarness.exportSession', () => { it('rejects missing session ids', async () => { const homeDir = await makeTempDir(); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); const missingExport = harness.exportSession({ id: 'ses_missing', version: '1.0.0-test' }); await expect(missingExport).rejects.toBeInstanceOf(KimiError); diff --git a/packages/node-sdk/test/list-sessions.test.ts b/packages/node-sdk/test/list-sessions.test.ts index f9c5775492..ed958d1fd8 100644 --- a/packages/node-sdk/test/list-sessions.test.ts +++ b/packages/node-sdk/test/list-sessions.test.ts @@ -5,7 +5,7 @@ import { basename, dirname, join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; -import { KimiHarness } from '#/index'; +import { createKimiHarness } from '#/index'; import type { KimiError } from '#/index'; import { @@ -273,7 +273,7 @@ describe('SessionStore.list', () => { describe('KimiHarness.listSessions', () => { it('rejects whitespace-only workDir with request.work_dir_required', async () => { const homeDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -292,7 +292,7 @@ describe('KimiHarness.listSessions', () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); const otherWorkDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -314,7 +314,7 @@ describe('KimiHarness.listSessions', () => { it('resolves relative workDir inputs before filtering', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -335,7 +335,7 @@ describe('KimiHarness.listSessions', () => { it('lists persisted sessions after the active Session has been closed', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); diff --git a/packages/node-sdk/test/local-logging.test.ts b/packages/node-sdk/test/local-logging.test.ts index 7e73e929d1..217e02afa6 100644 --- a/packages/node-sdk/test/local-logging.test.ts +++ b/packages/node-sdk/test/local-logging.test.ts @@ -5,7 +5,7 @@ import * as zlib from 'node:zlib'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { KimiHarness, log } from '#/index'; +import { createKimiHarness, log } from '#/index'; import { __resetRootLoggerForTest, getRootLogger } from '../../agent-core/src/logging/logger'; import { TEST_IDENTITY } from './test-identity'; @@ -104,7 +104,7 @@ describe('Local logging — harness integration', () => { const homeDir = await makeTempDir('kimi-log-home-'); const workDir = await makeTempDir('kimi-log-work-'); - const harness = new KimiHarness({ identity: TEST_IDENTITY, homeDir }); + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir }); const session = await harness.createSession({ id: 'ses_logging_int', workDir, @@ -142,7 +142,7 @@ describe('Local logging — harness integration', () => { it('default export bundles session log only; no globalLogPath in manifest', async () => { const homeDir = await makeTempDir('kimi-log-home-'); const workDir = await makeTempDir('kimi-log-work-'); - const harness = new KimiHarness({ identity: TEST_IDENTITY, homeDir }); + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir }); const session = await harness.createSession({ id: 'ses_default_export', workDir }); log.warn('session export marker', { sessionId: session.id }); @@ -170,7 +170,7 @@ describe('Local logging — harness integration', () => { it('default export works when no session log file exists', async () => { const homeDir = await makeTempDir('kimi-log-home-'); const workDir = await makeTempDir('kimi-log-work-'); - const harness = new KimiHarness({ identity: TEST_IDENTITY, homeDir }); + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir }); const session = await harness.createSession({ id: 'ses_no_session_log', workDir }); const outputPath = join(workDir, 'no-log.zip'); @@ -195,7 +195,7 @@ describe('Local logging — harness integration', () => { try { const homeDir = await makeTempDir('kimi-log-home-'); const workDir = await makeTempDir('kimi-log-work-'); - const harness = new KimiHarness({ identity: TEST_IDENTITY, homeDir }); + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir }); const session = await harness.createSession({ id: 'ses_rotated_export', workDir }); for (let i = 0; i < 16; i++) { log.warn(`rotated session marker ${i}`, { @@ -230,7 +230,7 @@ describe('Local logging — harness integration', () => { it('--include-global-log bundles global active and sets manifest field', async () => { const homeDir = await makeTempDir('kimi-log-home-'); const workDir = await makeTempDir('kimi-log-work-'); - const harness = new KimiHarness({ identity: TEST_IDENTITY, homeDir }); + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir }); const session = await harness.createSession({ id: 'ses_global_export', workDir }); log.warn('untagged probe'); @@ -256,9 +256,9 @@ describe('Local logging — harness integration', () => { const firstHome = await makeTempDir('kimi-log-home-a-'); const secondHome = await makeTempDir('kimi-log-home-b-'); const workDir = await makeTempDir('kimi-log-work-'); - const first = new KimiHarness({ identity: TEST_IDENTITY, homeDir: firstHome }); + const first = createKimiHarness({ identity: TEST_IDENTITY, homeDir: firstHome }); const firstSession = await first.createSession({ id: 'ses_first_global_export', workDir }); - const second = new KimiHarness({ identity: TEST_IDENTITY, homeDir: secondHome }); + const second = createKimiHarness({ identity: TEST_IDENTITY, homeDir: secondHome }); log.warn('active-global-export-marker'); await getRootLogger().flushGlobal(); @@ -283,7 +283,7 @@ describe('Local logging — harness integration', () => { it('logs export flush failures without failing the export', async () => { const homeDir = await makeTempDir('kimi-log-home-'); const workDir = await makeTempDir('kimi-log-work-'); - const harness = new KimiHarness({ identity: TEST_IDENTITY, homeDir }); + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir }); const session = await harness.createSession({ id: 'ses_flush_warning', workDir }); log.warn('flush warning setup', { sessionId: session.id }); log.warn('global untagged marker'); @@ -318,16 +318,16 @@ describe('Local logging — harness integration', () => { it('multiple KimiHarness constructions in the same process do not throw', async () => { const homeDir = await makeTempDir('kimi-log-home-'); - expect(() => new KimiHarness({ identity: TEST_IDENTITY, homeDir })).not.toThrow(); - expect(() => new KimiHarness({ identity: TEST_IDENTITY, homeDir })).not.toThrow(); - expect(() => new KimiHarness({ identity: TEST_IDENTITY, homeDir })).not.toThrow(); + expect(() => createKimiHarness({ identity: TEST_IDENTITY, homeDir })).not.toThrow(); + expect(() => createKimiHarness({ identity: TEST_IDENTITY, homeDir })).not.toThrow(); + expect(() => createKimiHarness({ identity: TEST_IDENTITY, homeDir })).not.toThrow(); }); it('uses the latest harness homeDir for global diagnostic logging', async () => { const firstHome = await makeTempDir('kimi-log-home-a-'); const secondHome = await makeTempDir('kimi-log-home-b-'); - const first = new KimiHarness({ identity: TEST_IDENTITY, homeDir: firstHome }); - const second = new KimiHarness({ identity: TEST_IDENTITY, homeDir: secondHome }); + const first = createKimiHarness({ identity: TEST_IDENTITY, homeDir: firstHome }); + const second = createKimiHarness({ identity: TEST_IDENTITY, homeDir: secondHome }); log.warn('second-home-marker'); await getRootLogger().flushGlobal(); @@ -363,7 +363,7 @@ describe('Local logging — harness integration', () => { try { const homeDir = await makeTempDir('kimi-log-home-'); const workDir = await makeTempDir('kimi-log-work-'); - const harness = new KimiHarness({ identity: TEST_IDENTITY, homeDir }); + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir }); await harness.createSession({ id: 'ses_off', workDir }); log.error('this should not write'); let logsDir: string[] = []; @@ -380,7 +380,7 @@ describe('Local logging — harness integration', () => { it('KimiHarness.close() flushes the global log', async () => { const homeDir = await makeTempDir('kimi-log-home-'); - const harness = new KimiHarness({ identity: TEST_IDENTITY, homeDir }); + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir }); log.warn('untagged before close'); // No `await flush()` here on purpose — close() must do it. await harness.close(); diff --git a/packages/node-sdk/test/rename-session.test.ts b/packages/node-sdk/test/rename-session.test.ts index 693b1050f9..446ff4cbe0 100644 --- a/packages/node-sdk/test/rename-session.test.ts +++ b/packages/node-sdk/test/rename-session.test.ts @@ -5,7 +5,7 @@ import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; -import { KimiError, KimiHarness, type Event } from '#/index'; +import { createKimiHarness, KimiError, type Event } from '#/index'; import { SessionStore } from '../../agent-core/src/session/store'; import { TEST_IDENTITY } from './test-identity'; @@ -125,7 +125,7 @@ describe('KimiHarness.renameSession', () => { it('persists titles through the public Harness API and emits an active session event', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -172,7 +172,7 @@ describe('KimiHarness.renameSession', () => { it('renames persisted sessions even when they are not active in memory', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -206,7 +206,7 @@ describe('KimiHarness.renameSession', () => { it('rejects missing session ids', async () => { const homeDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); diff --git a/packages/node-sdk/test/runtime-provider-identity.test.ts b/packages/node-sdk/test/runtime-provider-identity.test.ts index 0d2ef15687..e1156e627c 100644 --- a/packages/node-sdk/test/runtime-provider-identity.test.ts +++ b/packages/node-sdk/test/runtime-provider-identity.test.ts @@ -8,6 +8,7 @@ import type { KimiConfig } from '@moonshot-ai/agent-core'; import { createKimiDefaultHeaders, KIMI_CODE_PLATFORM } from '@moonshot-ai/kimi-code-oauth'; import { ProviderManager } from '../../agent-core/src/session/provider-manager'; +import { SDKRpcClient } from '#/index'; import { TEST_IDENTITY } from './test-identity'; const tempDirs: string[] = []; @@ -41,6 +42,29 @@ afterEach(async () => { }); describe('runtime provider identity headers', () => { + it('preserves the host user agent suffix in SDK RPC headers', async () => { + const homeDir = await makeTempDir(); + const client = new SDKRpcClient({ + homeDir, + identity: { + ...TEST_IDENTITY, + userAgentSuffix: 'web-runtime', + }, + }); + const core = client.core as unknown as { + readonly kimiRequestHeaders?: Record; + }; + + try { + expect(core.kimiRequestHeaders).toMatchObject({ + 'User-Agent': 'kimi-code-cli/0.0.0-test (web-runtime)', + 'X-Msh-Version': '0.0.0-test', + }); + } finally { + await client.close(); + } + }); + it('adds kimi-code-cli User-Agent and complete X-Msh headers to the default Kimi provider', async () => { const homeDir = await makeTempDir(); const kimiRequestHeaders = createKimiDefaultHeaders({ homeDir, ...TEST_IDENTITY }); diff --git a/packages/node-sdk/test/session-approval-handler.test.ts b/packages/node-sdk/test/session-approval-handler.test.ts index 72fd0f34d2..e3d63b08d8 100644 --- a/packages/node-sdk/test/session-approval-handler.test.ts +++ b/packages/node-sdk/test/session-approval-handler.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'; import type { ApprovalHandler, ApprovalRequest, ApprovalResponse } from '#/index'; import { Session } from '#/index'; -import type { SDKRpcClient } from '#/rpc'; +import type { SDKRpcClientBase } from '#/rpc'; describe('Session approval handler', () => { it('registers an approval handler and returns approved responses', async () => { @@ -183,8 +183,8 @@ class FakeSDKRpcClient { private readonly approvalHandlers = new Map(); readonly closeSession = vi.fn(async (_input: { readonly sessionId: string }) => {}); - asRpc(): SDKRpcClient { - return this as unknown as SDKRpcClient; + asRpc(): SDKRpcClientBase { + return this as unknown as SDKRpcClientBase; } setApprovalHandler(sessionId: string, handler: ApprovalHandler | undefined): void { diff --git a/packages/node-sdk/test/session-background-tasks.test.ts b/packages/node-sdk/test/session-background-tasks.test.ts index 72293b8a8d..bc124a6d70 100644 --- a/packages/node-sdk/test/session-background-tasks.test.ts +++ b/packages/node-sdk/test/session-background-tasks.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from 'vitest'; -import { KimiHarness, type KimiError } from '#/index'; +import { createKimiHarness, type KimiError } from '#/index'; import { makeTempDir, removeTempDirs } from './session-runtime-helpers'; import { TEST_IDENTITY } from './test-identity'; @@ -15,7 +15,7 @@ describe('Session.listBackgroundTasks / getBackgroundTaskOutput', () => { it('lists an empty task set for a fresh session', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_bg_list_empty', workDir }); @@ -32,7 +32,7 @@ describe('Session.listBackgroundTasks / getBackgroundTaskOutput', () => { it('returns empty output for an unknown task id', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_bg_unknown', workDir }); @@ -46,7 +46,7 @@ describe('Session.listBackgroundTasks / getBackgroundTaskOutput', () => { it('rejects empty task ids with a stable error code', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_bg_empty_id', workDir }); @@ -66,7 +66,7 @@ describe('Session.listBackgroundTasks / getBackgroundTaskOutput', () => { it('rejects after the session is closed', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_bg_closed', workDir }); @@ -92,7 +92,7 @@ describe('Session.listBackgroundTasks / getBackgroundTaskOutput', () => { it('stopBackgroundTask is a no-op for an unknown task id', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-bgtask-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_bg_stop_unknown', workDir }); diff --git a/packages/node-sdk/test/session-cancel.test.ts b/packages/node-sdk/test/session-cancel.test.ts index 51899247ab..efb1ce1c81 100644 --- a/packages/node-sdk/test/session-cancel.test.ts +++ b/packages/node-sdk/test/session-cancel.test.ts @@ -4,7 +4,7 @@ import { join } from 'node:path'; import type * as KosongModule from '@moonshot-ai/kosong'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import type { KimiError, Event } from '#/index'; +import { createKimiHarness, type KimiError, type Event } from '#/index'; import { makeTempDir, removeTempDirs, waitForSDKEvent } from './session-runtime-helpers'; import { TEST_IDENTITY } from './test-identity'; @@ -33,8 +33,6 @@ vi.mock('@moonshot-ai/kosong', async (importOriginal) => { }; }); -const { KimiHarness } = await import('#/index'); - const tempDirs: string[] = []; afterEach(async () => { @@ -46,7 +44,7 @@ describe('Session.cancel', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-cancel-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-cancel-work-'); await writeFakeModelConfig(homeDir); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_cancel_active_turn', workDir }); @@ -84,7 +82,7 @@ describe('Session.cancel', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-cancel-compact-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-cancel-compact-work-'); await writeFakeModelConfig(homeDir); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_cancel_compaction', workDir }); @@ -101,7 +99,7 @@ describe('Session.cancel', () => { it('rejects after the session is closed', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-cancel-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-cancel-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_cancel_closed', workDir }); diff --git a/packages/node-sdk/test/session-goal.test.ts b/packages/node-sdk/test/session-goal.test.ts index 15afa42e7e..dcfd76332f 100644 --- a/packages/node-sdk/test/session-goal.test.ts +++ b/packages/node-sdk/test/session-goal.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; import { Session } from '#/session'; -import type { SDKRpcClient } from '#/rpc'; +import type { SDKRpcClientBase } from '#/rpc'; function makeSession() { const rpc = { @@ -11,7 +11,7 @@ function makeSession() { resumeGoal: vi.fn(async () => ({ goalId: 'g1' })), cancelGoal: vi.fn(async () => ({ goalId: 'g1' })), clearSessionHandlers: vi.fn(), - } as unknown as SDKRpcClient; + } as unknown as SDKRpcClientBase; const session = new Session({ id: 'ses_goal', workDir: '/tmp/work', rpc }); return { session, rpc }; } diff --git a/packages/node-sdk/test/session-plan-compact-usage-resume.test.ts b/packages/node-sdk/test/session-plan-compact-usage-resume.test.ts index 11a8c12548..366bb2df74 100644 --- a/packages/node-sdk/test/session-plan-compact-usage-resume.test.ts +++ b/packages/node-sdk/test/session-plan-compact-usage-resume.test.ts @@ -3,7 +3,7 @@ import { dirname, join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; -import { KimiHarness, type Event, type KimiError } from '#/index'; +import { createKimiHarness, type Event, type KimiError } from '#/index'; import { makeTempDir, removeTempDirs } from './session-runtime-helpers'; import { TEST_IDENTITY } from './test-identity'; @@ -19,7 +19,7 @@ describe('Session plan, compact, usage, and resume APIs', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-plan-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-plan-work-'); await writeTestConfig(homeDir); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_plan_runtime', workDir }); @@ -58,7 +58,7 @@ describe('Session plan, compact, usage, and resume APIs', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-plan-toggle-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-plan-toggle-work-'); await writeTestConfig(homeDir); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_plan_toggle_runtime', workDir }); @@ -86,7 +86,7 @@ describe('Session plan, compact, usage, and resume APIs', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-compact-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-compact-work-'); await writeTestConfig(homeDir); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_compact_runtime', workDir }); @@ -104,7 +104,7 @@ describe('Session plan, compact, usage, and resume APIs', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-usage-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-usage-work-'); await writeTestConfig(homeDir); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_usage_runtime', workDir }); @@ -119,7 +119,7 @@ describe('Session plan, compact, usage, and resume APIs', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-resume-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-resume-work-'); await writeTestConfig(homeDir); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const created = await harness.createSession({ @@ -156,7 +156,7 @@ describe('Session plan, compact, usage, and resume APIs', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-resume-legacy-plan-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-resume-legacy-plan-work-'); await writeTestConfig(homeDir); - const createdHarness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const createdHarness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); let sessionId = ''; let sessionDir = ''; @@ -177,7 +177,7 @@ describe('Session plan, compact, usage, and resume APIs', () => { await removeManualPlanIds(sessionDir); - const resumedHarness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const resumedHarness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const resumed = await resumedHarness.resumeSession({ id: sessionId }); @@ -194,7 +194,7 @@ describe('Session plan, compact, usage, and resume APIs', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-fork-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-fork-work-'); await writeTestConfig(homeDir); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const source = await harness.createSession({ @@ -264,7 +264,7 @@ describe('Session plan, compact, usage, and resume APIs', () => { it('rejects an empty resume id', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-resume-empty-home-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { await expect(harness.resumeSession({ id: ' ' })).rejects.toMatchObject({ diff --git a/packages/node-sdk/test/session-prompt-events.test.ts b/packages/node-sdk/test/session-prompt-events.test.ts index 98c39aaf29..5cf4167bc9 100644 --- a/packages/node-sdk/test/session-prompt-events.test.ts +++ b/packages/node-sdk/test/session-prompt-events.test.ts @@ -8,7 +8,7 @@ import { KIMI_CODE_PLATFORM } from '@moonshot-ai/kimi-code-oauth'; import type * as KosongModule from '@moonshot-ai/kosong'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { Event } from '#/index'; +import { createKimiHarness, type Event, type KimiHarness } from '#/index'; import { TEST_IDENTITY } from './test-identity'; @@ -56,8 +56,6 @@ vi.mock('@moonshot-ai/kosong', async (importOriginal) => { }; }); -const { KimiHarness } = await import('#/index'); - const tempDirs: string[] = []; beforeEach(() => { @@ -99,7 +97,7 @@ describe('Session.prompt events', () => { it('persists sanitized prompt metadata without marking the title custom', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -175,7 +173,7 @@ describe('Session.prompt events', () => { it('emits mapped turn events through Session.onEvent', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -228,7 +226,7 @@ describe('Session.prompt events', () => { it('supports onEvent unsubscribe without touching runtime wire directly', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -255,7 +253,7 @@ describe('Session.prompt events', () => { it('runs init through generateAgentsMd RPC as a system trigger without prompt metadata updates', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -314,7 +312,7 @@ describe('Session.prompt events', () => { it('rejects empty prompt input', async () => { const homeDir = await makeTempDir(); const workDir = await makeTempDir(); - const harness = new KimiHarness({ + const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, }); @@ -331,7 +329,7 @@ describe('Session.prompt events', () => { }); }); -async function configureFakeProvider(harness: InstanceType): Promise { +async function configureFakeProvider(harness: KimiHarness): Promise { await harness.setConfig({ providers: { local: { diff --git a/packages/node-sdk/test/session-prompt-input.test.ts b/packages/node-sdk/test/session-prompt-input.test.ts index df32e2b4be..1874910ea2 100644 --- a/packages/node-sdk/test/session-prompt-input.test.ts +++ b/packages/node-sdk/test/session-prompt-input.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; -import type { SDKRpcClient } from '../src/rpc'; +import type { SDKRpcClientBase } from '../src/rpc'; import { Session } from '../src/session'; describe('Session.prompt input normalization', () => { @@ -9,7 +9,7 @@ describe('Session.prompt input normalization', () => { const session = new Session({ id: 'ses_multimodal_prompt', workDir: '/tmp/work', - rpc: { prompt } as unknown as SDKRpcClient, + rpc: { prompt } as unknown as SDKRpcClientBase, }); const input = [ { type: 'text', text: 'describe these' }, diff --git a/packages/node-sdk/test/session-question-handler.test.ts b/packages/node-sdk/test/session-question-handler.test.ts index 807202b32f..8d0c2855b7 100644 --- a/packages/node-sdk/test/session-question-handler.test.ts +++ b/packages/node-sdk/test/session-question-handler.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; import { Session, type QuestionHandler, type QuestionRequest, type QuestionResult } from '#/index'; -import type { SDKRpcClient } from '#/rpc'; +import type { SDKRpcClientBase } from '#/rpc'; describe('Session question handler', () => { it('registers a question handler and returns handler results', async () => { @@ -116,8 +116,8 @@ class FakeSDKRpcClient { private readonly questionHandlers = new Map(); readonly closeSession = vi.fn(async (_input: { readonly sessionId: string }) => {}); - asRpc(): SDKRpcClient { - return this as unknown as SDKRpcClient; + asRpc(): SDKRpcClientBase { + return this as unknown as SDKRpcClientBase; } setQuestionHandler(sessionId: string, handler: QuestionHandler | undefined): void { diff --git a/packages/node-sdk/test/session-set-model.test.ts b/packages/node-sdk/test/session-set-model.test.ts index b3bcdcf837..b0bf7f65af 100644 --- a/packages/node-sdk/test/session-set-model.test.ts +++ b/packages/node-sdk/test/session-set-model.test.ts @@ -3,7 +3,7 @@ import { join } from 'node:path'; import { FileTokenStorage, type TokenInfo } from '@moonshot-ai/kimi-code-oauth'; import { afterEach, describe, expect, it } from 'vitest'; -import { KimiHarness, type KimiError } from '#/index'; +import { createKimiHarness, type KimiError, type KimiHarness } from '#/index'; import { makeTempDir, removeTempDirs, waitForAgentWireEvent } from './session-runtime-helpers'; import { TEST_IDENTITY } from './test-identity'; @@ -28,7 +28,7 @@ describe('Session.setModel', () => { it('updates the runtime model and sends config.update with the resolved model', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-model-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-model-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { await configureLocalProvider(harness); @@ -61,7 +61,7 @@ describe('Session.setModel', () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-model-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-model-work-'); await new FileTokenStorage(join(homeDir, 'credentials')).save('kimi-code', freshToken()); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { await harness.setConfig({ @@ -117,7 +117,7 @@ describe('Session.setModel', () => { it('rejects empty model names', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-model-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-model-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { await configureLocalProvider(harness); @@ -135,7 +135,7 @@ describe('Session.setModel', () => { it('rejects after the session is closed', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-model-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-model-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { await configureLocalProvider(harness); diff --git a/packages/node-sdk/test/session-set-permission.test.ts b/packages/node-sdk/test/session-set-permission.test.ts index bf597364c6..285b9a4722 100644 --- a/packages/node-sdk/test/session-set-permission.test.ts +++ b/packages/node-sdk/test/session-set-permission.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from 'vitest'; -import { KimiHarness, type KimiError, type PermissionMode } from '#/index'; +import { createKimiHarness, type KimiError, type PermissionMode } from '#/index'; import { makeTempDir, removeTempDirs, waitForAgentWireEvent } from './session-runtime-helpers'; import { TEST_IDENTITY } from './test-identity'; @@ -16,7 +16,7 @@ describe('Session.setPermission', () => { async (mode: PermissionMode) => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-permission-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-permission-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ @@ -46,7 +46,7 @@ describe('Session.setPermission', () => { it('rejects invalid permission modes', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-permission-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-permission-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_permission_invalid', workDir }); @@ -63,7 +63,7 @@ describe('Session.setPermission', () => { it('rejects after the session is closed', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-permission-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-permission-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_permission_closed', workDir }); diff --git a/packages/node-sdk/test/session-set-thinking.test.ts b/packages/node-sdk/test/session-set-thinking.test.ts index 70e957a667..6664bb021a 100644 --- a/packages/node-sdk/test/session-set-thinking.test.ts +++ b/packages/node-sdk/test/session-set-thinking.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from 'vitest'; -import { KimiHarness, type KimiError } from '#/index'; +import { createKimiHarness, type KimiError } from '#/index'; import { makeTempDir, removeTempDirs, waitForAgentWireEvent } from './session-runtime-helpers'; import { TEST_IDENTITY } from './test-identity'; @@ -15,7 +15,7 @@ describe('Session.setThinking', () => { it('sends config.update with the new thinking level', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_thinking_wire', workDir }); @@ -41,7 +41,7 @@ describe('Session.setThinking', () => { it('rejects empty thinking levels', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_thinking_empty', workDir }); @@ -58,7 +58,7 @@ describe('Session.setThinking', () => { it('rejects after the session is closed', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-thinking-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_thinking_closed', workDir }); diff --git a/packages/node-sdk/test/session-skills.test.ts b/packages/node-sdk/test/session-skills.test.ts index 235bc75649..e007d431d4 100644 --- a/packages/node-sdk/test/session-skills.test.ts +++ b/packages/node-sdk/test/session-skills.test.ts @@ -4,8 +4,14 @@ import { join } from 'node:path'; import type * as KosongModule from '@moonshot-ai/kosong'; import { afterEach, beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest'; -import type { Event, KimiError, SkillActivatedEvent, SkillSummary } from '#/index'; -import type { SDKRpcClient } from '#/rpc'; +import { + createKimiHarness, + type Event, + type KimiError, + type SkillActivatedEvent, + type SkillSummary, +} from '#/index'; +import type { SDKRpcClientBase } from '#/rpc'; import { makeTempDir, @@ -52,7 +58,7 @@ vi.mock('@moonshot-ai/kosong', async (importOriginal) => { }; }); -const { KimiHarness, Session } = await import('#/index'); +const { Session } = await import('#/index'); const tempDirs: string[] = []; @@ -79,7 +85,7 @@ describe('Session skills', () => { '', 'Review the requested file.', ]); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_sdk_skill_list', workDir }); @@ -111,7 +117,7 @@ describe('Session skills', () => { '', 'Review the requested file.', ]); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_sdk_skill_activate', workDir }); @@ -200,7 +206,7 @@ describe('Session skills', () => { vi.stubEnv('KIMI_CODE_HOME', homeDir); await writeUserSkill(processHome, 'sdk-real-home-only', 'SDK real home skill'); await writeUserSkill(homeDir, 'sdk-sandbox-only', 'SDK sandbox skill'); - const harness = new KimiHarness({ identity: TEST_IDENTITY }); + const harness = createKimiHarness({ identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_sdk_skill_env_home', workDir }); @@ -226,7 +232,7 @@ describe('Session skills', () => { closeSession, clearSessionHandlers, listSkills, - } as unknown as SDKRpcClient, + } as unknown as SDKRpcClientBase, }); await expect(session.activateSkill(' ')).rejects.toMatchObject({ @@ -263,7 +269,7 @@ describe('Session skills', () => { closeSession, clearSessionHandlers, listSkills, - } as unknown as SDKRpcClient, + } as unknown as SDKRpcClientBase, }); await expect(session.close()).rejects.toThrow('flush failed'); diff --git a/packages/node-sdk/test/session-steer.test.ts b/packages/node-sdk/test/session-steer.test.ts index 727dbf18ef..5cceec0580 100644 --- a/packages/node-sdk/test/session-steer.test.ts +++ b/packages/node-sdk/test/session-steer.test.ts @@ -1,7 +1,7 @@ import type * as KosongModule from '@moonshot-ai/kosong'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { KimiError } from '#/index'; +import { createKimiHarness, type KimiError } from '#/index'; import { makeTempDir, removeTempDirs, waitForAgentWireEvent } from './session-runtime-helpers'; import { TEST_IDENTITY } from './test-identity'; @@ -41,8 +41,6 @@ vi.mock('@moonshot-ai/kosong', async (importOriginal) => { }; }); -const { KimiHarness } = await import('#/index'); - const tempDirs: string[] = []; beforeEach(() => { @@ -57,7 +55,7 @@ describe('Session.steer', () => { it('sends turn.steer to the core session runtime', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-steer-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-steer-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_steer_wire', workDir }); @@ -80,7 +78,7 @@ describe('Session.steer', () => { it('rejects empty steer input', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-steer-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-steer-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_steer_empty', workDir }); @@ -97,7 +95,7 @@ describe('Session.steer', () => { it('rejects after the session is closed', async () => { const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-steer-home-'); const workDir = await makeTempDir(tempDirs, 'kimi-sdk-steer-work-'); - const harness = new KimiHarness({ homeDir, identity: TEST_IDENTITY }); + const harness = createKimiHarness({ homeDir, identity: TEST_IDENTITY }); try { const session = await harness.createSession({ id: 'ses_steer_closed', workDir });