kimi-code/packages/klient
Haozhe ed7a4cc095
feat(kap-server): add session-less POST /workspace/fs:search route (#2437)
* feat(kap-server): let fs:search resolve a workspace ref for draft sessions

- fs:search accepts a workspace id or absolute root in the session_id slot
  so the @ file mention works before the session exists
- kimi-web searchFiles falls back to the active workspace id in draft state

* fix(agent-core-v2): report empty thinking level for unbound main agent

- sessionLegacyService.status returns thinking_level '' when the main
  agent has no bound model (mirroring model: undefined), so clients
  fall back to the catalog default instead of folding in the wire
  model's 'off' zero value
- add regression test for a never-bound main agent status
- add web changesets: draft @ file mention, new-session thinking level

* perf(minidb): make text index rebuilds async and non-blocking

- TextIndex.build() yields to the event loop during tokenization and
  batches postings writes (~1 MiB), so large rebuilds no longer
  hard-block the host process
- writes landing mid-build are queued and replayed onto the new base at
  swap time, keeping the rebuilt index exact
- PostingsFile.rebuildSync renamed to async rebuild with a synchronous
  commit section (beforeRename hook + atomic rename)
- onCompacted hook is now awaited (sync or async); open-time compaction
  runs in the background so open() returns without blocking on the
  snapshot rewrite and postings rebuild
- compaction skips the postings rebuild when the index's write buffer is
  clean (needsRebuild)
- createTextIndex registers before building so concurrent writes feed
  the build queue; dropTextIndex throws while a build is in flight

* refactor(agent-core-v2): rename workspaceHandler to sessionLifecycle

- rename IWorkspaceHandlerService to ISessionLifecycleService and move
  src/workspace/workspaceHandler/ to src/workspace/sessionLifecycle/;
  update all consumers (gateway, sessionExport, sessionLegacy,
  sessionLookup, kap-server, klient, node-sdk, kimi-inspect, kimi-code)
- rename IStateService to IAppStateService and add the Workspace-scope
  IWorkspaceStateService, so the state domain spans all four scope tiers
- add cascading StateRegistry.inspect(): each tier injects the parent
  tier's registry and folds App to current scope into one StateInspection
  tree; check-domain-layers gains a Rule 2b exemption for state-on-state
  imports

* feat(kap-server): add session-less POST /workspace/fs:search route

Carry the workspace reference (registered id or absolute root) in the
request body and resolve it to the same Workspace-scope fs service the
session route uses, so clients no longer borrow the session route's
{session_id} slot. kimi-web's @ file mention now calls this route with
the workspace ref instead of a session id; the session-route fallback
stays for wire compatibility.

* refactor(agent-core-v2): register workspace-scope service state into IWorkspaceStateService

- move workspaceDirs / workspaceInstructions / workspaceSkillCatalog / workspaceTrust
  runtime state from bare instance fields into the workspace state container
- extend gen-state-manifest.mts to scan app/workspace scopes, emitting
  AppStateSnapshot / WorkspaceStateSnapshot alongside Session/Agent
- regenerate docs/state-manifest.d.ts and update AGENTS.md + agent-core-dev skill
- update affected tests to register the state services and assert the new state keys
2026-07-31 12:11:26 +08:00
..
examples feat: unify the host identity across OAuth, telemetry, and kap-server (#2382) 2026-07-30 13:45:41 +08:00
scripts feat(klient): contract-driven facade with http/ipc/memory transports (#1768) 2026-07-16 16:43:09 +08:00
src feat(kap-server): add session-less POST /workspace/fs:search route (#2437) 2026-07-31 12:11:26 +08:00
test feat(kap-server): add session-less POST /workspace/fs:search route (#2437) 2026-07-31 12:11:26 +08:00
AGENTS.md refactor(agent-core-v2): rebuild the model wire layer on the kosong architecture (#1970) 2026-07-21 13:03:54 +08:00
CHANGELOG.md ci: release packages (#2342) 2026-07-30 14:56:30 +08:00
Dockerfile feat(klient): contract-driven facade with http/ipc/memory transports (#1768) 2026-07-16 16:43:09 +08:00
package.json ci: release packages (#2342) 2026-07-30 14:56:30 +08:00
README.md feat: agent-core-v2 permission/workspace refactors and transcript durability (#2021) 2026-07-22 19:21:56 +08:00
tsconfig.examples.json test(klient): add real-server smoke coverage (#1713) 2026-07-15 15:24:36 +08:00
tsconfig.json feat(klient): contract-driven facade with http/ipc/memory transports (#1768) 2026-07-16 16:43:09 +08:00
tsdown.config.ts feat(transcript): add unified transcript layer, drop the /api/v2 RPC surface (#1888) 2026-07-20 15:33:05 +08:00
vitest.config.ts feat(klient): contract-driven facade with http/ipc/memory transports (#1768) 2026-07-16 16:43:09 +08:00

@moonshot-ai/klient

Contract-driven client SDK for the agent-core-v2 engine. One facade, two transports — you pick the transport once at creation; everything after that is byte-identical:

import { bootstrap, logSeed, resolveLoggingConfig } from '@moonshot-ai/agent-core-v2';
import { createKlient } from '@moonshot-ai/klient/memory';   // or '/ipc'

const { app } = bootstrap({ homeDir }, [
  ...logSeed(resolveLoggingConfig({ homeDir, env: process.env })),
]);
const klient = createKlient({ scope: app });

const env = await klient.global.env();
const sessions = await klient.global.sessions.list({ limit: 20 });

const session = await klient.global.sessions.create({ workDir: process.cwd() });
const agent = klient.session(session.id).agent('main');
agent.events.on('assistant.delta', (e) => process.stdout.write(e.delta));
agent.events.on('prompt.completed', () => console.log('\ndone'));
await agent.prompt({ input: [{ type: 'text', text: 'Say OK.' }] });

await klient.close();

Architecture

facade (klient.global.*, klient.session(id).*, session.agent(id).*, *.events.*)
   ↓ single-object params, zod-validated
contract (procedure schemas, shared by all transports)
   ↓
KlientChannel { call, listen }   ← the only transport SPI
   ↓
ipc │ memory
  • Facade — aggregated methods, no engine service tokens, no onDid*/onWill* event names. There is no escape hatch to raw services: the facade is the public contract.
    • klient.global.*sessions.* (incl. create), workspaces.*, config.*, providers.*, models.*, catalog.*, auth.*, flags.*, plugins.*, hostFs.*, env().
    • klient.session(id).*get/setTitle/update/status/close/archive/ restore/fork/createChild, approvals.*, questions.*, interactions.*, agents().
    • session.agent(id).*prompt/steer/cancel/runShellCommand/ cancelShellCommand/getModel/setModel/setPermission/getUsage/getContext/ getPlan*/getTasks*/stopTask/getTaskOutput.
  • Contract — every method has a zod input tuple + output schema, validated on the client before send / after receive (default on; validate: false to disable). Validation is sub-µs for typical payloads — cheaper than the JSON serialization the wire already pays.
  • Eventsklient.events.on(...) for the global bus (config.changed, kosong.models.changed, session.archived, …), session(id).events.on('metadata.changed' | 'interactions.changed' | 'interactions.resolved'), and agent(id).events.on('turn.started' | 'assistant.delta' | 'tool.call.started' | 'prompt.completed' | …). Underlying subscriptions are shared and ref-counted; payloads are validated; bad payloads drop to events.onError.

Transports

entry options events
@moonshot-ai/klient/ipc { socketPath, token? } same socket
@moonshot-ai/klient/memory { scope } (a bootstrapped engine app scope) direct emitter/bus subscription

ipc and memory share one in-process dispatcher, so they behave identically by construction; memory additionally JSON round-trips every value so results cross the same JSON boundary a socket transport would impose. The IPC host ships with the transport: serveKlientIpc({ scope, socketPath }).

The same conformance suite runs against both transports in this package's tests (test/helpers/conformance.ts — one test file per transport).

This package also hosts the e2e suites (the retired server-e2e package was folded in here):

  • test/e2e/legacy/ + test/e2e/harness/ — the legacy /api/v1 live suites and their client harness (skip unless KIMI_SERVER_URL is set; the v1 surface has no in-memory equivalent, so these stay live-server-only).

The docker e2e runner (pnpm docker:e2e) runs this whole vitest suite inside a container against a container-local server. See AGENTS.md for the testing rules.

Scope

The facade covers the global (app), session, and agent surfaces shown above. What it deliberately leaves out (for now): onWill/hook-style interception (engine hooks are in-process OrderedHookSlots and not wire-exposable), file upload (v1 multipart REST only), and the terminal surface (v1 REST + WS only).

Smoke check

pnpm -C packages/klient smoke

examples/smoke.ts boots an in-process engine (memory transport) and asserts the global facade end-to-end — no server needed. examples/basic.ts is a shorter narrated tour; examples/context-usage.ts traces context-size readings through a real prompt (requires KIMI_EXAMPLE_MODEL + KIMI_EXAMPLE_API_KEY).