diff --git a/docs/en/reference/server-api.md b/docs/en/reference/server-api.md index f643d3f611..98c4dc208e 100644 --- a/docs/en/reference/server-api.md +++ b/docs/en/reference/server-api.md @@ -2,7 +2,7 @@ The local server started by `kimi web` exposes two programmatic surfaces: a REST API (`/api/v1`, plus `/api/v2/sessions`) and a WebSocket event stream (`/api/v1/ws`). This page is the protocol reference for both. For how to start the server and its command-line options, see the [kimi command](./kimi-command.md#kimi-web) reference; for an end-to-end walkthrough, see [Local server and API](../guides/server.md). -The complete request/response schema of every endpoint is owned by the server's live specification documents: `GET /openapi.json` (OpenAPI) and `GET /asyncapi.json` (AsyncAPI). Both require authentication. +This page is a curated, human-readable reference: it documents every endpoint's parameters, request bodies, and response shapes below. The precise machine-readable schema of every endpoint is owned by the server's live specification documents: `GET /openapi.json` (OpenAPI) and `GET /asyncapi.json` (AsyncAPI), both generated from the same validation schemas the server enforces at runtime. Both require authentication; when this page and the live spec ever disagree, the live spec wins. ::: warning The REST and WebSocket APIs described on this page are experimental: interface stability is not guaranteed, and endpoints, fields, and event types may change in any release. When integrating, rely on the `/openapi.json` and `/asyncapi.json` documents served by your version. @@ -91,8 +91,41 @@ Endpoints are grouped by resource below. A `:{action}` suffix in a path is the a | `GET /api/v1/meta` | Server version, capability map, `server_id`, experimental flags | | `POST /api/v1/shutdown` | Graceful shutdown (replies 200 first); mounted only on loopback binds | +#### `GET /api/v1/healthz` + +Liveness probe for scripts and process supervisors. It is the one `/api` endpoint exempt from the bearer token (see [Authentication](#authentication)) and answers without touching config or the engine. + +On success, `data` is `{ "ok": true }`. + +#### `GET /api/v1/meta` + +Returns this instance's identity and capability map. Most fields are frozen at boot; `experimental_flags` and `features` are resolved per request, so a flag flip or a failed feature shows up in the next response. + +On success, `data` carries: + +| Field | Type | Description | +| --- | --- | --- | +| `server_version` | string | Server version | +| `capabilities` | object | Capability map — `websocket`, `file_upload`, `fs_query`, `mcp`, `tasks`, `terminal`, all always `true` | +| `server_id` | string | Unique id of this server instance | +| `started_at` | string | Boot time, ISO 8601 | +| `open_in_apps` | array | Host apps usable as `open-in` targets (`finder` / `cursor` / `vscode` / `iterm` / `terminal`); currently always empty | +| `dangerous_bypass_auth` | boolean | Whether the server was started with `--dangerous-bypass-auth` (clients may skip the token prompt) | +| `backend` | string | Engine backend, `v1` or `v2`; always `v2` for this server | +| `web_title` | string | Custom browser tab title from `--web-title`; omitted when unset | +| `experimental_flags` | object | Experimental flag id → enabled, resolved at request time | +| `features` | array | Engine features as `{ name, state, meta }`; `state` is `Pending` / `Activating` / `Active` / `Unloading` / `Failed` | + +#### `POST /api/v1/shutdown` + +Asks the server to shut down gracefully. The reply is sent first and the shutdown runs immediately after, so the caller can trust the response it received. The route is mounted only on loopback binds — on a non-loopback bind it is not registered at all (requests hit a 404) unless the server was started with `--allow-remote-shutdown`. + +On success, `data` is `{ "ok": true }`. + ### Login and usage +These endpoints drive the managed Kimi OAuth login lifecycle and expose account-level information. The managed provider is named `managed:kimi-code`; the optional `provider` parameter on every endpoint below defaults to it. + | Method and path | Description | | --- | --- | | `GET /api/v1/auth` | Auth readiness snapshot | @@ -102,6 +135,80 @@ Endpoints are grouped by resource below. A `:{action}` suffix in a path is the a | `POST /api/v1/oauth/logout` | Log out the managed provider | | `GET /api/v1/oauth/usage` | Plan usage and limits | | `GET /api/v1/oauth/userinfo` | Account profile | +| `GET /api/v1/oauth/region` | Resolve the client region (`mainland-cn` / `global`) | + +#### `GET /api/v1/auth` + +Auth readiness snapshot: whether the server has a usable model configuration, plus the managed provider's login state. `ready` is `true` when at least one provider is configured, a default model is set, and the managed provider (when present) is not revoked. + +On success, `data` carries `ready` (boolean), `providers_count` (number of configured providers), `default_model` (the global default model alias, or `null`), and `managed_provider` (`null`, or `{ name, status }` with `status` one of `authenticated` / `expired` / `revoked` / `unauthenticated`). + +#### `POST /api/v1/oauth/login` + +Starts an OAuth device-code login flow for the managed provider; starting a new flow aborts any pending flow for the same provider. When the account is already authenticated, no user interaction is needed and the response reports `authenticated` immediately. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider` | body | string | Managed provider name. Default `managed:kimi-code` | +| `region` | body | string | `mainland-cn` or `global`; overrides the region resolution described under `GET /api/v1/oauth/region` for this flow | + +On success, `data` has one of two shapes. A pending flow — `{ flow_id, provider, status: "pending", verification_uri, verification_uri_complete, user_code, expires_in, interval, expires_at }`: open `verification_uri_complete` (or `verification_uri` and enter `user_code`), then poll `GET /api/v1/oauth/login` every `interval` seconds until the flow resolves or `expires_at` passes (`expires_in` is the same deadline in seconds). The already-authenticated fast path — `{ flow_id, provider, status: "authenticated" }`. + +#### `GET /api/v1/oauth/login` + +Polls the login flow state for a provider. Returns `null` when no flow has been started. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider` | query | string | Managed provider name. Default `managed:kimi-code` | + +On success, `data` is `null` or a flow snapshot: `{ flow_id, provider, status, verification_uri, verification_uri_complete, user_code, expires_in, expires_at, interval }`, where `status` is `pending` / `authenticated` / `denied` / `expired` / `cancelled`. Once the flow leaves `pending`, `resolved_at` records when it reached its terminal state and `error_message` describes a failed flow. + +#### `DELETE /api/v1/oauth/login` + +Cancels the pending login flow for a provider. When no flow is pending, the call is a no-op that reports the last known state. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider` | query | string | Managed provider name. Default `managed:kimi-code` | + +On success, `data` is `{ cancelled, status }`: `cancelled` is `true` only when a `pending` flow was actually aborted, and `status` is the flow state after the call. + +#### `POST /api/v1/oauth/logout` + +Logs out the managed provider: discards the stored OAuth credential, aborts any pending login flow, and removes the managed provider from the configuration. OAuth-managed providers reject manual edit and delete (see `PUT` / `DELETE /api/v1/providers/{provider_id}` below), so log out first to remove one. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider` | body | string | Managed provider name. Default `managed:kimi-code` | + +On success, `data` is `{ logged_out: true, provider }`. + +#### `GET /api/v1/oauth/usage` + +Plan usage and limits of the managed account, fetched live from the account service. An upstream failure does not fail the envelope — it comes back in-band with `kind: "error"`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider` | query | string | Managed provider name. Default `managed:kimi-code` | + +On success, `data` is `{ kind: "ok", summary, limits, extra_usage }` or `{ kind: "error", message, status? }`, where `status` is the upstream HTTP status when one exists. In the `ok` shape, `summary` (nullable) is the primary quota row and `limits` lists every quota window; a row is `{ name?, window?, used, limit, reset_at? }` with `window` as `{ duration, unit }`, `unit` one of `minute` / `hour` / `day` / `week`. `extra_usage` (nullable) is the pay-as-you-go wallet: `{ balance_cents, total_cents, monthly_charge_limit_enabled, monthly_charge_limit_cents, monthly_used_cents, currency }`. + +#### `GET /api/v1/oauth/userinfo` + +Profile of the managed account, with the same in-band `kind: "error"` convention as `GET /api/v1/oauth/usage`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider` | query | string | Managed provider name. Default `managed:kimi-code` | + +On success, `data` is `{ kind: "ok", userInfo }` or `{ kind: "error", message, status? }`. `userInfo` always carries `userId`, `nickname`, `status`, `region`, `userLevel`, `userLevelName`, `domain`, and `domainName`, and may add `globalId`, `bio`, `avatar`, `username`, `email`, `phone` (`{ countryCode, number }`), `createdTime`, and `lastLoginTime`. + +#### `GET /api/v1/oauth/region` + +Resolves which Kimi region this client belongs to. The answer is derived locally, not probed over the network: an OAuth host pinned by environment or config wins first, then the configured OAuth key, then the region marker file in the home directory; the default is `mainland-cn`. + +On success, `data` is `{ region }` with `region` one of `mainland-cn` / `global`. ### Config @@ -110,8 +217,71 @@ Endpoints are grouped by resource below. A `:{action}` suffix in a path is the a | `GET /api/v1/config` | Read the global config (secret fields redacted) | | `POST /api/v1/config` | Merge-patch the config; broadcasts `event.config.changed` | +#### `GET /api/v1/config` + +Returns the resolved global configuration — the effective result of `config.toml` plus overlays. Secrets are redacted: each provider reports only `has_api_key`, never the stored key. + +On success, `data` is the config object; its fields mirror the top-level domains documented under [Top-level fields](../configuration/config-files.md#top-level-fields): + +| Field | Type | Description | +| --- | --- | --- | +| `providers` | object | Map of provider id → `{ type, base_url?, default_model?, has_api_key }` | +| `default_provider` | string | Global default provider id | +| `default_model` | string | Global default model alias | +| `models` | object | Map of model alias → model record | +| `thinking` | object | Default parameters for Thinking mode | +| `plan_mode` | boolean | Plan mode flag | +| `yolo` | boolean | Derived: `true` when `default_permission_mode` is `yolo` | +| `default_permission_mode` | string | Default permission mode for new sessions | +| `default_plan_mode` | boolean | Whether new sessions start in Plan mode | +| `permission` | object | Initial permission rules | +| `hooks` | array | Lifecycle hooks | +| `services` | object | Built-in external service configuration | +| `merge_all_available_skills` | boolean | Whether to merge Agent Skills from all available directories | +| `extra_skill_dirs` | array | Extra skill search directories | +| `loop_control` | object | Agent loop control parameters | +| `background` | object | Background task runtime parameters | +| `subagent` | object | Subagent configuration | +| `secondary_model` | object | Secondary model pool for subagents | +| `experimental` | object | Experimental flag id → enabled | +| `telemetry` | boolean | Whether anonymous telemetry is enabled | +| `raw` | object | Raw parsed `config.toml` content, unmodeled fields included | + +#### `POST /api/v1/config` + +Merge-patches the global configuration: each top-level domain in the body is deep-merged into that domain, and domains absent from the body are left untouched. Setting `yolo` to `true` is shorthand for `default_permission_mode: "yolo"`. After a successful update the server broadcasts the global `event.config.changed` event with the changed field names and the full updated config; a rejected patch (invalid value or persistence failure) returns `40001` with the underlying message. + +The body is a partial config object — any subset of the response domains above except `raw`, all optional: + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `providers` | body | object | Map of provider id → provider table | +| `default_provider` | body | string | Global default provider id | +| `default_model` | body | string | Global default model alias | +| `models` | body | object | Map of model alias → model record | +| `thinking` | body | object | Default parameters for Thinking mode | +| `plan_mode` | body | boolean | Plan mode flag | +| `yolo` | body | boolean | `true` maps to `default_permission_mode: "yolo"`; `false` is ignored | +| `default_permission_mode` | body | string | `manual` / `yolo` / `auto` | +| `default_plan_mode` | body | boolean | Whether new sessions start in Plan mode | +| `permission` | body | object | Initial permission rules | +| `hooks` | body | array | Lifecycle hooks | +| `services` | body | object | Built-in external service configuration | +| `merge_all_available_skills` | body | boolean | Whether to merge Agent Skills from all available directories | +| `extra_skill_dirs` | body | array | Extra skill search directories | +| `loop_control` | body | object | Agent loop control parameters | +| `background` | body | object | Background task runtime parameters | +| `subagent` | body | object | Subagent configuration | +| `secondary_model` | body | object | Secondary model pool for subagents | +| `experimental` | body | object | Experimental flag id → enabled | +| `telemetry` | body | boolean | Whether anonymous telemetry is enabled | + +On success, `data` is the full updated config in the same shape as `GET /api/v1/config`. + ### Models and providers +These endpoints manage the two halves of model configuration — the [providers](../configuration/providers.md) table and the model-alias table of `config.toml` — plus a server-proxied models.dev directory for one-shot imports. A model alias id is the exact configured alias key: aliases created through the provider-management endpoints take the form `provider_id/model` (for example `my-provider/kimi-for-coding`), while a bare model-table key such as `turbo` is used as-is; anywhere the API takes a `model_id`, including the global `default_model`, it means this alias id. An unsupported action on a `:{action}` route returns `40001`. + | Method and path | Description | | --- | --- | | `GET /api/v1/models` | List configured model aliases | @@ -126,8 +296,200 @@ Endpoints are grouped by resource below. A `:{action}` suffix in a path is the a | `GET /api/v1/catalog/providers` | Browse the models.dev directory (server-proxied) | | `GET /api/v1/catalog/providers/{catalog_id}` | Read one directory entry | +#### `GET /api/v1/models` + +Lists every configured model alias across all providers. + +On success, `data.items` is an array of `{ provider, model, display_name?, max_context_size, capabilities?, support_efforts?, default_effort? }`: `model` is the alias id (`provider_id/model` for provider-managed aliases, otherwise the bare key), `provider` the owning provider id, `max_context_size` the context window in tokens, and `capabilities` / `support_efforts` / `default_effort` describe capability flags and Thinking-mode effort support. + +#### `POST /api/v1/models/{model_id}:set_default` + +Sets the global `default_model` to an existing alias. `model_id` is the exact configured alias key — for a bare key like `turbo` the call is `POST /api/v1/models/turbo:set_default`; URL-encode the id when it contains `/`, as in `POST /api/v1/models/my-provider%2Fkimi-for-coding:set_default`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `model_id` | path | string | **Required.** The exact configured model alias key; URL-encode it when it contains `/` | + +On success, `data` is `{ default_model, model }` — the alias now in effect and its catalog item (same shape as a `GET /api/v1/models` item). + +- `40001`: malformed or unsupported action suffix in the path +- `40413`: no model alias with that id + +#### `GET /api/v1/providers` + +Lists every configured provider with its credential and model-discovery state, without revealing any key. This is the provider item shape referenced by the other provider endpoints. + +On success, `data.items` is an array of: + +| Field | Type | Description | +| --- | --- | --- | +| `id` | string | Provider id | +| `type` | string | Wire protocol: `kimi` / `openai` / `openai_responses` / `anthropic` / `google-genai` / `vertexai` | +| `base_url` | string | API base URL, when set | +| `default_model` | string | The provider's default model alias, when set | +| `has_api_key` | boolean | Whether a credential is stored | +| `status` | string | `connected` when an API key or cached OAuth token exists, `unconfigured` otherwise (`error` is reserved in the schema) | +| `models` | array | The provider's model alias ids | + +#### `POST /api/v1/providers` + +Creates a provider and its model aliases in one save; the reply is HTTP 201 with the standard envelope. When no global `default_model` is configured at all (fresh setup), it is seeded with the new provider's `default_model` (or first model); an existing default is never modified. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `id` | body | string | **Required.** Provider id — letters, digits, `-`, `_`, and spaces; must start with a letter or digit | +| `type` | body | string | **Required.** Wire protocol: `kimi` / `openai` / `openai_responses` / `anthropic` / `google-genai` / `vertexai` | +| `api_key` | body | string | API key, stored in `config.toml` | +| `base_url` | body | string | API base URL; must not contain an environment variable placeholder (`${...}`) | +| `default_model` | body | string | The provider's default model; must be one of `models[].model` | +| `models` | body | array | **Required.** At least one entry, no duplicate `model` values; entry shape below | + +Each `models[]` entry declares one alias whose id becomes `id/model`: + +| Field | Type | Description | +| --- | --- | --- | +| `model` | string | **Required.** Upstream model name | +| `max_context_size` | integer | **Required.** Context window in tokens, ≥ 1 | +| `display_name` | string | Display name | +| `capabilities` | array | Capability flags such as `thinking` or `image_in` | +| `max_output_size` | integer | Max output tokens, ≥ 1 | +| `support_efforts` | array | Supported Thinking-mode effort levels | +| `adaptive_thinking` | boolean | Adaptive thinking toggle | + +On success, `data` is the created provider item (same shape as a `GET /api/v1/providers` item). + +- `40921`: a provider with this `id` already exists + +#### `GET /api/v1/providers/{provider_id}` + +Reads one provider. Unlike the list route, the response reveals the stored `api_key` when one is set, so a local edit form can prefill — keep this in mind when exposing the port. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider_id` | path | string | **Required.** Provider id | + +On success, `data` is the provider item plus `api_key` when a key is stored. + +- `40412`: provider not found + +#### `PUT /api/v1/providers/{provider_id}` + +Replaces a provider in one save: `type`, `base_url`, and the model list are rewritten, and the provider's aliases are rebuilt from `models` — aliases no longer listed disappear from `config.toml`, while other providers' aliases are untouched. `api_key` is tri-state: omitted keeps the stored key, `""` clears it, any other value replaces it. Beyond the `new_id` rename migration, the global default pointers are never modified. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider_id` | path | string | **Required.** Current provider id | +| `new_id` | body | string | Rename the provider; the providers key, model aliases, `default_provider`, a `default_model` pointing at an old alias, and the subagent secondary-model pool all migrate. Same id rules as `POST /api/v1/providers` | +| `type` | body | string | **Required.** Wire protocol: `kimi` / `openai` / `openai_responses` / `anthropic` / `google-genai` / `vertexai` | +| `api_key` | body | string | Tri-state, see above | +| `base_url` | body | string | API base URL; must not contain an environment variable placeholder (`${...}`) | +| `default_model` | body | string | The provider's default model; must be one of `models[].model` | +| `models` | body | array | **Required.** At least one entry, no duplicate `model` values; same entry shape as `POST /api/v1/providers` | + +On success, `data` is `{ provider }` with the saved provider item. + +- `40001`: a renamed alias id would collide with another provider's alias +- `40003`: provider is OAuth-managed — log out via `POST /api/v1/oauth/logout` instead +- `40412`: provider not found +- `40921`: `new_id` is already taken + +#### `DELETE /api/v1/providers/{provider_id}` + +Deletes a provider and all of its model aliases; the subagent secondary-model pool is cascaded. The global `default_provider` / `default_model` pointers are left untouched, even when they point at the deleted provider — they are the user's settings, not this endpoint's to garbage-collect. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider_id` | path | string | **Required.** Provider id | + +On success the server answers 204 with no body — the status line itself reports the delete (see [Response envelope](#response-envelope)). + +- `40003`: provider is OAuth-managed — log out via `POST /api/v1/oauth/logout` instead +- `40412`: provider not found + +#### `POST /api/v1/providers/{provider_id}:refresh` + +Re-discovers one provider's model metadata from its upstream source and rewrites the provider's aliases. Providers with a static model source are reported `unchanged` without any network call. When at least one provider's aliases change, the server broadcasts the global `event.model_catalog.changed` event. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `provider_id` | path | string | **Required.** Provider id | + +On success, `data` is a refresh report: `changed` is an array of `{ provider_id, provider_name, added, removed }` (added/removed alias counts), `unchanged` is an array of provider ids with no diff, and `failed` is an array of `{ provider, reason }`. + +- `40001`: malformed or unsupported action suffix in the path +- `40412`: provider not found + +#### `POST /api/v1/providers:refresh` + +Refreshes model metadata for every provider. The body is optional and ignored. + +On success, `data` is the same refresh report as `POST /api/v1/providers/{provider_id}:refresh` (`changed` / `unchanged` / `failed`). + +#### `POST /api/v1/providers:refresh_oauth` + +Same refresh as `POST /api/v1/providers:refresh`, limited to OAuth-backed providers. The body is optional and ignored. + +On success, `data` is the refresh report (`changed` / `unchanged` / `failed`). + +#### `POST /api/v1/providers:import_catalog` + +Imports one models.dev directory entry as a configured provider; the reply is HTTP 201 with the standard envelope. The wire protocol and endpoint come from the catalog resolution, and every catalogued model is written as an alias. Importing an id that already exists is a refresh — the provider entry and its aliases are rewritten from the catalog, and an omitted `api_key` keeps the stored key. The global default pointers are never modified, except that `default_model` is seeded from the first imported model when none is configured at all. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `catalog_id` | body | string | **Required.** Directory entry id from `GET /api/v1/catalog/providers` | +| `id` | body | string | Override the catalog id as the local provider id. Same id rules as `POST /api/v1/providers` | +| `api_key` | body | string | API key for the imported provider | +| `base_url` | body | string | Override the catalog-resolved endpoint; required when the entry's `needs_base_url` is `true` | + +On success, `data` is `{ provider, models_imported }` — the provider item and the number of aliases written. + +- `40001`: `catalog_id` missing or another body validation failure +- `40003`: the target provider exists and is OAuth-managed +- `40004`: the entry cannot be imported (rejected, requires a `base_url`, has no importable models, or its id is unusable as a provider id) +- `40417`: no directory entry with that `catalog_id` +- `50004`: the models.dev directory is unavailable + +#### `POST /api/v1/providers:import_registry` + +Imports a models.dev-shaped private registry — an `api.json` URL plus an optional Bearer key — as configured providers; the reply is HTTP 201 with the standard envelope. Every listed provider is written with a `source` record so scheduled refreshes rediscover it. Re-importing the same URL removes providers that disappeared upstream — the URL is the registry's stable identity, so rotating the key is safe. The global default pointers follow the same rules as `:import_catalog`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `url` | body | string | **Required.** URL of the registry's `api.json` | +| `api_key` | body | string | Bearer key for the registry; when omitted, the key from the previous import of the same URL is reused | + +On success, `data` is `{ providers, models_imported }` — an array of provider items and the total number of aliases written. + +- `40001`: `url` missing or another body validation failure +- `40003`: a listed provider exists and is OAuth-managed +- `40005`: the registry cannot be fetched or parsed, or lists no importable providers + +#### `GET /api/v1/catalog/providers` + +Browses the models.dev directory, proxied by the server with a 10-minute in-memory cache and a built-in snapshot fallback. Items keep the upstream directory order. Entries the server cannot import carry `rejected: true` with a machine-readable `reject_reason`; entries with `needs_base_url: true` require a base URL at import time. + +On success, `data.items` is an array of `{ id, name, wire_type, guessed, needs_base_url, rejected, reject_reason, env_key, models }`: `wire_type` is the resolved protocol (nullable, same enum as a provider `type`), `guessed` marks a heuristic resolution, `env_key` is the upstream's conventional API-key environment variable (nullable), and `models` is an array of `{ id, name?, max_context_size, capabilities?, reasoning }`. + +- `50004`: the directory is unavailable (both the live fetch and the built-in snapshot failed) + +#### `GET /api/v1/catalog/providers/{catalog_id}` + +Reads one models.dev directory entry by catalog id — the same item shape as `GET /api/v1/catalog/providers`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `catalog_id` | path | string | **Required.** Directory entry id | + +On success, `data` is the directory entry (same shape as a `GET /api/v1/catalog/providers` item). + +- `40417`: no directory entry with that `catalog_id` +- `50004`: the directory is unavailable + ### Sessions +These endpoints create, list, and inspect sessions, drive session-level actions (fork, compact, undo, and friends), and read per-session rollups. Most of them return a session in the wire shape documented once under [The session object](#the-session-object); non-CRUD operations use the `:{action}` convention described above. + | Method and path | Description | | --- | --- | | `POST /api/v1/sessions` | Create a session (requires `workspace_id` or `metadata.cwd`) | @@ -135,119 +497,1576 @@ Endpoints are grouped by resource below. A `:{action}` suffix in a path is the a | `GET /api/v1/sessions/{session_id}` | Read one session | | `GET /api/v1/sessions/{session_id}/profile` | Read the session profile | | `POST /api/v1/sessions/{session_id}/profile` | Update title, metadata, agent config | +| `POST /api/v1/sessions/{session_id}/title/generate` | Generate a title via the managed `chat_title` tool | | `POST /api/v1/sessions/{session_id}:{action}` | Session actions: `fork` / `compact` / `undo` / `abort` / `btw` / `archive` / `restore` | | `GET /api/v1/sessions/{session_id}/children` | List child sessions | | `POST /api/v1/sessions/{session_id}/children` | Create a child session (fork with a tag) | | `GET /api/v1/sessions/{session_id}/status` | Realtime status rollup | | `GET /api/v1/sessions/{session_id}/goal` | Current goal snapshot (`null` when none) | | `GET /api/v1/sessions/{session_id}/warnings` | Session-level warnings | +| `GET /api/v1/sessions/{session_id}/runtime` | Read the main agent's runtime binding | +| `POST /api/v1/sessions/{session_id}/runtime` | Switch the main agent's runtime binding | | `POST /api/v1/sessions/{session_id}/export` | Export the session with diagnostics (zip stream, not enveloped) | | `GET /api/v1/sessions/{session_id}/snapshot` | Full snapshot for client rebuilds (with `as_of_seq` and `epoch`) | +| `GET /api/v1/sessions/{session_id}/media/{file_id}` | Download prompt media by file id (binary) | -### Messages and transcript +#### The session object -| Method and path | Description | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/messages` | Page messages (`before_id` / `after_id` / `role`) | -| `GET /api/v1/sessions/{session_id}/messages/{message_id}` | Read one message | -| `GET /api/v1/sessions/{session_id}/transcript` | Turn-paged transcript (requires `agent_id`); global state rides along unpaginated | -| `GET /api/v1/sessions/{session_id}/transcript/ops` | Op-batch catch-up (`since_seq`); `complete: false` means a full refresh is needed | -| `GET /api/v1/sessions/{session_id}/transcript/user-messages` | Turn-opening user inputs, unpaginated | -| `GET /api/v1/sessions/{session_id}/transcript/plan` | ExitPlanMode plan content, path, and review outcome | +Every endpoint that returns a session uses this wire shape. The live facts (`busy`, `main_turn_active`, `pending_interaction`, `last_turn_reason`) are resolved from the session's activity aggregate: a session that is not loaded in this server process (a cold session) always reports not-busy with no pending interaction. A few fields are placeholders in the current projection — this is noted per field. -### Prompts +| Field | Type | Description | +| --- | --- | --- | +| `id` | string | Session id (`session_...`) | +| `workspace_id` | string | Owning workspace id | +| `title` | string | Session title; `""` when untitled | +| `created_at` / `updated_at` | string | Creation and last-update times, ISO 8601 | +| `archived` | boolean | Whether the session is archived (hidden from the default session list) | +| `archived_at` | string | Archive time, ISO 8601; present only when archived | +| `busy` | boolean | Any agent has an active turn or background task | +| `main_turn_active` | boolean | The main agent has an active turn | +| `pending_interaction` | string | `none` / `approval` / `question` — an unanswered interaction is waiting | +| `last_turn_reason` | string | Main agent's latest turn outcome: `completed` / `cancelled` / `failed` | +| `last_prompt` | string | Most recent user prompt text, when present | +| `metadata` | object | Custom metadata; always carries `cwd` (the session's working directory) | +| `agent_config` | object | Projected as `{ model }`; `model` is `""` in most responses and only filled with the live model by `GET /api/v1/sessions/{session_id}/snapshot` | +| `usage` | object | Token rollup `{ input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, context_tokens, context_limit?, total_cost_usd?, turn_count? }`; all zeros outside the snapshot endpoint | +| `permission_rules` | array | Session permission rules; currently always `[]` | +| `message_count` | integer | Message count; currently always `0` | +| `last_seq` | integer | Last event sequence number; currently always `0` | + +#### `POST /api/v1/sessions` + +Creates a session and returns it. The target directory comes from `workspace_id` (an already-registered workspace) or from `metadata.cwd` (the workspace is registered on first use); passing both requires them to agree. Creation broadcasts the global `event.session.created` event. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `workspace_id` | body | string | **Required** when `metadata.cwd` is absent. Registered workspace id; the session is created at that workspace's root | +| `metadata` | body | object | Custom metadata. `metadata.cwd` is the working directory and is **required** when `workspace_id` is absent; with both given, it must equal the workspace root | +| `title` | body | string | Initial title (at least 1 character); the session is untitled otherwise | +| `agent_config` | body | object | Accepted by the schema but currently not applied — set the model and modes through `POST /api/v1/sessions/{session_id}/profile` | -| Method and path | Description | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/prompts` | Active and queued prompts | -| `POST /api/v1/sessions/{session_id}/prompts` | Submit a prompt (content-part array, optional model / permission-mode overrides) | -| `POST /api/v1/sessions/{session_id}/prompts:steer` | Steer queued prompts into the active turn | -| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:abort` | Abort a running prompt | -| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:steer` | Steer one queued prompt | +On success, `data` is [the session object](#the-session-object) of the new session. -### Approvals and questions +- `40001`: neither `workspace_id` nor `metadata.cwd` given, or `metadata.cwd` does not match the workspace root (`details` lists the field) +- `40409`: the working directory does not exist or is not a directory +- `40410`: no registered workspace with that `workspace_id` -| Method and path | Description | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/approvals` | List approval requests (filter with `status=pending`) | -| `POST /api/v1/sessions/{session_id}/approvals/{approval_id}` | Resolve an approval | -| `GET /api/v1/sessions/{session_id}/questions` | List questions | -| `POST /api/v1/sessions/{session_id}/questions/{question_id}` | Answer a question | -| `POST /api/v1/sessions/{session_id}/questions/{question_id}:dismiss` | Dismiss a question | +#### `GET /api/v1/sessions` -### Background tasks +Lists sessions across workspaces, newest `updated_at` first. Cursor pagination follows [Pagination](#pagination), with one twist: without `page_size` (and without `archived_only`) the response is a single unpaginated window whose `has_more` is always `false`, so pass `page_size` to actually page. -| Method and path | Description | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/tasks` | List background tasks | -| `GET /api/v1/sessions/{session_id}/tasks/{task_id}` | Read a task (optional output preview) | -| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` | Cancel a task | +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `before_id` | query | string | Only sessions older than this id; mutually exclusive with `after_id` | +| `after_id` | query | string | Only sessions newer than this id; mutually exclusive with `before_id` | +| `page_size` | query | integer | 1–100. When paging applies, the default is `20`; see the note above for the unpaginated default behavior | +| `busy` | query | boolean | Keep only busy (or only idle) sessions | +| `include_archive` | query | boolean | Include archived sessions alongside live ones. Default `false` | +| `archived_only` | query | boolean | Keep only archived sessions; mutually exclusive with `include_archive`; implies cursor paging even without `page_size` | +| `exclude_empty` | query | boolean | Drop sessions that carry no user prompt | +| `workspace_id` | query | string | Restrict to one workspace (aliases are resolved) | -### Skills, tools, and MCP +On success, `data` is `{ items, has_more }` where each item is [the session object](#the-session-object). -| Method and path | Description | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/skills` | Per-session skill catalog | -| `GET /api/v1/workspaces/{workspace_id}/skills` | Session-less skill catalog for a workspace | -| `POST /api/v1/sessions/{session_id}/skills/{skill_name}:activate` | Activate a skill (starts a turn) | -| `GET /api/v1/tools` | List tools of the effective agent | -| `GET /api/v1/mcp/servers` | List MCP servers | -| `POST /api/v1/mcp/servers/{mcp_server_id}:restart` | Restart an MCP server | +- `40001`: validation failure — for example `before_id` combined with `after_id`, or `archived_only` combined with `include_archive` +- `40410`: unknown `workspace_id` -### Terminals +#### `GET /api/v1/sessions/{session_id}` -PTY terminal endpoints; mounted only on loopback binds. +Reads one session from the index. Live facts are included when the session is loaded in this process; a cold session reports not-busy with its last persisted turn outcome. -| Method and path | Description | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/terminals` | List terminals | -| `POST /api/v1/sessions/{session_id}/terminals` | Create a terminal | -| `GET /api/v1/sessions/{session_id}/terminals/{terminal_id}` | Read a terminal (including scrollback) | -| `POST /api/v1/sessions/{session_id}/terminals/{terminal_id}:close` | Close a terminal | +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | -### Workspaces +On success, `data` is [the session object](#the-session-object). -| Method and path | Description | -| --- | --- | -| `GET /api/v1/workspaces` | List registered workspaces | -| `POST /api/v1/workspaces` | Register a workspace (idempotent on the root path) | -| `PATCH /api/v1/workspaces/{workspace_id}` | Rename | -| `DELETE /api/v1/workspaces/{workspace_id}` | Unregister (keeps on-disk content) | -| `GET /api/v1/workspaces/{workspace_id}/trust` | Read the trust state | -| `POST /api/v1/workspaces/{workspace_id}/trust` | Grant trust | -| `POST /api/v1/workspaces/{workspace_id}/untrust` | Revoke trust | +- `40401`: session not found, or its workspace can no longer be resolved -### File system +#### `GET /api/v1/sessions/{session_id}/profile` -In-session file operations go through `POST /api/v1/sessions/{session_id}/fs:{action}` with JSON bodies; actions are `list` / `read` / `list_many` / `stat` / `stat_many` / `mkdir` / `search` / `grep` / `git_status` / `diff` / `open` / `open-in` / `reveal`. In addition: +Reads the session profile — the same wire payload as `GET /api/v1/sessions/{session_id}`. -| Method and path | Description | -| --- | --- | -| `POST /api/v1/workspace/fs:search` | Session-less workspace search (the body carries the workspace reference) | -| `GET /api/v1/sessions/{session_id}/fs/{path}:download` | Download a session file (binary, see below) | -| `GET /api/v1/fs:browse` | List host directories (folder picker) | -| `GET /api/v1/fs:home` | The user's home directory and recent workspaces | -| `GET /api/v1/fs:content` | Raw bytes of any host file (gated only by the token — be careful when exposing the port) | -| `POST /api/v1/fs:mkdir` | Create a directory by absolute path | +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | -### File uploads +On success, `data` is [the session object](#the-session-object). -| Method and path | Description | -| --- | --- | -| `POST /api/v1/files` | Multipart upload (`file` field, optional `name` and `expires_in_sec`); returns file metadata | -| `GET /api/v1/files/{file_id}` | Download (binary; errors use real HTTP statuses) | -| `DELETE /api/v1/files/{file_id}` | Delete | +- `40401`: session not found -### Global search and misc +#### `POST /api/v1/sessions/{session_id}/profile` -| Method and path | Description | -| --- | --- | -| `POST /api/v1/search` | Cross-session full-text search; `mode` is `terms` (default) or `literal` (exact substring); `page_token` pagination | -| `GET /api/v1/connections` | List live WebSocket connections | -| `GET /api/v2/sessions` | Next-generation session list, see below | -| `POST /api/v2/sessions:archive` | Batch-archive sessions, see below | -| `POST /api/v2/sessions:restore` | Batch-restore archived sessions, see below | -| `/api/v1/debug/*` | Reflection debug RPC; mounted only with `--debug-endpoints` on loopback, not a stable protocol | +Updates the session's profile: title, custom metadata, and the main agent's config. A title set here becomes a custom title, which wins over generated titles; setting one broadcasts the global `session.meta.updated` event. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `title` | body | string | New title (at least 1 character); becomes a custom title | +| `metadata` | body | object | Keys merged into the session's custom metadata | +| `agent_config` | body | object | Partial main-agent config; fields below, all optional | + +Each `agent_config` field is applied immediately to the main agent: + +| Field | Type | Description | +| --- | --- | --- | +| `model` | string | Model alias id; an empty string is ignored | +| `thinking` | string | Thinking-mode effort level | +| `permission_mode` | string | `manual` / `yolo` / `auto` | +| `plan_mode` | boolean | Enter or exit Plan mode | +| `swarm_mode` | boolean | Enter or exit swarm mode | +| `goal_objective` | string | Create a goal with this objective | +| `goal_control` | string | `pause` / `resume` / `cancel` the current goal | + +The schema also accepts `system_prompt`, `tools`, `mcp_servers` inside `agent_config`, and a top-level `permission_rules` array, but the update route currently does not apply them. + +On success, `data` is the updated [session object](#the-session-object). + +- `40401`: session not found + +#### `POST /api/v1/sessions/{session_id}/title/generate` + +Generates a title from the session's prompts through the managed provider's `chat_title` tool and applies it, broadcasting `session.meta.updated`. Generation requires the managed OAuth login and the `auto_session_title` experimental flag; without `force`, a session that already has a custom or generated title is reported unavailable instead of being overwritten. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `force` | body | boolean | Regenerate even when a custom or generated title exists. Default `false` | +| `source` | body | string | Title input: `user_prompts` (default) / `first_turn` / `digest` | + +On success, `data` is `{ title }` — the title now applied to the session. + +- `40401`: session not found +- `40923`: generation unavailable — the flag is off, there is no managed OAuth login or no prompt content yet, an existing title without `force`, or the backend request failed + +#### `POST /api/v1/sessions/{session_id}:{action}` + +Session actions are dispatched through one route: the path tail is parsed as `{session_id}:{action}`, the body is validated against the action's schema, and a missing or unknown action fails `40001` (`unsupported action: ...`). Every action resolves the session first, so all of them can return `40401` for an unknown session. The supported actions are documented one by one below. + +#### `POST /api/v1/sessions/{session_id}:fork` + +Copies the session — its transcript, agent state, and files — into a new session in the same workspace, and broadcasts `event.session.created`. Forking is rejected while any of the session's agents has an active turn. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `title` | body | string | Title for the fork (at least 1 character). Default `Fork: ` | +| `metadata` | body | object | Custom metadata for the fork | + +On success, `data` is [the session object](#the-session-object) of the new session. + +- `40901`: the session has an active turn and cannot be forked + +#### `POST /api/v1/sessions/{session_id}:compact` + +Starts a manual full compaction of the main agent's context. The call returns immediately; progress and completion are delivered as the `compaction.*` WebSocket events. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `instruction` | body | string | Extra guidance for the compaction summary; a blank value is ignored | + +On success, `data` is an empty object. + +- `40910`: a turn or another context change is active, or the history has nothing to compact + +#### `POST /api/v1/sessions/{session_id}:undo` + +Rewinds the main agent's conversation by `count` turns and reconciles the derived session state (including the session's `last_prompt`). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `count` | body | integer | Number of turns to undo; positive integer. Default `1` | +| `page_size` | body | integer | Size of the returned history window, 1–100. Default `50` | + +On success, `data` is `{ messages, status }`: `messages` is a `{ items, has_more }` page of the remaining context messages, newest first, and `status` is the same rollup as `GET /api/v1/sessions/{session_id}/status`. + +- `40901`: a turn is active or a compaction is running — wait for it to finish, then retry +- `40911`: that many turns cannot be undone (a compaction boundary or lost checkpoints); `data` carries `{ reason, requestedCount, undoableCount }` + +#### `POST /api/v1/sessions/{session_id}:abort` + +Cancels the main agent's running turn — the programmatic equivalent of the user aborting the turn in the TUI. + +On success, `data` is `{ aborted: true }`. + +#### `POST /api/v1/sessions/{session_id}:btw` + +Starts a "by the way" side conversation: forks the main agent into a child agent whose tool calls are disabled, so quick side questions run in isolation without touching the working context. Requires a usable model configuration. + +On success, `data` is `{ agent_id }` — the id of the new child agent. + +#### `POST /api/v1/sessions/{session_id}:archive` + +Marks the session archived: it disappears from the default session list (it stays listed with `include_archive` or `archived_only`), and the server broadcasts the global `event.session.archived` event. + +On success, `data` is `{ archived: true }`. + +#### `POST /api/v1/sessions/{session_id}:restore` + +Un-archives the session and resumes it. + +On success, `data` is [the session object](#the-session-object) with `archived: false`. + +#### `GET /api/v1/sessions/{session_id}/children` + +Lists the session's children — the sessions created through `POST /api/v1/sessions/{session_id}/children`. Cursor pagination follows [Pagination](#pagination). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `before_id` | query | string | Only children older than this id; mutually exclusive with `after_id` | +| `after_id` | query | string | Only children newer than this id; mutually exclusive with `before_id` | +| `page_size` | query | integer | 1–100. Default `100` | +| `busy` | query | boolean | Keep only busy (or only idle) children | + +On success, `data` is `{ items, has_more }` where each item is [the session object](#the-session-object). + +- `40401`: session not found + +#### `POST /api/v1/sessions/{session_id}/children` + +Creates a child session: a fork of this session recorded as its child, so it shows up under `GET /api/v1/sessions/{session_id}/children`. The same active-turn restriction as `:fork` applies. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `title` | body | string | Title for the child (at least 1 character). Default `Child: ` | +| `metadata` | body | object | Custom metadata for the child | + +On success, `data` is [the session object](#the-session-object) of the new session, and the server broadcasts `event.session.created`. + +- `40901`: the session has an active turn and cannot be forked + +#### `GET /api/v1/sessions/{session_id}/status` + +Realtime status rollup of the main agent; reading it resumes the session if it is cold. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | + +On success, `data` is `{ busy, model?, thinking_level, permission, plan_mode, swarm_mode, context_tokens, max_context_tokens?, context_usage? }`: `busy` reports an active turn, `model` / `thinking_level` / `permission` are the effective agent settings, `plan_mode` / `swarm_mode` are the mode flags, and `context_tokens` with `max_context_tokens` and `context_usage` (0–1) describe context-window consumption. + +- `40401`: session not found + +#### `GET /api/v1/sessions/{session_id}/goal` + +Reads the session's current goal snapshot, or `null` when no goal is active. Note that this payload uses camelCase keys, unlike most of this API. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | + +On success, `data` is `null` or `{ goalId, objective, completionCriterion?, status, turnsUsed, tokensUsed, wallClockMs, budget, terminalReason? }`, where `status` is `active` / `paused` / `blocked` / `complete` and `budget` reports the token, turn, and wall-clock budgets together with the remaining amounts and per-budget reached flags (each nullable when no such budget is set). + +- `40401`: session not found + +#### `GET /api/v1/sessions/{session_id}/warnings` + +Reads session-level warnings. The current producer is the oversized `AGENTS.md` check (`agents-md-oversized`), so the list is empty for most sessions. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | + +On success, `data` is `{ warnings }`, each entry `{ code, message, severity }` with `severity` one of `info` / `warning` / `error`. + +- `40401`: session not found + +#### `GET /api/v1/sessions/{session_id}/runtime` + +Reads the main agent's runtime binding — which runtime the session's agent loop runs on. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | + +On success, `data` is `{ workspace_id, runtime_id }`. + +- `40401`: session not found + +#### `POST /api/v1/sessions/{session_id}/runtime` + +Switches the main agent's runtime binding. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `runtime_id` | body | string | **Required.** Target runtime id | + +On success, `data` is the new binding `{ workspace_id, runtime_id }`. + +- `40420`: no runtime with that `runtime_id` +- `40926`: the runtime exists but is unavailable + +#### `POST /api/v1/sessions/{session_id}/export` + +Exports the session together with diagnostic logs as a zip attachment (`kimi-session-.zip`). The response is a binary stream, not a JSON envelope — capabilities and failure semantics are covered under [Binary and streaming endpoints](#binary-and-streaming-endpoints). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `web_log` | body | string | Client log text to include in the archive, at most 256 KB UTF-8 | +| `desktop` | body | boolean | Also include the desktop host's log. Default `false` | + +#### `GET /api/v1/sessions/{session_id}/snapshot` + +Assembles an atomic snapshot for rebuilding a client after a resync: the session, recent messages, the in-flight turn, live subagents, and pending interactions, all stamped with the `as_of_seq` watermark and `epoch` used to resubscribe — see [Reconnect and recovery](#reconnect-and-recovery). Unlike the plain session endpoints, the embedded session carries the live `agent_config.model` and real `usage` totals. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | + +On success, `data` is `{ as_of_seq, epoch, session, messages, in_flight_turn, subagents?, pending_approvals, pending_questions }`: `session` is [the session object](#the-session-object), `messages` is the newest 100 messages as `{ items, has_more }`, `in_flight_turn` is the partially streamed turn (`null` when idle, with `current_prompt_id` when known), `subagents` lists live subagent tasks, and `pending_approvals` / `pending_questions` carry the unanswered interactions. + +- `40401`: session not found + +#### `GET /api/v1/sessions/{session_id}/media/{file_id}` + +Downloads a prompt media file (an image or other attachment referenced by the session's prompts) by file id; an id not yet committed to the session falls back to the staged uploads. The response is binary with `Range` support (206 on ranged requests) — see [Binary and streaming endpoints](#binary-and-streaming-endpoints) for the shared conventions; unlike the enveloped endpoints there, a missing session or file answers with a real 404 status carrying an envelope body. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `file_id` | path | string | **Required.** Media file id | + +### Messages and transcript + +The `messages` endpoints page the main agent's flattened message history, while the `transcript` endpoints serve the structured per-agent transcript — turns, tasks, interactions, attachments — that the WebSocket [Transcript protocol](#transcript-protocol) streams live. Use these endpoints for history paging and catch-up, and the WebSocket subscription for the live tail. + +| Method and path | Description | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/messages` | Page messages (`before_id` / `after_id` / `role`) | +| `GET /api/v1/sessions/{session_id}/messages/{message_id}` | Read one message | +| `GET /api/v1/sessions/{session_id}/transcript` | Turn-paged transcript (requires `agent_id`); global state rides along unpaginated | +| `GET /api/v1/sessions/{session_id}/transcript/ops` | Op-batch catch-up (`since_seq`); `complete: false` means a full refresh is needed | +| `GET /api/v1/sessions/{session_id}/transcript/user-messages` | Turn-opening user inputs, unpaginated | +| `GET /api/v1/sessions/{session_id}/transcript/plan` | ExitPlanMode plan content, path, and review outcome | + +#### `GET /api/v1/sessions/{session_id}/messages` + +Pages the main agent's message history — the flattened context transcript shared with the session snapshot — newest first. Cursor pagination follows [Pagination](#pagination); reading the history resumes the session when it is cold. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `before_id` | query | string | Only messages older than this message id; mutually exclusive with `after_id` | +| `after_id` | query | string | Only messages newer than this message id; mutually exclusive with `before_id` | +| `page_size` | query | integer | 1–100. Default `50` | +| `role` | query | string | Keep only one role: `user` / `assistant` / `tool` / `system`. The filter applies after the page is sliced, so a filtered page can hold fewer than `page_size` items while `has_more` is still `true` — keep paging until `has_more` is `false` | + +On success, `data` is `{ items, has_more }` where each item is a message object `{ id, session_id, role, content, created_at, prompt_id?, parent_message_id?, metadata? }`; `content` is an array of content parts in the wire format documented under [Prompts](#prompts) (`text`, `tool_use`, `tool_result`, `image`, `video`, `file`, `thinking`). + +- `40001`: validation failure — for example `before_id` combined with `after_id` +- `40401`: session not found + +#### `GET /api/v1/sessions/{session_id}/messages/{message_id}` + +Reads one message from the same history by id. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `message_id` | path | string | **Required.** Message id | + +On success, `data` is the message object in the item shape documented under `GET /api/v1/sessions/{session_id}/messages` above. + +- `40401`: session not found +- `40403`: no message with that id in this session + +#### `GET /api/v1/sessions/{session_id}/transcript` + +Returns one page of an agent's structured transcript: turns (with their steps and frames) plus the markers and task references between them. Live sessions answer from the in-memory store (the requested agent's persisted history is backfilled first); cold sessions rebuild the agent from the persisted wire records. This is the history half of the transcript surface — the live streaming half is the [Transcript protocol](#transcript-protocol) subscription. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `agent_id` | query | string | **Required.** Agent whose transcript to read; must be a plain agent id (letters, digits, `.`, `_`, `-` — no path separators) | +| `before_turn` | query | string | Only turns older than this turn id; mutually exclusive with `after_turn` | +| `after_turn` | query | string | Only turns newer than this turn id; mutually exclusive with `before_turn` | +| `page_size` | query | integer | 1–100 turns. Default `20` | + +The page unit is the turn: without a cursor the newest page is returned, and `has_more` reports that older turns remain. On success, `data` is `{ agent_id, items, has_more, tasks, interactions, attachments, todos, meta, agents, pending_interactions, seq? }` — `items` is the paged turn slice, `tasks` / `interactions` / `attachments` / `todos` / `meta` / `agents` / `pending_interactions` are global agent state that ships unpaginated with every response, and `seq` is the agent's op-batch watermark for resuming the stream (live sessions only). + +- `40001`: validation failure — `before_turn` combined with `after_turn`, or a non-plain `agent_id` +- `40401`: session not found + +#### `GET /api/v1/sessions/{session_id}/transcript/ops` + +Serves point-to-point catch-up from the server's op journal: the journaled op batches with `seq > since_seq` for one agent, oldest first. It is the REST counterpart of the `transcript_since` resume cursor described in [Transcript protocol](#transcript-protocol) and shares the same bounded journal, so the same fallback rule applies. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `agent_id` | query | string | **Required.** Agent id (plain id, same constraint as the transcript endpoint) | +| `since_seq` | query | integer | **Required.** The caller's last applied op-batch seq, minimum `0`; batches above it are returned | + +On success, `data` is `{ agent_id, batches, latest_seq, complete }`, each batch `{ seq, ops }`. `complete: true` means every batch up to `latest_seq` is present; `complete: false` means the journal no longer reaches back to `since_seq` (or the session is not live at all), and the caller must fall back to a full `GET .../transcript` refresh. + +- `40001`: validation failure +- `40401`: session not found + +#### `GET /api/v1/sessions/{session_id}/transcript/user-messages` + +Lists every turn-opening input of the session, grouped per agent and unpaginated: real user text, user-slash skill and plugin commands, and cron prompts — distinguishable via `origin` — plus attachment-only prompts projected with an empty `prompt`. Attachment entities referenced by the listed messages ride along (metadata only, never bytes). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `agent_id` | query | string | Read one agent only (plain id). Default reads every rostered agent | + +On success, `data` is `{ agents }` where each entry is `{ agent_id, messages, attachments }`; a message is `{ turn_id, ordinal, state, origin, prompt, attachment_ids?, started_at? }` with `state` the turn state (`queued` / `running` / `completed` / `failed` / `cancelled`). + +- `40001`: validation failure — a non-plain `agent_id` +- `40401`: session not found + +#### `GET /api/v1/sessions/{session_id}/transcript/plan` + +Reads the plan information of an agent's `ExitPlanMode` tool calls — plan content, plan file path, offered options, and the review outcome — in timeline order. Content is projected from the first available fact: the linked approval interaction (interactive reviews), the live tool frame's display (auto mode), or the tool result output text; each entry records which one in `source`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `agent_id` | query | string | **Required.** Agent id (plain id) | +| `tool_call_id` | query | string | Narrow the read to one `ExitPlanMode` call; absent lists every call with recoverable plan content | + +On success, `data` is `{ agent_id, plans }` where each plan is `{ tool_call_id, turn_id, source, plan, path?, options?, review? }`: `source` is `interaction` / `display` / `output`, `options` are the review choices as `{ label, description? }`, and `review` (present only for interactive reviews) is `{ state, selected_option?, feedback? }` with `state` one of `pending` / `approved` / `rejected` / `cancelled`. + +- `40001`: validation failure +- `40401`: session not found +- `40416`: `tool_call_id` given, but no `ExitPlanMode` call with that id exists + +### Prompts + +A prompt is one unit of user input: submitting one enqueues it on the session's main agent (or a named agent), a queued prompt can be steered into the active turn, and a running prompt can be aborted. Turn progress itself streams over the WebSocket [events](#events), not these endpoints. + +| Method and path | Description | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/prompts` | Active and queued prompts | +| `POST /api/v1/sessions/{session_id}/prompts` | Submit a prompt (content-part array, optional model / permission-mode overrides) | +| `POST /api/v1/sessions/{session_id}/prompts:steer` | Steer queued prompts into the active turn | +| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:abort` | Abort a running prompt | +| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:steer` | Steer one queued prompt | + +#### `GET /api/v1/sessions/{session_id}/prompts` + +Reads the main agent's prompt queue snapshot. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | + +On success, `data` is `{ active, queued }`: `active` is the running prompt (`null` when idle) and `queued` lists the pending prompts in order. A prompt is `{ prompt_id, user_message_id, status, content, created_at }` with `status` one of `running` / `queued` / `blocked` and `content` in the content-part format accepted by `POST /api/v1/sessions/{session_id}/prompts`. + +- `40401`: session not found + +#### `POST /api/v1/sessions/{session_id}/prompts` + +Submits a user prompt to the session. Media references are validated first, then the optional overrides are applied to the target agent — `profile` (bound together with `model` / `thinking`), then `model`, `thinking`, `permission_mode`, and `disabled_tools` — and the prompt is enqueued; the response returns as soon as the prompt is accepted, without waiting for the turn. With `skills`, the prompt runs as a bundled skill activation instead of a plain user prompt. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `content` | body | array | **Required.** Non-empty array of content parts; variants below | +| `agent_id` | body | string | Target agent. Default the main agent | +| `prompt_id` | body | string | Client-chosen prompt id for idempotent submission; an id already reserved by an in-flight prompt fails `40927`, one that has already completed fails `40903`. Cannot be combined with `skills` | +| `skills` | body | array | Bundled skill activations, at least 1 entry of `{ name, args? }`; every skill must exist and be user-activatable | +| `profile` | body | string | Agent profile to bind before submitting | +| `model` | body | string | Model alias to switch the agent to | +| `thinking` | body | string | Thinking-mode effort level | +| `permission_mode` | body | string | `manual` / `yolo` / `auto` | +| `disabled_tools` | body | array | Tool names to disable for the session | + +The schema also accepts `metadata`, `plan_mode`, `swarm_mode`, `goal_objective`, and `goal_control`, but the submit route currently does not apply them. Each `content` part is an object discriminated by `type`: + +| Part | Fields | Description | +| --- | --- | --- | +| `text` | `text` | Plain text | +| `image` / `video` | `source` | Media input; `source` is one of `{ kind: "url", url, id? }`, `{ kind: "base64", media_type, data }`, `{ kind: "file", file_id }` (an upload from `POST /api/v1/files`), or `{ kind: "session_media", file_id }` (media already committed to this session) | +| `file` | `file_id`, `name`, `media_type`, `size` | A file attachment uploaded through `POST /api/v1/files` | + +The schema also accepts the `tool_use`, `tool_result`, and `thinking` parts of the shared message format, but they are not meaningful in a user prompt. Unknown or mis-kinded `file_id` references are rejected before the prompt is created and before any override is applied. + +On success, `data` is the accepted prompt `{ prompt_id, user_message_id, status, content, created_at }`. + +- `40001`: validation failure — for example `prompt_id` combined with `skills`, or an unknown `profile` +- `40110`: no provider configured yet — finish login first +- `40111`: the resolved provider has no credential (`details.provider_id`) +- `40112`: the provider's credential was rejected (`details.provider_id`) +- `40113`: the model could not be resolved (`details.model_id` / `details.provider_id` when known) +- `40401`: session not found +- `40407`: a referenced `file_id` does not exist (or does not match the part's media kind) +- `40415`: a `skills` entry names an unknown skill +- `40903`: `prompt_id` belongs to an already-completed prompt; `data` carries `{ aborted: false }` +- `40912`: the skill exists but cannot be activated by the user +- `40927`: `prompt_id` is already reserved by an in-flight prompt + +#### `POST /api/v1/sessions/{session_id}/prompts:steer` + +Steers queued prompts into the active turn, so the running turn consumes them immediately instead of finishing first. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `prompt_ids` | body | array | **Required.** Non-empty array of queued prompt ids | + +On success, `data` is `{ steered: true, prompt_ids }`. + +- `40001`: validation failure +- `40401`: session not found +- `40402`: a listed prompt id is not in the queue + +#### `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:abort` + +Aborts a running prompt. This endpoint and `:steer` below dispatch through one route, `POST /api/v1/sessions/{session_id}/prompts/{tail}`: the tail is parsed as `{prompt_id}:{action}`, and a missing or unknown action fails `40001` (`unsupported action: ...`). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `prompt_id` | path | string | **Required.** Prompt id | + +On success, `data` is `{ aborted: true }`. + +- `40401`: session not found +- `40402`: no prompt with that id +- `40903`: the prompt already completed; `data` carries `{ aborted: false }` + +#### `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:steer` + +Steers one queued prompt into the active turn — the single-prompt form of `POST /api/v1/sessions/{session_id}/prompts:steer`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `prompt_id` | path | string | **Required.** Queued prompt id | + +On success, `data` is `{ steered: true, prompt_ids: [prompt_id] }`. + +- `40401`: session not found +- `40402`: no queued prompt with that id + +### Approvals and questions + +Approvals and questions are the session's two pending-interaction kinds: an approval asks permission for a tool call, a question asks for structured input with labeled options. These endpoints list and resolve them; new requests arrive over the WebSocket as `event.approval.requested` and `event.question.requested`. + +| Method and path | Description | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/approvals` | List pending approval requests (`status=pending` is required) | +| `POST /api/v1/sessions/{session_id}/approvals/{approval_id}` | Resolve an approval | +| `GET /api/v1/sessions/{session_id}/questions` | List pending questions (`status=pending` is required) | +| `POST /api/v1/sessions/{session_id}/questions/{question_id}` | Answer a question | +| `POST /api/v1/sessions/{session_id}/questions/{question_id}:dismiss` | Dismiss a question | + +#### `GET /api/v1/sessions/{session_id}/approvals` + +Lists the session's pending approval requests — the permission prompts raised by tool calls. Reading the list resumes the session when it is cold. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `status` | query | string | **Required.** Must be `pending` | + +On success, `data` is `{ items }` where each item is `{ approval_id, session_id, turn_id?, tool_call_id, tool_name, action, tool_input_display, created_at, expires_at }`: `tool_name` / `action` / `tool_input_display` describe the call waiting for permission, and `expires_at` is 24 hours after `created_at`. + +- `40001`: `status` missing or not `pending` +- `40401`: session not found + +#### `POST /api/v1/sessions/{session_id}/approvals/{approval_id}` + +Resolves a pending approval request, letting the waiting tool call proceed (or not). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `approval_id` | path | string | **Required.** Approval request id | +| `decision` | body | string | **Required.** `approved` / `rejected` / `cancelled` | +| `scope` | body | string | With `approved`, `session` (the only value) also remembers the approval rule for the rest of the session | +| `feedback` | body | string | Free-form feedback handed back to the agent | +| `selected_label` | body | string | The label of the chosen option, when the request offered labeled choices (for example a plan review) | + +On success, `data` is `{ resolved: true, resolved_at }`. + +- `40001`: validation failure +- `40401`: session not found +- `40404`: no pending approval with that id +- `40902`: the approval was already resolved; `data` carries `{ resolved: false }` + +#### `GET /api/v1/sessions/{session_id}/questions` + +Lists the session's pending questions. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `status` | query | string | **Required.** Must be `pending` | + +On success, `data` is `{ items }` where each item is `{ question_id, session_id, turn_id?, tool_call_id?, questions, created_at }`. `questions` holds 1–4 items `{ id, question, header?, body?, options, multi_select?, allow_other?, other_label?, other_description? }`, each with 2–4 `options` of `{ id, label, description? }`; `multi_select` allows several options, `allow_other` a free-text answer. + +- `40001`: `status` missing or not `pending` +- `40401`: session not found + +#### `POST /api/v1/sessions/{session_id}/questions/{question_id}` + +Answers a pending question. Both question endpoints dispatch through one route, `POST /api/v1/sessions/{session_id}/questions/{tail}`: a bare question id answers the question, a `{question_id}:dismiss` tail dismisses it, and anything else fails `40001`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `question_id` | path | string | **Required.** Question id | +| `answers` | body | object | **Required.** Map of question item id (`q_0`, …) to an answer object; variants below | +| `method` | body | string | How the answer was produced: `enter` / `space` / `number_key` / `click` | +| `note` | body | string | Free-form note attached to the response | + +Each answer is an object discriminated by `kind`: + +| Kind | Fields | Description | +| --- | --- | --- | +| `single` | `option_id` | One chosen option | +| `multi` | `option_ids` | Several chosen options (at least 1) | +| `other` | `text` | A free-text answer | +| `multi_with_other` | `option_ids`, `other_text` | Options plus free text | +| `skipped` | — | The item was skipped | + +On success, `data` is `{ resolved: true, resolved_at }`. + +- `40001`: validation failure (`details` lists each field) +- `40401`: session not found +- `40405`: no pending question with that id +- `40902`: the question was already resolved; `data` carries `{ resolved: false }` + +#### `POST /api/v1/sessions/{session_id}/questions/{question_id}:dismiss` + +Dismisses a pending question without answering it. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `question_id` | path | string | **Required.** Question id | + +On success the envelope `code` is `40909` (`question dismissed`) rather than `0`, with `data` `{ dismissed: true, dismissed_at }` — clients must special-case this endpoint's success code. + +- `40401`: session not found +- `40405`: no pending question with that id +- `40902`: the question was already resolved; `data` carries `{ resolved: false }` + +### Background tasks + +Background tasks are the session's asynchronous units — background shells, subagents, and long-running tool tasks. The registry is live-only: a session not loaded in this server process reports an empty list. + +| Method and path | Description | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/tasks` | List background tasks | +| `GET /api/v1/sessions/{session_id}/tasks/{task_id}` | Read a task (optional output preview) | +| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` | Cancel a task | + +#### `GET /api/v1/sessions/{session_id}/tasks` + +Lists the session's background tasks. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `status` | query | string | Keep only one status: `running` / `completed` / `failed` / `cancelled` | + +On success, `data` is `{ items }` where each item is a task object `{ id, session_id, kind, description, status, created_at, started_at?, completed_at?, command?, model?, thinking_effort?, agent_id?, subagent_type?, parent_tool_call_id?, output_preview?, output_bytes? }`. `kind` is `bash` / `subagent` / `tool`; `command` is set for `bash` tasks, the model and agent fields for `subagent` tasks, and the output fields only when a task is read with `with_output`. Timed-out and lost tasks report `failed`; killed tasks report `cancelled`. + +- `40001`: validation failure — an unknown `status` +- `40401`: session not found + +#### `GET /api/v1/sessions/{session_id}/tasks/{task_id}` + +Reads one background task, optionally with a tail of its output. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `task_id` | path | string | **Required.** Task id | +| `with_output` | query | boolean | Include an output tail in the response. Default `false` | +| `output_bytes` | query | integer | Size of the requested output tail in bytes, minimum `0`. Default `32768` | + +On success, `data` is the task object documented under `GET /api/v1/sessions/{session_id}/tasks` above; with `with_output=true` and non-empty output, `output_preview` carries the tail text and `output_bytes` its byte length. + +- `40001`: validation failure +- `40401`: session not found +- `40406`: no task with that id (a cold session has no live tasks at all) + +#### `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` + +Cancels a running task. It dispatches through `POST /api/v1/sessions/{session_id}/tasks/{tail}` with `cancel` as the only action — a bare task id or an unknown action fails `40001`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `task_id` | path | string | **Required.** Task id | + +On success, `data` is `{ cancelled: true }`. + +- `40001`: missing or unknown action suffix +- `40401`: session not found +- `40406`: no task with that id +- `40904`: the task already finished; `data` carries `{ cancelled: false }` and `details.current_status` the terminal status + +### Skills, tools, and MCP + +These endpoints expose the skill catalogs a session or workspace sees, the effective agent's tool list, and its MCP servers. Skill activation and MCP restart use the `:{action}` convention; activation is the REST analogue of the `/` slash command. + +| Method and path | Description | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/skills` | Per-session skill catalog | +| `GET /api/v1/workspaces/{workspace_id}/skills` | Session-less skill catalog for a workspace | +| `POST /api/v1/sessions/{session_id}/skills/{skill_name}:activate` | Activate a skill (starts a turn) | +| `GET /api/v1/tools` | List tools of the effective agent | +| `GET /api/v1/mcp/servers` | List MCP servers | +| `POST /api/v1/mcp/servers/{mcp_server_id}:restart` | Restart an MCP server | + +#### `GET /api/v1/sessions/{session_id}/skills` + +Lists the skills available to one session, merged from every source (built-in, plugin, extra, user, project) with the session's precedence applied. Reading the catalog resumes the session when it is cold. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | + +On success, `data` is `{ skills }` where each item is a skill descriptor `{ name, description, path, source, type?, disable_model_invocation? }`: `source` is `project` / `user` / `extra` / `builtin`, `type` classifies the skill (only user-activatable types can be activated), and `disable_model_invocation` hides the skill from the model. + +- `40401`: session not found (or not activated) + +#### `GET /api/v1/workspaces/{workspace_id}/skills` + +Lists the skill catalog a session in this workspace would see, without creating or resuming a session — the same merge of built-in, plugin, extra, user, and project sources computed for the workspace root. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **Required.** Registered workspace id | + +On success, `data` is `{ skills }` with the skill descriptor documented under `GET /api/v1/sessions/{session_id}/skills` above. + +- `40410`: workspace not found + +#### `POST /api/v1/sessions/{session_id}/skills/{skill_name}:activate` + +Activates a skill in the session — the REST analogue of the `/` slash command — starting a turn on the main agent with the skill's content plus `args` and attachments. The endpoint dispatches through one route, `POST /api/v1/sessions/{session_id}/skills/{tail}`: the tail is parsed as `{skill_name}:{action}`, `activate` is the only action, and a bare name or an unknown action fails `40001` (`unsupported action: ...`). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `skill_name` | path | string | **Required.** Name of the skill to activate | +| `args` | body | string | Free-form arguments handed to the skill, like the text after a slash command | +| `attachments` | body | array | Media parts attached to the activation. Image and video parts carry a `source` object whose `kind` is `url` / `base64` / `file` / `session_media` (same shapes as the prompt content parts); file parts carry the top-level `file_id`, `name`, `media_type`, and `size` | + +On success, `data` is `{ activated: true, skill_name }`. + +- `40001`: validation failure or unsupported action suffix +- `40401`: session not found (or not activated) +- `40407`: a referenced attachment file does not exist +- `40415`: no skill with that name +- `40912`: the skill exists but its type cannot be activated by the user + +#### `GET /api/v1/tools` + +Lists the tools of the effective agent — the main agent of the session given by `session_id`, or of the most recently created session when the parameter is omitted. When no such session is live in this server process, the list is empty. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | query | string | Session whose main agent to inspect. Default the most recently created session | + +On success, `data` is `{ tools }` where each item is `{ name, description, input_schema, source, mcp_server_id?, active? }`: `source` is `builtin` / `skill` / `mcp`, `mcp_server_id` is set on MCP tools (parsed from the `mcp____` name), and `active` reports the tool policy's verdict. `input_schema` is currently always `null`. + +#### `GET /api/v1/mcp/servers` + +Lists the MCP servers configured for the effective agent (the most recently created live session's main agent, as in `GET /api/v1/tools`). With no live session, the list is empty. + +On success, `data` is `{ servers }` where each item is `{ id, name, transport, status, last_error?, tool_count }`: `transport` is `stdio` / `http` / `sse`, `status` is `connected` / `connecting` / `disconnected` / `error`, and `last_error` carries the failure text when the server is in `error`. + +#### `POST /api/v1/mcp/servers/{mcp_server_id}:restart` + +Reconnects one MCP server of the effective agent. The endpoint dispatches through `POST /api/v1/mcp/servers/{tail}` with `restart` as the only action — a bare server id or an unknown action fails `40001`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `mcp_server_id` | path | string | **Required.** MCP server id (its configured name) | + +On success, `data` is `{ restarting: true }`. + +- `40001`: missing or unknown action suffix +- `40408`: no MCP server with that id (also reported when no session is live) + +### Capabilities and plugins + +Capabilities are built-in features with layered readiness — detection steps plus a background install; the current build registers `kimi-cu` (Kimi Computer Use) and `kimi-webbridge` (Kimi WebBridge). Plugins are installed packages of skills, MCP servers, hooks, and commands. These endpoints report capability status and drive capability installs, and manage the plugin lifecycle from marketplace listing to removal. + +| Method and path | Description | +| --- | --- | +| `GET /api/v1/capabilities` | List built-in capabilities with readiness status | +| `GET /api/v1/capabilities/{capability_id}` | Read one capability's status | +| `POST /api/v1/capabilities/{capability_id}:install` | Start a capability install (background; poll GET for progress) | +| `GET /api/v1/plugins` | List installed plugins | +| `POST /api/v1/plugins` | Install a plugin from a local path, zip URL, or GitHub repo | +| `GET /api/v1/plugins/marketplace` | Marketplace catalog merged with live install state | +| `POST /api/v1/plugins/{plugin_id}:{action}` | Plugin actions: `enable` / `disable` / `remove` | + +#### `GET /api/v1/capabilities` + +Lists every registered capability with its readiness status. + +On success, `data` is `{ capabilities }` where each item is a capability status object `{ id, pluginId?, displayName, description, supported, state, version?, steps, install }`. `state` is `ready` (every required detection step `ok`) / `partial` (some step `ok`) / `not_installed` / `unsupported` (not available on this platform/architecture); `steps` lists the detection steps as `{ id, state, detail?, optional? }` with `state` one of `ok` / `missing` / `failed`; `install` is the install progress `{ running, step?, percent?, error?, note? }` with `percent` between 0 and 100. + +#### `GET /api/v1/capabilities/{capability_id}` + +Reads one capability's readiness status — the polling counterpart of the `:install` action. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `capability_id` | path | string | **Required.** Capability id | + +On success, `data` is the capability status object documented under `GET /api/v1/capabilities` above. + +- `40418`: no capability with that id + +#### `POST /api/v1/capabilities/{capability_id}:install` + +Starts installing a capability in the background and returns immediately with the current status (`install.running` is `true`); poll `GET /api/v1/capabilities/{capability_id}` for progress. The endpoint dispatches through `POST /api/v1/capabilities/{tail}` with `install` as the only action — a bare id or an unknown action fails `40001`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `capability_id` | path | string | **Required.** Capability id | + +On success, `data` is the capability status object documented under `GET /api/v1/capabilities` above. + +- `40001`: missing or unknown action suffix +- `40418`: no capability with that id +- `40924`: an install of this capability is already running +- `40925`: the capability is not supported on this platform/architecture + +#### `GET /api/v1/plugins` + +Lists installed plugins. + +On success, `data` is `{ plugins }` where each item is `{ id, displayName, version?, enabled, state, skillCount, mcpServerCount, enabledMcpServerCount, hookCount, commandCount, hasErrors, source, originalSource?, github? }`: `state` is `ok` / `error` (load failures also set `hasErrors`), `source` is `local-path` / `zip-url` / `github`, and `github` carries the provenance `{ owner, repo, ref, installedSha? }` with `ref` `{ kind: branch|tag|sha, value }` for GitHub-sourced plugins. + +#### `POST /api/v1/plugins` + +Installs a plugin and returns its summary. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `source` | body | string | **Required.** Where to install from: an absolute local path, an `http(s)` URL to a zip archive, or a GitHub URL — `https://github.com//`, optionally pinned with `/tree/`, `/releases/tag/`, or `/commit/` | + +On success, `data` is the plugin summary documented under `GET /api/v1/plugins` above. + +- `40001`: validation failure — for example `source` is neither a URL nor an absolute path, or the plugin failed to load +- `40409`: the local path does not exist + +#### `GET /api/v1/plugins/marketplace` + +Lists the plugin marketplace catalog merged with live install state. The catalog is fetched per request (10-second timeout) from the configured marketplace URL; with the default catalog, built-in capabilities missing from the catalog are merged in as rows (with `capabilityId` set) and rows whose capability is unsupported on this platform are dropped. + +On success, `data` is `{ entries }` where each item is `{ id, tier, displayName, description?, homepage?, keywords?, version?, source, installed?, updateAvailable?, capabilityId? }`: `tier` is `official` / `curated` / `third-party`, `installed` is `{ version?, enabled }` when the plugin is installed, and `updateAvailable` marks rows whose catalog version is newer than the installed one. An entry's `source` feeds the `source` field of `POST /api/v1/plugins`. + +- `50001`: the marketplace is unreachable or returned an invalid catalog + +#### `POST /api/v1/plugins/{plugin_id}:enable` + +Enables an installed plugin. Plugin actions dispatch through one route, `POST /api/v1/plugins/{tail}`: the tail is parsed as `{plugin_id}:{action}` with `enable` / `disable` / `remove` as the actions, and a bare id or an unknown action fails `40001` (`unsupported action: ...`). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `plugin_id` | path | string | **Required.** Installed plugin id | + +On success, `data` is `{ ok: true }`. + +- `40001`: missing or unknown action suffix +- `40419`: no installed plugin with that id + +#### `POST /api/v1/plugins/{plugin_id}:disable` + +Disables an installed plugin without removing it; the dispatch contract matches `:enable` above. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `plugin_id` | path | string | **Required.** Installed plugin id | + +On success, `data` is `{ ok: true }`. + +- `40001`: missing or unknown action suffix +- `40419`: no installed plugin with that id + +#### `POST /api/v1/plugins/{plugin_id}:remove` + +Removes an installed plugin; the dispatch contract matches `:enable` above. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `plugin_id` | path | string | **Required.** Installed plugin id | + +On success, `data` is `{ ok: true }`. + +- `40001`: missing or unknown action suffix +- `40419`: no installed plugin with that id + +### Terminals + +PTY terminal endpoints; mounted only on loopback binds (a non-loopback bind skips them unless `--allow-remote-terminals` is passed). Terminal input, output, and resize flow over WebSocket `terminal_*` frames — the REST surface manages the terminal lifecycle only. + +| Method and path | Description | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/terminals` | List terminals | +| `POST /api/v1/sessions/{session_id}/terminals` | Create a terminal | +| `GET /api/v1/sessions/{session_id}/terminals/{terminal_id}` | Read a terminal | +| `POST /api/v1/sessions/{session_id}/terminals/{terminal_id}:close` | Close a terminal | + +#### `GET /api/v1/sessions/{session_id}/terminals` + +Lists the session's terminals. Reading the list resumes the session when it is cold. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | + +On success, `data` is `{ items }` where each item is a terminal object `{ id, session_id, cwd, shell, cols, rows, status, created_at, exited_at?, exit_code? }`: `status` is `running` / `exited`, and an exited terminal carries `exited_at` plus `exit_code` (`null` when the process reported none, for example after a signal). Scrollback is not part of the object — output replays and streams over the WebSocket. + +- `40401`: session not found + +#### `POST /api/v1/sessions/{session_id}/terminals` + +Creates a PTY terminal for the session. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `runtime_id` | body | string | Runtime to spawn in. Default `local` | +| `cwd` | body | string | Working directory, relative to the session workspace (an absolute path fails validation). Default the workspace root | +| `shell` | body | string | Shell executable. Default the runtime's shell | +| `cols` | body | integer | Terminal width, positive. Default `80` | +| `rows` | body | integer | Terminal height, positive. Default `24` | + +On success, `data` is the terminal object documented under `GET /api/v1/sessions/{session_id}/terminals` above. + +- `40001`: validation failure (`details` lists each field) +- `40401`: session not found +- `41304`: `cwd` resolves outside the session workspace + +#### `GET /api/v1/sessions/{session_id}/terminals/{terminal_id}` + +Reads one terminal. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `terminal_id` | path | string | **Required.** Terminal id | + +On success, `data` is the terminal object documented under `GET /api/v1/sessions/{session_id}/terminals` above. + +- `40401`: session not found +- `40414`: no terminal with that id + +#### `POST /api/v1/sessions/{session_id}/terminals/{terminal_id}:close` + +Closes a terminal, killing its process. The endpoint dispatches through `POST /api/v1/sessions/{session_id}/terminals/{tail}` with `close` as the only action — a bare id or an unknown action fails `40001`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `terminal_id` | path | string | **Required.** Terminal id | + +On success, `data` is `{ closed: true }`. + +- `40001`: missing or unknown action suffix +- `40401`: session not found +- `40414`: no terminal with that id + +### Workspaces + +Workspaces are the registered project directories sessions live in. These endpoints manage the registry — list, register, rename, unregister — plus the per-workspace trust state that gates project-level MCP config. Every endpoint that returns a workspace uses the wire shape documented once under [The workspace object](#the-workspace-object). + +| Method and path | Description | +| --- | --- | +| `GET /api/v1/workspaces` | List registered workspaces | +| `POST /api/v1/workspaces` | Register a workspace (idempotent on the root path) | +| `PATCH /api/v1/workspaces/{workspace_id}` | Rename | +| `DELETE /api/v1/workspaces/{workspace_id}` | Unregister (keeps on-disk content) | +| `GET /api/v1/workspaces/{workspace_id}/trust` | Read the trust state | +| `POST /api/v1/workspaces/{workspace_id}/trust` | Grant trust | +| `POST /api/v1/workspaces/{workspace_id}/untrust` | Revoke trust | + +#### The workspace object + +Every endpoint that returns a workspace uses this wire shape. Registration and rename broadcast the global `event.workspace.created` / `event.workspace.updated` events. + +| Field | Type | Description | +| --- | --- | --- | +| `id` | string | Workspace id, a `wd__` string derived from the root path | +| `root` | string | Absolute path of the project directory | +| `name` | string | Display name, 1–100 characters; defaults to the root's base name | +| `created_at` | string | Registration time, ISO 8601 | +| `last_opened_at` | string | Last time the workspace was opened or re-registered, ISO 8601 | +| `session_count` | integer | Number of sessions in the workspace | + +#### `GET /api/v1/workspaces` + +Lists every registered workspace. + +On success, `data` is `{ items }` where each item is [the workspace object](#the-workspace-object). + +#### `POST /api/v1/workspaces` + +Registers a workspace and returns it. Registration is idempotent on the root path: registering an already-registered root returns the existing workspace with only `last_opened_at` refreshed (the stored name is kept), broadcasting `event.workspace.updated` instead of `event.workspace.created`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `root` | body | string | **Required.** Absolute path of an existing directory | +| `name` | body | string | Display name, 1–100 characters. Default the root's base name | + +On success, `data` is [the workspace object](#the-workspace-object). + +- `40001`: `root` is missing or not an absolute path (`details` lists the field) +- `40409`: `root` does not exist or is not a directory + +#### `PATCH /api/v1/workspaces/{workspace_id}` + +Renames a workspace — the display name only; the root path never changes. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **Required.** Workspace id | +| `name` | body | string | **Required.** New display name, 1–100 characters | + +On success, `data` is [the workspace object](#the-workspace-object). + +- `40001`: validation failure (`details` lists each field) +- `40410`: workspace not found + +#### `DELETE /api/v1/workspaces/{workspace_id}` + +Unregisters a workspace. Only the registry entry is removed — the on-disk directory is untouched. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **Required.** Workspace id | + +On success, `data` is `{ deleted: true }`. + +- `40410`: workspace not found + +#### `GET /api/v1/workspaces/{workspace_id}/trust` + +Reads the workspace trust state. Trust gates whether project-level MCP config loads for the workspace. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **Required.** Workspace id | + +On success, `data` is `{ trusted }`. + +- `40410`: workspace not found + +#### `POST /api/v1/workspaces/{workspace_id}/trust` + +Marks the workspace trusted, loading its project-level MCP config. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **Required.** Workspace id | + +On success, `data` is `{ trusted: true }`. + +- `40410`: workspace not found + +#### `POST /api/v1/workspaces/{workspace_id}/untrust` + +Revokes workspace trust, unloading its project-level MCP config. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **Required.** Workspace id | + +On success, `data` is `{ trusted: false }`. + +- `40410`: workspace not found + +### File system + +In-session file operations go through `POST /api/v1/sessions/{session_id}/fs:{action}` with JSON bodies; actions are `list` / `read` / `list_many` / `stat` / `stat_many` / `mkdir` / `search` / `grep` / `git_status` / `diff` / `open` / `open-in` / `reveal`. Every action body also accepts an optional `runtime_id` (string, default `local`) selecting the runtime that executes the operation; `open`, `open-in`, and `reveal` only work on the `local` runtime. In addition: + +| Method and path | Description | +| --- | --- | +| `POST /api/v1/workspace/fs:search` | Session-less workspace search (the body carries the workspace reference) | +| `POST /api/v1/workspace/fs:suggest` | Session-less file-completion candidates (for `@` file mentions) | +| `GET /api/v1/sessions/{session_id}/fs/{path}:download` | Download a session file (binary, see below) | +| `GET /api/v1/fs:browse` | List host directories (folder picker) | +| `GET /api/v1/fs:home` | The user's home directory and recent workspaces | +| `GET /api/v1/fs:content` | Raw bytes of any host file (gated only by the token — be careful when exposing the port) | +| `POST /api/v1/fs:mkdir` | Create a directory by absolute path | + +#### `POST /api/v1/sessions/{session_id}/fs:list` + +Lists the entries of a session workspace directory, optionally recursing into subdirectories. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `path` | body | string | Directory to list, relative to the session work directory. Default `.` | +| `depth` | body | integer | Recursion depth, 1–10. Default `1` | +| `limit` | body | integer | Maximum entries, 1–1000. Default `200` | +| `show_hidden` | body | boolean | Include dotfiles. Default `false` | +| `follow_gitignore` | body | boolean | Skip gitignored paths. Default `true` | +| `exclude_globs` | body | string[] | Additional globs to skip | +| `sort` | body | string | `type_first` (default) / `name_asc` / `name_desc` / `mtime_desc` / `size_desc` | +| `include_git_status` | body | boolean | Attach each entry's git status. Default `false` | + +On success, `data` is `{ items, truncated }` — plus `children_by_path` (a path → entries map) when `depth` is greater than 1. Each item is an entry object `{ path, name, kind, size?, modified_at, etag?, mime?, language_id?, is_binary?, is_symlink_to?, git_status?, child_count? }`, where `kind` is `file` / `directory` / `symlink` and `git_status` (present only with `include_git_status: true`) is one of `clean` / `modified` / `added` / `deleted` / `renamed` / `untracked` / `ignored` / `conflicted`; `truncated` reports that `limit` cut the listing short. + +- `40001`: body validation failure +- `40401`: session not found +- `40409`: path not found (including a `path` that is not a directory) +- `41304`: path escapes the session workspace + +#### `POST /api/v1/sessions/{session_id}/fs:read` + +Reads a slice of a session file as text or base64. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `path` | body | string | **Required.** File path, relative to the session work directory | +| `offset` | body | integer | Byte offset to start at. Default `0` | +| `length` | body | integer | Bytes to read, 1–10485760 (10 MiB). Default `1048576` (1 MiB) | +| `encoding` | body | string | `auto` (default) / `utf-8` / `base64` | + +On success, `data` is `{ path, content, encoding, size, truncated, etag, mime, language_id?, line_count?, is_binary }`, where `encoding` reports the encoding actually used (`utf-8` or `base64`) and `size` is the full file size. With `encoding: "auto"`, text comes back as `utf-8` (non-UTF-8 text is transcoded) and binary content as `base64`; `encoding: "utf-8"` forces text and rejects binary files. + +- `40001`: body validation failure +- `40401`: session not found +- `40409`: path not found +- `40906`: path is a directory +- `40907`: binary file requested with `encoding: "utf-8"` +- `41302`: file exceeds the 10 MiB read ceiling +- `41304`: path escapes the session workspace + +#### `POST /api/v1/sessions/{session_id}/fs:list_many` + +Lists several session directories in one call; a failing path folds into the response instead of failing the whole request. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `paths` | body | string[] | **Required.** Directories to list, 1–100 entries | + +The remaining body fields (`depth`, `limit`, `show_hidden`, `follow_gitignore`, `exclude_globs`, `sort`, `include_git_status`) have the same types, ranges, and defaults as `fs:list`. On success, `data` is `{ results }`, a map from each requested path to its entry array (entry objects as described under `fs:list`), plus `truncated_paths` (paths whose listing hit `limit`) and `partial_errors`, a map from a failed path to its `{ code, msg }` error. + +- `40001`: body validation failure +- `40401`: session not found + +#### `POST /api/v1/sessions/{session_id}/fs:stat` + +Stats one path in the session workspace. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `path` | body | string | **Required.** Path to stat, relative to the session work directory | + +On success, `data` is the entry object described under `fs:list`. + +- `40001`: body validation failure +- `40401`: session not found +- `40409`: path not found +- `41304`: path escapes the session workspace + +#### `POST /api/v1/sessions/{session_id}/fs:stat_many` + +Stats many session paths in one call; missing paths report `null` instead of failing the request. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `paths` | body | string[] | **Required.** Paths to stat, 1–1000 entries | + +On success, `data` is `{ entries }`, a map from each requested path to its entry object (as described under `fs:list`) or `null` when the path does not exist. + +- `40001`: body validation failure +- `40401`: session not found + +#### `POST /api/v1/sessions/{session_id}/fs:mkdir` + +Creates a directory inside the session workspace. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `path` | body | string | **Required.** Directory to create, relative to the session work directory | +| `recursive` | body | boolean | Create missing parent directories. Default `false` | + +On success, `data` is the created directory's entry object (as described under `fs:list`). + +- `40001`: body validation failure +- `40401`: session not found +- `40409`: parent directory not found (non-recursive create) +- `40919`: path already exists (non-recursive create) +- `41304`: path escapes the session workspace + +#### `POST /api/v1/sessions/{session_id}/fs:search` + +Fuzzy-searches file and directory names across the session workspace. An empty `query` lists the top-level entries instead. When the `{session_id}` slot carries a workspace reference (a registered workspace id or an absolute root) rather than a session id, the search runs against that workspace — the session-less form for a not-yet-created draft session; the first-class session-less endpoint is `POST /api/v1/workspace/fs:search`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id, or a workspace reference | +| `query` | body | string | **Required.** Search text; `""` lists the top level | +| `limit` | body | integer | Maximum hits, 1–200. Default `50` | +| `include_globs` | body | string[] | Only paths matching one of these globs | +| `exclude_globs` | body | string[] | Skip paths matching these globs | +| `follow_gitignore` | body | boolean | Skip gitignored paths. Default `true` | + +On success, `data` is `{ items, truncated }` where each item is `{ path, name, kind, score, match_positions }` — `kind` is `file` / `directory` / `symlink`, `score` is the fuzzy-match score between 0 and 1, and `match_positions` lists the matched character offsets. Hits sort by score (ties by path), and `truncated` reports that hits beyond `limit` were dropped. + +- `40001`: body validation failure +- `40401`: neither a session nor a resolvable workspace with that reference + +#### `POST /api/v1/sessions/{session_id}/fs:grep` + +Searches file contents across the session workspace — a literal string by default, a regular expression with `regex: true`. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `pattern` | body | string | **Required.** Text or regex to search for | +| `regex` | body | boolean | Treat `pattern` as a regular expression. Default `false` | +| `case_sensitive` | body | boolean | Default `true` | +| `include_globs` | body | string[] | Only files matching one of these globs | +| `exclude_globs` | body | string[] | Skip files matching these globs | +| `follow_gitignore` | body | boolean | Skip gitignored paths. Default `true` | +| `max_files` | body | integer | Files to scan at most, 1–10000. Default `200` | +| `max_matches_per_file` | body | integer | Matches kept per file, 1–10000. Default `50` | +| `max_total_matches` | body | integer | Matches kept overall, 1–100000. Default `5000` | +| `context_lines` | body | integer | Context lines around each match, 0–10. Default `2` | + +On success, `data` is `{ files, files_scanned, truncated, elapsed_ms }` where each entry of `files` is `{ path, matches }` and each match is `{ line, col, text, before, after }` (`before` / `after` carry up to `context_lines` surrounding lines); `truncated` reports that one of the match budgets cut the results short. + +- `40001`: body validation failure +- `40401`: session not found +- `41305`: the search timed out + +#### `POST /api/v1/sessions/{session_id}/fs:git_status` + +Reads the git status of the session workspace, optionally restricted to a set of paths. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `paths` | body | string[] | Restrict the status to these paths; omitted means the whole workspace | + +On success, `data` is `{ branch, ahead, behind, entries, additions, deletions, pullRequest }` where `entries` maps each changed path to its status (`clean` / `modified` / `added` / `deleted` / `renamed` / `untracked` / `ignored` / `conflicted`) and `pullRequest` is `{ number, state, url }` (`state` is `open` / `merged` / `closed` / `draft`) or `null`. + +- `40001`: body validation failure +- `40401`: session not found +- `40908`: git is unavailable (not a repository, or no git binary) + +#### `POST /api/v1/sessions/{session_id}/fs:diff` + +Returns the unified git diff of one file in the session workspace. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `path` | body | string | **Required.** File to diff, relative to the session work directory | + +On success, `data` is `{ path, diff, truncated }` where `diff` is the unified diff text and `truncated` reports an over-long diff cut short. + +- `40001`: body validation failure +- `40401`: session not found +- `40908`: git is unavailable (not a repository, or no git binary) +- `41304`: path escapes the session workspace + +#### `POST /api/v1/sessions/{session_id}/fs:open` + +Opens a session file with the host operating system's default handler. Local runtime only. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `path` | body | string | **Required.** File to open, relative to the session work directory | +| `line` | body | integer | Line number to jump to where the handler supports it (positive integer) | + +On success, `data` is `{ opened: true }`. + +- `40001`: body validation failure +- `40401`: session not found +- `40409`: path not found +- `41304`: path escapes the session workspace + +#### `POST /api/v1/sessions/{session_id}/fs:open-in` + +Opens a session file or directory in a specific host application. Local runtime only. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `app_id` | body | string | **Required.** Target application: `finder` / `cursor` / `vscode` / `iterm` / `terminal` | +| `path` | body | string | **Required.** File or directory to open, relative to the session work directory | +| `line` | body | integer | Line number to jump to where the application supports it (positive integer) | + +On success, `data` is `{ opened: true }`. + +- `40001`: body validation failure +- `40401`: session not found +- `40409`: path not found +- `41304`: path escapes the session workspace +- `50001`: the application failed to launch + +#### `POST /api/v1/sessions/{session_id}/fs:reveal` + +Reveals a session file in the host operating system's file manager. Local runtime only. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `path` | body | string | **Required.** File to reveal, relative to the session work directory | + +On success, `data` is `{ revealed: true }`. + +- `40001`: body validation failure +- `40401`: session not found +- `40409`: path not found +- `41304`: path escapes the session workspace + +#### `GET /api/v1/sessions/{session_id}/fs/{path}:download` + +Downloads a file from the session workspace; `{path}` is the workspace-relative file path with the literal `:download` suffix. The response is a binary stream with range and ETag support — see [Binary and streaming endpoints](#binary-and-streaming-endpoints). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `session_id` | path | string | **Required.** Session id | +| `path` | path | string | **Required.** Workspace-relative file path plus the `:download` suffix | +| `runtime_id` | query | string | Runtime to read from. Default `local` | + +- `40001`: missing or empty path +- `40401`: session not found +- `40409`: path not found +- `41304`: path escapes the session workspace + +#### `POST /api/v1/workspace/fs:search` + +The session-less form of `fs:search`: the workspace travels in the body instead of the URL. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `workspace` | body | string | **Required.** Registered workspace id or absolute root (registered on the spot) | +| `query` | body | string | **Required.** Search text; `""` lists the top level | +| `limit` | body | integer | Maximum hits, 1–200. Default `50` | +| `include_globs` | body | string[] | Only paths matching one of these globs | +| `exclude_globs` | body | string[] | Skip paths matching these globs | +| `follow_gitignore` | body | boolean | Skip gitignored paths. Default `true` | +| `runtime_id` | body | string | Runtime to search on. Default `local` | + +On success, `data` is `{ items, truncated }` with the same hit shape and ordering as `fs:search`. + +- `40001`: body validation failure +- `40410`: workspace not found and not a usable absolute path + +#### `POST /api/v1/workspace/fs:suggest` + +Suggests file and directory completion candidates in a workspace without a session — the backend for `@` file mentions in the composer. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `workspace` | body | string | **Required.** Registered workspace id or absolute root (registered on the spot) | +| `query` | body | string | **Required.** Partial path text to complete | +| `limit` | body | integer | Maximum candidates, 1–200. Default `50` | +| `follow_gitignore` | body | boolean | Skip gitignored paths. Default `true` | +| `show_hidden` | body | boolean | Include dotfiles. Default `false` | +| `include_globs` | body | string[] | Only paths matching one of these globs | +| `exclude_globs` | body | string[] | Skip paths matching these globs | +| `runtime_id` | body | string | Runtime to complete on. Default `local` | + +On success, `data` is `{ items, truncated }` where each item is `{ path, name, kind, score, match_positions }`, the same hit shape as `fs:search`. + +- `40001`: body validation failure +- `40410`: workspace not found and not a usable absolute path + +#### `GET /api/v1/fs:browse` + +Lists the subdirectories of one host directory — the backend of the folder picker. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `path` | query | string | Absolute directory path. Default the user's home directory | + +On success, `data` is `{ path, parent, entries }` where `path` is the resolved directory, `parent` its parent (`null` at the filesystem root), and each entry is `{ name, path, is_dir: true }`. + +- `40001`: `path` is not absolute +- `40409`: path not found +- `40411`: permission denied + +#### `GET /api/v1/fs:home` + +Returns the folder picker's landing payload. No parameters. + +On success, `data` is `{ home, recent_roots }` where `home` is the user's home directory and `recent_roots` lists the roots of the registered workspaces. + +#### `GET /api/v1/fs:content` + +Streams the raw bytes of any file on the host filesystem — gated only by the API token, so be careful when exposing the port. Range requests and ETag caching are supported; see [Binary and streaming endpoints](#binary-and-streaming-endpoints). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `path` | query | string | **Required.** Absolute file path | + +- `40001`: `path` is not absolute, or not a regular file +- `40409`: path not found +- `40411`: permission denied +- `40906`: path is a directory + +#### `POST /api/v1/fs:mkdir` + +Creates one directory on the host filesystem by absolute path — the folder picker's "new folder" backend. Non-recursive: the parent directory must already exist. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `path` | body | string | **Required.** Absolute directory path | + +On success, `data` is `{ path }`. + +- `40001`: `path` is not absolute +- `40409`: parent path not found +- `40411`: permission denied +- `40919`: path already exists + +### File uploads + +| Method and path | Description | +| --- | --- | +| `POST /api/v1/files` | Multipart upload (`file` field, optional `name` and `expires_in_sec`); returns file metadata | +| `GET /api/v1/files/{file_id}` | Download (binary; errors use real HTTP statuses) | +| `DELETE /api/v1/files/{file_id}` | Delete | + +#### `POST /api/v1/files` + +Uploads a file as `multipart/form-data` for later reference (for example as a prompt attachment). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `file` | body | binary | **Required.** The multipart file part | +| `name` | body | string | Stored display name. Default the uploaded filename | +| `expires_in_sec` | body | number | Seconds until the file expires (non-negative). Default never expires | + +On success, `data` is the file metadata `{ id, name, media_type, size, created_at, expires_at? }` with `media_type` taken from the upload's content type. + +- `40001`: the multipart body has no `file` field + +#### `GET /api/v1/files/{file_id}` + +Downloads an uploaded file. The response is a binary stream that honors range requests but ignores `If-None-Match`; failures use real HTTP statuses — see [Binary and streaming endpoints](#binary-and-streaming-endpoints). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `file_id` | path | string | **Required.** File id from the upload response | + +- `40407` (HTTP 404): no file with that id (including an expired file) + +#### `DELETE /api/v1/files/{file_id}` + +Deletes an uploaded file. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `file_id` | path | string | **Required.** File id from the upload response | + +On success, `data` is `{ deleted: true }`. + +- `40407` (HTTP 404): no file with that id + +### GUI store + +A server-backed key/value store that mirrors the browser `localStorage` interface, persisted under the server's home directory; the web UI keeps cross-client UI state here. Values are opaque strings — serialization is the caller's job. + +| Method and path | Description | +| --- | --- | +| `GET /api/v1/gui/store/length` | Number of stored keys | +| `GET /api/v1/gui/store/getItem` | Read a value by key | +| `POST /api/v1/gui/store/setItem` | Write a value by key | +| `POST /api/v1/gui/store/removeItem` | Delete a value by key | +| `POST /api/v1/gui/store/clear` | Delete all values | + +#### `GET /api/v1/gui/store/length` + +Returns the number of stored keys (mirrors `localStorage.length`). No parameters. + +On success, `data` is `{ length }`. + +#### `GET /api/v1/gui/store/getItem` + +Reads one value (mirrors `localStorage.getItem`). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `key` | query | string | **Required.** Key to read, 1–256 characters | + +On success, `data` is `{ value }`, the stored string or `null` when the key does not exist. + +#### `POST /api/v1/gui/store/setItem` + +Writes one value (mirrors `localStorage.setItem`). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `key` | body | string | **Required.** Key to write, 1–256 characters | +| `value` | body | string | **Required.** Value to store | + +On success, `data` is `null`. + +#### `POST /api/v1/gui/store/removeItem` + +Deletes one value (mirrors `localStorage.removeItem`). + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `key` | body | string | **Required.** Key to delete, 1–256 characters | + +On success, `data` is `null`. + +#### `POST /api/v1/gui/store/clear` + +Deletes every stored value (mirrors `localStorage.clear`). No parameters. + +On success, `data` is `null`. + +### Global search and misc + +| Method and path | Description | +| --- | --- | +| `POST /api/v1/search` | Cross-session full-text search; `mode` is `terms` (default) or `literal` (exact substring); `page_token` pagination | +| `GET /api/v1/connections` | List live WebSocket connections | +| `GET /api/v2/sessions` | Next-generation session list, see below | +| `POST /api/v2/sessions:archive` | Batch-archive sessions, see below | +| `POST /api/v2/sessions:restore` | Batch-restore archived sessions, see below | +| `/api/v1/debug/*` | Reflection debug RPC; mounted only with `--debug-endpoints` on loopback, not a stable protocol | + +#### `POST /api/v1/search` + +Cross-session full-text search over user messages, assistant replies, and session titles, backed by the server's persistent search index. When `container.session_id` names a session live in this server process, the search instead scans that session's in-memory transcript directly, and the response's `source` field (`index` or `live`) reports which path served the page. + +| Parameter | In | Type | Description | +| --- | --- | --- | --- | +| `query` | body | string | **Required.** Search text | +| `mode` | body | string | `terms` (default) / `literal` | +| `op` | body | string | Term combiner in `terms` mode: `AND` (default) / `OR` | +| `container` | body | object | Restrict the search to `{ session_id?, agent_id? }` | +| `role` | body | string | Restrict to `user` / `assistant` / `title` hits | +| `start_time` | body | integer | Only hits at or after this time (epoch milliseconds) | +| `end_time` | body | integer | Only hits at or before this time (epoch milliseconds) | +| `sort` | body | string | `score` (default) / `time_desc` / `time_asc`; ignored by `literal` mode, which always returns newest-first | +| `page_size` | body | integer | Hits per page, 1–50. Default `20` | +| `page_token` | body | string | Token from the previous page's response | + +In `terms` mode the query is tokenized (ASCII words plus CJK n-grams), deduplicated, and matched against the inverted index with at most 32 terms; `literal` mode is an exact substring search with zero false positives. On success, `data` is `{ items, has_more, page_token?, index_state, source }` where each item is `{ session_id, workspace_id, session_title, agent_id, role, snippet, time, turn?, step_id?, score }`. `index_state` is `{ state, indexed_sessions, total_sessions, documents, stale?, degraded? }` with `state` one of `building` / `ready` / `readonly`; `stale` marks a behind view still catching up, and `degraded` carries the last refresh failure. An over-budget page additionally carries `incomplete`, one of `candidate_cap` / `postings_budget` / `deadline`. Page tokens pin the index generation and the query conditions — a rebuild or a changed query invalidates them. + +- `40001`: body validation failure, an unusable query (empty, or more than 32 terms), or an invalid page token + +#### `GET /api/v1/connections` + +Lists the WebSocket clients currently connected to this server, oldest connection first. No parameters. + +On success, `data` is `{ connections }` where each item is `{ id, connected_at, remote_address, user_agent, has_client_hello, subscriptions }`: `connected_at` is an ISO 8601 timestamp, `remote_address` and `user_agent` are `null` when unknown, `has_client_hello` reports whether the client sent its handshake frame, and `subscriptions` lists the session ids the connection is subscribed to. ### `GET /api/v2/sessions` diff --git a/docs/zh/reference/server-api.md b/docs/zh/reference/server-api.md index c780659351..d729a13a84 100644 --- a/docs/zh/reference/server-api.md +++ b/docs/zh/reference/server-api.md @@ -1,18 +1,18 @@ # 服务 API -`kimi web` 启动的本地服务暴露两组程序化接口:REST API(`/api/v1`,另有 `/api/v2/sessions`)和 WebSocket 事件流(`/api/v1/ws`)。本页是这两组接口的协议参考;服务的启动方式与命令行选项见 [kimi 命令](./kimi-command.md#kimi-web),端到端的上手流程见[本地服务与 API](../guides/server.md)。 +`kimi web` 启动的本地服务暴露两组程序化接口:REST API(`/api/v1`,另有 `/api/v2/sessions`)和 WebSocket 事件流(`/api/v1/ws`)。本页是这两组接口的协议参考。如何启动服务及其命令行选项见 [kimi 命令](./kimi-command.md#kimi-web) 参考;端到端的上手流程见 [本地服务与 API](../guides/server.md)。 -每个端点的完整请求 / 响应 schema 以服务自描述的规范文档为准:`GET /openapi.json`(OpenAPI)与 `GET /asyncapi.json`(AsyncAPI),两者都需要鉴权。 +本页是一份经过整理、面向人阅读的参考:下文逐一记录每个端点的参数、请求体与响应结构。每个端点精确的机器可读 schema 以服务的在线规范文档为准:`GET /openapi.json`(OpenAPI)与 `GET /asyncapi.json`(AsyncAPI),两者都由服务运行时实际执行的校验 schema 生成。两者都需要鉴权;当本页与在线规范不一致时,以在线规范为准。 ::: warning 注意 -本页描述的 REST 与 WebSocket API 为实验性特性:不保证接口稳定性,端点、字段与事件类型可能随版本随时更改。集成时请以当前版本服务的 `/openapi.json` 与 `/asyncapi.json` 为准。 +本页描述的 REST 与 WebSocket API 为实验性特性:不保证接口稳定性,端点、字段与事件类型可能随任何版本更改。集成时请以你所用版本服务的 `/openapi.json` 与 `/asyncapi.json` 文档为准。 ::: ## 基础约定 ### 地址 -默认地址 `http://127.0.0.1:58627`;端口被占用时自动 +1 重试(至多 100 次),可用 `--port` / `--host` 修改。同一 home 目录可并存多个实例,运行中的实例登记在 `~/.kimi-code/server/instances/`。 +默认地址为 `http://127.0.0.1:58627`。端口被占用时,服务会用下一个端口重试(至多 100 次);可用 `--port` / `--host` 修改绑定。同一 home 目录下可并存多个实例,运行中的实例登记在 `~/.kimi-code/server/instances/`。 ### 鉴权 @@ -22,9 +22,9 @@ - `GET /api/v1/healthz`(探活) - 静态 web 资源(非 `/api/` 路径) -携带方式:REST 用 `Authorization: Bearer ` 请求头;WebSocket 升级请求可用同一请求头,或子协议 `kimi-code.bearer.`。token 的生成与轮换见[本地服务与 API:鉴权](../guides/server.md#鉴权)。 +携带方式:REST 用 `Authorization: Bearer ` 请求头;WebSocket 升级请求接受同一请求头,或子协议 `kimi-code.bearer.`。token 的生成与轮换见 [本地服务与 API:鉴权](../guides/server.md#authentication)。 -鉴权失败返回 HTTP 401,信封 `code` 为 `40101`。在非 loopback 绑定上,同一来源 60 秒内鉴权失败 10 次会被封禁 60 秒,期间一律返回 HTTP 429(`code` 为 `42901`)。 +鉴权失败返回 HTTP 401,信封 `code` 为 `40101`。在非 loopback 绑定上,同一来源 60 秒内鉴权失败 10 次会被封禁 60 秒,期间每个请求都返回 HTTP 429(`code` 为 `42901`)。 ### 响应信封 @@ -53,7 +53,7 @@ HTTP 状态码几乎总是 200,业务结果以 `code` 为准。例外情况: | 二进制与流式端点 | 支持时返回 206(Range 分段)/ 304(ETag 未变),各端点能力不同,详见「[二进制与流式端点](#二进制与流式端点)」 | | `GET /api/v1/files/{file_id}` 下载错误 | 真实 404 / 500(响应体仍为信封) | -其中 201 的响应体仍是标准信封(`code` 为 `0`),只是状态行遵循 REST 的资源创建习惯;204 按 HTTP 语义没有响应体,删除成功以状态码本身为准。 +其中 201 的响应体仍是标准信封(`code` 为 `0`),只是状态行遵循 REST 的资源创建惯例;204 按定义没有响应体,删除成功以状态码本身为准。 ### 错误码 @@ -77,31 +77,138 @@ HTTP 状态码几乎总是 200,业务结果以 `code` 为准。例外情况: 列表端点有两种分页风格: - **游标式**:`before_id` / `after_id`(互斥)加 `page_size`(1–100),响应为 `{ items, has_more }`。用于会话列表、消息列表、转录等。 -- **`page_token`**:不透明令牌(内部绑定了查询条件指纹),用于 `POST /api/v1/search` 与 `GET /api/v2/sessions`。翻页途中改变任何查询条件会使令牌失效:v2 返回 `40922`,search 返回 `40001`。`GET /api/v2/sessions` 另提供无状态的 `page` 页码模式作为替代。 +- **`page_token`**:不透明令牌(绑定了查询条件的指纹),用于 `POST /api/v1/search` 与 `GET /api/v2/sessions`。翻页途中改变任何查询条件会使令牌失效:v2 返回 `40922`,search 返回 `40001`。`GET /api/v2/sessions` 另提供无状态的 `page` 页码模式作为替代。 ## REST 端点 -按资源分组列出端点。路径里的 `:{action}` 是动作后缀约定——对单个资源 POST 到 `路径:动作` 执行非 CRUD 操作(如会话的 `:fork`、`:archive`)。 +下文按资源分组列出端点。路径里的 `:{action}` 后缀是动作约定——对单个资源 POST 到 `路径:动作` 执行非 CRUD 操作(如会话的 `:fork`、`:archive`)。 ### 服务与元信息 | 方法与路径 | 说明 | | --- | --- | | `GET /api/v1/healthz` | 探活,免鉴权 | -| `GET /api/v1/meta` | 服务版本、能力集、`server_id`、实验开关等 | +| `GET /api/v1/meta` | 服务版本、能力集、`server_id`、实验开关 | | `POST /api/v1/shutdown` | 优雅退出(先回 200 再关闭);仅 loopback 绑定时挂载 | +#### `GET /api/v1/healthz` + +供脚本与进程管理器使用的探活端点。它是唯一豁免 bearer token 的 `/api` 端点(见 [鉴权](#鉴权)),应答时不触碰配置与引擎。 + +成功时 `data` 为 `{ "ok": true }`。 + +#### `GET /api/v1/meta` + +返回本实例的身份信息与能力集。大多数字段在启动时即固定;`experimental_flags` 与 `features` 按请求实时解析,因此开关翻转或某个 feature 失败会体现在下一次响应中。 + +成功时 `data` 携带: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `server_version` | string | 服务版本 | +| `capabilities` | object | 能力集——`websocket`、`file_upload`、`fs_query`、`mcp`、`tasks`、`terminal`,均恒为 `true` | +| `server_id` | string | 本服务实例的唯一 id | +| `started_at` | string | 启动时间,ISO 8601 格式 | +| `open_in_apps` | array | 可作为 `open-in` 目标的宿主应用(`finder` / `cursor` / `vscode` / `iterm` / `terminal`);目前恒为空 | +| `dangerous_bypass_auth` | boolean | 服务是否以 `--dangerous-bypass-auth` 启动(客户端可跳过 token 提示) | +| `backend` | string | 引擎后端,`v1` 或 `v2`;本服务恒为 `v2` | +| `web_title` | string | 来自 `--web-title` 的自定义浏览器标签页标题;未设置时省略 | +| `experimental_flags` | object | 实验开关 id → 是否启用,按请求时解析 | +| `features` | array | 引擎 feature,形如 `{ name, state, meta }`;`state` 为 `Pending` / `Activating` / `Active` / `Unloading` / `Failed` | + +#### `POST /api/v1/shutdown` + +请求服务优雅退出。响应先发出,随后立即执行关闭,因此调用方可以信任收到的响应。该路由仅在 loopback 绑定时挂载——非 loopback 绑定时它根本不会被注册(请求得到 404),除非服务以 `--allow-remote-shutdown` 启动。 + +成功时 `data` 为 `{ "ok": true }`。 + ### 登录与用量 +这组端点驱动托管 Kimi OAuth 登录的生命周期,并暴露账号级信息。托管供应商名为 `managed:kimi-code`;下面每个端点上可选的 `provider` 参数都默认取它。 + | 方法与路径 | 说明 | | --- | --- | -| `GET /api/v1/auth` | 登录就绪状态快照 | +| `GET /api/v1/auth` | 鉴权就绪状态快照 | | `POST /api/v1/oauth/login` | 发起 OAuth device-code 登录流程 | | `GET /api/v1/oauth/login` | 轮询登录流程状态 | | `DELETE /api/v1/oauth/login` | 取消进行中的登录流程 | | `POST /api/v1/oauth/logout` | 登出托管供应商 | -| `GET /api/v1/oauth/usage` | 查询套餐用量与限额 | -| `GET /api/v1/oauth/userinfo` | 查询账号资料 | +| `GET /api/v1/oauth/usage` | 套餐用量与限额 | +| `GET /api/v1/oauth/userinfo` | 账号资料 | +| `GET /api/v1/oauth/region` | 解析客户端所属区域(`mainland-cn` / `global`) | + +#### `GET /api/v1/auth` + +鉴权就绪状态快照:服务是否具备可用的模型配置,以及托管供应商的登录状态。当至少配置了一个供应商、设置了默认模型、且托管供应商(如存在)未被吊销时,`ready` 为 `true`。 + +成功时 `data` 携带 `ready`(布尔值)、`providers_count`(已配置供应商数量)、`default_model`(全局默认模型别名,或 `null`)与 `managed_provider`(`null`,或 `{ name, status }`,其中 `status` 为 `authenticated` / `expired` / `revoked` / `unauthenticated` 之一)。 + +#### `POST /api/v1/oauth/login` + +为托管供应商发起 OAuth device-code 登录流程;发起新流程会中止同一供应商进行中的流程。账号已登录时无需用户交互,响应会立即报告 `authenticated`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider` | body | string | 托管供应商名称。默认 `managed:kimi-code` | +| `region` | body | string | `mainland-cn` 或 `global`;覆盖 `GET /api/v1/oauth/region` 一节描述的区域解析结果,仅对本次流程生效 | + +成功时 `data` 有两种形态。进行中的流程——`{ flow_id, provider, status: "pending", verification_uri, verification_uri_complete, user_code, expires_in, interval, expires_at }`:打开 `verification_uri_complete`(或打开 `verification_uri` 并输入 `user_code`),然后每隔 `interval` 秒轮询 `GET /api/v1/oauth/login`,直到流程完结或超过 `expires_at`(`expires_in` 是以秒表示的同一时限)。已登录的快速路径——`{ flow_id, provider, status: "authenticated" }`。 + +#### `GET /api/v1/oauth/login` + +轮询某供应商的登录流程状态。尚未发起过流程时返回 `null`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider` | query | string | 托管供应商名称。默认 `managed:kimi-code` | + +成功时 `data` 为 `null` 或流程快照:`{ flow_id, provider, status, verification_uri, verification_uri_complete, user_code, expires_in, expires_at, interval }`,其中 `status` 为 `pending` / `authenticated` / `denied` / `expired` / `cancelled`。流程离开 `pending` 后,`resolved_at` 记录其到达终态的时间,`error_message` 描述失败的流程。 + +#### `DELETE /api/v1/oauth/login` + +取消某供应商进行中的登录流程。没有进行中的流程时,该调用为空操作,返回最近一次已知状态。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider` | query | string | 托管供应商名称。默认 `managed:kimi-code` | + +成功时 `data` 为 `{ cancelled, status }`:只有确实中止了一个 `pending` 流程时 `cancelled` 才为 `true`,`status` 为调用后的流程状态。 + +#### `POST /api/v1/oauth/logout` + +登出托管供应商:丢弃已存储的 OAuth 凭据、中止进行中的登录流程,并把托管供应商从配置中移除。OAuth 托管的供应商拒绝手动编辑与删除(见下文 `PUT` / `DELETE /api/v1/providers/{provider_id}`),因此要移除它需先登出。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider` | body | string | 托管供应商名称。默认 `managed:kimi-code` | + +成功时 `data` 为 `{ logged_out: true, provider }`。 + +#### `GET /api/v1/oauth/usage` + +托管账号的套餐用量与限额,实时取自账号服务。上游失败不会让信封失败——它以 `kind: "error"` 的形式带内返回。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider` | query | string | 托管供应商名称。默认 `managed:kimi-code` | + +成功时 `data` 为 `{ kind: "ok", summary, limits, extra_usage }` 或 `{ kind: "error", message, status? }`,其中 `status` 为上游 HTTP 状态码(如存在)。在 `ok` 形态中,`summary`(可空)是主配额行,`limits` 列出每个配额窗口;一行的结构为 `{ name?, window?, used, limit, reset_at? }`,其中 `window` 为 `{ duration, unit }`,`unit` 为 `minute` / `hour` / `day` / `week` 之一。`extra_usage`(可空)是按量付费钱包:`{ balance_cents, total_cents, monthly_charge_limit_enabled, monthly_charge_limit_cents, monthly_used_cents, currency }`。 + +#### `GET /api/v1/oauth/userinfo` + +托管账号的资料,带内 `kind: "error"` 约定与 `GET /api/v1/oauth/usage` 相同。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider` | query | string | 托管供应商名称。默认 `managed:kimi-code` | + +成功时 `data` 为 `{ kind: "ok", userInfo }` 或 `{ kind: "error", message, status? }`。`userInfo` 始终携带 `userId`、`nickname`、`status`、`region`、`userLevel`、`userLevelName`、`domain`、`domainName`,并可能附加 `globalId`、`bio`、`avatar`、`username`、`email`、`phone`(`{ countryCode, number }`)、`createdTime` 与 `lastLoginTime`。 + +#### `GET /api/v1/oauth/region` + +解析该客户端所属的 Kimi 区域。结果在本地推导,不经网络探测:优先取环境变量或配置固定的 OAuth host,其次是已配置的 OAuth key,再次是 home 目录中的区域标记文件;默认为 `mainland-cn`。 + +成功时 `data` 为 `{ region }`,`region` 为 `mainland-cn` / `global` 之一。 ### 配置 @@ -110,8 +217,71 @@ HTTP 状态码几乎总是 200,业务结果以 `code` 为准。例外情况: | `GET /api/v1/config` | 读取全局配置(密钥字段脱敏) | | `POST /api/v1/config` | 合并式更新配置,并广播 `event.config.changed` | +#### `GET /api/v1/config` + +返回解析后的全局配置——`config.toml` 叠加覆盖层后的生效结果。密钥已脱敏:每个供应商只报告 `has_api_key`,绝不返回存储的密钥。 + +成功时 `data` 为配置对象;其字段与 [顶层字段](../configuration/config-files.md#top-level-fields) 记录的顶层域一一对应: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `providers` | object | 供应商 id → `{ type, base_url?, default_model?, has_api_key }` 的映射 | +| `default_provider` | string | 全局默认供应商 id | +| `default_model` | string | 全局默认模型别名 | +| `models` | object | 模型别名 → 模型记录的映射 | +| `thinking` | object | Thinking 模式的默认参数 | +| `plan_mode` | boolean | Plan 模式开关 | +| `yolo` | boolean | 派生值:`default_permission_mode` 为 `yolo` 时为 `true` | +| `default_permission_mode` | string | 新会话的默认权限模式 | +| `default_plan_mode` | boolean | 新会话是否以 Plan 模式启动 | +| `permission` | object | 初始权限规则 | +| `hooks` | array | 生命周期钩子 | +| `services` | object | 内置外部服务配置 | +| `merge_all_available_skills` | boolean | 是否合并所有可用目录中的 Agent Skills | +| `extra_skill_dirs` | array | 额外的 Skill 搜索目录 | +| `loop_control` | object | Agent 循环控制参数 | +| `background` | object | 后台任务运行参数 | +| `subagent` | object | subagent 配置 | +| `secondary_model` | object | subagent 的次级模型池 | +| `experimental` | object | 实验开关 id → 是否启用 | +| `telemetry` | boolean | 是否启用匿名遥测 | +| `raw` | object | 原始解析的 `config.toml` 内容,包含未建模字段 | + +#### `POST /api/v1/config` + +合并式更新全局配置:请求体中的每个顶层域被深合并进对应域,未出现在请求体中的域保持不动。把 `yolo` 设为 `true` 是 `default_permission_mode: "yolo"` 的简写。更新成功后,服务会广播全局 `event.config.changed` 事件,携带变更的字段名与完整的更新后配置;被拒绝的补丁(值非法或持久化失败)返回 `40001` 与底层错误信息。 + +请求体是部分配置对象——上述响应域中除 `raw` 外的任意子集,均为可选: + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `providers` | body | object | 供应商 id → 供应商表的映射 | +| `default_provider` | body | string | 全局默认供应商 id | +| `default_model` | body | string | 全局默认模型别名 | +| `models` | body | object | 模型别名 → 模型记录的映射 | +| `thinking` | body | object | Thinking 模式的默认参数 | +| `plan_mode` | body | boolean | Plan 模式开关 | +| `yolo` | body | boolean | `true` 映射为 `default_permission_mode: "yolo"`;`false` 被忽略 | +| `default_permission_mode` | body | string | `manual` / `yolo` / `auto` | +| `default_plan_mode` | body | boolean | 新会话是否以 Plan 模式启动 | +| `permission` | body | object | 初始权限规则 | +| `hooks` | body | array | 生命周期钩子 | +| `services` | body | object | 内置外部服务配置 | +| `merge_all_available_skills` | body | boolean | 是否合并所有可用目录中的 Agent Skills | +| `extra_skill_dirs` | body | array | 额外的 Skill 搜索目录 | +| `loop_control` | body | object | Agent 循环控制参数 | +| `background` | body | object | 后台任务运行参数 | +| `subagent` | body | object | subagent 配置 | +| `secondary_model` | body | object | subagent 的次级模型池 | +| `experimental` | body | object | 实验开关 id → 是否启用 | +| `telemetry` | body | boolean | 是否启用匿名遥测 | + +成功时 `data` 为完整的更新后配置,形态与 `GET /api/v1/config` 相同。 + ### 模型与供应商 +这组端点管理模型配置的两半——`config.toml` 的 [供应商](../configuration/providers.md) 表与模型别名表——外加一个由服务端代理的 models.dev 目录,用于一次性导入。模型别名 id 就是配置中的别名键:通过供应商管理端点创建的别名形如 `provider_id/model`(例如 `my-provider/kimi-for-coding`),而模型别名表中的裸键(如 `turbo`)原样使用;API 中任何接收 `model_id` 的地方(包括全局 `default_model`)指的都是这个别名 id。`:{action}` 路由上不支持的动作返回 `40001`。 + | 方法与路径 | 说明 | | --- | --- | | `GET /api/v1/models` | 列出已配置的模型别名 | @@ -126,128 +296,1777 @@ HTTP 状态码几乎总是 200,业务结果以 `code` 为准。例外情况: | `GET /api/v1/catalog/providers` | 浏览 models.dev 目录(服务端代理) | | `GET /api/v1/catalog/providers/{catalog_id}` | 读取目录中单个条目 | +#### `GET /api/v1/models` + +列出所有供应商下已配置的模型别名。 + +成功时 `data.items` 为 `{ provider, model, display_name?, max_context_size, capabilities?, support_efforts?, default_effort? }` 数组:`model` 是别名 id(供应商管理的别名为 `provider_id/model`,否则为裸键),`provider` 是所属供应商 id,`max_context_size` 是以 token 计的上下文窗口,`capabilities` / `support_efforts` / `default_effort` 描述能力标志与 Thinking 模式的 effort 支持。 + +#### `POST /api/v1/models/{model_id}:set_default` + +把全局 `default_model` 设为一个已存在的别名。`model_id` 是配置中的别名键原样——裸键如 `POST /api/v1/models/turbo:set_default`;当 id 含 `/` 时需做 URL 编码,如 `POST /api/v1/models/my-provider%2Fkimi-for-coding:set_default`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `model_id` | path | string | **必填。** 配置中的模型别名键原样;含 `/` 时需 URL 编码 | + +成功时 `data` 为 `{ default_model, model }`——当前生效的别名及其目录项(形态与 `GET /api/v1/models` 的单项相同)。 + +- `40001`:路径中的动作后缀非法或不支持 +- `40413`:不存在该 id 的模型别名 + +#### `GET /api/v1/providers` + +列出每个已配置供应商及其凭据与模型发现状态,不泄露任何密钥。这也是其他供应商端点引用的供应商条目形态。 + +成功时 `data.items` 为如下结构的数组: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `id` | string | 供应商 id | +| `type` | string | 通信协议:`kimi` / `openai` / `openai_responses` / `anthropic` / `google-genai` / `vertexai` | +| `base_url` | string | API 基础 URL,如已设置 | +| `default_model` | string | 该供应商的默认模型别名,如已设置 | +| `has_api_key` | boolean | 是否已存储凭据 | +| `status` | string | 存在 API 密钥或缓存的 OAuth token 时为 `connected`,否则为 `unconfigured`(`error` 在 schema 中保留) | +| `models` | array | 该供应商的模型别名 id | + +#### `POST /api/v1/providers` + +一次保存创建供应商及其模型别名;响应为 HTTP 201 加标准信封。当全局 `default_model` 完全未配置时(全新安装),会以新供应商的 `default_model`(或第一个模型)播种;已有默认值绝不被修改。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `id` | body | string | **必填。** 供应商 id——字母、数字、`-`、`_` 与空格;必须以字母或数字开头 | +| `type` | body | string | **必填。** 通信协议:`kimi` / `openai` / `openai_responses` / `anthropic` / `google-genai` / `vertexai` | +| `api_key` | body | string | API 密钥,存储于 `config.toml` | +| `base_url` | body | string | API 基础 URL;不得包含环境变量占位符(`${...}`) | +| `default_model` | body | string | 该供应商的默认模型;必须是 `models[].model` 之一 | +| `models` | body | array | **必填。** 至少一条,不允许重复的 `model` 值;条目结构见下文 | + +每个 `models[]` 条目声明一个别名,其 id 为 `id/model`: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `model` | string | **必填。** 上游模型名 | +| `max_context_size` | integer | **必填。** 以 token 计的上下文窗口,≥ 1 | +| `display_name` | string | 显示名 | +| `capabilities` | array | 能力标志,如 `thinking` 或 `image_in` | +| `max_output_size` | integer | 最大输出 token 数,≥ 1 | +| `support_efforts` | array | 支持的 Thinking 模式 effort 档位 | +| `adaptive_thinking` | boolean | 自适应 thinking 开关 | + +成功时 `data` 为创建好的供应商条目(形态与 `GET /api/v1/providers` 的单项相同)。 + +- `40921`:已存在该 `id` 的供应商 + +#### `GET /api/v1/providers/{provider_id}` + +读取单个供应商。与列表路由不同,设置了密钥时响应会暴露存储的 `api_key`,以便本地编辑表单预填——暴露端口时请牢记这一点。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider_id` | path | string | **必填。** 供应商 id | + +成功时 `data` 为供应商条目,存有密钥时附带 `api_key`。 + +- `40412`:供应商不存在 + +#### `PUT /api/v1/providers/{provider_id}` + +一次保存整体替换供应商:`type`、`base_url` 与模型列表被重写,该供应商的别名按 `models` 重建——不再列出的别名从 `config.toml` 中消失,其他供应商的别名不受影响。`api_key` 是三态的:省略表示保留已存密钥,`""` 表示清除,其他值表示替换。除 `new_id` 重命名迁移外,全局默认指针绝不被修改。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider_id` | path | string | **必填。** 当前供应商 id | +| `new_id` | body | string | 重命名供应商;providers 键、模型别名、`default_provider`、指向旧别名的 `default_model` 以及 subagent 次级模型池都会随之迁移。id 规则与 `POST /api/v1/providers` 相同 | +| `type` | body | string | **必填。** 通信协议:`kimi` / `openai` / `openai_responses` / `anthropic` / `google-genai` / `vertexai` | +| `api_key` | body | string | 三态,见上文 | +| `base_url` | body | string | API 基础 URL;不得包含环境变量占位符(`${...}`) | +| `default_model` | body | string | 该供应商的默认模型;必须是 `models[].model` 之一 | +| `models` | body | array | **必填。** 至少一条,不允许重复的 `model` 值;条目结构与 `POST /api/v1/providers` 相同 | + +成功时 `data` 为 `{ provider }`,即保存后的供应商条目。 + +- `40001`:重命名后的别名 id 会与其他供应商的别名冲突 +- `40003`:供应商由 OAuth 托管——请改用 `POST /api/v1/oauth/logout` 登出 +- `40412`:供应商不存在 +- `40921`:`new_id` 已被占用 + +#### `DELETE /api/v1/providers/{provider_id}` + +删除供应商及其全部模型别名;subagent 次级模型池会级联清理。全局 `default_provider` / `default_model` 指针保持不动,即使它们指向被删的供应商——那是用户的设置,不由本端点代为回收。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider_id` | path | string | **必填。** 供应商 id | + +成功时服务应答 204 且无响应体——状态行本身即表示删除成功(见 [响应信封](#响应信封))。 + +- `40003`:供应商由 OAuth 托管——请改用 `POST /api/v1/oauth/logout` 登出 +- `40412`:供应商不存在 + +#### `POST /api/v1/providers/{provider_id}:refresh` + +从上游来源重新发现单个供应商的模型元数据,并重写该供应商的别名。模型来源为静态的供应商不经任何网络调用直接报告 `unchanged`。至少一个供应商的别名发生变化时,服务会广播全局 `event.model_catalog.changed` 事件。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `provider_id` | path | string | **必填。** 供应商 id | + +成功时 `data` 为刷新报告:`changed` 是 `{ provider_id, provider_name, added, removed }`(新增 / 移除的别名数)的数组,`unchanged` 是无差异的供应商 id 数组,`failed` 是 `{ provider, reason }` 的数组。 + +- `40001`:路径中的动作后缀非法或不支持 +- `40412`:供应商不存在 + +#### `POST /api/v1/providers:refresh` + +刷新每个供应商的模型元数据。请求体可选且被忽略。 + +成功时 `data` 为与 `POST /api/v1/providers/{provider_id}:refresh` 相同的刷新报告(`changed` / `unchanged` / `failed`)。 + +#### `POST /api/v1/providers:refresh_oauth` + +与 `POST /api/v1/providers:refresh` 相同的刷新,仅限 OAuth 凭据的供应商。请求体可选且被忽略。 + +成功时 `data` 为刷新报告(`changed` / `unchanged` / `failed`)。 + +#### `POST /api/v1/providers:import_catalog` + +把一个 models.dev 目录条目导入为已配置供应商;响应为 HTTP 201 加标准信封。通信协议与端点来自目录解析,目录中的每个模型都写为一个别名。导入已存在的 id 等同于刷新——供应商条目及其别名按目录重写,省略 `api_key` 表示保留已存密钥。全局默认指针绝不被修改,仅在完全未配置默认模型时,以第一个导入的模型播种 `default_model`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `catalog_id` | body | string | **必填。** 来自 `GET /api/v1/catalog/providers` 的目录条目 id | +| `id` | body | string | 覆盖目录 id 作为本地供应商 id。id 规则与 `POST /api/v1/providers` 相同 | +| `api_key` | body | string | 导入供应商的 API 密钥 | +| `base_url` | body | string | 覆盖目录解析出的端点;条目的 `needs_base_url` 为 `true` 时必填 | + +成功时 `data` 为 `{ provider, models_imported }`——供应商条目与写入的别名数量。 + +- `40001`:缺少 `catalog_id` 或其他请求体校验失败 +- `40003`:目标供应商已存在且由 OAuth 托管 +- `40004`:条目无法导入(被拒绝、要求 `base_url`、没有可导入的模型,或其 id 不能用作供应商 id) +- `40417`:不存在该 `catalog_id` 的目录条目 +- `50004`:models.dev 目录不可用 + +#### `POST /api/v1/providers:import_registry` + +把一个 models.dev 形态的私有注册表——一个 `api.json` URL 加可选的 Bearer key——导入为已配置供应商;响应为 HTTP 201 加标准信封。每个列出的供应商都带 `source` 记录写入,以便定时刷新重新发现。重复导入同一 URL 会移除上游已消失的供应商——URL 是注册表的稳定身份,因此轮换 key 是安全的。全局默认指针遵循与 `:import_catalog` 相同的规则。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `url` | body | string | **必填。** 注册表 `api.json` 的 URL | +| `api_key` | body | string | 注册表的 Bearer key;省略时复用上一次导入同一 URL 所用的 key | + +成功时 `data` 为 `{ providers, models_imported }`——供应商条目数组与写入的别名总数。 + +- `40001`:缺少 `url` 或其他请求体校验失败 +- `40003`:某个列出的供应商已存在且由 OAuth 托管 +- `40005`:注册表无法获取或解析,或未列出可导入的供应商 + +#### `GET /api/v1/catalog/providers` + +浏览 models.dev 目录,由服务端代理,带 10 分钟内存缓存与内置快照兜底。条目保持上游目录顺序。服务无法导入的条目携带 `rejected: true` 与机器可读的 `reject_reason`;`needs_base_url: true` 的条目在导入时要求提供 base URL。 + +成功时 `data.items` 为 `{ id, name, wire_type, guessed, needs_base_url, rejected, reject_reason, env_key, models }` 数组:`wire_type` 是解析出的协议(可空,枚举与供应商 `type` 相同),`guessed` 标记启发式解析,`env_key` 是上游约定的 API 密钥环境变量(可空),`models` 是 `{ id, name?, max_context_size, capabilities?, reasoning }` 的数组。 + +- `50004`:目录不可用(在线拉取与内置快照均失败) + +#### `GET /api/v1/catalog/providers/{catalog_id}` + +按 catalog id 读取单个 models.dev 目录条目——条目形态与 `GET /api/v1/catalog/providers` 相同。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `catalog_id` | path | string | **必填。** 目录条目 id | + +成功时 `data` 为该目录条目(形态与 `GET /api/v1/catalog/providers` 的单项相同)。 + +- `40417`:不存在该 `catalog_id` 的目录条目 +- `50004`:目录不可用 + ### 会话 +这些端点用于创建、列出和查看会话,执行会话级动作(fork、compact、undo 等),并读取会话级汇总。其中大多数返回的会话采用 [session 对象](#session-对象) 中统一说明的线上格式;非 CRUD 操作使用上文介绍的 `:{action}` 约定。 + | 方法与路径 | 说明 | | --- | --- | | `POST /api/v1/sessions` | 创建会话(需 `workspace_id` 或 `metadata.cwd`) | | `GET /api/v1/sessions` | 列出会话,游标分页,支持 `busy` / `archived_only` 等过滤 | | `GET /api/v1/sessions/{session_id}` | 读取单个会话 | | `GET /api/v1/sessions/{session_id}/profile` | 读取会话档案 | -| `POST /api/v1/sessions/{session_id}/profile` | 更新标题、元数据、agent 配置 | +| `POST /api/v1/sessions/{session_id}/profile` | 更新标题、元数据、Agent 配置 | +| `POST /api/v1/sessions/{session_id}/title/generate` | 通过托管的 `chat_title` 工具生成标题 | | `POST /api/v1/sessions/{session_id}:{action}` | 会话动作:`fork` / `compact` / `undo` / `abort` / `btw` / `archive` / `restore` | | `GET /api/v1/sessions/{session_id}/children` | 列出子会话 | | `POST /api/v1/sessions/{session_id}/children` | 创建子会话(fork 并打标) | | `GET /api/v1/sessions/{session_id}/status` | 实时状态汇总 | | `GET /api/v1/sessions/{session_id}/goal` | 当前目标快照(无则 `null`) | | `GET /api/v1/sessions/{session_id}/warnings` | 会话级告警 | +| `GET /api/v1/sessions/{session_id}/runtime` | 读取 main agent 的运行时绑定 | +| `POST /api/v1/sessions/{session_id}/runtime` | 切换 main agent 的运行时绑定 | | `POST /api/v1/sessions/{session_id}/export` | 导出会话与诊断信息(zip 流,不走信封) | | `GET /api/v1/sessions/{session_id}/snapshot` | 客户端重建用全量快照(含 `as_of_seq` 与 `epoch`) | +| `GET /api/v1/sessions/{session_id}/media/{file_id}` | 按文件 id 下载提示词媒体(二进制) | -### 消息与转录 +#### session 对象 -| 方法与路径 | 说明 | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/messages` | 消息分页(`before_id` / `after_id` / `role`) | -| `GET /api/v1/sessions/{session_id}/messages/{message_id}` | 读取单条消息 | -| `GET /api/v1/sessions/{session_id}/transcript` | 转录按轮次分页(需 `agent_id`),全局状态不分页随响应返回 | -| `GET /api/v1/sessions/{session_id}/transcript/ops` | 转录批次补漏(`since_seq`),`complete: false` 时需全量刷新 | -| `GET /api/v1/sessions/{session_id}/transcript/user-messages` | 各轮次的用户输入,不分页 | -| `GET /api/v1/sessions/{session_id}/transcript/plan` | ExitPlanMode 计划内容、路径与审阅结果 | +每个返回会话的端点都使用这种线上格式。实时状态字段(`busy`、`main_turn_active`、`pending_interaction`、`last_turn_reason`)由会话的活动聚合解析得出:未加载到本服务进程中的会话(冷会话)始终上报为不忙碌且无待处理交互。少数字段在当前投影中是占位值——已逐字段注明。 -### 提示词 +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `id` | string | 会话 id(`session_...`) | +| `workspace_id` | string | 所属工作区 id | +| `title` | string | 会话标题;无标题时为 `""` | +| `created_at` / `updated_at` | string | 创建时间与最后更新时间,ISO 8601 | +| `archived` | boolean | 会话是否已归档(归档后从默认会话列表中隐藏) | +| `archived_at` | string | 归档时间,ISO 8601;仅在已归档时存在 | +| `busy` | boolean | 是否有任一 Agent 存在进行中的轮次或后台任务 | +| `main_turn_active` | boolean | main agent 是否有进行中的轮次 | +| `pending_interaction` | string | `none` / `approval` / `question`——有未答复的交互在等待 | +| `last_turn_reason` | string | main agent 最近一次轮次的结果:`completed` / `cancelled` / `failed` | +| `last_prompt` | string | 最近一条用户提示词文本(如有) | +| `metadata` | object | 自定义元数据;始终携带 `cwd`(会话的工作目录) | +| `agent_config` | object | 投影为 `{ model }`;`model` 在大多数响应中为 `""`,仅由 `GET /api/v1/sessions/{session_id}/snapshot` 填入实时模型 | +| `usage` | object | token 汇总 `{ input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, context_tokens, context_limit?, total_cost_usd?, turn_count? }`;在 snapshot 端点之外全为零 | +| `permission_rules` | array | 会话权限规则;当前始终为 `[]` | +| `message_count` | integer | 消息数;当前始终为 `0` | +| `last_seq` | integer | 最后的事件序列号;当前始终为 `0` | + +#### `POST /api/v1/sessions` + +创建会话并返回。目标目录来自 `workspace_id`(已注册的工作区)或 `metadata.cwd`(首次使用时注册该工作区);两者同时提供时必须一致。创建时会广播全局 `event.session.created` 事件。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `workspace_id` | body | string | 未提供 `metadata.cwd` 时**必填**。已注册的工作区 id;会话创建于该工作区的根目录 | +| `metadata` | body | object | 自定义元数据。`metadata.cwd` 为工作目录,未提供 `workspace_id` 时**必填**;两者同时提供时必须等于工作区根目录 | +| `title` | body | string | 初始标题(至少 1 个字符);否则会话无标题 | +| `agent_config` | body | object | schema 接受该字段但当前不会应用——模型与各模式请通过 `POST /api/v1/sessions/{session_id}/profile` 设置 | -| 方法与路径 | 说明 | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/prompts` | 进行中与排队中的提示词 | -| `POST /api/v1/sessions/{session_id}/prompts` | 提交提示词(内容块数组,可带模型 / 权限模式等覆盖) | -| `POST /api/v1/sessions/{session_id}/prompts:steer` | 把排队的提示词插入当前轮次 | -| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:abort` | 中止进行中的提示词 | -| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:steer` | 插入单个排队提示词 | +成功时,`data` 为新会话的 [session 对象](#session-对象)。 -### 审批与提问 +- `40001`:`workspace_id` 与 `metadata.cwd` 都未提供,或 `metadata.cwd` 与工作区根目录不一致(`details` 会列出该字段) +- `40409`:工作目录不存在或不是目录 +- `40410`:没有以该 `workspace_id` 注册的工作区 -| 方法与路径 | 说明 | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/approvals` | 列出审批请求(可按 `status=pending` 过滤) | -| `POST /api/v1/sessions/{session_id}/approvals/{approval_id}` | 答复审批 | -| `GET /api/v1/sessions/{session_id}/questions` | 列出提问 | -| `POST /api/v1/sessions/{session_id}/questions/{question_id}` | 回答提问 | -| `POST /api/v1/sessions/{session_id}/questions/{question_id}:dismiss` | 忽略提问 | +#### `GET /api/v1/sessions` -### 后台任务 +跨工作区列出会话,按 `updated_at` 最新在前。游标分页遵循 [分页](#分页),但有一个特例:不提供 `page_size`(且不提供 `archived_only`)时,响应是单个不分页的窗口,其 `has_more` 恒为 `false`,因此要真正翻页请传入 `page_size`。 -| 方法与路径 | 说明 | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/tasks` | 列出后台任务 | -| `GET /api/v1/sessions/{session_id}/tasks/{task_id}` | 读取任务(可选输出预览) | -| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` | 取消任务 | +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `before_id` | query | string | 只保留早于该 id 的会话;与 `after_id` 互斥 | +| `after_id` | query | string | 只保留晚于该 id 的会话;与 `before_id` 互斥 | +| `page_size` | query | integer | 1–100。分页生效时默认为 `20`;不分页的默认行为见上文说明 | +| `busy` | query | boolean | 只保留忙碌(或只保留空闲)的会话 | +| `include_archive` | query | boolean | 在活跃会话之外同时包含已归档会话。默认 `false` | +| `archived_only` | query | boolean | 只保留已归档会话;与 `include_archive` 互斥;即使不提供 `page_size` 也会启用游标分页 | +| `exclude_empty` | query | boolean | 去掉没有任何用户提示词的会话 | +| `workspace_id` | query | string | 限定到单个工作区(别名会被解析) | -### 技能、工具与 MCP +成功时,`data` 为 `{ items, has_more }`,其中每个元素为 [session 对象](#session-对象)。 -| 方法与路径 | 说明 | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/skills` | 会话级技能目录 | -| `GET /api/v1/workspaces/{workspace_id}/skills` | 无会话的工作区技能目录 | -| `POST /api/v1/sessions/{session_id}/skills/{skill_name}:activate` | 激活技能(开启一个轮次) | -| `GET /api/v1/tools` | 列出当前生效 agent 的工具 | -| `GET /api/v1/mcp/servers` | 列出 MCP 服务 | -| `POST /api/v1/mcp/servers/{mcp_server_id}:restart` | 重启 MCP 服务 | +- `40001`:校验失败——例如 `before_id` 与 `after_id` 同用,或 `archived_only` 与 `include_archive` 同用 +- `40410`:未知的 `workspace_id` -### 终端 +#### `GET /api/v1/sessions/{session_id}` -PTY 终端接口,仅 loopback 绑定时挂载。 +从索引中读取单个会话。会话已加载到本进程时会包含实时状态字段;冷会话上报为不忙碌,并携带其最后持久化的轮次结果。 -| 方法与路径 | 说明 | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/terminals` | 列出终端 | -| `POST /api/v1/sessions/{session_id}/terminals` | 创建终端 | -| `GET /api/v1/sessions/{session_id}/terminals/{terminal_id}` | 读取终端(含回滚缓冲) | -| `POST /api/v1/sessions/{session_id}/terminals/{terminal_id}:close` | 关闭终端 | +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | -### 工作区 +成功时,`data` 为 [session 对象](#session-对象)。 -| 方法与路径 | 说明 | -| --- | --- | -| `GET /api/v1/workspaces` | 列出已注册工作区 | -| `POST /api/v1/workspaces` | 注册工作区(按根路径幂等) | -| `PATCH /api/v1/workspaces/{workspace_id}` | 重命名 | -| `DELETE /api/v1/workspaces/{workspace_id}` | 注销(保留磁盘内容) | -| `GET /api/v1/workspaces/{workspace_id}/trust` | 读取信任状态 | -| `POST /api/v1/workspaces/{workspace_id}/trust` | 授予信任 | -| `POST /api/v1/workspaces/{workspace_id}/untrust` | 撤销信任 | +- `40401`:会话不存在,或其工作区已无法解析 -### 文件系统 +#### `GET /api/v1/sessions/{session_id}/profile` -会话内文件操作为 `POST /api/v1/sessions/{session_id}/fs:{action}`,动作包括 `list` / `read` / `list_many` / `stat` / `stat_many` / `mkdir` / `search` / `grep` / `git_status` / `diff` / `open` / `open-in` / `reveal`,请求体为 JSON。另有: +读取会话档案——与 `GET /api/v1/sessions/{session_id}` 相同的线上载荷。 -| 方法与路径 | 说明 | -| --- | --- | -| `POST /api/v1/workspace/fs:search` | 无会话的工作区搜索(body 携带工作区引用) | -| `GET /api/v1/sessions/{session_id}/fs/{path}:download` | 下载会话文件(二进制,见下文) | -| `GET /api/v1/fs:browse` | 列出本机目录(文件夹选择器用) | -| `GET /api/v1/fs:home` | 用户主目录与最近工作区 | -| `GET /api/v1/fs:content` | 读取本机任意文件原始字节(仅受 token 保护,谨慎暴露端口) | -| `POST /api/v1/fs:mkdir` | 按绝对路径创建目录 | +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | -### 文件上传 +成功时,`data` 为 [session 对象](#session-对象)。 -| 方法与路径 | 说明 | -| --- | --- | -| `POST /api/v1/files` | multipart 上传(字段 `file`,可选 `name`、`expires_in_sec`),返回文件元信息 | -| `GET /api/v1/files/{file_id}` | 下载(二进制,错误用真实 HTTP 状态码) | -| `DELETE /api/v1/files/{file_id}` | 删除 | +- `40401`:会话不存在 -### 全局搜索与其他 +#### `POST /api/v1/sessions/{session_id}/profile` -| 方法与路径 | 说明 | -| --- | --- | -| `POST /api/v1/search` | 跨会话全文搜索,`mode` 为 `terms`(默认)或 `literal`(精确子串),`page_token` 分页 | -| `GET /api/v1/connections` | 列出当前在线的 WebSocket 连接 | -| `GET /api/v2/sessions` | 新一代会话列表,见下节 | -| `POST /api/v2/sessions:archive` | 批量归档会话,见下节 | -| `POST /api/v2/sessions:restore` | 批量恢复已归档会话,见下节 | -| `/api/v1/debug/*` | 反射式调试 RPC,仅 `--debug-endpoints` 且 loopback 时挂载,不属于稳定协议 | +更新会话档案:标题、自定义元数据以及 main agent 的配置。在这里设置的标题会成为自定义标题,优先级高于生成的标题;设置标题会广播全局 `session.meta.updated` 事件。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `title` | body | string | 新标题(至少 1 个字符);会成为自定义标题 | +| `metadata` | body | object | 合并进会话自定义元数据的键 | +| `agent_config` | body | object | main agent 的部分配置;字段如下,均为可选 | + +每个 `agent_config` 字段都会立即应用到 main agent: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `model` | string | 模型别名 id;空字符串会被忽略 | +| `thinking` | string | Thinking 强度等级 | +| `permission_mode` | string | `manual` / `yolo` / `auto` | +| `plan_mode` | boolean | 进入或退出 Plan 模式 | +| `swarm_mode` | boolean | 进入或退出 swarm 模式 | +| `goal_objective` | string | 以该文本为内容创建一个目标 | +| `goal_control` | string | `pause` / `resume` / `cancel` 当前目标 | + +schema 还接受 `agent_config` 内的 `system_prompt`、`tools`、`mcp_servers`,以及顶层的 `permission_rules` 数组,但更新路由当前不会应用它们。 + +成功时,`data` 为更新后的 [session 对象](#session-对象)。 + +- `40401`:会话不存在 + +#### `POST /api/v1/sessions/{session_id}/title/generate` + +通过托管供应商的 `chat_title` 工具根据会话的提示词生成标题并应用,同时广播 `session.meta.updated`。生成需要托管 OAuth 登录和 `auto_session_title` 实验开关;未提供 `force` 时,已有自定义标题或已生成标题的会话会上报为不可用,而不会被覆盖。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `force` | body | boolean | 即使已有自定义或生成的标题也重新生成。默认 `false` | +| `source` | body | string | 标题输入:`user_prompts`(默认)/ `first_turn` / `digest` | + +成功时,`data` 为 `{ title }`——当前应用到会话的标题。 + +- `40401`:会话不存在 +- `40923`:生成不可用——开关未开启、没有托管 OAuth 登录或尚无任何提示词内容、已有标题但未提供 `force`,或后端请求失败 + +#### `POST /api/v1/sessions/{session_id}:{action}` + +会话动作通过同一条路由分发:路径尾部解析为 `{session_id}:{action}`,请求体按该动作的 schema 校验,动作缺失或未知时返回 `40001`(`unsupported action: ...`)。每个动作都会先解析会话,因此会话未知时都可能返回 `40401`。支持的动作在下面逐一说明。 + +#### `POST /api/v1/sessions/{session_id}:fork` + +将会话——其转录、Agent 状态与文件——复制到同一工作区中的新会话,并广播 `event.session.created`。当会话中任一 Agent 有进行中的轮次时,fork 会被拒绝。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `title` | body | string | fork 的标题(至少 1 个字符)。默认 `Fork: ` | +| `metadata` | body | object | fork 的自定义元数据 | + +成功时,`data` 为新会话的 [session 对象](#session-对象)。 + +- `40901`:会话有进行中的轮次,无法 fork + +#### `POST /api/v1/sessions/{session_id}:compact` + +对 main agent 的上下文发起一次手动全量压缩。调用立即返回;进度与完成通过 `compaction.*` WebSocket 事件投递。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `instruction` | body | string | 给压缩摘要的额外指引;空值会被忽略 | + +成功时,`data` 为空对象。 + +- `40910`:有轮次或其他上下文变更正在进行,或历史中没有可压缩的内容 + +#### `POST /api/v1/sessions/{session_id}:undo` + +将 main agent 的对话回退 `count` 个轮次,并同步修正派生的会话状态(包括会话的 `last_prompt`)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `count` | body | integer | 要撤销的轮次数;正整数。默认 `1` | +| `page_size` | body | integer | 返回的历史窗口大小,1–100。默认 `50` | + +成功时,`data` 为 `{ messages, status }`:`messages` 是剩余上下文消息按最新在前的 `{ items, has_more }` 分页,`status` 与 `GET /api/v1/sessions/{session_id}/status` 的汇总相同。 + +- `40901`:有轮次正在进行或压缩正在运行——等其结束后重试 +- `40911`:无法撤销那么多轮次(遇到压缩边界或检查点丢失);`data` 携带 `{ reason, requestedCount, undoableCount }` + +#### `POST /api/v1/sessions/{session_id}:abort` + +取消 main agent 正在运行的轮次——等同于用户在 TUI 中中止轮次的程序化版本。 + +成功时,`data` 为 `{ aborted: true }`。 + +#### `POST /api/v1/sessions/{session_id}:btw` + +开启一个 `"by the way"` 旁路对话:把 main agent fork 成一个禁用工具调用的子 Agent,让快速的临时问题在隔离环境中运行,不触碰工作上下文。需要可用的模型配置。 + +成功时,`data` 为 `{ agent_id }`——新子 Agent 的 id。 + +#### `POST /api/v1/sessions/{session_id}:archive` + +将会话标记为已归档:它从默认会话列表中消失(使用 `include_archive` 或 `archived_only` 时仍会列出),并且服务端广播全局 `event.session.archived` 事件。 + +成功时,`data` 为 `{ archived: true }`。 + +#### `POST /api/v1/sessions/{session_id}:restore` + +取消会话的归档状态并恢复它。 + +成功时,`data` 为 `archived: false` 的 [session 对象](#session-对象)。 + +#### `GET /api/v1/sessions/{session_id}/children` + +列出会话的子会话——即通过 `POST /api/v1/sessions/{session_id}/children` 创建的会话。游标分页遵循 [分页](#分页)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `before_id` | query | string | 只保留早于该 id 的子会话;与 `after_id` 互斥 | +| `after_id` | query | string | 只保留晚于该 id 的子会话;与 `before_id` 互斥 | +| `page_size` | query | integer | 1–100。默认 `100` | +| `busy` | query | boolean | 只保留忙碌(或只保留空闲)的子会话 | + +成功时,`data` 为 `{ items, has_more }`,其中每个元素为 [session 对象](#session-对象)。 + +- `40401`:会话不存在 + +#### `POST /api/v1/sessions/{session_id}/children` + +创建子会话:fork 当前会话并记录为其子会话,因此会出现在 `GET /api/v1/sessions/{session_id}/children` 下。适用与 `:fork` 相同的进行中轮次限制。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `title` | body | string | 子会话的标题(至少 1 个字符)。默认 `Child: ` | +| `metadata` | body | object | 子会话的自定义元数据 | + +成功时,`data` 为新会话的 [session 对象](#session-对象),并且服务端广播 `event.session.created`。 + +- `40901`:会话有进行中的轮次,无法 fork + +#### `GET /api/v1/sessions/{session_id}/status` + +main agent 的实时状态汇总;读取它会在会话为冷态时将其恢复。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | + +成功时,`data` 为 `{ busy, model?, thinking_level, permission, plan_mode, swarm_mode, context_tokens, max_context_tokens?, context_usage? }`:`busy` 表示是否有进行中的轮次,`model` / `thinking_level` / `permission` 为当前生效的 Agent 设置,`plan_mode` / `swarm_mode` 为模式标志,`context_tokens` 与 `max_context_tokens`、`context_usage`(0–1)描述上下文窗口的占用情况。 + +- `40401`:会话不存在 + +#### `GET /api/v1/sessions/{session_id}/goal` + +读取会话当前的目标快照;没有活跃目标时为 `null`。注意,与本 API 的大多数载荷不同,该载荷使用 camelCase 键。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | + +成功时,`data` 为 `null` 或 `{ goalId, objective, completionCriterion?, status, turnsUsed, tokensUsed, wallClockMs, budget, terminalReason? }`,其中 `status` 为 `active` / `paused` / `blocked` / `complete`,`budget` 报告 token、轮次与 wall-clock 三项预算,以及各自的剩余量与每项预算的 reached 标志(未设置对应预算时各项为 null)。 + +- `40401`:会话不存在 + +#### `GET /api/v1/sessions/{session_id}/warnings` + +读取会话级告警。目前的产生者只有 `AGENTS.md` 过大检查(`agents-md-oversized`),因此大多数会话的列表为空。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | + +成功时,`data` 为 `{ warnings }`,每个条目为 `{ code, message, severity }`,其中 `severity` 为 `info` / `warning` / `error` 之一。 + +- `40401`:会话不存在 + +#### `GET /api/v1/sessions/{session_id}/runtime` + +读取 main agent 的运行时绑定——即该会话的 Agent 循环运行在哪个运行时上。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | + +成功时,`data` 为 `{ workspace_id, runtime_id }`。 + +- `40401`:会话不存在 + +#### `POST /api/v1/sessions/{session_id}/runtime` + +切换 main agent 的运行时绑定。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `runtime_id` | body | string | **必填。** 目标运行时 id | + +成功时,`data` 为新的绑定 `{ workspace_id, runtime_id }`。 + +- `40420`:不存在该 `runtime_id` 的运行时 +- `40926`:运行时存在但不可用 + +#### `POST /api/v1/sessions/{session_id}/export` + +将会话连同诊断日志一起导出为 zip 附件(`kimi-session-.zip`)。响应是二进制流,不是 JSON 信封——能力与失败语义见 [二进制与流式端点](#二进制与流式端点)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `web_log` | body | string | 要包含在归档中的客户端日志文本,最多 256 KB UTF-8 | +| `desktop` | body | boolean | 同时包含桌面宿主的日志。默认 `false` | + +#### `GET /api/v1/sessions/{session_id}/snapshot` + +为重新同步后重建客户端组装一份原子快照:会话、最近的消息、进行中的轮次、存活的 subagent 以及待处理交互,全部盖上 `as_of_seq` 水位与用于重新订阅的 `epoch`——见 [断线恢复](#断线恢复)。与普通的会话端点不同,内嵌的会话携带实时的 `agent_config.model` 与真实的 `usage` 总计。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | + +成功时,`data` 为 `{ as_of_seq, epoch, session, messages, in_flight_turn, subagents?, pending_approvals, pending_questions }`:`session` 为 [session 对象](#session-对象),`messages` 为最新 100 条消息的 `{ items, has_more }`,`in_flight_turn` 为已部分流式输出的轮次(空闲时为 `null`,已知时带 `current_prompt_id`),`subagents` 列出存活的 subagent 任务,`pending_approvals` / `pending_questions` 承载未答复的交互。 + +- `40401`:会话不存在 + +#### `GET /api/v1/sessions/{session_id}/media/{file_id}` + +按文件 id 下载提示词媒体文件(会话提示词引用的图片或其他附件);尚未提交到会话的 id 会回退到暂存的上传中查找。响应为二进制并支持 `Range`(范围请求返回 206)——共享约定见 [二进制与流式端点](#二进制与流式端点);与那里走信封的端点不同,会话或文件不存在时会返回真正的 404 状态码并携带信封体。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `file_id` | path | string | **必填。** 媒体文件 id | + +### 消息与转录 + +`messages` 端点分页返回 main agent 的扁平化消息历史,`transcript` 端点则提供按 Agent 组织的结构化转录——轮次、任务、交互、附件——即 WebSocket [转录协议](#转录协议) 实时流式推送的内容。历史分页与补漏用这些端点,实时尾部用 WebSocket 订阅。 + +| 方法与路径 | 说明 | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/messages` | 消息分页(`before_id` / `after_id` / `role`) | +| `GET /api/v1/sessions/{session_id}/messages/{message_id}` | 读取单条消息 | +| `GET /api/v1/sessions/{session_id}/transcript` | 按轮次分页的转录(需 `agent_id`);全局状态不分页随响应返回 | +| `GET /api/v1/sessions/{session_id}/transcript/ops` | op 批次补漏(`since_seq`);`complete: false` 表示需要全量刷新 | +| `GET /api/v1/sessions/{session_id}/transcript/user-messages` | 各轮次起始的用户输入,不分页 | +| `GET /api/v1/sessions/{session_id}/transcript/plan` | ExitPlanMode 计划内容、路径与审阅结果 | + +#### `GET /api/v1/sessions/{session_id}/messages` + +分页返回 main agent 的消息历史——与会话快照共享的扁平化上下文转录——最新在前。游标分页遵循 [分页](#分页);读取历史会在会话为冷态时将其恢复。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `before_id` | query | string | 只保留早于该消息 id 的消息;与 `after_id` 互斥 | +| `after_id` | query | string | 只保留晚于该消息 id 的消息;与 `before_id` 互斥 | +| `page_size` | query | integer | 1–100。默认 `50` | +| `role` | query | string | 只保留单一角色:`user` / `assistant` / `tool` / `system`。过滤在分页切片之后应用,因此过滤后的一页可能少于 `page_size` 条而 `has_more` 仍为 `true`——持续翻页直到 `has_more` 为 `false` | + +成功时,`data` 为 `{ items, has_more }`,其中每个元素是消息对象 `{ id, session_id, role, content, created_at, prompt_id?, parent_message_id?, metadata? }`;`content` 是按 [提示词](#提示词) 中说明的线上格式组成的内容块数组(`text`、`tool_use`、`tool_result`、`image`、`video`、`file`、`thinking`)。 + +- `40001`:校验失败——例如 `before_id` 与 `after_id` 同用 +- `40401`:会话不存在 + +#### `GET /api/v1/sessions/{session_id}/messages/{message_id}` + +按 id 从同一历史中读取单条消息。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `message_id` | path | string | **必填。** 消息 id | + +成功时,`data` 为上文 `GET /api/v1/sessions/{session_id}/messages` 中说明的元素形态的消息对象。 + +- `40401`:会话不存在 +- `40403`:该会话中不存在此 id 的消息 + +#### `GET /api/v1/sessions/{session_id}/transcript` + +返回某个 Agent 的结构化转录中的一页:轮次(含其步骤与帧)以及轮次之间的标记与任务引用。活跃会话从内存存储应答(先回填所请求 Agent 的持久化历史);冷会话则从持久化的线上记录重建 Agent。这是转录能力的历史半边——实时流式半边是 [转录协议](#转录协议) 订阅。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `agent_id` | query | string | **必填。** 要读取其转录的 Agent;必须是纯文本形式的 agent id(字母、数字、`.`、`_`、`-`——不含路径分隔符) | +| `before_turn` | query | string | 只保留早于该轮次 id 的轮次;与 `after_turn` 互斥 | +| `after_turn` | query | string | 只保留晚于该轮次 id 的轮次;与 `before_turn` 互斥 | +| `page_size` | query | integer | 1–100 个轮次。默认 `20` | + +分页单位是轮次:不带游标时返回最新的一页,`has_more` 表示还有更早的轮次。成功时,`data` 为 `{ agent_id, items, has_more, tasks, interactions, attachments, todos, meta, agents, pending_interactions, seq? }`——`items` 是本次分页的轮次切片,`tasks` / `interactions` / `attachments` / `todos` / `meta` / `agents` / `pending_interactions` 是不分页、随每次响应一起返回的全局 Agent 状态,`seq` 是该 Agent 用于恢复流的 op 批次水位(仅活跃会话)。 + +- `40001`:校验失败——`before_turn` 与 `after_turn` 同用,或 `agent_id` 不是纯文本形式 +- `40401`:会话不存在 + +#### `GET /api/v1/sessions/{session_id}/transcript/ops` + +从服务端的 op 日志提供点对点的补漏:某个 Agent 的 `seq > since_seq` 的已记录 op 批次,最旧在前。它是 [转录协议](#转录协议) 中 `transcript_since` 恢复游标的 REST 对应物,共享同一份有界日志,因此适用相同的回退规则。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `agent_id` | query | string | **必填。** Agent id(纯文本形式,约束与转录端点相同) | +| `since_seq` | query | integer | **必填。** 调用方已应用的最后一个 op 批次 seq,最小为 `0`;返回其之后的批次 | + +成功时,`data` 为 `{ agent_id, batches, latest_seq, complete }`,每个批次为 `{ seq, ops }`。`complete: true` 表示直到 `latest_seq` 的每个批次都在;`complete: false` 表示日志已不再覆盖到 `since_seq`(或会话根本不是活跃状态),调用方必须回退为一次完整的 `GET .../transcript` 刷新。 + +- `40001`:校验失败 +- `40401`:会话不存在 + +#### `GET /api/v1/sessions/{session_id}/transcript/user-messages` + +列出会话中每个开启轮次的输入,按 Agent 分组且不分页:真实用户文本、以斜杠命令形式使用的 Skill 与插件命令、以及 cron 提示词——可通过 `origin` 区分——另有仅含附件的提示词,其 `prompt` 投影为空。所列消息引用的附件实体会随响应一起返回(仅元数据,绝不包含字节内容)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `agent_id` | query | string | 只读取一个 Agent(纯文本 id)。默认读取所有在册 Agent | + +成功时,`data` 为 `{ agents }`,每个条目为 `{ agent_id, messages, attachments }`;消息为 `{ turn_id, ordinal, state, origin, prompt, attachment_ids?, started_at? }`,其中 `state` 为轮次状态(`queued` / `running` / `completed` / `failed` / `cancelled`)。 + +- `40001`:校验失败——`agent_id` 不是纯文本形式 +- `40401`:会话不存在 + +#### `GET /api/v1/sessions/{session_id}/transcript/plan` + +按时间线顺序读取某个 Agent 的 `ExitPlanMode` 工具调用的计划信息——计划内容、计划文件路径、提供的选项以及审阅结果。内容投影自第一个可用的事实来源:关联的审批交互(交互式审阅)、实时工具帧的展示(auto 模式),或工具结果的输出文本;每个条目在 `source` 中记录了具体来源。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `agent_id` | query | string | **必填。** Agent id(纯文本形式) | +| `tool_call_id` | query | string | 将读取范围限定到单次 `ExitPlanMode` 调用;不提供时列出所有可恢复计划内容的调用 | + +成功时,`data` 为 `{ agent_id, plans }`,每个计划为 `{ tool_call_id, turn_id, source, plan, path?, options?, review? }`:`source` 为 `interaction` / `display` / `output`,`options` 是审阅选项,形如 `{ label, description? }`,`review`(仅交互式审阅时存在)为 `{ state, selected_option?, feedback? }`,其中 `state` 为 `pending` / `approved` / `rejected` / `cancelled` 之一。 + +- `40001`:校验失败 +- `40401`:会话不存在 +- `40416`:提供了 `tool_call_id`,但不存在该 id 的 `ExitPlanMode` 调用 + +### 提示词 + +提示词是一次用户输入的单位:提交一条提示词会把它排入会话的 main agent(或指定 Agent)的队列,排队中的提示词可以插入进行中的轮次,运行中的提示词可以中止。轮次进度本身通过 WebSocket [事件](#事件) 流式推送,不经过这些端点。 + +| 方法与路径 | 说明 | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/prompts` | 进行中与排队中的提示词 | +| `POST /api/v1/sessions/{session_id}/prompts` | 提交提示词(内容块数组,可带模型 / 权限模式覆盖) | +| `POST /api/v1/sessions/{session_id}/prompts:steer` | 把排队的提示词插入进行中的轮次 | +| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:abort` | 中止运行中的提示词 | +| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:steer` | 插入单条排队的提示词 | + +#### `GET /api/v1/sessions/{session_id}/prompts` + +读取 main agent 的提示词队列快照。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | + +成功时,`data` 为 `{ active, queued }`:`active` 是运行中的提示词(空闲时为 `null`),`queued` 按顺序列出等待中的提示词。提示词为 `{ prompt_id, user_message_id, status, content, created_at }`,其中 `status` 为 `running` / `queued` / `blocked` 之一,`content` 采用 `POST /api/v1/sessions/{session_id}/prompts` 接受的内容块格式。 + +- `40401`:会话不存在 + +#### `POST /api/v1/sessions/{session_id}/prompts` + +向会话提交一条用户提示词。先校验媒体引用,然后把可选的覆盖项应用到目标 Agent——`profile`(与 `model` / `thinking` 一起绑定),接着是 `model`、`thinking`、`permission_mode` 和 `disabled_tools`——随后提示词入队;响应在提示词被接受后立即返回,不等待轮次执行。提供 `skills` 时,提示词以打包的 Skill 激活方式运行,而不是普通用户提示词。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `content` | body | array | **必填。** 非空的内容块数组;变体见下 | +| `agent_id` | body | string | 目标 Agent。默认为 main agent | +| `prompt_id` | body | string | 客户端选定的提示词 id,用于幂等提交;已被进行中提示词占用的 id 返回 `40927`,已完成的返回 `40903`。不能与 `skills` 同用 | +| `skills` | body | array | 打包的 Skill 激活,至少 1 个 `{ name, args? }` 条目;每个 Skill 必须存在且可由用户激活 | +| `profile` | body | string | 提交前要绑定的 Agent 档案 | +| `model` | body | string | 要切换到的模型别名 | +| `thinking` | body | string | Thinking 强度等级 | +| `permission_mode` | body | string | `manual` / `yolo` / `auto` | +| `disabled_tools` | body | array | 要为会话禁用的工具名 | + +schema 还接受 `metadata`、`plan_mode`、`swarm_mode`、`goal_objective` 和 `goal_control`,但提交路由当前不会应用它们。每个 `content` 内容块是按 `type` 区分的对象: + +| 内容块 | 字段 | 说明 | +| --- | --- | --- | +| `text` | `text` | 纯文本 | +| `image` / `video` | `source` | 媒体输入;`source` 为 `{ kind: "url", url, id? }`、`{ kind: "base64", media_type, data }`、`{ kind: "file", file_id }`(来自 `POST /api/v1/files` 的上传)或 `{ kind: "session_media", file_id }`(已提交到本会话的媒体)之一 | +| `file` | `file_id`、`name`、`media_type`、`size` | 通过 `POST /api/v1/files` 上传的文件附件 | + +schema 还接受共享消息格式中的 `tool_use`、`tool_result` 和 `thinking` 内容块,但它们在用户提示词中没有意义。未知或 kind 不匹配的 `file_id` 引用会在提示词创建之前、任何覆盖项应用之前被拒绝。 + +成功时,`data` 为被接受的提示词 `{ prompt_id, user_message_id, status, content, created_at }`。 + +- `40001`:校验失败——例如 `prompt_id` 与 `skills` 同用,或未知的 `profile` +- `40110`:尚未配置供应商——请先完成登录 +- `40111`:解析出的供应商没有凭据(`details.provider_id`) +- `40112`:供应商的凭据被拒绝(`details.provider_id`) +- `40113`:模型无法解析(已知时带 `details.model_id` / `details.provider_id`) +- `40401`:会话不存在 +- `40407`:引用的 `file_id` 不存在(或与内容块的媒体 kind 不匹配) +- `40415`:某个 `skills` 条目指向未知的 Skill +- `40903`:`prompt_id` 属于已完成的提示词;`data` 携带 `{ aborted: false }` +- `40912`:Skill 存在但无法由用户激活 +- `40927`:`prompt_id` 已被进行中的提示词占用 + +#### `POST /api/v1/sessions/{session_id}/prompts:steer` + +把排队的提示词插入进行中的轮次,让运行中的轮次立即消费它们,而不是先运行结束。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `prompt_ids` | body | array | **必填。** 非空的排队提示词 id 数组 | + +成功时,`data` 为 `{ steered: true, prompt_ids }`。 + +- `40001`:校验失败 +- `40401`:会话不存在 +- `40402`:所列提示词 id 不在队列中 + +#### `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:abort` + +中止运行中的提示词。本端点与下面的 `:steer` 通过同一条路由 `POST /api/v1/sessions/{session_id}/prompts/{tail}` 分发:尾部解析为 `{prompt_id}:{action}`,动作缺失或未知时返回 `40001`(`unsupported action: ...`)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `prompt_id` | path | string | **必填。** 提示词 id | + +成功时,`data` 为 `{ aborted: true }`。 + +- `40401`:会话不存在 +- `40402`:不存在该 id 的提示词 +- `40903`:提示词已完成;`data` 携带 `{ aborted: false }` + +#### `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:steer` + +把单条排队的提示词插入进行中的轮次——是 `POST /api/v1/sessions/{session_id}/prompts:steer` 的单提示词形式。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `prompt_id` | path | string | **必填。** 排队中的提示词 id | + +成功时,`data` 为 `{ steered: true, prompt_ids: [prompt_id] }`。 + +- `40401`:会话不存在 +- `40402`:没有该 id 的排队提示词 + +### 审批与提问 + +审批与提问是会话的两类待处理交互:审批是为工具调用请求许可,提问是请求带标签选项的结构化输入。这些端点用于列出和答复它们;新的请求通过 WebSocket 以 `event.approval.requested` 与 `event.question.requested` 到达。 + +| 方法与路径 | 说明 | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/approvals` | 列出待处理的审批请求(必须 `status=pending`) | +| `POST /api/v1/sessions/{session_id}/approvals/{approval_id}` | 答复审批 | +| `GET /api/v1/sessions/{session_id}/questions` | 列出待处理的提问(必须 `status=pending`) | +| `POST /api/v1/sessions/{session_id}/questions/{question_id}` | 回答提问 | +| `POST /api/v1/sessions/{session_id}/questions/{question_id}:dismiss` | 忽略提问 | + +#### `GET /api/v1/sessions/{session_id}/approvals` + +列出会话待处理的审批请求——即工具调用发起的权限提示。读取列表会在会话为冷态时将其恢复。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `status` | query | string | **必填。** 必须为 `pending` | + +成功时,`data` 为 `{ items }`,每个元素为 `{ approval_id, session_id, turn_id?, tool_call_id, tool_name, action, tool_input_display, created_at, expires_at }`:`tool_name` / `action` / `tool_input_display` 描述等待许可的调用,`expires_at` 为 `created_at` 之后 24 小时。 + +- `40001`:`status` 缺失或不是 `pending` +- `40401`:会话不存在 + +#### `POST /api/v1/sessions/{session_id}/approvals/{approval_id}` + +答复一个待处理的审批请求,让等待中的工具调用继续执行(或不执行)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `approval_id` | path | string | **必填。** 审批请求 id | +| `decision` | body | string | **必填。** `approved` / `rejected` / `cancelled` | +| `scope` | body | string | 配合 `approved` 使用,`session`(唯一取值)还会让该审批规则在会话的剩余时间内被记住 | +| `feedback` | body | string | 回传给 Agent 的自由文本反馈 | +| `selected_label` | body | string | 当请求提供了带标签的选项时(例如计划审阅),所选选项的标签 | + +成功时,`data` 为 `{ resolved: true, resolved_at }`。 + +- `40001`:校验失败 +- `40401`:会话不存在 +- `40404`:没有该 id 的待处理审批 +- `40902`:审批已被答复;`data` 携带 `{ resolved: false }` + +#### `GET /api/v1/sessions/{session_id}/questions` + +列出会话待处理的提问。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `status` | query | string | **必填。** 必须为 `pending` | + +成功时,`data` 为 `{ items }`,每个元素为 `{ question_id, session_id, turn_id?, tool_call_id?, questions, created_at }`。`questions` 包含 1–4 个 `{ id, question, header?, body?, options, multi_select?, allow_other?, other_label?, other_description? }` 条目,每个条目带 2–4 个 `{ id, label, description? }` 形式的 `options`;`multi_select` 允许选择多个选项,`allow_other` 允许自由文本回答。 + +- `40001`:`status` 缺失或不是 `pending` +- `40401`:会话不存在 + +#### `POST /api/v1/sessions/{session_id}/questions/{question_id}` + +回答一个待处理的提问。两个提问端点通过同一条路由 `POST /api/v1/sessions/{session_id}/questions/{tail}` 分发:单独的提问 id 表示回答问题,`{question_id}:dismiss` 尾部表示忽略问题,其他情况返回 `40001`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `question_id` | path | string | **必填。** 提问 id | +| `answers` | body | object | **必填。** 提问条目 id(`q_0`……)到答案对象的映射;变体见下 | +| `method` | body | string | 答案的产生方式:`enter` / `space` / `number_key` / `click` | +| `note` | body | string | 附在回答上的自由文本备注 | + +每个答案是按 `kind` 区分的对象: + +| kind 值 | 字段 | 说明 | +| --- | --- | --- | +| `single` | `option_id` | 选中的单个选项 | +| `multi` | `option_ids` | 选中的多个选项(至少 1 个) | +| `other` | `text` | 自由文本回答 | +| `multi_with_other` | `option_ids`、`other_text` | 选项加自由文本 | +| `skipped` | — | 跳过了该条目 | + +成功时,`data` 为 `{ resolved: true, resolved_at }`。 + +- `40001`:校验失败(`details` 列出每个字段) +- `40401`:会话不存在 +- `40405`:没有该 id 的待处理提问 +- `40902`:提问已被答复;`data` 携带 `{ resolved: false }` + +#### `POST /api/v1/sessions/{session_id}/questions/{question_id}:dismiss` + +忽略一个待处理的提问,不作回答。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `question_id` | path | string | **必填。** 提问 id | + +成功时信封的 `code` 是 `40909`(`question dismissed`)而不是 `0`,`data` 为 `{ dismissed: true, dismissed_at }`——客户端必须特殊处理该端点的成功码。 + +- `40401`:会话不存在 +- `40405`:没有该 id 的待处理提问 +- `40902`:提问已被答复;`data` 携带 `{ resolved: false }` + +### 后台任务 + +后台任务是会话的异步单元——后台 Shell、subagent 与长时间运行的工具任务。注册表仅包含实时数据:未加载到本服务进程中的会话会返回空列表。 + +| 方法与路径 | 说明 | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/tasks` | 列出后台任务 | +| `GET /api/v1/sessions/{session_id}/tasks/{task_id}` | 读取任务(可选输出预览) | +| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` | 取消任务 | + +#### `GET /api/v1/sessions/{session_id}/tasks` + +列出会话的后台任务。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `status` | query | string | 只保留单一状态:`running` / `completed` / `failed` / `cancelled` | + +成功时,`data` 为 `{ items }`,每个元素是任务对象 `{ id, session_id, kind, description, status, created_at, started_at?, completed_at?, command?, model?, thinking_effort?, agent_id?, subagent_type?, parent_tool_call_id?, output_preview?, output_bytes? }`。`kind` 为 `bash` / `subagent` / `tool`;`command` 仅在 `bash` 任务时设置,模型与 Agent 字段仅在 `subagent` 任务时设置,输出字段仅在以 `with_output` 读取任务时设置。超时与丢失的任务上报为 `failed`;被杀死的任务上报为 `cancelled`。 + +- `40001`:校验失败——未知的 `status` +- `40401`:会话不存在 + +#### `GET /api/v1/sessions/{session_id}/tasks/{task_id}` + +读取单个后台任务,可选携带输出的末尾片段。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `task_id` | path | string | **必填。** 任务 id | +| `with_output` | query | boolean | 在响应中包含输出末尾片段。默认 `false` | +| `output_bytes` | query | integer | 请求的输出末尾片段的字节大小,最小 `0`。默认 `32768` | + +成功时,`data` 为上文 `GET /api/v1/sessions/{session_id}/tasks` 中说明的任务对象;当 `with_output=true` 且输出非空时,`output_preview` 携带末尾片段文本,`output_bytes` 为其字节长度。 + +- `40001`:校验失败 +- `40401`:会话不存在 +- `40406`:没有该 id 的任务(冷会话完全没有实时任务) + +#### `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` + +取消运行中的任务。它通过 `POST /api/v1/sessions/{session_id}/tasks/{tail}` 分发,`cancel` 是唯一的动作——单独的任务 id 或未知动作返回 `40001`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `task_id` | path | string | **必填。** 任务 id | + +成功时,`data` 为 `{ cancelled: true }`。 + +- `40001`:动作后缀缺失或未知 +- `40401`:会话不存在 +- `40406`:没有该 id 的任务 +- `40904`:任务已结束;`data` 携带 `{ cancelled: false }`,`details.current_status` 为最终状态 + +### 技能、工具与 MCP + +这组端点暴露会话或工作区可见的技能目录、当前生效 agent 的工具列表及其 MCP 服务。技能激活与 MCP 重启使用 `:{action}` 约定;激活即斜杠命令 `/` 的 REST 等价形式。 + +| 方法与路径 | 说明 | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/skills` | 会话级技能目录 | +| `GET /api/v1/workspaces/{workspace_id}/skills` | 无会话的工作区技能目录 | +| `POST /api/v1/sessions/{session_id}/skills/{skill_name}:activate` | 激活技能(开启一个轮次) | +| `GET /api/v1/tools` | 列出当前生效 agent 的工具 | +| `GET /api/v1/mcp/servers` | 列出 MCP 服务 | +| `POST /api/v1/mcp/servers/{mcp_server_id}:restart` | 重启 MCP 服务 | + +#### `GET /api/v1/sessions/{session_id}/skills` + +列出单个会话可用的技能,按会话的优先级合并所有来源(内置、插件、extra、用户、项目)。会话处于冷态时,读取目录会恢复该会话。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | + +成功时 `data` 为 `{ skills }`,每项是一个技能描述符 `{ name, description, path, source, type?, disable_model_invocation? }`:`source` 为 `project` / `user` / `extra` / `builtin`;`type` 标识技能类别(只有用户可激活的类型才能被激活);`disable_model_invocation` 会让技能对模型不可见。 + +- `40401`:会话不存在(或未激活) + +#### `GET /api/v1/workspaces/{workspace_id}/skills` + +列出该工作区中的会话将看到的技能目录,但不创建或恢复会话——即针对工作区根目录计算出的同一套内置、插件、extra、用户、项目来源合并结果。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **必填。** 已注册工作区 id | + +成功时 `data` 为 `{ skills }`,技能描述符见上文 `GET /api/v1/sessions/{session_id}/skills` 的说明。 + +- `40410`:工作区不存在 + +#### `POST /api/v1/sessions/{session_id}/skills/{skill_name}:activate` + +在会话中激活技能——即斜杠命令 `/` 的 REST 等价形式——以技能内容加上 `args` 与附件在 main agent 上开启一个轮次。该端点经单一路由 `POST /api/v1/sessions/{session_id}/skills/{tail}` 分发:尾部按 `{skill_name}:{action}` 解析,`activate` 是唯一动作;只给名称或动作未知时返回 `40001`(`unsupported action: ...`)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `skill_name` | path | string | **必填。** 要激活的技能名 | +| `args` | body | string | 传给技能的自由文本参数,相当于斜杠命令后的文本 | +| `attachments` | body | array | 随激活携带的媒体块。`image` / `video` 块带 `source` 对象(`kind` 为 `url` / `base64` / `file` / `session_media`,与提示词内容块同形);`file` 块带顶层 `file_id`、`name`、`media_type`、`size` | + +成功时 `data` 为 `{ activated: true, skill_name }`。 + +- `40001`:校验失败或动作后缀不支持 +- `40401`:会话不存在(或未激活) +- `40407`:引用的附件文件不存在 +- `40415`:没有该名称的技能 +- `40912`:技能存在,但其类型不允许用户激活 + +#### `GET /api/v1/tools` + +列出当前生效 agent 的工具——即 `session_id` 指定会话的 main agent;省略参数时取最近创建的会话。若该会话不在本服务进程中存活,列表为空。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | query | string | 要查看其 main agent 的会话。默认最近创建的会话 | + +成功时 `data` 为 `{ tools }`,每项为 `{ name, description, input_schema, source, mcp_server_id?, active? }`:`source` 为 `builtin` / `skill` / `mcp`;`mcp_server_id` 仅 MCP 工具携带(从 `mcp____` 名称解析);`active` 报告工具策略的判定结果。`input_schema` 目前恒为 `null`。 + +#### `GET /api/v1/mcp/servers` + +列出当前生效 agent 配置的 MCP 服务(与 `GET /api/v1/tools` 相同,取最近创建的存活会话的 main agent)。没有存活会话时列表为空。 + +成功时 `data` 为 `{ servers }`,每项为 `{ id, name, transport, status, last_error?, tool_count }`:`transport` 为 `stdio` / `http` / `sse`;`status` 为 `connected` / `connecting` / `disconnected` / `error`;服务处于 `error` 时 `last_error` 携带失败信息。 + +#### `POST /api/v1/mcp/servers/{mcp_server_id}:restart` + +重新连接当前生效 agent 的某个 MCP 服务。该端点经 `POST /api/v1/mcp/servers/{tail}` 分发,`restart` 是唯一动作——只给服务 id 或动作未知时返回 `40001`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `mcp_server_id` | path | string | **必填。** MCP 服务 id(即其配置名称) | + +成功时 `data` 为 `{ restarting: true }`。 + +- `40001`:缺少动作后缀或动作未知 +- `40408`:没有该 id 的 MCP 服务(无存活会话时同样返回此错误) + +### 能力与插件 + +能力是带有分层就绪状态的内置特性——由检测步骤加后台安装组成;当前版本注册了 `kimi-cu`(Kimi Computer Use)与 `kimi-webbridge`(Kimi WebBridge)。插件是已安装的技能、MCP 服务、hook 与命令的打包集合。这组端点报告能力状态、驱动能力安装,并管理插件从市场列表到移除的整个生命周期。 + +| 方法与路径 | 说明 | +| --- | --- | +| `GET /api/v1/capabilities` | 列出内置能力及其就绪状态 | +| `GET /api/v1/capabilities/{capability_id}` | 读取单个能力的状态 | +| `POST /api/v1/capabilities/{capability_id}:install` | 开始安装能力(后台进行,轮询 GET 查看进度) | +| `GET /api/v1/plugins` | 列出已安装插件 | +| `POST /api/v1/plugins` | 从本地路径、zip URL 或 GitHub 仓库安装插件 | +| `GET /api/v1/plugins/marketplace` | 插件市场目录,合并实时安装状态 | +| `POST /api/v1/plugins/{plugin_id}:{action}` | 插件动作:`enable` / `disable` / `remove` | + +#### `GET /api/v1/capabilities` + +列出所有已注册能力及其就绪状态。 + +成功时 `data` 为 `{ capabilities }`,每项是一个能力状态对象 `{ id, pluginId?, displayName, description, supported, state, version?, steps, install }`。`state` 为 `ready`(所有必需检测步骤均为 `ok`)/ `partial`(部分步骤 `ok`)/ `not_installed` / `unsupported`(当前平台/架构不可用);`steps` 以 `{ id, state, detail?, optional? }` 列出各检测步骤,其 `state` 为 `ok` / `missing` / `failed` 之一;`install` 为安装进度 `{ running, step?, percent?, error?, note? }`,其中 `percent` 取值 0 到 100。 + +#### `GET /api/v1/capabilities/{capability_id}` + +读取单个能力的就绪状态——即 `:install` 动作的轮询对应端点。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `capability_id` | path | string | **必填。** 能力 id | + +成功时 `data` 为上文 `GET /api/v1/capabilities` 说明的能力状态对象。 + +- `40418`:没有该 id 的能力 + +#### `POST /api/v1/capabilities/{capability_id}:install` + +在后台开始安装能力并立即返回当前状态(`install.running` 为 `true`);轮询 `GET /api/v1/capabilities/{capability_id}` 查看进度。该端点经 `POST /api/v1/capabilities/{tail}` 分发,`install` 是唯一动作——只给 id 或动作未知时返回 `40001`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `capability_id` | path | string | **必填。** 能力 id | + +成功时 `data` 为上文 `GET /api/v1/capabilities` 说明的能力状态对象。 + +- `40001`:缺少动作后缀或动作未知 +- `40418`:没有该 id 的能力 +- `40924`:该能力的安装已在进行中 +- `40925`:当前平台/架构不支持该能力 + +#### `GET /api/v1/plugins` + +列出已安装插件。 + +成功时 `data` 为 `{ plugins }`,每项为 `{ id, displayName, version?, enabled, state, skillCount, mcpServerCount, enabledMcpServerCount, hookCount, commandCount, hasErrors, source, originalSource?, github? }`:`state` 为 `ok` / `error`(加载失败也会置 `hasErrors`);`source` 为 `local-path` / `zip-url` / `github`;GitHub 来源的插件由 `github` 携带来源信息 `{ owner, repo, ref, installedSha? }`,其中 `ref` 为 `{ kind: branch|tag|sha, value }`。 + +#### `POST /api/v1/plugins` + +安装插件并返回其摘要。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `source` | body | string | **必填。** 安装来源:本地绝对路径、指向 zip 压缩包的 `http(s)` URL,或 GitHub URL——`https://github.com//`,可选地用 `/tree/`、`/releases/tag/` 或 `/commit/` 锁定版本 | + +成功时 `data` 为上文 `GET /api/v1/plugins` 说明的插件摘要。 + +- `40001`:校验失败——例如 `source` 既不是 URL 也不是绝对路径,或插件加载失败 +- `40409`:本地路径不存在 + +#### `GET /api/v1/plugins/marketplace` + +列出插件市场目录并合并实时安装状态。目录按请求从配置的市场 URL 拉取(超时 10 秒);使用默认目录时,目录中缺少的内置能力会作为条目合并进来(带 `capabilityId`),而当前平台不支持的能力对应条目会被剔除。 + +成功时 `data` 为 `{ entries }`,每项为 `{ id, tier, displayName, description?, homepage?, keywords?, version?, source, installed?, updateAvailable?, capabilityId? }`:`tier` 为 `official` / `curated` / `third-party`;插件已安装时 `installed` 为 `{ version?, enabled }`;`updateAvailable` 标记目录版本新于已安装版本的条目。条目的 `source` 即 `POST /api/v1/plugins` 的 `source` 字段取值。 + +- `50001`:市场不可达或返回了非法目录 + +#### `POST /api/v1/plugins/{plugin_id}:enable` + +启用一个已安装插件。插件动作经单一路由 `POST /api/v1/plugins/{tail}` 分发:尾部按 `{plugin_id}:{action}` 解析,动作为 `enable` / `disable` / `remove`;只给 id 或动作未知时返回 `40001`(`unsupported action: ...`)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `plugin_id` | path | string | **必填。** 已安装插件 id | + +成功时 `data` 为 `{ ok: true }`。 + +- `40001`:缺少动作后缀或动作未知 +- `40419`:没有该 id 的已安装插件 + +#### `POST /api/v1/plugins/{plugin_id}:disable` + +停用一个已安装插件但不移除它;分发约定同上文 `:enable`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `plugin_id` | path | string | **必填。** 已安装插件 id | + +成功时 `data` 为 `{ ok: true }`。 + +- `40001`:缺少动作后缀或动作未知 +- `40419`:没有该 id 的已安装插件 + +#### `POST /api/v1/plugins/{plugin_id}:remove` + +移除一个已安装插件;分发约定同上文 `:enable`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `plugin_id` | path | string | **必填。** 已安装插件 id | + +成功时 `data` 为 `{ ok: true }`。 + +- `40001`:缺少动作后缀或动作未知 +- `40419`:没有该 id 的已安装插件 + +### 终端 + +PTY 终端接口;仅在 loopback 绑定时挂载(非 loopback 绑定会跳过它们,除非传入 `--allow-remote-terminals`)。终端的输入、输出与尺寸调整经 WebSocket 的 `terminal_*` 帧传输——REST 侧只管理终端生命周期。 + +| 方法与路径 | 说明 | +| --- | --- | +| `GET /api/v1/sessions/{session_id}/terminals` | 列出终端 | +| `POST /api/v1/sessions/{session_id}/terminals` | 创建终端 | +| `GET /api/v1/sessions/{session_id}/terminals/{terminal_id}` | 读取终端 | +| `POST /api/v1/sessions/{session_id}/terminals/{terminal_id}:close` | 关闭终端 | + +#### `GET /api/v1/sessions/{session_id}/terminals` + +列出会话的终端。会话处于冷态时,读取列表会恢复该会话。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | + +成功时 `data` 为 `{ items }`,每项是一个终端对象 `{ id, session_id, cwd, shell, cols, rows, status, created_at, exited_at?, exit_code? }`:`status` 为 `running` / `exited`;已退出的终端携带 `exited_at` 与 `exit_code`(进程未报告退出码时为 `null`,例如因信号终止)。回滚缓冲不属于该对象——输出经 WebSocket 回放与流式推送。 + +- `40401`:会话不存在 + +#### `POST /api/v1/sessions/{session_id}/terminals` + +为会话创建一个 PTY 终端。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `runtime_id` | body | string | 生成终端进程的运行时。默认 `local` | +| `cwd` | body | string | 工作目录,相对于会话工作区(传绝对路径会校验失败)。默认工作区根目录 | +| `shell` | body | string | Shell 可执行文件。默认该运行时的 shell | +| `cols` | body | integer | 终端宽度,正数。默认 `80` | +| `rows` | body | integer | 终端高度,正数。默认 `24` | + +成功时 `data` 为上文 `GET /api/v1/sessions/{session_id}/terminals` 说明的终端对象。 + +- `40001`:校验失败(`details` 逐字段说明) +- `40401`:会话不存在 +- `41304`:`cwd` 解析后越出会话工作区 + +#### `GET /api/v1/sessions/{session_id}/terminals/{terminal_id}` + +读取单个终端。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `terminal_id` | path | string | **必填。** 终端 id | + +成功时 `data` 为上文 `GET /api/v1/sessions/{session_id}/terminals` 说明的终端对象。 + +- `40401`:会话不存在 +- `40414`:没有该 id 的终端 + +#### `POST /api/v1/sessions/{session_id}/terminals/{terminal_id}:close` + +关闭终端并结束其进程。该端点经 `POST /api/v1/sessions/{session_id}/terminals/{tail}` 分发,`close` 是唯一动作——只给 id 或动作未知时返回 `40001`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `terminal_id` | path | string | **必填。** 终端 id | + +成功时 `data` 为 `{ closed: true }`。 + +- `40001`:缺少动作后缀或动作未知 +- `40401`:会话不存在 +- `40414`:没有该 id 的终端 + +### 工作区 + +工作区是已注册的项目目录,会话都落在其中。这组端点管理注册表——列出、注册、重命名、注销——以及控制项目级 MCP 配置是否加载的每工作区信任状态。所有返回工作区的端点都使用 [workspace 对象](#workspace-对象) 中统一说明的传输结构。 + +| 方法与路径 | 说明 | +| --- | --- | +| `GET /api/v1/workspaces` | 列出已注册工作区 | +| `POST /api/v1/workspaces` | 注册工作区(按根路径幂等) | +| `PATCH /api/v1/workspaces/{workspace_id}` | 重命名 | +| `DELETE /api/v1/workspaces/{workspace_id}` | 注销(保留磁盘内容) | +| `GET /api/v1/workspaces/{workspace_id}/trust` | 读取信任状态 | +| `POST /api/v1/workspaces/{workspace_id}/trust` | 授予信任 | +| `POST /api/v1/workspaces/{workspace_id}/untrust` | 撤销信任 | + +#### workspace 对象 + +所有返回工作区的端点都使用此传输结构。注册与重命名会广播全局事件 `event.workspace.created` / `event.workspace.updated`。 + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `id` | string | 工作区 id,由根路径派生的 `wd__` 字符串 | +| `root` | string | 项目目录的绝对路径 | +| `name` | string | 显示名,1–100 个字符;默认取根目录的基名 | +| `created_at` | string | 注册时间,ISO 8601 | +| `last_opened_at` | string | 最近一次打开或重新注册工作区的时间,ISO 8601 | +| `session_count` | integer | 工作区内的会话数 | + +#### `GET /api/v1/workspaces` + +列出所有已注册工作区。 + +成功时 `data` 为 `{ items }`,每项是一个 [workspace 对象](#workspace-对象)。 + +#### `POST /api/v1/workspaces` + +注册工作区并返回它。注册按根路径幂等:重复注册同一根路径会返回已存在的工作区,仅刷新 `last_opened_at`(保留已存名称),并广播 `event.workspace.updated` 而非 `event.workspace.created`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `root` | body | string | **必填。** 已存在目录的绝对路径 | +| `name` | body | string | 显示名,1–100 个字符。默认根目录的基名 | + +成功时 `data` 为 [workspace 对象](#workspace-对象)。 + +- `40001`:`root` 缺失或不是绝对路径(`details` 会列出该字段) +- `40409`:`root` 不存在或不是目录 + +#### `PATCH /api/v1/workspaces/{workspace_id}` + +重命名工作区——仅修改显示名,根路径不变。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **必填。** 工作区 id | +| `name` | body | string | **必填。** 新的显示名,1–100 个字符 | + +成功时 `data` 为 [workspace 对象](#workspace-对象)。 + +- `40001`:校验失败(`details` 逐字段说明) +- `40410`:工作区不存在 + +#### `DELETE /api/v1/workspaces/{workspace_id}` + +注销工作区。只移除注册表条目——磁盘上的目录不受影响。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **必填。** 工作区 id | + +成功时 `data` 为 `{ deleted: true }`。 + +- `40410`:工作区不存在 + +#### `GET /api/v1/workspaces/{workspace_id}/trust` + +读取工作区信任状态。信任状态决定是否为该工作区加载项目级 MCP 配置。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **必填。** 工作区 id | + +成功时 `data` 为 `{ trusted }`。 + +- `40410`:工作区不存在 + +#### `POST /api/v1/workspaces/{workspace_id}/trust` + +将工作区标记为信任,并加载其项目级 MCP 配置。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **必填。** 工作区 id | + +成功时 `data` 为 `{ trusted: true }`。 + +- `40410`:工作区不存在 + +#### `POST /api/v1/workspaces/{workspace_id}/untrust` + +撤销工作区信任,并卸载其项目级 MCP 配置。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `workspace_id` | path | string | **必填。** 工作区 id | + +成功时 `data` 为 `{ trusted: false }`。 + +- `40410`:工作区不存在 + +### 文件系统 + +会话内文件操作走 `POST /api/v1/sessions/{session_id}/fs:{action}`,请求体为 JSON;动作包括 `list` / `read` / `list_many` / `stat` / `stat_many` / `mkdir` / `search` / `grep` / `git_status` / `diff` / `open` / `open-in` / `reveal`。每个动作的请求体还接受可选的 `runtime_id`(string,默认 `local`),用于选择执行操作的运行时;`open`、`open-in` 与 `reveal` 仅在 `local` 运行时上可用。另有: + +| 方法与路径 | 说明 | +| --- | --- | +| `POST /api/v1/workspace/fs:search` | 无会话的工作区搜索(body 携带工作区引用) | +| `POST /api/v1/workspace/fs:suggest` | 无会话的文件补全候选(用于 `@` 文件提及) | +| `GET /api/v1/sessions/{session_id}/fs/{path}:download` | 下载会话文件(二进制,见下文) | +| `GET /api/v1/fs:browse` | 列出本机目录(文件夹选择器用) | +| `GET /api/v1/fs:home` | 用户主目录与最近工作区 | +| `GET /api/v1/fs:content` | 读取本机任意文件原始字节(仅受 token 保护,谨慎暴露端口) | +| `POST /api/v1/fs:mkdir` | 按绝对路径创建目录 | + +#### `POST /api/v1/sessions/{session_id}/fs:list` + +列出会话工作区目录下的条目,可选递归子目录。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `path` | body | string | 要列出的目录,相对于会话工作目录。默认 `.` | +| `depth` | body | integer | 递归深度,1–10。默认 `1` | +| `limit` | body | integer | 最大条目数,1–1000。默认 `200` | +| `show_hidden` | body | boolean | 包含点文件。默认 `false` | +| `follow_gitignore` | body | boolean | 跳过 gitignore 的路径。默认 `true` | +| `exclude_globs` | body | string[] | 额外要跳过的 glob | +| `sort` | body | string | `type_first`(默认)/ `name_asc` / `name_desc` / `mtime_desc` / `size_desc` | +| `include_git_status` | body | boolean | 附带每个条目的 git 状态。默认 `false` | + +成功时 `data` 为 `{ items, truncated }`——`depth` 大于 1 时另附 `children_by_path`(路径 → 条目的映射)。每项是一个条目对象 `{ path, name, kind, size?, modified_at, etag?, mime?, language_id?, is_binary?, is_symlink_to?, git_status?, child_count? }`,其中 `kind` 为 `file` / `directory` / `symlink`;`git_status`(仅 `include_git_status: true` 时存在)为 `clean` / `modified` / `added` / `deleted` / `renamed` / `untracked` / `ignored` / `conflicted` 之一;`truncated` 表示 `limit` 截断了列表。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `40409`:路径不存在(包括 `path` 不是目录的情况) +- `41304`:路径越出会话工作区 + +#### `POST /api/v1/sessions/{session_id}/fs:read` + +以文本或 base64 读取会话文件的一段内容。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `path` | body | string | **必填。** 文件路径,相对于会话工作目录 | +| `offset` | body | integer | 起始字节偏移。默认 `0` | +| `length` | body | integer | 读取字节数,1–10485760(10 MiB)。默认 `1048576`(1 MiB) | +| `encoding` | body | string | `auto`(默认)/ `utf-8` / `base64` | + +成功时 `data` 为 `{ path, content, encoding, size, truncated, etag, mime, language_id?, line_count?, is_binary }`,其中 `encoding` 报告实际使用的编码(`utf-8` 或 `base64`),`size` 为文件完整大小。`encoding: "auto"` 时文本以 `utf-8` 返回(非 UTF-8 文本会被转码),二进制内容以 `base64` 返回;`encoding: "utf-8"` 强制按文本读取并拒绝二进制文件。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `40409`:路径不存在 +- `40906`:路径是目录 +- `40907`:二进制文件却指定了 `encoding: "utf-8"` +- `41302`:文件超过 10 MiB 读取上限 +- `41304`:路径越出会话工作区 + +#### `POST /api/v1/sessions/{session_id}/fs:list_many` + +一次调用列出多个会话目录;失败的路径会折进响应里,而不是让整个请求失败。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `paths` | body | string[] | **必填。** 要列出的目录,1–100 条 | + +其余请求体字段(`depth`、`limit`、`show_hidden`、`follow_gitignore`、`exclude_globs`、`sort`、`include_git_status`)的类型、取值范围与默认值同 `fs:list`。成功时 `data` 为 `{ results }`——每个请求路径到其条目数组(条目对象见 `fs:list` 的说明)的映射,另附 `truncated_paths`(达到 `limit` 的路径)与 `partial_errors`(失败路径到其 `{ code, msg }` 错误的映射)。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 + +#### `POST /api/v1/sessions/{session_id}/fs:stat` + +查询会话工作区内单个路径的元信息。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `path` | body | string | **必填。** 要查询的路径,相对于会话工作目录 | + +成功时 `data` 为 `fs:list` 中说明的条目对象。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `40409`:路径不存在 +- `41304`:路径越出会话工作区 + +#### `POST /api/v1/sessions/{session_id}/fs:stat_many` + +一次调用查询多个会话路径的元信息;不存在的路径返回 `null`,不会让整个请求失败。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `paths` | body | string[] | **必填。** 要查询的路径,1–1000 条 | + +成功时 `data` 为 `{ entries }`——每个请求路径到其条目对象(见 `fs:list` 的说明)的映射,路径不存在时为 `null`。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 + +#### `POST /api/v1/sessions/{session_id}/fs:mkdir` + +在会话工作区内创建目录。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `path` | body | string | **必填。** 要创建的目录,相对于会话工作目录 | +| `recursive` | body | boolean | 创建缺失的父目录。默认 `false` | + +成功时 `data` 为所建目录的条目对象(见 `fs:list` 的说明)。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `40409`:父目录不存在(非递归创建) +- `40919`:路径已存在(非递归创建) +- `41304`:路径越出会话工作区 + +#### `POST /api/v1/sessions/{session_id}/fs:search` + +在会话工作区内模糊搜索文件与目录名。`query` 为空时改为列出顶层条目。当 `{session_id}` 位置携带的是工作区引用(已注册工作区 id 或绝对根路径)而非会话 id 时,搜索针对该工作区执行——这是为尚未创建的草稿会话准备的无会话形式;正式的无会话端点是 `POST /api/v1/workspace/fs:search`。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id,或工作区引用 | +| `query` | body | string | **必填。** 搜索文本;`""` 表示列出顶层 | +| `limit` | body | integer | 最大命中数,1–200。默认 `50` | +| `include_globs` | body | string[] | 只保留匹配这些 glob 之一的路径 | +| `exclude_globs` | body | string[] | 跳过匹配这些 glob 的路径 | +| `follow_gitignore` | body | boolean | 跳过 gitignore 的路径。默认 `true` | + +成功时 `data` 为 `{ items, truncated }`,每项为 `{ path, name, kind, score, match_positions }`——`kind` 为 `file` / `directory` / `symlink`,`score` 为 0 到 1 之间的模糊匹配得分,`match_positions` 列出匹配到的字符偏移。命中按得分排序(同分按路径),`truncated` 表示超出 `limit` 的命中被丢弃。 + +- `40001`:请求体校验失败 +- `40401`:该引用既不是会话,也不是可解析的工作区 + +#### `POST /api/v1/sessions/{session_id}/fs:grep` + +在会话工作区内搜索文件内容——默认按字面字符串,`regex: true` 时按正则表达式。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `pattern` | body | string | **必填。** 要搜索的文本或正则 | +| `regex` | body | boolean | 将 `pattern` 视为正则表达式。默认 `false` | +| `case_sensitive` | body | boolean | 默认 `true` | +| `include_globs` | body | string[] | 只保留匹配这些 glob 之一的文件 | +| `exclude_globs` | body | string[] | 跳过匹配这些 glob 的文件 | +| `follow_gitignore` | body | boolean | 跳过 gitignore 的路径。默认 `true` | +| `max_files` | body | integer | 最多扫描的文件数,1–10000。默认 `200` | +| `max_matches_per_file` | body | integer | 每个文件保留的匹配数,1–10000。默认 `50` | +| `max_total_matches` | body | integer | 总共保留的匹配数,1–100000。默认 `5000` | +| `context_lines` | body | integer | 每个匹配携带的上下文行数,0–10。默认 `2` | + +成功时 `data` 为 `{ files, files_scanned, truncated, elapsed_ms }`,其中 `files` 的每项为 `{ path, matches }`,每个匹配为 `{ line, col, text, before, after }`(`before` / `after` 最多携带 `context_lines` 行上下文);`truncated` 表示某个匹配配额截断了结果。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `41305`:搜索超时 + +#### `POST /api/v1/sessions/{session_id}/fs:git_status` + +读取会话工作区的 git 状态,可选限定在一组路径内。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `paths` | body | string[] | 将状态限定在这些路径;省略表示整个工作区 | + +成功时 `data` 为 `{ branch, ahead, behind, entries, additions, deletions, pullRequest }`,其中 `entries` 把每个变更路径映射到其状态(`clean` / `modified` / `added` / `deleted` / `renamed` / `untracked` / `ignored` / `conflicted`),`pullRequest` 为 `{ number, state, url }`(`state` 为 `open` / `merged` / `closed` / `draft`)或 `null`。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `40908`:git 不可用(不是仓库,或没有 git 可执行文件) + +#### `POST /api/v1/sessions/{session_id}/fs:diff` + +返回会话工作区内单个文件的 unified git diff。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `path` | body | string | **必填。** 要 diff 的文件,相对于会话工作目录 | + +成功时 `data` 为 `{ path, diff, truncated }`,其中 `diff` 为 unified diff 文本,`truncated` 表示过长的 diff 被截断。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `40908`:git 不可用(不是仓库,或没有 git 可执行文件) +- `41304`:路径越出会话工作区 + +#### `POST /api/v1/sessions/{session_id}/fs:open` + +用宿主操作系统的默认程序打开会话文件。仅限 local 运行时。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `path` | body | string | **必填。** 要打开的文件,相对于会话工作目录 | +| `line` | body | integer | 在处理程序支持时跳转到的行号(正整数) | + +成功时 `data` 为 `{ opened: true }`。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `40409`:路径不存在 +- `41304`:路径越出会话工作区 + +#### `POST /api/v1/sessions/{session_id}/fs:open-in` + +在指定的宿主应用程序中打开会话文件或目录。仅限 local 运行时。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `app_id` | body | string | **必填。** 目标应用:`finder` / `cursor` / `vscode` / `iterm` / `terminal` | +| `path` | body | string | **必填。** 要打开的文件或目录,相对于会话工作目录 | +| `line` | body | integer | 在应用支持时跳转到的行号(正整数) | + +成功时 `data` 为 `{ opened: true }`。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `40409`:路径不存在 +- `41304`:路径越出会话工作区 +- `50001`:应用启动失败 + +#### `POST /api/v1/sessions/{session_id}/fs:reveal` + +在宿主操作系统的文件管理器中显示会话文件。仅限 local 运行时。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `path` | body | string | **必填。** 要显示的文件,相对于会话工作目录 | + +成功时 `data` 为 `{ revealed: true }`。 + +- `40001`:请求体校验失败 +- `40401`:会话不存在 +- `40409`:路径不存在 +- `41304`:路径越出会话工作区 + +#### `GET /api/v1/sessions/{session_id}/fs/{path}:download` + +从会话工作区下载文件;`{path}` 是相对于工作区的文件路径,并带字面量 `:download` 后缀。响应为支持 Range 与 ETag 的二进制流——见 [二进制与流式端点](#二进制与流式端点)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `session_id` | path | string | **必填。** 会话 id | +| `path` | path | string | **必填。** 相对于工作区的文件路径,加 `:download` 后缀 | +| `runtime_id` | query | string | 从哪个运行时读取。默认 `local` | + +- `40001`:路径缺失或为空 +- `40401`:会话不存在 +- `40409`:路径不存在 +- `41304`:路径越出会话工作区 + +#### `POST /api/v1/workspace/fs:search` + +`fs:search` 的无会话形式:工作区改由请求体而非 URL 携带。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `workspace` | body | string | **必填。** 已注册工作区 id 或绝对根路径(当场注册) | +| `query` | body | string | **必填。** 搜索文本;`""` 表示列出顶层 | +| `limit` | body | integer | 最大命中数,1–200。默认 `50` | +| `include_globs` | body | string[] | 只保留匹配这些 glob 之一的路径 | +| `exclude_globs` | body | string[] | 跳过匹配这些 glob 的路径 | +| `follow_gitignore` | body | boolean | 跳过 gitignore 的路径。默认 `true` | +| `runtime_id` | body | string | 在哪个运行时上搜索。默认 `local` | + +成功时 `data` 为 `{ items, truncated }`,命中结构与排序同 `fs:search`。 + +- `40001`:请求体校验失败 +- `40410`:工作区不存在,且不是可用的绝对路径 + +#### `POST /api/v1/workspace/fs:suggest` + +在无会话的情况下给出工作区内的文件与目录补全候选——即输入框中 `@` 文件提及的后端。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `workspace` | body | string | **必填。** 已注册工作区 id 或绝对根路径(当场注册) | +| `query` | body | string | **必填。** 要补全的部分路径文本 | +| `limit` | body | integer | 最大候选数,1–200。默认 `50` | +| `follow_gitignore` | body | boolean | 跳过 gitignore 的路径。默认 `true` | +| `show_hidden` | body | boolean | 包含点文件。默认 `false` | +| `include_globs` | body | string[] | 只保留匹配这些 glob 之一的路径 | +| `exclude_globs` | body | string[] | 跳过匹配这些 glob 的路径 | +| `runtime_id` | body | string | 在哪个运行时上补全。默认 `local` | + +成功时 `data` 为 `{ items, truncated }`,每项为 `{ path, name, kind, score, match_positions }`,命中结构同 `fs:search`。 + +- `40001`:请求体校验失败 +- `40410`:工作区不存在,且不是可用的绝对路径 + +#### `GET /api/v1/fs:browse` + +列出某个本机目录的子目录——文件夹选择器的后端。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `path` | query | string | 绝对目录路径。默认用户主目录 | + +成功时 `data` 为 `{ path, parent, entries }`,其中 `path` 为解析后的目录,`parent` 为其父目录(文件系统根处为 `null`),每条目为 `{ name, path, is_dir: true }`。 + +- `40001`:`path` 不是绝对路径 +- `40409`:路径不存在 +- `40411`:权限不足 + +#### `GET /api/v1/fs:home` + +返回文件夹选择器的落地数据。无参数。 + +成功时 `data` 为 `{ home, recent_roots }`,其中 `home` 为用户主目录,`recent_roots` 列出已注册工作区的根目录。 + +#### `GET /api/v1/fs:content` + +以流式返回本机文件系统上任意文件的原始字节——仅受 API token 保护,暴露端口时务必谨慎。支持 Range 请求与 ETag 缓存;见 [二进制与流式端点](#二进制与流式端点)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `path` | query | string | **必填。** 绝对文件路径 | + +- `40001`:`path` 不是绝对路径,或不是普通文件 +- `40409`:路径不存在 +- `40411`:权限不足 +- `40906`:路径是目录 + +#### `POST /api/v1/fs:mkdir` + +按绝对路径在本机文件系统上创建一个目录——文件夹选择器「新建文件夹」的后端。非递归:父目录必须已存在。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `path` | body | string | **必填。** 绝对目录路径 | + +成功时 `data` 为 `{ path }`。 + +- `40001`:`path` 不是绝对路径 +- `40409`:父路径不存在 +- `40411`:权限不足 +- `40919`:路径已存在 + +### 文件上传 + +| 方法与路径 | 说明 | +| --- | --- | +| `POST /api/v1/files` | multipart 上传(字段 `file`,可选 `name`、`expires_in_sec`),返回文件元信息 | +| `GET /api/v1/files/{file_id}` | 下载(二进制,错误用真实 HTTP 状态码) | +| `DELETE /api/v1/files/{file_id}` | 删除 | + +#### `POST /api/v1/files` + +以 `multipart/form-data` 上传文件,供后续引用(例如作为提示词附件)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `file` | body | binary | **必填。** multipart 的文件部分 | +| `name` | body | string | 存储的显示名。默认上传文件名 | +| `expires_in_sec` | body | number | 文件过期前的秒数(非负)。默认永不过期 | + +成功时 `data` 为文件元信息 `{ id, name, media_type, size, created_at, expires_at? }`,其中 `media_type` 取自上传的内容类型。 + +- `40001`:multipart 请求体缺少 `file` 字段 + +#### `GET /api/v1/files/{file_id}` + +下载已上传的文件。响应为二进制流,支持 Range 请求但不处理 `If-None-Match`;失败使用真实 HTTP 状态码——见 [二进制与流式端点](#二进制与流式端点)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `file_id` | path | string | **必填。** 上传响应返回的文件 id | + +- `40407`(HTTP 404):没有该 id 的文件(包括已过期的文件) + +#### `DELETE /api/v1/files/{file_id}` + +删除已上传的文件。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `file_id` | path | string | **必填。** 上传响应返回的文件 id | + +成功时 `data` 为 `{ deleted: true }`。 + +- `40407`(HTTP 404):没有该 id 的文件 + +### GUI 存储 + +由服务端支撑的键值存储,接口对齐浏览器的 `localStorage`,持久化在服务的 home 目录下;web UI 用它保存跨客户端的 UI 状态。值是不透明字符串——序列化由调用方负责。 + +| 方法与路径 | 说明 | +| --- | --- | +| `GET /api/v1/gui/store/length` | 已存键的数量 | +| `GET /api/v1/gui/store/getItem` | 按键读取值 | +| `POST /api/v1/gui/store/setItem` | 按键写入值 | +| `POST /api/v1/gui/store/removeItem` | 按键删除值 | +| `POST /api/v1/gui/store/clear` | 删除所有值 | + +#### `GET /api/v1/gui/store/length` + +返回已存键的数量(对齐 `localStorage.length`)。无参数。 + +成功时 `data` 为 `{ length }`。 + +#### `GET /api/v1/gui/store/getItem` + +读取一个值(对齐 `localStorage.getItem`)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `key` | query | string | **必填。** 要读取的键,1–256 个字符 | + +成功时 `data` 为 `{ value }`——已存字符串,键不存在时为 `null`。 + +#### `POST /api/v1/gui/store/setItem` + +写入一个值(对齐 `localStorage.setItem`)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `key` | body | string | **必填。** 要写入的键,1–256 个字符 | +| `value` | body | string | **必填。** 要存储的值 | + +成功时 `data` 为 `null`。 + +#### `POST /api/v1/gui/store/removeItem` + +删除一个值(对齐 `localStorage.removeItem`)。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `key` | body | string | **必填。** 要删除的键,1–256 个字符 | + +成功时 `data` 为 `null`。 + +#### `POST /api/v1/gui/store/clear` + +删除所有已存值(对齐 `localStorage.clear`)。无参数。 + +成功时 `data` 为 `null`。 + +### 全局搜索与其他 + +| 方法与路径 | 说明 | +| --- | --- | +| `POST /api/v1/search` | 跨会话全文搜索,`mode` 为 `terms`(默认)或 `literal`(精确子串),`page_token` 分页 | +| `GET /api/v1/connections` | 列出当前在线的 WebSocket 连接 | +| `GET /api/v2/sessions` | 新一代会话列表,见下文 | +| `POST /api/v2/sessions:archive` | 批量归档会话,见下文 | +| `POST /api/v2/sessions:restore` | 批量恢复已归档会话,见下文 | +| `/api/v1/debug/*` | 反射式调试 RPC,仅 `--debug-endpoints` 且 loopback 时挂载,不属于稳定协议 | + +#### `POST /api/v1/search` + +跨会话全文搜索,覆盖 User 消息、Assistant 回复与会话标题,由服务端的持久搜索索引支撑。当 `container.session_id` 指向本服务进程中存活的会话时,搜索改为直接扫描该会话的内存转录,响应的 `source` 字段(`index` 或 `live`)会报告本页结果由哪条路径提供。 + +| 参数 | 位置 | 类型 | 说明 | +| --- | --- | --- | --- | +| `query` | body | string | **必填。** 搜索文本 | +| `mode` | body | string | `terms`(默认)/ `literal` | +| `op` | body | string | `terms` 模式下的词项组合符:`AND`(默认)/ `OR` | +| `container` | body | object | 将搜索限定在 `{ session_id?, agent_id? }` | +| `role` | body | string | 限定 `user` / `assistant` / `title` 命中 | +| `start_time` | body | integer | 只看不早于该时间的命中(epoch 毫秒) | +| `end_time` | body | integer | 只看不晚于该时间的命中(epoch 毫秒) | +| `sort` | body | string | `score`(默认)/ `time_desc` / `time_asc`;`literal` 模式忽略此参数,始终最新在前 | +| `page_size` | body | integer | 每页命中数,1–50。默认 `20` | +| `page_token` | body | string | 上一页响应返回的令牌 | + +`terms` 模式下查询会被分词(ASCII 词加 CJK n-gram)、去重,并以至多 32 个词项匹配倒排索引;`literal` 模式是零误报的精确子串搜索。成功时 `data` 为 `{ items, has_more, page_token?, index_state, source }`,每项为 `{ session_id, workspace_id, session_title, agent_id, role, snippet, time, turn?, step_id?, score }`。`index_state` 为 `{ state, indexed_sessions, total_sessions, documents, stale?, degraded? }`,其中 `state` 为 `building` / `ready` / `readonly` 之一;`stale` 标记仍在追赶的落后视图,`degraded` 携带最近一次刷新失败的信息。超出预算的页会额外携带 `incomplete`,取值为 `candidate_cap` / `postings_budget` / `deadline` 之一。分页令牌锁定索引代际与查询条件——索引重建或查询变更会使其失效。 + +- `40001`:请求体校验失败、查询不可用(为空或超过 32 个词项),或分页令牌非法 + +#### `GET /api/v1/connections` + +列出当前连接到本服务的 WebSocket 客户端,按连接时间最早在前。无参数。 + +成功时 `data` 为 `{ connections }`,每项为 `{ id, connected_at, remote_address, user_agent, has_client_hello, subscriptions }`:`connected_at` 为 ISO 8601 时间戳;`remote_address` 与 `user_agent` 未知时为 `null`;`has_client_hello` 报告客户端是否已发送握手帧;`subscriptions` 列出该连接订阅的会话 id。 ### `GET /api/v2/sessions` @@ -282,7 +2101,7 @@ PTY 终端接口,仅 loopback 绑定时挂载。 "groups": [ { "workspace": { "id": "wd_my-app_a1b2c3d4e5f6", "cwd": "/Users/dev/my-app" }, - "sessions": [ { "id": "session_...", "workspace": { "id": "wd_my-app_a1b2c3d4e5f6", "cwd": "/Users/dev/my-app" }, "meta": { "title": "修复登录页", "last_prompt": "调整按钮间距", "created_at": 1787000000000, "updated_at": 1787000100000, "archived": false, "archived_at": null }, "activity": { "status": "idle" } } ], + "sessions": [ { "id": "session_...", "workspace": { "id": "wd_my-app_a1b2c3d4e5f6", "cwd": "/Users/dev/my-app" }, "meta": { "title": "Fix the login page", "last_prompt": "adjust the button spacing", "created_at": 1787000000000, "updated_at": 1787000100000, "archived": false, "archived_at": null }, "activity": { "status": "idle" } } ], "total": 42 } ], @@ -322,7 +2141,7 @@ PTY 终端接口,仅 loopback 绑定时挂载。 ### 建立连接 -唯一端点是 `ws://:/api/v1/ws`,升级请求即完成鉴权(方式见上文「鉴权」)。连接建立后服务端立即发送 `server_hello`: +唯一端点是 `ws://:/api/v1/ws`;鉴权在升级请求时完成(见上文 [鉴权](#鉴权))。连接建立后服务端立即发送 `server_hello`: ```json { @@ -392,7 +2211,7 @@ PTY 终端接口,仅 loopback 绑定时挂载。 | `GET /api/v1/fs:content` | 读取本机任意文件(仅受 token 保护,谨慎暴露端口) | 支持 | 支持 | | `POST /api/v1/sessions/{session_id}/export` | 导出会话与诊断信息(zip 流) | 不支持 | 不支持 | -错误语义也不相同:`GET /api/v1/files/{file_id}` 对查找和存储失败返回真实 404 / 500 状态码(参数校验失败仍走 HTTP 200 信封),其余三个端点的所有失败都走标准[响应信封](#响应信封)——客户端在这三个端点上仍需检查信封中的 `code`。 +错误语义也不相同:`GET /api/v1/files/{file_id}` 对查找和存储失败返回真实 404 / 500 状态码(参数校验失败仍走 HTTP 200 信封),其余三个端点的所有失败都走标准 [响应信封](#响应信封)——客户端在这三个端点上仍需检查信封中的 `code`。 ## 下一步