File size: 5,331 Bytes
4e23b01 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | /**
* Assert-based smoke check for klient against an in-process engine (memory
* transport). Exercises the `global` facade end-to-end: env snapshot, read
* models, a workspace round-trip, a provider set/delete round-trip with the
* `kosong.providers.changed` event, an anonymous-provider set/delete
* round-trip with the `kosong.models.changed` event, the read-only model
* catalog, and the error path.
*
* pnpm -C packages/klient smoke
*/
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { EXAMPLE_CLIENT_IDENTITY } from './identity.js';
import { bootstrap, logSeed, resolveLoggingConfig } from '@moonshot-ai/agent-core-v2';
import { createKlient } from '@moonshot-ai/klient/memory';
function assert(cond: boolean, message: string): asserts cond {
if (!cond) throw new Error(`assertion failed: ${message}`);
}
const tick = (ms: number): Promise<void> =>
new Promise((resolve) => {
setTimeout(resolve, ms);
});
async function main(): Promise<void> {
const homeDir = await mkdtemp(join(tmpdir(), 'klient-smoke-'));
const { app } = bootstrap({ homeDir, clientIdentity: EXAMPLE_CLIENT_IDENTITY }, [
...logSeed(resolveLoggingConfig({ homeDir, env: process.env })),
]);
try {
const klient = createKlient({ scope: app });
const env = await klient.global.env();
assert(env.platform.length > 0 && env.homeDir.length > 0, 'env snapshot is populated');
console.log('[ok] env');
const page = await klient.global.sessions.list({ limit: 5 });
assert(Array.isArray(page.items), 'sessions.list returns a page');
console.log('[ok] sessions.list ->', page.items.length);
const workspaces = await klient.global.workspaces.list();
assert(Array.isArray(workspaces), 'workspaces.list returns an array');
console.log('[ok] workspaces.list ->', workspaces.length);
// Provider round-trip with the klient-level event.
const seen: string[] = [];
const sub = klient.events.on('kosong.providers.changed', (event) => {
seen.push(...event.added, ...event.changed, ...event.removed);
});
const name = '__klient_smoke__';
await klient.global.kosong.addProvider(name, {
type: 'openai',
auth: { method: 'api-key', apiKey: 'smoke-key' },
});
const got = await klient.global.kosong.getProvider(name);
assert(got !== undefined, 'kosong.getProvider returns the new provider');
const deadline = Date.now() + 5_000;
while (!seen.includes(name) && Date.now() < deadline) await tick(25);
assert(seen.includes(name), 'kosong.providers.changed fired for the new provider');
await klient.global.kosong.removeProvider(name);
sub.dispose();
console.log('[ok] kosong addProvider/getProvider/removeProvider + kosong.providers.changed');
// Anonymous provider round-trip (single-model, all fields inline).
const seenModels: string[] = [];
const modelSub = klient.events.on('kosong.models.changed', (event) => {
seenModels.push(...event.added, ...event.changed, ...event.removed);
});
const modelId = '__klient_smoke__';
await klient.global.kosong.addProvider({
id: modelId,
model: 'smoke-model',
protocol: 'openai',
baseUrl: 'http://127.0.0.1:1',
auth: { method: 'api-key', apiKey: 'smoke-key' },
maxContextSize: 8192,
});
const modelDeadline = Date.now() + 5_000;
while (!seenModels.includes(modelId) && Date.now() < modelDeadline) await tick(25);
assert(seenModels.includes(modelId), 'kosong.models.changed fired for the new model');
await klient.global.kosong.removeProvider(modelId);
modelSub.dispose();
console.log('[ok] kosong anonymous addProvider/removeProvider + kosong.models.changed');
// The read-only catalog projection over the same materialization.
assert(
Array.isArray(await klient.global.kosong.listModels()),
'kosong.listModels returns an array',
);
assert(
Array.isArray(await klient.global.kosong.listProviders()),
'kosong.listProviders returns an array',
);
console.log('[ok] kosong.listModels / listProviders');
const config = await klient.global.config.getAll();
assert(typeof config === 'object' && config !== null, 'config.getAll returns an object');
console.log('[ok] config.getAll');
assert(Array.isArray(await klient.global.flags.list()), 'flags.list returns an array');
assert(Array.isArray(await klient.global.plugins.list()), 'plugins.list returns an array');
const auth = await klient.global.auth.status();
assert(typeof auth.loggedIn === 'boolean', 'auth.status returns a status');
console.log('[ok] flags / plugins / auth');
let rpcError: { name: string; code?: number } | undefined;
try {
await klient.global.plugins.info('__definitely_missing__');
} catch (error) {
rpcError = error as { name: string; code?: number };
}
assert(rpcError !== undefined, 'missing plugin surfaces an error');
console.log('[ok] error path ->', rpcError.name, rpcError.code);
await klient.close();
console.log('smoke: OK');
} finally {
app.dispose();
await rm(homeDir, { recursive: true, force: true });
}
}
try {
await main();
} catch (error) {
console.error(error);
process.exit(1);
}
|