kimi-code / packages /klient /test /facade.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4e23b01 verified
Raw History Blame Contribute Delete
26.1 kB
import { describe, expect, it, vi } from 'vitest';
import type {
EventSourceRef,
IDisposable,
KlientChannel,
ScopeRef,
} from '../src/core/channel.js';
import { createKlientFromChannel } from '../src/core/klient.js';
import { KlientValidationError } from '../src/core/validation.js';
const tick = (ms = 0): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
/** Records calls, replays scripted results, and captures listen subscriptions. */
class FakeChannel implements KlientChannel {
readonly calls: Array<{ scope: ScopeRef; service: string; method: string; args: unknown[] }> = [];
readonly subscriptions: Array<{
scope: ScopeRef;
source: EventSourceRef;
dispose: ReturnType<typeof vi.fn>;
}> = [];
result: unknown;
/** Keyed `${service}.${method}` result overrides. */
readonly results = new Map<string, unknown>();
private readonly handlers = new Map<number, (data: unknown) => void>();
private nextSub = 0;
call(scope: ScopeRef, service: string, method: string, args: unknown[]): Promise<unknown> {
this.calls.push({ scope, service, method, args });
const key = `${service}.${method}`;
return Promise.resolve(this.results.has(key) ? this.results.get(key) : this.result);
}
// eslint-disable-next-line @typescript-eslint/require-await
async *stream(_scope: ScopeRef, _service: string, _method: string, _args: unknown[]): AsyncIterableIterator<unknown> {
// stub — streaming is not exercised in facade tests
}
listen(scope: ScopeRef, source: EventSourceRef, handler: (data: unknown) => void): IDisposable {
const id = this.nextSub;
this.nextSub += 1;
this.handlers.set(id, handler);
const dispose = vi.fn(() => {
this.handlers.delete(id);
});
this.subscriptions.push({ scope, source, dispose });
return { dispose };
}
/** Push a raw payload into the Nth subscription (0-based). */
emit(index: number, data: unknown): void {
this.handlers.get(index)?.(data);
}
close(): Promise<void> {
return Promise.resolve();
}
}
const SUMMARY = {
id: 's1',
workspaceId: 'w1',
createdAt: 1,
updatedAt: 2,
archived: false,
};
describe('facade routing', () => {
it('reshapes single-object params into positional wire args', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.result = { id: 'w1', root: '/x', name: 'n', createdAt: 1, lastOpenedAt: 2 };
await klient.global.workspaces.createOrTouch({ root: '/x', name: 'n' });
expect(channel.calls[0]).toMatchObject({
service: 'workspaceService',
method: 'createOrTouch',
args: ['/x', 'n'],
});
channel.result = undefined; // void output
await klient.global.plugins.setMcpServerEnabled({ id: 'p', server: 's', enabled: true });
expect(channel.calls[1]).toMatchObject({
service: 'pluginService',
method: 'setPluginMcpServerEnabled',
args: [{ id: 'p', server: 's', enabled: true }],
});
channel.results.set('oauthService.status', { loggedIn: false });
await klient.global.auth.status();
expect(channel.calls[2]).toMatchObject({
service: 'oauthService',
method: 'status',
args: [undefined],
});
});
it('forwards the login region option through the wire contract', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.results.set('oauthService.startLogin', {
flow_id: 'f1',
provider: 'managed:kimi-code',
status: 'pending',
verification_uri: 'https://example.com/device',
verification_uri_complete: 'https://example.com/device?user_code=ABCD',
user_code: 'ABCD',
expires_in: 1800,
expires_at: '2026-08-19T15:00:00.000Z',
interval: 5,
});
await klient.global.auth.startLogin('managed:kimi-code', { region: 'global' });
expect(channel.calls[0]).toMatchObject({
service: 'oauthService',
method: 'startLogin',
args: ['managed:kimi-code', { region: 'global' }],
});
});
it('routes capability calls through the registered app service contract', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const status = {
id: 'kimi-cu',
displayName: 'Kimi Computer Use',
description: 'Background GUI automation',
supported: true,
state: 'partial',
steps: [{ id: 'permissions', state: 'missing' }],
// The completed-install note survives the contract parse (not stripped).
install: { running: false, note: 'user-skill-migrated' },
};
channel.result = [status];
await expect(klient.global.capabilities.list()).resolves.toEqual([status]);
channel.result = status;
await expect(klient.global.capabilities.get('kimi-cu')).resolves.toEqual(status);
await expect(klient.global.capabilities.install('kimi-cu')).resolves.toEqual(status);
expect(channel.calls).toEqual([
{ scope: {}, service: 'capabilityService', method: 'listCapabilities', args: [] },
{ scope: {}, service: 'capabilityService', method: 'getCapability', args: ['kimi-cu'] },
{ scope: {}, service: 'capabilityService', method: 'installCapability', args: ['kimi-cu'] },
]);
});
it('env() fans out property reads and merges them', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.result = 'v';
channel.results.set('bootstrapService.clientIdentity', {
productName: 'v',
version: 'v',
platform: 'v',
});
const env = await klient.global.env();
expect(env.platform).toBe('v');
expect(env.logsDir).toBe('v');
expect(env.clientVersion).toBe('v');
expect(channel.calls).toHaveLength(12);
expect(channel.calls.every((call) => call.service === 'bootstrapService')).toBe(true);
});
it('env() resolves once and serves repeats from the cache', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.result = 'v';
channel.results.set('bootstrapService.clientIdentity', {
productName: 'v',
version: 'v',
platform: 'v',
});
await klient.global.env();
expect(channel.calls).toHaveLength(12);
const again = await klient.global.env();
expect(again.platform).toBe('v');
expect(channel.calls).toHaveLength(12);
});
});
describe('agent profile routing', () => {
it('thinking calls route to agentProfileService with the agent scope', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const agent = klient.session('s1').agent('main');
channel.result = undefined; // void output
await agent.setThinking('on');
expect(channel.calls[0]).toEqual({
scope: { sessionId: 's1', agentId: 'main' },
service: 'agentProfileService',
method: 'setThinking',
args: ['on'],
});
channel.result = 'high';
await expect(agent.getThinking()).resolves.toBe('high');
expect(channel.calls[1]).toEqual({
scope: { sessionId: 's1', agentId: 'main' },
service: 'agentProfileService',
method: 'getEffectiveThinkingLevel',
args: [],
});
});
});
describe('agent skill routing', () => {
it('promptWithSkills routes to agentSkillService.promptWithSkills with the agent scope', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const agent = klient.session('s1').agent('main');
channel.result = {
turn_id: 7,
prompt_id: 'p1',
created_at: '2026-01-01T00:00:00.000Z',
state: 'running',
};
await expect(
agent.promptWithSkills({
input: [{ type: 'text', text: 'Review this change.' }],
skills: [{ name: 'review' }, { name: 'security', args: 'src/app.ts' }],
}),
).resolves.toEqual({
turn_id: 7,
prompt_id: 'p1',
created_at: '2026-01-01T00:00:00.000Z',
state: 'running',
});
expect(channel.calls[0]).toEqual({
scope: { sessionId: 's1', agentId: 'main' },
service: 'agentSkillService',
method: 'promptWithSkills',
args: [
{
input: [{ type: 'text', text: 'Review this change.' }],
skills: [{ name: 'review' }, { name: 'security', args: 'src/app.ts' }],
},
],
});
});
});
describe('session skills routing', () => {
it('skills.list routes to sessionSkillCatalog.list with the session scope', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const summaries = [
{
name: 'review',
description: 'review changes',
path: '/skills/review/SKILL.md',
source: 'project',
},
];
channel.result = summaries;
await expect(klient.session('s1').skills.list()).resolves.toEqual(summaries);
expect(channel.calls[0]).toEqual({
scope: { sessionId: 's1' },
service: 'sessionSkillCatalog',
method: 'list',
args: [],
});
});
it('skills.changed maps to the sessionSkillCatalog emitter', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const seen: unknown[] = [];
klient.session('s1').events.on('skills.changed', (event) => seen.push(event));
expect(channel.subscriptions[0]?.source).toEqual({
kind: 'emitter',
service: 'sessionSkillCatalog',
event: 'onDidChange',
});
channel.emit(0, 'workspace');
await tick();
expect(seen).toEqual(['workspace']);
});
it('activateSkill routes to agentSkillService with the agent scope', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const agent = klient.session('s1').agent('main');
channel.result = { turn_id: 3 };
await expect(agent.activateSkill({ name: 'review', args: 'src/app.ts' })).resolves.toEqual({
turn_id: 3,
});
expect(channel.calls[0]).toEqual({
scope: { sessionId: 's1', agentId: 'main' },
service: 'agentSkillService',
method: 'activate',
args: [{ name: 'review', args: 'src/app.ts' }],
});
});
it('turn-driving calls route to their domain services with the agent scope', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const agent = klient.session('s1').agent('main');
const scope = { sessionId: 's1', agentId: 'main' };
channel.results.set('agentPromptService.submit', { turn_id: 1 });
channel.results.set('agentPromptService.submitSteer', { turn_id: 1 });
channel.results.set('agentCommandService.list', []);
await agent.prompt({ input: [{ type: 'text', text: 'hi' }] });
await agent.steer({ input: [{ type: 'text', text: 'steer' }] });
await agent.cancel({ turnId: 2 });
await agent.cancel();
await agent.setPermission('yolo');
await agent.listCommands();
await agent.runCommand({ name: 'cmd', args: 'a b' });
await agent.runCommand({ name: 'plain' });
expect(channel.calls).toEqual([
{
scope,
service: 'agentPromptService',
method: 'submit',
args: [{ input: [{ type: 'text', text: 'hi' }] }],
},
{
scope,
service: 'agentPromptService',
method: 'submitSteer',
args: [{ input: [{ type: 'text', text: 'steer' }] }],
},
{ scope, service: 'agentLoopService', method: 'cancelFromUser', args: [2] },
{ scope, service: 'agentLoopService', method: 'cancelFromUser', args: [] },
{ scope, service: 'agentPermissionModeService', method: 'setModeAndBroadcast', args: ['yolo'] },
{ scope, service: 'agentCommandService', method: 'list', args: [] },
{ scope, service: 'agentCommandService', method: 'run', args: ['cmd', 'a b'] },
{ scope, service: 'agentCommandService', method: 'run', args: ['plain'] },
]);
});
it('getContext merges the contextMemory and tokenCounting reads', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const agent = klient.session('s1').agent('main');
const scope = { sessionId: 's1', agentId: 'main' };
channel.results.set('agentContextMemoryService.get', [{ role: 'user' }]);
channel.results.set('agentTokenCountingService.statusSize', 42);
await expect(agent.getContext()).resolves.toEqual({
history: [{ role: 'user' }],
tokenCount: 42,
});
expect(channel.calls).toEqual([
{ scope, service: 'agentContextMemoryService', method: 'get', args: [] },
{ scope, service: 'agentTokenCountingService', method: 'statusSize', args: [] },
]);
});
});
describe('agent mcp / compaction routing', () => {
it('getMcpServers returns the live snapshot with the agent scope', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const agent = klient.session('s1').agent('main');
const entries = [
{ name: 'mock', transport: 'stdio', status: 'pending', toolCount: 0 },
];
channel.results.set('agentMcpService.list', entries);
await expect(agent.getMcpServers()).resolves.toEqual(entries);
expect(channel.calls[0]).toEqual({
scope: { sessionId: 's1', agentId: 'main' },
service: 'agentMcpService',
method: 'list',
args: [],
});
expect(channel.calls).toHaveLength(1);
});
it('compact issues a manual begin with the optional instruction', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const agent = klient.session('s1').agent('main');
channel.result = true;
await expect(agent.compact()).resolves.toBe(true);
expect(channel.calls[0]).toEqual({
scope: { sessionId: 's1', agentId: 'main' },
service: 'agentFullCompactionService',
method: 'begin',
args: [{ source: 'manual', instruction: undefined }],
});
channel.result = false;
await expect(agent.compact({ instruction: 'keep the plan' })).resolves.toBe(false);
expect(channel.calls[1]).toEqual({
scope: { sessionId: 's1', agentId: 'main' },
service: 'agentFullCompactionService',
method: 'begin',
args: [{ source: 'manual', instruction: 'keep the plan' }],
});
});
});
describe('session lifecycle routing', () => {
it('delete calls the App session manager', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.results.set('sessionManager.delete', undefined);
await klient.session('s1').delete();
expect(channel.calls).toEqual([
{ scope: {}, service: 'sessionManager', method: 'delete', args: ['s1'] },
]);
});
it('restore forwards resume options to the App session manager', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.results.set('sessionManager.restore', { id: 's1', kind: 'session' });
const opts = {
mcpServers: { example: { transport: 'stdio' as const, command: 'node' } },
};
await expect(klient.session('s1').restore(opts)).resolves.toBe(true);
expect(channel.calls[0]).toEqual({
scope: {},
service: 'sessionManager',
method: 'restore',
args: ['s1', opts],
});
});
it('sessions.create forwards mcpServers to the App session manager', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.results.set('sessionManager.create', { id: 's1', kind: 'session' });
channel.results.set('sessionMetadata.read', {
id: 's1',
createdAt: 1,
updatedAt: 2,
archived: false,
});
const mcpServers = {
example: { transport: 'stdio' as const, command: 'node', args: ['server.mjs'] },
};
await klient.global.sessions.create({ workDir: '/x', mcpServers });
expect(channel.calls[0]).toMatchObject({
scope: {},
service: 'sessionManager',
method: 'create',
args: [{ workDir: '/x', mcpServers }],
});
});
it('sessions.create rejects malformed mcpServers before the call leaves the client', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
await expect(
klient.global.sessions.create({
workDir: '/x',
mcpServers: { bad: { transport: 'http', url: 'not-a-url' } },
}),
).rejects.toBeInstanceOf(KlientValidationError);
expect(channel.calls.some((call) => call.method === 'create')).toBe(false);
});
});
describe('contract validation', () => {
it('rejects invalid input before the call leaves the client', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
await expect(
klient.global.sessions.list({ limit: '20' as unknown as number }),
).rejects.toBeInstanceOf(KlientValidationError);
expect(channel.calls).toHaveLength(0);
});
it('rejects drifted output payloads', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.result = { id: 's1' }; // missing required SessionSummary fields
await expect(klient.global.sessions.get('s1')).rejects.toBeInstanceOf(KlientValidationError);
});
it('passes valid payloads through and returns parsed output', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.result = SUMMARY;
await expect(klient.global.sessions.get('s1')).resolves.toEqual(SUMMARY);
});
it('validate:false skips both directions', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel, { validate: false });
channel.result = { anything: true };
await expect(
klient.global.sessions.list({ limit: '20' as unknown as number }),
).resolves.toEqual({ anything: true });
});
});
describe('event hub', () => {
it('maps public names to emitter sources and validates payloads', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const seen: unknown[] = [];
const errors: Error[] = [];
klient.events.onError((error) => {
errors.push(error);
});
klient.events.on('kosong.providers.changed', (event) => seen.push(event));
expect(channel.subscriptions[0]?.source).toEqual({
kind: 'emitter',
service: 'providerService',
event: 'onDidChangeProviders',
});
channel.emit(0, { added: ['p1'], removed: [], changed: [] });
channel.emit(0, { added: 'not-an-array' });
await tick();
expect(seen).toEqual([{ added: ['p1'], removed: [], changed: [] }]);
expect(errors).toHaveLength(1);
expect(errors[0]).toBeInstanceOf(KlientValidationError);
});
it('shares one bus subscription across bus-derived events and filters by type', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const archived: unknown[] = [];
const catalog: unknown[] = [];
const subA = klient.events.on('session.archived', (event) => archived.push(event));
const subB = klient.events.on('kosong.changed', (event) => catalog.push(event));
expect(channel.subscriptions).toHaveLength(1);
expect(channel.subscriptions[0]?.source).toEqual({ kind: 'stream', name: 'events' });
channel.emit(0, { type: 'event.session.archived', payload: { sessionId: 's1' } });
channel.emit(0, { type: 'event.model_catalog.changed', payload: { changed: [], unchanged: [], failed: [] } });
channel.emit(0, { type: 'unrelated.type', payload: {} });
await tick();
expect(archived).toEqual([{ sessionId: 's1' }]);
expect(catalog).toEqual([{ changed: [], unchanged: [], failed: [] }]);
subA.dispose();
expect(channel.subscriptions[0]?.dispose).not.toHaveBeenCalled();
subB.dispose();
expect(channel.subscriptions[0]?.dispose).toHaveBeenCalledTimes(1);
});
it('delivers session.metaUpdated when the patch carries no lastPrompt', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const seen: unknown[] = [];
const errors: Error[] = [];
klient.events.onError((error) => {
errors.push(error);
});
klient.events.on('session.metaUpdated', (event) => seen.push(event));
channel.emit(0, {
type: 'session.meta.updated',
payload: {
agentId: 'main',
sessionId: 's1',
title: 'generated title',
patch: { title: 'generated title', isCustomTitle: false },
},
});
await tick();
expect(seen).toEqual([
{
agentId: 'main',
sessionId: 's1',
title: 'generated title',
patch: { title: 'generated title', isCustomTitle: false },
},
]);
expect(errors).toHaveLength(0);
});
it('disposes the emitter subscription when the last listener detaches', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const a = klient.events.on('config.changed', () => undefined);
const b = klient.events.on('config.changed', () => undefined);
expect(channel.subscriptions).toHaveLength(1);
a.dispose();
expect(channel.subscriptions[0]?.dispose).not.toHaveBeenCalled();
b.dispose();
expect(channel.subscriptions[0]?.dispose).toHaveBeenCalledTimes(1);
});
it('forwards the newly registered agent stream events and validates payloads', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
const agent = klient.session('s1').agent('main');
const seen = {
delta: [] as unknown[],
progress: [] as unknown[],
started: [] as unknown[],
blocked: [] as unknown[],
cancelled: [] as unknown[],
completed: [] as unknown[],
};
const errors: Error[] = [];
agent.events.onError((error) => {
errors.push(error);
});
agent.events.on('tool.call.delta', (event) => seen.delta.push(event));
agent.events.on('tool.progress', (event) => seen.progress.push(event));
agent.events.on('compaction.started', (event) => seen.started.push(event));
agent.events.on('compaction.blocked', (event) => seen.blocked.push(event));
agent.events.on('compaction.cancelled', (event) => seen.cancelled.push(event));
agent.events.on('compaction.completed', (event) => seen.completed.push(event));
// All six registrations share one `events` stream subscription bound to
// the agent scope.
expect(channel.subscriptions).toHaveLength(1);
expect(channel.subscriptions[0]?.scope).toEqual({ sessionId: 's1', agentId: 'main' });
expect(channel.subscriptions[0]?.source).toEqual({ kind: 'stream', name: 'events' });
const delta = { type: 'tool.call.delta', turnId: 1, toolCallId: 'tc1', name: 'Bash', argumentsPart: '{"command":' };
const progress = {
type: 'tool.progress',
turnId: 1,
toolCallId: 'tc1',
update: { kind: 'stdout', text: 'chunk' },
};
const started = { type: 'compaction.started', trigger: 'auto' };
const blocked = { type: 'compaction.blocked', turnId: 2 };
const cancelled = { type: 'compaction.cancelled' };
const completed = {
type: 'compaction.completed',
result: { summary: 's', compactedCount: 3, tokensBefore: 100, tokensAfter: 40 },
};
channel.emit(0, delta);
channel.emit(0, progress);
channel.emit(0, started);
channel.emit(0, blocked);
channel.emit(0, cancelled);
channel.emit(0, completed);
channel.emit(0, { type: 'tool.progress', turnId: 1, toolCallId: 'tc1' }); // missing update
channel.emit(0, { type: 'unregistered.type', turnId: 1 });
await tick();
expect(seen.delta).toEqual([delta]);
expect(seen.progress).toEqual([progress]);
expect(seen.started).toEqual([started]);
expect(seen.blocked).toEqual([blocked]);
expect(seen.cancelled).toEqual([cancelled]);
expect(seen.completed).toEqual([completed]);
expect(errors).toHaveLength(1);
expect(errors[0]).toBeInstanceOf(KlientValidationError);
});
});
describe('files routing', () => {
const META = {
id: 'f_1',
name: 'a.png',
media_type: 'image/png',
size: 4,
created_at: '2026-01-01T00:00:00.000Z',
};
it('routes the files save/get/delete lifecycle through fileService', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
channel.result = META;
const meta = await klient.global.files.save({
data: new Uint8Array([1, 2, 3, 4]),
filename: 'a.png',
mimeType: 'image/png',
});
expect(meta).toEqual(META);
expect(channel.calls[0]).toMatchObject({
scope: {},
service: 'fileService',
method: 'save',
args: ['AQIDBA==', 'a.png', { mimeType: 'image/png' }],
});
channel.result = { meta: META, data: 'AQIDBA==' };
const got = await klient.global.files.get('f_1');
expect(got.meta).toEqual(META);
expect([...got.data]).toEqual([1, 2, 3, 4]);
expect(channel.calls[1]).toMatchObject({
scope: {},
service: 'fileService',
method: 'get',
args: ['f_1'],
});
channel.result = undefined;
await expect(klient.global.files.delete('f_1')).resolves.toBeUndefined();
expect(channel.calls[2]).toMatchObject({
scope: {},
service: 'fileService',
method: 'delete',
args: ['f_1'],
});
});
it('files.save rejects invalid input before it hits the wire', async () => {
const channel = new FakeChannel();
const klient = createKlientFromChannel(channel);
await expect(
klient.global.files.save({ data: new Uint8Array(0), filename: '' }),
).rejects.toBeInstanceOf(KlientValidationError);
expect(channel.calls).toHaveLength(0);
});
});