mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-25 09:04:54 +00:00
171 lines
5.3 KiB
TypeScript
171 lines
5.3 KiB
TypeScript
import { writeFile } from 'node:fs/promises';
|
|
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 { makeTempDir, removeTempDirs, waitForSDKEvent } from './session-runtime-helpers';
|
|
import { TEST_IDENTITY } from './test-identity';
|
|
|
|
vi.mock('@moonshot-ai/kosong', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof KosongModule>();
|
|
return {
|
|
...actual,
|
|
createProvider: () => ({
|
|
name: 'fake',
|
|
modelName: 'fake-model',
|
|
thinkingEffort: null,
|
|
async generate(
|
|
_systemPrompt: string,
|
|
_tools: unknown,
|
|
_history: unknown,
|
|
options?: { readonly signal?: AbortSignal },
|
|
) {
|
|
await waitForAbort(options?.signal);
|
|
throwAbortError();
|
|
},
|
|
withThinking() {
|
|
return this;
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
const { KimiHarness } = await import('#/index');
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
await removeTempDirs(tempDirs);
|
|
});
|
|
|
|
describe('Session.cancel', () => {
|
|
it('cancels an active streaming turn and emits turn_ended(cancelled)', async () => {
|
|
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 });
|
|
|
|
try {
|
|
const session = await harness.createSession({ id: 'ses_cancel_active_turn', workDir });
|
|
const events: Event[] = [];
|
|
const unsubscribe = session.onEvent((event) => {
|
|
events.push(event);
|
|
});
|
|
const started = waitForSDKEvent(session, (event) => event.type === 'turn.started');
|
|
const ended = waitForSDKEvent(session, (event) => event.type === 'turn.ended');
|
|
|
|
await session.prompt('start a turn that will be cancelled');
|
|
const startedEvent = await started;
|
|
await session.cancel();
|
|
const endedEvent = await ended;
|
|
unsubscribe();
|
|
|
|
expect(startedEvent).toMatchObject({
|
|
type: 'turn.started',
|
|
sessionId: session.id,
|
|
});
|
|
expect(endedEvent).toMatchObject({
|
|
type: 'turn.ended',
|
|
sessionId: session.id,
|
|
turnId: startedEvent.type === 'turn.started' ? startedEvent.turnId : undefined,
|
|
reason: 'cancelled',
|
|
});
|
|
expect(events).toContainEqual(expect.objectContaining({ type: 'turn.started' }));
|
|
expect(events).toContainEqual(expect.objectContaining({ type: 'turn.ended' }));
|
|
} finally {
|
|
await harness.close();
|
|
}
|
|
});
|
|
|
|
it('cancels an active manual compaction and emits compaction.cancelled', async () => {
|
|
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 });
|
|
|
|
try {
|
|
const session = await harness.createSession({ id: 'ses_cancel_compaction', workDir });
|
|
const started = waitForSDKEvent(session, (event) => event.type === 'compaction.started');
|
|
const cancelled = waitForSDKEvent(session, (event) => event.type === 'compaction.cancelled');
|
|
|
|
await session.compact({ instruction: 'Keep the compact test pending.' });
|
|
await expect(started).resolves.toMatchObject({
|
|
type: 'compaction.started',
|
|
sessionId: session.id,
|
|
trigger: 'manual',
|
|
});
|
|
await session.cancelCompaction();
|
|
|
|
await expect(cancelled).resolves.toMatchObject({
|
|
type: 'compaction.cancelled',
|
|
sessionId: session.id,
|
|
});
|
|
} finally {
|
|
await harness.close();
|
|
}
|
|
});
|
|
|
|
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 });
|
|
|
|
try {
|
|
const session = await harness.createSession({ id: 'ses_cancel_closed', workDir });
|
|
await session.close();
|
|
|
|
await expect(session.cancel()).rejects.toMatchObject({
|
|
name: 'KimiError',
|
|
code: 'session.closed',
|
|
} satisfies Partial<KimiError>);
|
|
await expect(session.cancelCompaction()).rejects.toMatchObject({
|
|
name: 'KimiError',
|
|
code: 'session.closed',
|
|
} satisfies Partial<KimiError>);
|
|
} finally {
|
|
await harness.close();
|
|
}
|
|
});
|
|
});
|
|
|
|
async function writeFakeModelConfig(homeDir: string): Promise<void> {
|
|
await writeFile(
|
|
join(homeDir, 'config.toml'),
|
|
`
|
|
default_model = "fake-model"
|
|
|
|
[providers.local]
|
|
type = "kimi"
|
|
base_url = "https://example.test/v1"
|
|
api_key = "sk-test"
|
|
|
|
[models.fake-model]
|
|
provider = "local"
|
|
model = "fake-model"
|
|
max_context_size = 1000
|
|
`,
|
|
'utf-8',
|
|
);
|
|
}
|
|
|
|
function waitForAbort(signal: AbortSignal | undefined): Promise<void> {
|
|
if (signal?.aborted === true) {
|
|
return Promise.resolve();
|
|
}
|
|
return new Promise((resolve) => {
|
|
signal?.addEventListener(
|
|
'abort',
|
|
() => {
|
|
resolve();
|
|
},
|
|
{ once: true },
|
|
);
|
|
});
|
|
}
|
|
|
|
function throwAbortError(): never {
|
|
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
}
|