kimi-code/packages/klient/AGENTS.md
Haozhe 6dd4fd3368
refactor(agent-core-v2): rebuild the model wire layer on the kosong architecture (#1970)
* refactor(v2): land kosong contract layer (L0 wire contract)

* refactor(v2): land kosong protocol layer (L1 traits and base registry)

* refactor(v2): land kosong provider layer (bases, trait composers, kimi definition)

* refactor(v2): land kosong model and catalog layers

* refactor(v2): migrate engine callers to kosong, drop old llmProtocol layer

* test(v2): migrate agent-core-v2 tests and harness to kosong

* refactor: sync peripheral packages to kosong architecture

* refactor(v2): replace provider dialects with per-protocol definitions

* refactor(v2): merge the vertexai protocol into google-genai via providerOptions

* refactor(v2): remove vendor-name gates outside the kosong layer

* feat(v2): add model resolution inspection and connectivity ping

* refactor(v2): remove the unused platform config layer

* test(klient): pin invalid-input behavior across providers in e2e

* refactor(agent-core-v2): merge kimi traits, split bases by protocol

- merge the seven kimi trait modules into kimi.contrib.ts as two trait
  objects: kimiOpenAITrait (all native-transport hooks) and
  kimiAnthropicTrait (thinking only)
- move bases/* implementations into per-protocol directories (openai/,
  anthropic/, google-genai/) and rename openai.contrib.ts to
  openai-legacy.contrib.ts
- add per-directory index.ts registration barrels (import = registration)
  and exempt them in check-domain-layers.mjs alongside *.contrib.ts

* refactor(agent-core-v2): reorganize model and request-layer types

- consolidate shared model types (ModelOverrides, CompletionBudgetConfig/Params,
  ResolvedModelAuthMaterial, ThinkingDefaults/ModelThinkingMetadata) into
  kosong/model/model.types.ts and drop modelOverrides.ts
- rename L2 request types to the ModelRequest* prefix: LLMEvent ->
  ModelRequestEvent, LLMRequestInput -> ModelRequestInput,
  LLMCallParams -> ModelRequestParams
- extract ModelRequestTiming to replace three duplicated copies of the
  stream-timing shape
- rename L3 llmRequester types with the Agent prefix to match
  IAgentLLMRequesterService (AgentLLMRequestOverrides/Finish/Task/Source/
  PartHandler/LogFields)
- delete the unused LLMRequestParams type

* refactor(agent-core-v2): fold kosong/catalog into kosong/model

- merge IModelCatalogService's enumeration surface (listModels /
  listProviders / getProvider / setDefaultModel and the wire shapes) into
  IModelCatalog; delete kosong/catalog/modelCatalog.ts
- move the remote refresh path to the new IProviderDiscoveryService
  (discovery.ts + discoveryService.ts, renamed from
  catalog/modelCatalogService.ts)
- relocate configSection / errors to discoveryConfigSection.ts / errors.ts;
  DEFAULT_MODEL_SECTION now lives in kosong/model/model.ts
- drop the L3 catalog layer from check-domain-layers.mjs
- kap-server routes / refresh scheduler / channelRegistry follow the split;
  klient renames the contract modelCatalogService to modelResolver and adds
  providerDiscovery; kimi-inspect reads via IModelCatalog
- modelRequesterImpl: drop the streamedAnyPart backfill, onMessagePart
  already delivers every part
- tests: remove the app/modelCatalog and kosong/catalog suites, add the
  kosong/model catalog and discovery suites

* feat(klient): trim trailing undefined args and add boundary smoke probes

- add trimTrailingUndefined helper so optional trailing args no longer
  cross the wire as null in http/ipc transports, which defeated
  server-side default parameters
- add model-requester-boundary smoke probe for ChatProvider error
  wrapping behavior against real config and a local stub
- add kimi-select-tools smoke probe verifying the kimi-only wire
  encoding of dynamic tool declarations
- extend smoke.ts with a models set/get/delete round-trip, catalog
  list assertions, and update AGENTS.md with the new scripts

* fix(agent-core-v2): declare openai chat hooks as function properties

Method-shorthand members on OpenAIChatCompletionsHooks tripped
typescript-eslint(unbound-method) at every extraction site
(`const hook = this._hooks?.convertMessage` and friends), failing the
repo-wide lint job. Every implementation is a plain closure composed by
openaiHooks.ts, so declare the members as function-typed properties,
which matches the actual semantics and clears the four errors.

* fix: repair stale references surfaced by the origin/main rebase

- agent-core-v2: point vacuousContent's ContentPart import at kosong/contract
- kap-server: rewrite the transcript test seed as IModelCatalog (IModelResolver is gone)
- klient: inline onceEvent/waitFor after the http transport helpers were dropped
- kimi-inspect: remove useLiveEvent from ModelCatalogView; catalog polls on a slow interval

* chore: downgrade the kosong architecture changeset to patch
2026-07-21 13:03:54 +08:00

4.2 KiB

klient Agent Guide

Package-local rules for packages/klient.

Architecture

The package is layered; keep the layers strict when changing code:

  • Facade (src/core/facade/, src/core/klient.ts) — the only public API: aggregated global.* / session(id).* / session(id).agent(id).* methods and their events.* hubs. No engine service tokens, no onDid*/onWill* names, and no escape hatch to raw services — do not reintroduce a service locator (core()/service()/makeProxy).
  • Contract (src/contract/) — zod input/output schemas for every wire method plus event payload schemas. Schemas are hand-mirrored from agent-core-v2 types and pinned by the compile-time parity assertions in test/contract-parity.ts; when the engine types change, tsc fails here first. maybe()/noResult() in src/contract/helpers.ts encode the HTTP wire's null-vs-undefined semantics — use them for every X | undefined / void result.
  • Transports (src/transports/{ipc,memory}) — each implements the KlientChannel SPI (src/core/channel.ts) and nothing else. ipc frames the same dispatcher traffic as NDJSON over a unix socket and shares the in-process dispatcher with memory; memory JSON round-trips every value so both transports return byte-identical data.

The facade only covers services that behave identically on both transports (the in-process dispatcher mirrors the server's scope resolution, including main-agent materialization via ensureMainAgent). onWill/hook-style interception is not wire-exposable (engine hooks are in-process OrderedHookSlots); file upload and the terminal surface are v1-only and live in the legacy suites.

Testing

  • One shared conformance suite (test/helpers/conformance.ts) runs unchanged against every transport — one test file per transport under test/. Add new global facade coverage there, not per-transport.
  • test/e2e/legacy/ + test/e2e/harness/ — the legacy /api/v1 live suites (moved from server-e2e). They skip unless KIMI_SERVER_URL points at a running server and must keep running unchanged; the v1 surface has no in-memory equivalent, so these stay live-server-only — do not try to run them against the in-process transports.
  • The retired scenarios/ scripts were rewritten as suites: image-upload and terminal (v1-only surfaces) live in test/e2e/legacy/.

Observability (inherited from server-e2e)

  • Keep observability inside each e2e case; every live case prints structured, case-scoped details (requests, envelopes, WS handshakes, terminal frames, error envelopes) through the shared logger in test/e2e/legacy/log.ts, not ad hoc console.log.
  • Logs must stay visible for passing Vitest cases — write through stdout.
  • When adding or changing an e2e case, update its observability at the same time; do not add a scenario solely to print data an existing case should already expose.

Command reference

  • pnpm --filter @moonshot-ai/klient test — all Vitest suites (unit + conformance + e2e; live cases skip without their env).
  • KIMI_SERVER_URL=http://127.0.0.1:58627 pnpm --filter @moonshot-ai/klient test — include the live legacy cases against a running server.
  • pnpm --filter @moonshot-ai/klient docker:e2e — docker e2e; the run derives its runner name/namespace from the current workspace to avoid cross-workspace conflicts.
  • pnpm --filter @moonshot-ai/klient typecheck / pnpm smoke (in-process smoke over the memory transport; see examples/smoke.ts).
  • pnpm --filter @moonshot-ai/klient smoke:boundary — ModelRequester boundary probe: pings every model configured in the real ~/.kimi-code/config.toml through the in-process engine, then drives deterministic failure modes against a local stub to show which errors the ChatProvider layer wraps and which the requester owns (see examples/model-requester-boundary.ts).
  • pnpm --filter @moonshot-ai/klient smoke:select-tools — select_tools (progressive tool disclosure) probe for kimi-type providers: stub-verifies the kimi-only wire encoding of dynamic tool declarations, then runs a live two-step select→use flow per real kimi model (see examples/kimi-select-tools.ts).