kimi-code / packages /node-sdk /test /session-prompt-events.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4e23b01 verified
Raw History Blame Contribute Delete
30.2 kB
import { mkdtemp, readdir, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { setTimeout as delay } from 'node:timers/promises';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createKimiHarness, type Event, type KimiHarness } from '#/index';
import { TEST_IDENTITY } from './test-identity';
const MODEL_URL = 'https://model.example.test/v1/chat/completions';
const fakeProviderState = vi.hoisted(() => ({
calls: [] as Array<{
readonly systemPrompt: string;
readonly history: unknown;
}>,
responseText: 'hello from fake provider',
}));
function sseChunk(delta: Record<string, unknown>, finishReason: string | null = null): string {
return `data: ${JSON.stringify({
id: 'chatcmpl-stub',
object: 'chat.completion.chunk',
created: 0,
model: 'fake-model',
choices: [{ index: 0, delta, finish_reason: finishReason }],
})}`;
}
function sseBody(text: string): string {
return [sseChunk({ role: 'assistant', content: text }), sseChunk({}, 'stop'), 'data: [DONE]']
.map((line) => `${line}\n\n`)
.join('');
}
let fetchStub: ReturnType<typeof vi.spyOn> | undefined;
const tempDirs: string[] = [];
beforeEach(() => {
fakeProviderState.calls.length = 0;
fakeProviderState.responseText = 'hello from fake provider';
fetchStub = vi.spyOn(globalThis, 'fetch').mockImplementation(async (input, init) => {
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
if (url !== MODEL_URL) {
throw new Error(`Unexpected fetch: ${url}`);
}
const body = JSON.parse(typeof init?.body === 'string' ? init.body : '{}') as {
readonly messages?: ReadonlyArray<{ readonly role: string; readonly content: unknown }>;
};
const messages = body.messages ?? [];
const system = messages.find((message) => message.role === 'system');
fakeProviderState.calls.push({
systemPrompt: typeof system?.content === 'string' ? system.content : '',
history: messages,
});
return new Response(sseBody(fakeProviderState.responseText), {
status: 200,
headers: { 'Content-Type': 'text/event-stream' },
});
});
});
afterEach(async () => {
fetchStub?.mockRestore();
fetchStub = undefined;
for (const dir of tempDirs.splice(0)) {
await removeTempDir(dir);
}
});
async function makeTempDir(): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), 'kimi-sdk-prompt-'));
tempDirs.push(dir);
return dir;
}
async function removeTempDir(dir: string): Promise<void> {
for (let attempt = 0; attempt < 10; attempt += 1) {
try {
await rm(dir, { recursive: true, force: true });
return;
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code !== 'ENOTEMPTY' && code !== 'EBUSY' && code !== 'EPERM') {
throw error;
}
await delay(10);
}
}
await rm(dir, { recursive: true, force: true });
}
describe('Session.prompt events', () => {
it('continues notifying sessions after disabling and restarting with identical prompt and tools', async () => {
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '0');
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_NOTIFY_USER', '');
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const requests: Array<{ tools: unknown; messages: Array<{ role: string; content: unknown }> }> =
[];
fetchStub!.mockImplementation(
async (input: Parameters<typeof fetch>[0], init: Parameters<typeof fetch>[1]) => {
const url =
typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
if (url !== MODEL_URL) throw new Error(`Unexpected fetch: ${url}`);
if (typeof init?.body !== 'string') throw new Error('Expected a JSON request body');
requests.push(JSON.parse(init.body));
const body =
requests.length % 2 === 1
? [
sseChunk({
role: 'assistant',
tool_calls: [
{
index: 0,
id: `notify-${requests.length}`,
type: 'function',
function: {
name: 'NotifyUser',
arguments: JSON.stringify({ message: 'Checking the work.' }),
},
},
],
}),
sseChunk({}, 'tool_calls'),
'data: [DONE]',
]
.map((line) => `${line}\n\n`)
.join('')
: sseBody('Work completed.');
return new Response(body, {
status: 200,
headers: { 'Content-Type': 'text/event-stream' },
});
},
);
let harness = createKimiHarness({
identity: TEST_IDENTITY,
homeDir,
uiCapabilities: ['update_panel'],
});
try {
await configureFakeProvider(harness);
await harness.setConfig({ experimental: { notify_user: true } });
let session = await harness.createSession({ id: 'ses_notify_continue', workDir });
const events: Event[] = [];
let unsubscribe = session.onEvent((event) => events.push(event));
for (let turn = 0; turn < 3; turn++) {
if (turn === 1) await harness.setConfig({ experimental: { notify_user: false } });
if (turn === 2) {
unsubscribe();
await harness.close();
harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir, uiCapabilities: [] });
session = await harness.resumeSession({ id: 'ses_notify_continue' });
unsubscribe = session.onEvent((event) => events.push(event));
}
const done = waitForEvent(session, (event) => event.type === 'turn.ended');
await session.prompt(`Continue the work, turn ${turn + 1}.`);
expect(await done).toMatchObject({ reason: 'completed' });
}
unsubscribe();
expect(requests).toHaveLength(6);
const system = (request: (typeof requests)[number]) =>
request.messages.filter((message) => message.role === 'system');
for (const request of requests) {
expect(request.tools).toEqual(requests[0]!.tools);
expect(system(request)).toEqual(system(requests[0]!));
}
const results = events.filter((event) => event.type === 'tool.result');
expect(results).toHaveLength(3);
expect(results.every((event) => event.isError !== true)).toBe(true);
expect(JSON.stringify(results[0])).toContain('Update shown to the user.');
expect(JSON.stringify(results.slice(1))).toContain(
'Notifications are disabled; the update was not displayed.',
);
} finally {
await harness.close();
vi.unstubAllEnvs();
}
});
it('preserves existing custom metadata when an SDK metadata patch is resumed', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir });
try {
await configureFakeProvider(harness);
const session = await harness.createSession({
id: 'ses_update_metadata',
workDir,
metadata: { source: 'vscode' },
});
await session.createGoal({ objective: 'Keep core-owned metadata' });
await session.updateMetadata({
vscode_legacy_approval: { yolo: true, afk: false },
});
await session.close();
const resumed = await harness.resumeSession({ id: session.id });
expect(resumed.summary?.metadata).toEqual({
source: 'vscode',
vscode_legacy_approval: { yolo: true, afk: false },
});
await expect(resumed.getGoal()).resolves.toMatchObject({
goal: { objective: 'Keep core-owned metadata' },
});
} finally {
await harness.close();
}
});
it('persists sanitized prompt metadata without marking the title custom', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({
identity: TEST_IDENTITY,
homeDir,
});
try {
await configureFakeProvider(harness);
const session = await harness.createSession({ id: 'ses_prompt_meta', workDir });
const events: Event[] = [];
const unsubscribe = session.onEvent((event) => {
events.push(event);
});
let done = waitForEvent(session, (event) => event.type === 'turn.ended');
await session.prompt('use api_key=secret-value for the request');
await done;
expect(events).toContainEqual(
expect.objectContaining({
type: 'session.meta.updated',
title: 'use api_key=[redacted] for the request',
patch: expect.objectContaining({
isCustomTitle: false,
lastPrompt: 'use api_key=[redacted] for the request',
}),
}),
);
events.length = 0;
done = waitForEvent(session, (event) => event.type === 'turn.ended');
await session.prompt('second prompt');
await done;
expect(events).toContainEqual(
expect.objectContaining({
type: 'session.meta.updated',
patch: expect.objectContaining({
lastPrompt: 'second prompt',
}),
}),
);
events.length = 0;
done = waitForEvent(session, (event) => event.type === 'turn.ended');
await session.prompt([{ type: 'image_url', imageUrl: { url: 'https://example.com/a.png' } }]);
await done;
unsubscribe();
expect(events).toContainEqual(
expect.objectContaining({
type: 'session.meta.updated',
patch: expect.objectContaining({
lastPrompt: '[image]',
}),
}),
);
} finally {
await harness.close();
}
});
it('emits mapped turn events through Session.onEvent', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({
identity: TEST_IDENTITY,
homeDir,
});
try {
await configureFakeProvider(harness);
const session = await harness.createSession({ id: 'ses_prompt_events', workDir });
const events: Event[] = [];
const done = waitForEvent(session, (event) => event.type === 'turn.ended');
const unsubscribe = session.onEvent((event) => {
events.push(event);
});
await session.prompt('hello');
await done;
unsubscribe();
expect(events.some((event) => event.type === 'turn.started')).toBe(true);
expect(events).toContainEqual(
expect.objectContaining({
type: 'assistant.delta',
sessionId: session.id,
turnId: 0,
delta: 'hello from fake provider',
}),
);
expect(events).toContainEqual(
expect.objectContaining({
type: 'turn.ended',
sessionId: session.id,
turnId: 0,
reason: 'completed',
}),
);
expect(fakeProviderState.calls[0]?.systemPrompt).toContain('You are Kimi Code CLI');
expect(fakeProviderState.calls[0]?.systemPrompt).toContain('Available skills');
} finally {
await harness.close();
}
});
it('supports onEvent unsubscribe without touching runtime wire directly', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({
identity: TEST_IDENTITY,
homeDir,
});
try {
await configureFakeProvider(harness);
const session = await harness.createSession({ id: 'ses_prompt_unsubscribe', workDir });
const unsubscribedEvents: Event[] = [];
const unsubscribe = session.onEvent((event) => {
unsubscribedEvents.push(event);
});
unsubscribe();
const done = waitForEvent(session, (event) => event.type === 'turn.ended');
await session.prompt([{ type: 'text', text: 'hello' }]);
await done;
expect(unsubscribedEvents).toEqual([]);
} finally {
await harness.close();
}
});
it('runs init through generateAgentsMd RPC as a subagent system trigger without prompt metadata updates', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({
identity: TEST_IDENTITY,
homeDir,
});
try {
await configureFakeProvider(harness);
const session = await harness.createSession({ id: 'ses_init_rpc', workDir });
const events: Event[] = [];
const unsubscribe = session.onEvent((event) => {
events.push(event);
});
await session.init();
unsubscribe();
const spawned = events.find((event) => event.type === 'subagent.spawned');
expect(spawned).toMatchObject({
type: 'subagent.spawned',
sessionId: session.id,
agentId: 'main',
subagentName: 'coder',
parentToolCallId: 'generate-agents-md',
});
expect(events).toContainEqual(
expect.objectContaining({
type: 'turn.started',
sessionId: session.id,
agentId: spawned?.type === 'subagent.spawned' ? spawned.subagentId : undefined,
origin: { kind: 'system_trigger', name: 'subagent' },
}),
);
expect(events).not.toContainEqual(
expect.objectContaining({
type: 'session.meta.updated',
}),
);
expect(JSON.stringify(fakeProviderState.calls[0]?.history)).toContain('Task requirements:');
} finally {
await harness.close();
}
});
it('carries the prompt on the public turn.started event for subagent system triggers', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({
identity: TEST_IDENTITY,
homeDir,
});
try {
await configureFakeProvider(harness);
const session = await harness.createSession({ id: 'ses_init_rpc_v2', workDir });
const events: Event[] = [];
const unsubscribe = session.onEvent((event) => {
events.push(event);
});
await session.init();
unsubscribe();
const spawned = events.find((event) => event.type === 'subagent.spawned');
expect(events).toContainEqual(
expect.objectContaining({
type: 'turn.started',
sessionId: session.id,
agentId: spawned?.type === 'subagent.spawned' ? spawned.subagentId : undefined,
origin: { kind: 'system_trigger', name: 'subagent' },
prompt: expect.stringContaining('Task requirements:'),
}),
);
} finally {
await harness.close();
}
});
it('includes persisted subagent replay only when resume explicitly requests it', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir });
try {
await configureFakeProvider(harness);
const session = await harness.createSession({ id: 'ses_subagent_replay', workDir });
const events: Event[] = [];
const unsubscribe = session.onEvent((event) => events.push(event));
await session.init();
unsubscribe();
const spawned = events.find((event) => event.type === 'subagent.spawned');
if (spawned?.type !== 'subagent.spawned') throw new Error('Expected persisted subagent');
await session.close();
const defaultResume = await harness.resumeSession({ id: session.id });
expect(defaultResume.getResumeState()?.agents).not.toHaveProperty(spawned.subagentId);
await defaultResume.close();
const fullResume = await harness.resumeSession({
id: session.id,
includeSubagents: true,
});
expect(fullResume.getResumeState()?.agents[spawned.subagentId]?.replay).toContainEqual(
expect.objectContaining({
type: 'message',
message: expect.objectContaining({ role: 'assistant' }),
}),
);
} finally {
await harness.close();
}
});
it('starts btw through RPC as a forked subagent without prompt metadata updates', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({
identity: TEST_IDENTITY,
homeDir,
});
try {
await configureFakeProvider(harness);
const session = await harness.createSession({ id: 'ses_btw_rpc', workDir });
const events: Event[] = [];
const unsubscribe = session.onEvent((event) => {
events.push(event);
});
let done = waitForEvent(session, (event) => event.type === 'turn.ended');
await session.prompt('main task context');
await done;
fakeProviderState.responseText = 'The main agent is working from the existing context.';
events.length = 0;
done = waitForEvent(
session,
(event) => event.type === 'turn.ended' && event.agentId !== 'main',
);
const agentId = await session.startBtw();
await harness.withInteractiveAgent(agentId, () =>
session.prompt('What are you working on right now?'),
);
await done;
unsubscribe();
expect(harness.interactiveAgentId).toBe('main');
const started = events.find(
(event) =>
event.type === 'turn.started' &&
event.agentId === agentId &&
event.origin.kind === 'user',
);
expect(events).toContainEqual(
expect.objectContaining({
type: 'turn.started',
sessionId: session.id,
agentId,
origin: { kind: 'user' },
}),
);
expect(started?.agentId).not.toBe('main');
expect(events).not.toContainEqual(expect.objectContaining({ type: 'subagent.spawned' }));
expect(events).not.toContainEqual(expect.objectContaining({ type: 'subagent.completed' }));
expect(events).not.toContainEqual(expect.objectContaining({ type: 'subagent.failed' }));
expect(events).not.toContainEqual(
expect.objectContaining({
type: 'session.meta.updated',
}),
);
expect(fakeProviderState.calls[1]?.systemPrompt).toBe(
fakeProviderState.calls[0]?.systemPrompt,
);
const btwHistoryText = JSON.stringify(fakeProviderState.calls[1]?.history);
expect(btwHistoryText).toContain('main task context');
expect(btwHistoryText).toContain('What are you working on right now?');
await harness.closeSession(session.id);
const resumed = await harness.resumeSession({ id: session.id });
const resumeState = resumed.getResumeState();
expect(resumeState?.agents).toMatchObject({ main: expect.any(Object) });
expect(resumeState?.agents).not.toHaveProperty(agentId);
} finally {
await harness.close();
}
});
it('persists only conversation through the selected turn across resume', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir });
try {
await configureFakeProvider(harness);
const source = await harness.createSession({ id: 'ses_turn_fork_source', workDir });
await runPrompt(source, 'first question', 'first answer');
await runPrompt(source, 'second question', 'second answer');
await runPrompt(source, 'third question', 'third answer');
const fork = await harness.forkSession({
id: source.id,
forkId: 'ses_turn_fork_child',
turnIndex: 1,
});
await fork.close();
const resumed = await harness.resumeSession({ id: fork.id });
const replayText = visibleReplayText(resumed.getResumeState()?.agents['main']?.replay ?? []);
expect(replayText).toEqual([
'user:first question',
'assistant:first answer',
'user:second question',
'assistant:second answer',
]);
} finally {
await harness.close();
}
});
it('returns the requested identity and derives metadata from the selected historical turn', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir });
try {
await configureFakeProvider(harness);
const source = await harness.createSession({
id: 'ses_turn_fork_metadata_source',
workDir,
metadata: { source: 'vscode' },
});
await runPrompt(source, 'branch here', 'kept answer');
await runPrompt(source, 'future prompt', 'discarded answer');
const fork = await harness.forkSession({
id: source.id,
forkId: 'ses_turn_fork_metadata_child',
title: 'Historical branch',
metadata: { branch: 'historical' },
turnIndex: 0,
});
const state = fork.getResumeState();
expect(fork.id).toBe('ses_turn_fork_metadata_child');
expect(fork.workDir).toBe(source.workDir);
expect(state?.sessionMetadata.forkedFrom).toBe(source.id);
expect(fork.summary).toMatchObject({
title: 'Historical branch',
lastPrompt: 'branch here',
metadata: { source: 'vscode', branch: 'historical' },
});
expect(state?.sessionMetadata).toMatchObject({
title: 'Historical branch',
lastPrompt: 'branch here',
custom: { source: 'vscode', branch: 'historical' },
});
} finally {
await harness.close();
}
});
it('flattens the undo branch out of a turn-sliced fork', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir });
try {
await configureFakeProvider(harness);
const source = await harness.createSession({ id: 'ses_turn_fork_undo_source', workDir });
await runPrompt(source, 'kept prompt one', 'kept answer one');
await runPrompt(source, 'kept prompt two', 'kept answer two');
await runPrompt(source, 'retracted prompt', 'retracted answer');
await source.undoHistory(1);
await runPrompt(source, 'follow-up prompt', 'follow-up answer');
const fork = await harness.forkSession({
id: source.id,
forkId: 'ses_turn_fork_undo_child',
turnIndex: 2,
});
await fork.close();
const resumed = await harness.resumeSession({ id: fork.id });
const replayText = visibleReplayText(resumed.getResumeState()?.agents['main']?.replay ?? []);
expect(replayText).toEqual([
'user:kept prompt one',
'assistant:kept answer one',
'user:kept prompt two',
'assistant:kept answer two',
'user:follow-up prompt',
'assistant:follow-up answer',
]);
const wireFiles = (await readdir(homeDir, { recursive: true })).filter((path) =>
path.endsWith(join('agents', 'main', 'wire.jsonl')),
);
const forkedLines = (await readFile(
join(homeDir, wireFiles.find((path) => path.includes(fork.id))!),
'utf8',
))
.trimEnd()
.split('\n');
const types = forkedLines.map((line) => (JSON.parse(line) as { type: string }).type);
expect(types).not.toContain('agent.switched');
expect(types).not.toContain('context.undo');
expect(types).not.toContain('context.undone');
const sourceLines = (await readFile(
join(homeDir, wireFiles.find((path) => path.includes(source.id))!),
'utf8',
))
.trimEnd()
.split('\n');
const sourceTypes = sourceLines.map((line) => (JSON.parse(line) as { type: string }).type);
expect(sourceTypes).toContain('agent.switched');
expect(sourceLines.some((line) => line.includes('retracted'))).toBe(true);
const retractedTypes = forkedLines
.filter((line) => line.includes('retracted'))
.map((line) => (JSON.parse(line) as { type: string }).type);
expect(retractedTypes).toEqual([]);
} finally {
await harness.close();
}
});
it('continues with the next turn id after a historical fork', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir });
try {
await configureFakeProvider(harness);
const source = await harness.createSession({ id: 'ses_turn_fork_id_source', workDir });
await runPrompt(source, 'kept prompt', 'kept answer');
await runPrompt(source, 'future prompt', 'future answer');
const fork = await harness.forkSession({ id: source.id, turnIndex: 0 });
const started = waitForEvent(fork, (event) => event.type === 'turn.started');
const ended = waitForEvent(fork, (event) => event.type === 'turn.ended');
await fork.prompt('branch continuation');
await expect(started).resolves.toMatchObject({ type: 'turn.started', turnId: 1 });
await ended;
} finally {
await harness.close();
}
});
it('omits subagents created after the selected historical turn', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir });
try {
await configureFakeProvider(harness);
const source = await harness.createSession({ id: 'ses_turn_fork_agents_source', workDir });
await runPrompt(source, 'kept prompt', 'kept answer');
await runPrompt(source, 'future prompt', 'future answer');
await source.init();
const fork = await harness.forkSession({ id: source.id, turnIndex: 0 });
expect(Object.keys(fork.getResumeState()?.sessionMetadata.agents ?? {})).toEqual(['main']);
} finally {
await harness.close();
}
});
it('rejects a negative historical turn index with request.invalid', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir });
try {
const source = await harness.createSession({ id: 'ses_turn_fork_negative', workDir });
await expect(
harness.forkSession({ id: source.id, turnIndex: -1 }),
).rejects.toMatchObject({
name: 'KimiError',
code: 'request.invalid',
});
} finally {
await harness.close();
}
});
it('rejects an out-of-range historical turn without creating the fork', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({ identity: TEST_IDENTITY, homeDir });
try {
await configureFakeProvider(harness);
const source = await harness.createSession({ id: 'ses_turn_fork_range_source', workDir });
await runPrompt(source, 'only question', 'only answer');
await expect(
harness.forkSession({
id: source.id,
forkId: 'ses_turn_fork_range_child',
turnIndex: 1,
}),
).rejects.toMatchObject({
name: 'KimiError',
code: 'request.invalid',
details: { turnIndex: 1, availableTurns: 1 },
});
await expect(
harness.listSessions({ sessionId: 'ses_turn_fork_range_child' }),
).resolves.toEqual([]);
} finally {
await harness.close();
}
});
it('rejects empty prompt input', async () => {
const homeDir = await makeTempDir();
const workDir = await makeTempDir();
const harness = createKimiHarness({
identity: TEST_IDENTITY,
homeDir,
});
try {
const session = await harness.createSession({ id: 'ses_empty_prompt', workDir });
await expect(session.prompt(' ')).rejects.toMatchObject({
name: 'KimiError',
code: 'request.prompt_input_empty',
});
} finally {
await harness.close();
}
});
});
async function runPrompt(
session: Parameters<typeof waitForEvent>[0] & { prompt(input: string): Promise<void> },
input: string,
response: string,
): Promise<void> {
fakeProviderState.responseText = response;
const done = waitForEvent(session, (event) => event.type === 'turn.ended');
await session.prompt(input);
await done;
}
function visibleReplayText(
records: readonly {
readonly type: string;
readonly message?: {
readonly role: string;
readonly content: ReadonlyArray<{ readonly type: string; readonly text?: string }>;
readonly origin?: { readonly kind: string };
};
}[],
): readonly string[] {
const entries: string[] = [];
for (const record of records) {
if (record.type !== 'message' || record.message === undefined) continue;
const { message } = record;
if (message.role === 'user' && message.origin?.kind !== 'user') continue;
if (message.role !== 'user' && message.role !== 'assistant') continue;
const text = message.content
.filter((part) => part.type === 'text')
.map((part) => part.text ?? '')
.join('');
entries.push(`${message.role}:${text}`);
}
return entries;
}
async function configureFakeProvider(harness: KimiHarness): Promise<void> {
await harness.setConfig({
providers: {
local: {
type: 'openai',
baseUrl: 'https://model.example.test/v1',
apiKey: 'sk-test',
},
},
models: {
'fake-model': {
provider: 'local',
model: 'fake-model',
maxContextSize: 262144,
},
},
defaultModel: 'fake-model',
});
}
function waitForEvent(
session: {
onEvent(listener: (event: Event) => void): () => void;
},
predicate: (event: Event) => boolean,
): Promise<Event> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
unsubscribe();
reject(new Error('Timed out waiting for session event'));
}, 1_000);
const unsubscribe = session.onEvent((event) => {
if (!predicate(event)) return;
clearTimeout(timeout);
unsubscribe();
resolve(event);
});
});
}