|
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(web): support multi-level thinking effort selection Surface each model's declared reasoning efforts (support_efforts / default_effort) in the web model picker as a segmented control, replacing the on/off toggle. ThinkingLevel is now an open string, the model catalog carries the effort metadata to the web app, and the mobile settings sheet and /thinking command cycle through the available levels. * fix(web): preserve persisted thinking level before models load coerceThinkingForModel returned 'on' for any non-'off' level when the active model was still undefined (catalog not loaded yet), which rewrote a persisted/default effort like 'high' to 'on' and silently dropped the model's declared effort on later prompts. Keep the requested level as-is until loadModels() re-runs coercion with the real model. Addresses Codex P1 review on modelThinking.ts. * fix(web): coerce stale thinking level against the active model When selecting another session, the persisted thinking level can be a boolean 'on'/'off' carried over from a previous model. Deriving the pill suffix and active segment from that raw value could show "thinking: off" on an always-on model or hide the concrete effort behind a bare "thinking" tag. Coerce the level against the current model before deriving display state. Addresses Codex P2 review on Composer.vue. * fix(web): submit coerced thinking level for the prompt's target model The send paths (submitPromptInternal / steerPrompt) previously submitted rawState.thinking verbatim, so a value carried over from another session (e.g. 'max' from an effort model) was sent to a model that doesn't declare it, even though the composer already showed the coerced default. Coerce the level against the target model before submitting so the first turn runs with the level the UI displays. Addresses Codex P2 review on Composer.vue. * fix(web): coerce stale thinking in /thinking and mobile settings Two more surfaces derived their active state from the raw persisted level, which is stale when the saved level came from a different model: - /thinking slash command: indexing the raw value (e.g. 'on' from a boolean model) into an effort model's segments returned -1 and jumped to 'off' instead of advancing from the model's default effort. - Mobile settings sheet: clamping the raw prop fell back to the first segment, showing/selecting 'off' or the first effort while the composer and prompt submission coerce to the model default. Coerce the level against the active model in both places, matching the composer and send-path behavior. Addresses Codex P2 reviews on App.vue and MobileSettingsSheet.vue. |
||
|---|---|---|
| .. | ||
| design | ||
| public | ||
| scripts | ||
| src | ||
| test | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| index.html | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.ts | ||
Kimi Web
A browser client for Kimi Code — a peer to the TUI (apps/kimi-code) that talks
to a local server over REST + WebSocket. Vue 3 + Vite + TypeScript.
Quick start
# 1) Against a REAL server (the server must be running and reachable)
WEB_PORT=5197 KIMI_SERVER_URL=http://192.168.97.91:58627 pnpm -C apps/kimi-web run dev
# …or from the repo root: pnpm dev:web (uses the defaults below)
# 2) Offline / no server — a stub that fakes the server API + event stream
pnpm -C apps/kimi-web run dev:stub # then run dev in another shell
# checks
pnpm -C apps/kimi-web run typecheck # vue-tsc --noEmit
pnpm -C apps/kimi-web run test # vitest (pure logic only)
pnpm -C apps/kimi-web run build # vite build
How it connects to the server
The browser cannot reach the server cross-origin (no CORS), so Vite same-origin
proxies /api/v1 (HTTP + WS) to the server (vite.config.ts):
| env var | default | meaning |
|---|---|---|
WEB_PORT |
5175 |
port the dev server listens on |
KIMI_SERVER_URL |
http://127.0.0.1:58627 |
where /api/v1 (and /api/v1/ws) is forwarded |
Behind a corporate HTTP proxy, also set
NO_PROXY=<server-host>(for example,NO_PROXY=127.0.0.1,localhost) so the proxy forward reaches the server directly.
Architecture
A strict one-direction data flow; components never touch the network or the reducer — they consume computed view props and call actions.
server (REST + WS)
└─ src/api/daemon/client.ts REST adapter (envelope → AppX types)
└─ src/api/daemon/ws.ts WS frames → classify → projector/reducer
└─ agentEventProjector.ts RAW agent-core events → AppEvent[]
└─ eventReducer.ts AppEvent[] → state
└─ src/composables/useKimiWebClient.ts the ONLY place that imports api + state;
exposes computed view props + actions
└─ src/components/*.vue render props, emit intents (no api access)
The directory name
src/api/daemon/is historical and kept to minimise diff churn; conceptually it is the server adapter.
- Adapter (
src/api/): wire types are snake_case;AppXtypes are camelCase.config.tsbuilds/api/v1URLs. - Event projector (
agentEventProjector.ts): the server streams raw agent-core events (noevent.prefix).classifyFrameroutes raw vs protocol (event.*) frames; the projector converts them toAppEvents. - i18n (
src/i18n/): vue-i18n, en/zh, per-namespace flat camelCase keys. Detect order:localStorage('kimi-locale')→navigator.language→en.
Server contract — non-obvious notes
The server's wire protocol has a few things that will bite you if forgotten:
- Envelope: every response is
{ code, msg, data, request_id }and the HTTP status is always 200 — checkcode(0 = ok), not the status. - Prompts require five fields.
POST /sessions/{id}/promptsmust carry{ content, model, thinking, permission_mode, plan_mode }. The web fills these from settings (model ← session/default_model, thinking/permission/plan ← the StatusLine controls). Sending only{ content }→40001 model …. - Creating a session needs a registered workspace.
workspace_idmust be awd_<slug>_<hash>id that exists in the server's registry. Sessions get one auto-assigned by cwd, but it isn't registered until youPOST /workspaces { root }(idempotent). The web registers on demand beforecreateSession(otherwise:workspace not found: wd_…). - Persisted sessions are directly promptable — selecting an old session and
sending a message just works; there is no
:activatestep. - Workspaces = real folders.
GET/POST/PATCH/DELETE /workspaces,GET /fs:browse?path=,GET /fs:homeback the rail + folder picker.
Release & deployment
Kimi Web is not published as a standalone package. It ships as the built-in
web UI of the kimi CLI (apps/kimi-code).
Current release flow
- Develop —
pnpm dev:web(orpnpm -C apps/kimi-web run dev). - Build —
pnpm -C apps/kimi-web run buildproducesapps/kimi-web/dist. - Bundle into CLI —
pnpm -C apps/kimi-code run buildrunsscripts/copy-web-assets.mjs, which copiesapps/kimi-web/distintoapps/kimi-code/dist-web. - Publish — the root
.github/workflows/release.ymlpublishes@moonshot-ai/kimi-codeto npm;dist-webis listed in the packagefilesarray, so the built web assets travel with the CLI package. - Serve —
kimi server run/kimi webservesdist-webfrom the installed package.
The web UI does not display its own package version or build commit. It is
bundled into the CLI package and follows the published @moonshot-ai/kimi-code
release.
Suggested improvements
- Keep the current coupling for now. Because Kimi Code is primarily a local CLI/server product, bundling the web UI into the CLI package keeps installs self-contained and avoids cross-origin/CORS complexity.
- Add an independent web-deploy workflow only when needed. If a public
standalone web deployment is required later, create
.github/workflows/web-deploy.ymlthat buildsapps/kimi-weband uploadsdist/to the chosen static host (S3/CloudFront, Cloudflare Pages, Vercel, etc.). Until then, do not maintain a separate deploy target. - Keep versioning owned by the CLI release.
apps/kimi-web/package.jsonremains internal workspace metadata; do not surface it as a separate user version unless the web app becomes an independently published product. - Ensure the web build is exercised in CI. The root
buildscript already builds every workspace, sopnpm run buildin CI coversapps/kimi-web. Keep it that way; do not bypass the web build in release pipelines.