kimi-code/packages/klient/examples/basic.ts
haozhe.yang 2c5a4a3799 feat(api-v2): add klient SDK and reflection-based channel registry
- replace per-method actionMap (resource:action) with a channel registry: each Service registers once by decorator id and all methods are invoked by reflection
- routes move from /api/v2/:sa to /api/v2/:service/:method across HTTP routes and the WS protocol
- add @moonshot-ai/klient: typed core/session/agent client over the HTTP channel that reuses agent-core-v2 service interfaces
- register klient in flake.nix and pnpm-lock; refresh kap-server tests, e2e, and the apiSurface snapshot
2026-07-11 12:33:48 +08:00

63 lines
2.5 KiB
TypeScript

/**
* Minimal end-to-end example driving a running `kap-server` over `/api/v2` with
* klient — core + session scopes, both the generic typed proxy and the
* explicit `SessionIndexClient`, plus the error path.
*
* Run against a local dev server (auth bypassed for dev):
* pnpm dev:server --dangerous-bypass-auth
* pnpm -C packages/klient exec tsx examples/basic.ts
*/
import { HttpChannel, Klient, SessionIndexClient } from '@moonshot-ai/klient';
import { ISessionIndex } from '@moonshot-ai/agent-core-v2/app/sessionIndex/sessionIndex';
import { ISessionMetadata } from '@moonshot-ai/agent-core-v2/session/sessionMetadata/sessionMetadata';
const BASE = process.env.KIMI_SERVER_URL ?? 'http://127.0.0.1:58627';
async function createSessionViaV1(cwd: string): Promise<string> {
const res = await fetch(`${BASE}/api/v1/sessions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ metadata: { cwd } }),
});
const env = (await res.json()) as { code: number; data: { id: string } };
if (env.code !== 0) throw new Error(`create session failed: ${JSON.stringify(env)}`);
return env.data.id;
}
async function main(): Promise<void> {
const client = new Klient({ url: BASE });
// 1) Generic typed proxy — core scope. The token carries type + channel name.
const page = await client.core(ISessionIndex).list({});
console.log('[core] sessionIndex.list ->', page.items.length, 'sessions');
// 2) Explicit impl — same interface, hand-written class on a bound channel.
const index: ISessionIndex = new SessionIndexClient(
new HttpChannel({ baseUrl: `${BASE}/api/v2/sessionIndex` }),
);
const page2 = await index.list({});
console.log('[core] SessionIndexClient.list ->', page2.items.length, 'sessions (explicit)');
// 3) Session scope — create a session via /api/v1, then read metadata via v2.
const id = await createSessionViaV1('/tmp/klient-example');
console.log('[v1] created session ->', id);
const meta = await client.session(id).service(ISessionMetadata).read();
console.log('[session] sessionMetadata.read ->', {
id: meta.id,
cwd: meta.cwd,
archived: meta.archived,
});
// 4) Error path — unknown method -> RPCError(40001).
try {
await (client.core(ISessionIndex) as unknown as { nope(): Promise<unknown> }).nope();
} catch (err) {
const e = err as { name: string; code: number };
console.log('[error] unknown method ->', e.name, e.code);
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});