* feat(media): materialize video uploads to cache and reference by path - copy TUI video placeholders into the shared cache instead of inlining the original source path - emit <video path="..."> tags so ReadMediaFile / the provider's VideoUploader owns upload behavior - apply the same cache materialization to server prompt video submissions, matching the TUI flow - update TUI unit tests and server e2e test to assert cache-path behavior * fix(web): make uploaded videos play in the chat Render the server's <video path> tag as a real video and reconcile the echoed user message so the bubble no longer shows raw markup or a duplicate. Serve file downloads with byte-range support and fetch video bytes with the bearer credential into a blob URL, since browsers cannot authorize a <video> src on their own. Also let users click an uploaded image to open it in the preview panel. * fix(web): use authenticated source for uploaded image previews openMediaPreview stored the raw getFileUrl as sourceUrl, and FilePreview renders it with a native <img> that sends no Authorization header, so the enlarge action 401'd for uploaded images. When the media carries a fileId, fetch the bytes through the authenticated API client and preview a blob URL instead, revoking it when the preview is replaced or closed. * fix(web): ignore stale authenticated media fetches AuthMedia fetches the file bytes asynchronously; when the component is reused with a new fileId before a prior fetch resolves (e.g. queued thumbnails keyed by index), the older response could still create a blob URL and show the previous file. Add a per-request sequence guard (and an unmount guard) so a stale response is discarded and its blob URL revoked instead of being applied. * fix(web): gate media path tags on file-store id shape Treating any standalone <video path="..."> text as an uploaded daemon file and stripping the basename into getFileUrl is only valid for server cache files named after the file-store id (f_…). TUI/ReadMediaFile tags use arbitrary cache names like <uuid>-<label>, and older transcripts may point at paths like /tmp/foo.mp4; those produced a broken /files/<basename> request. Only extract a fileId when the basename matches the file-store id shape, otherwise leave the raw tag as text. * fix(web): invalidate pending media preview on close Closing an uploaded-image preview before getFileBlob() resolved left previewRequestSeq untouched, so the fetch callback still passed its seq check, created a blob URL, then skipped attaching it because previewFile was already null — leaking up to the file size until another preview opened. Bump previewRequestSeq on close so the in-flight callback bails before creating the blob URL. * fix(web): defer authenticated media fetch until near viewport AuthMedia fetched the full image/video into a Blob on mount whenever a fileId was present, bypassing native loading="lazy" and preload="metadata". Opening a session with several historical large video uploads started many full downloads and held all blobs in memory even if the user never scrolled to or played them. Use an IntersectionObserver to defer the fetch until the element nears the viewport. * fix(web): revoke preview blob when leaving the file panel Switching to another detail panel only flips detailTarget and never calls closeFilePreview, so an in-flight getFileBlob could still create a blob URL after the file panel hid, and an already-shown blob URL was held until the next file preview. Check detailTarget before creating the blob URL, and reset/revoke the preview when detailTarget leaves 'file'. --------- Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai> |
||
|---|---|---|
| .. | ||
| scratch | ||
| src | ||
| test | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| package.json | ||
| README.md | ||
| SECURITY.md | ||
| tsconfig.json | ||
| tsdown.config.ts | ||
| vitest.config.ts | ||
@moonshot-ai/server
Local REST + WebSocket server that exposes the Kimi Code SDK over a stable wire
protocol. It hosts agent-core sessions and serves them under a single
/api/v1 prefix. This package is private — it is not published on its own;
it ships inside the kimi CLI (apps/kimi-code) and is launched via
kimi server run.
What it does
- Hosts
agent-coresessions, prompts, tools, approvals, questions, and workspaces in process. - Exposes them over REST (Fastify) and WebSocket (
ws) under/api/v1. - Serves the built-in web UI (
apps/kimi-web) as static assets when awebAssetsDiris provided. - Publishes machine-readable contract docs:
/openapi.json,/asyncapi.json.
Running it
# From the repo root — dev server with auto-restart
pnpm dev:server
pnpm dev:server:restart
# Checks
pnpm --filter @moonshot-ai/server typecheck # tsc --noEmit
pnpm --filter @moonshot-ai/server test # vitest run
pnpm --filter @moonshot-ai/server build # tsdown
The public entry point is startServer(opts) in src/start.ts, which returns a
RunningServer. In production the CLI command kimi server run
(apps/kimi-code/src/cli/sub/server/run.ts) imports and calls it. This package
has no dev script of its own — always start it from the repo root or via the
CLI.
By default the server listens on 127.0.0.1:58627; e2e clients target it with
KIMI_SERVER_URL (default http://127.0.0.1:58627).
Architecture
apps/kimi-code (CLI) apps/kimi-web (browser)
│ │
└──────────┬───────────────────┘
│ REST + WebSocket, /api/v1
┌──────────▼───────────┐
│ @moonshot-ai/server │
│ Fastify REST │
│ ws gateway │
│ DI container │ ← @moonshot-ai/agent-core
│ agent-core sessions │ ← @moonshot-ai/agent-core
└──────────────────────┘
- REST (
src/routes/): domain modules aggregated byregisterApiV1Routes.ts. Routes are declared withmiddleware/defineRoute.ts, which bundles Zod validators with the OpenAPI response schema. - WebSocket (
src/ws/,src/services/gateway/): per-sessionseq,server_hello/ack/event/resync_requiredframes, replay and fan-out. - DI (
src/services/serviceCollection.ts): seeds the container from@moonshot-ai/agent-core(getSingletonServiceDescriptors()) and layers in server-owned gateways plusIApprovalService/IQuestionServiceimplementations. - OS service managers (
src/svc/): launchd / systemd / schtasks backends forkimi server install/start.
Wire protocol notes
- Envelope: every REST response is
{ code, msg, data, request_id }and the HTTP status is effectively always 200 — checkcode(0 = ok), not the status. :actionendpoints: some routes use an:id:actionsuffix (e.g./sessions/{id}:undo); the suffix is parsed byroutes/action-suffix.ts.- Single-instance lock: a running server acquires a lock; a second start on
the same home throws
ServerLockedError. Tests pass a uniquelockPath/port.
Related packages
@moonshot-ai/agent-core— the agent engine the server hosts, including the in-process DI service layer it wires together.@moonshot-ai/protocol— wire types and the AsyncAPI document.@moonshot-ai/node-sdk— typed in-process facade for user code (KimiHarness,Session); prefer it over hand-rolling REST/WS calls.@moonshot-ai/server-e2e— wire-level e2e client and scenarios against a running server.
Development
For conventions, gotchas, and the boot wiring order, see
packages/server/AGENTS.md. For the service naming and
registration rules, see
packages/services/AGENTS.md.