返回 DeepSeek-TUI-2026
RUNTIME_API.md
根目录 / docs / RUNTIME_API.md
1 # Runtime API & Integration Contract
2
3 DeepSeek TUI exposes a local runtime API through `deepseek serve --http` and
4 machine-readable health via `deepseek doctor --json`. It also exposes
5 `deepseek serve --acp` for editor clients that speak the Agent Client Protocol
6 over stdio. This document is the stable integration contract for native macOS
7 workbench applications (and other local supervisors) that embed the DeepSeek
8 engine without screen-scraping terminal output.
9
10 ## Architecture
11
12 ```
13 macOS workbench (or any local supervisor)
14
15 ├─ deepseek doctor --json → machine-readable health & capability
16 ├─ deepseek serve --http → HTTP/SSE runtime API
17 ├─ deepseek serve --acp → ACP stdio agent for editors such as Zed
18 ├─ deepseek serve --mcp → MCP stdio server
19 └─ deepseek [args] → interactive TUI session
20 ```
21
22 The engine runs as a local-only process. All APIs bind to `localhost` by
23 default. No hosted relay, no provider-token custody, no secret leakage.
24
25 ## ACP stdio adapter: `deepseek serve --acp`
26
27 `deepseek serve --acp` speaks JSON-RPC 2.0 over newline-delimited stdio for
28 ACP-compatible editor clients. The initial adapter implements the ACP baseline:
29
30 - `initialize`
31 - `session/new`
32 - `session/prompt`
33 - `session/cancel`
34
35 Prompt requests are routed through the configured DeepSeek client and current
36 default model. Responses are emitted as `session/update` agent message chunks
37 followed by a `session/prompt` response with `stopReason: "end_turn"`.
38
39 The adapter is intentionally conservative: it does not yet expose shell tools,
40 file-write tools, checkpoint replay, or session loading through ACP. Use
41 `deepseek serve --http` for the full local runtime API and `deepseek serve --mcp`
42 when another client needs DeepSeek's tools as MCP tools.
43
44 ## Capability endpoint: `deepseek doctor --json`
45
46 Returns a JSON object describing the current installation's readiness state.
47 Suitable for health-check polling from a macOS workbench.
48
49 ```bash
50 deepseek doctor --json
51 ```
52
53 ### Response schema (key fields)
54
55 | Field | Type | Description |
56 |---|---|---|
57 | `version` | string | Installed version (e.g. `"0.8.9"`) |
58 | `config_path` | string | Resolved config file path |
59 | `config_present` | bool | Whether the config file exists |
60 | `workspace` | string | Default workspace directory |
61 | `api_key.source` | string | `env`, `config`, or `missing` |
62 | `base_url` | string | API base URL |
63 | `default_text_model` | string | Default model |
64 | `memory.enabled` | bool | Whether the memory feature is on |
65 | `memory.path` | string | Path to memory file |
66 | `memory.file_present` | bool | Whether memory file exists |
67 | `mcp.config_path` | string | MCP config file path |
68 | `mcp.present` | bool | Whether MCP config exists |
69 | `mcp.servers` | array | Per-server health: `{name, enabled, status, detail}` |
70 | `skills.selected` | string | Resolved skills directory |
71 | `skills.global.path` / `.present` / `.count` | — | DeepSeek global skills dir (`~/.deepseek/skills`) |
72 | `skills.agents.path` / `.present` / `.count` | — | Workspace `.agents/skills/` dir |
73 | `skills.agents_global.path` / `.present` / `.count` | — | agentskills.io global skills dir (`~/.agents/skills`) |
74 | `skills.local.path` / `.present` / `.count` | — | `skills/` dir |
75 | `skills.opencode.path` / `.present` / `.count` | — | `.opencode/skills/` dir |
76 | `skills.claude.path` / `.present` / `.count` | — | `.claude/skills/` dir |
77 | `tools.path` / `.present` / `.count` | — | Global tools directory |
78 | `plugins.path` / `.present` / `.count` | — | Global plugins directory |
79 | `sandbox.available` | bool | Whether sandbox is supported on this OS |
80 | `sandbox.kind` | string or null | Sandbox kind (e.g. `"macos_seatbelt"`) |
81 | `storage.spillover.path` / `.present` / `.count` | — | Tool output spillover dir |
82 | `storage.stash.path` / `.present` / `.count` | — | Composer stash |
83
84 ### Example
85
86 ```json
87 {
88 "version": "0.8.9",
89 "config_path": "/Users/you/.deepseek/config.toml",
90 "config_present": true,
91 "workspace": "/Users/you/projects/deepseek-tui",
92 "api_key": {
93 "source": "env"
94 },
95 "base_url": "https://api.deepseek.com",
96 "default_text_model": "deepseek-v4-pro",
97 "memory": {
98 "enabled": false,
99 "path": "/Users/you/.deepseek/memory.md",
100 "file_present": true
101 },
102 "mcp": {
103 "config_path": "/Users/you/.deepseek/mcp.json",
104 "present": true,
105 "servers": [
106 {"name": "filesystem", "enabled": true, "status": "ok", "detail": "ready"}
107 ]
108 },
109 "sandbox": {
110 "available": true,
111 "kind": "macos_seatbelt"
112 }
113 }
114 ```
115
116 ## HTTP/SSE runtime API: `deepseek serve --http`
117
118 ```bash
119 deepseek serve --http [--host 127.0.0.1] [--port 7878] [--workers 2] [--auth-token TOKEN]
120 ```
121
122 Defaults: host `127.0.0.1`, port `7878`, 2 workers (clamped 1–8).
123
124 The server binds to `localhost` by default. Configuration is via CLI flags —
125 there is no `[app_server]` config section.
126
127 By default, existing local behavior is unchanged and `/v1/*` routes are not
128 authenticated. To require a bearer token for `/v1/*` routes, pass
129 `--auth-token TOKEN` or set `DEEPSEEK_RUNTIME_TOKEN=TOKEN` before starting the
130 server. `/health` remains public for local process supervision and readiness
131 checks.
132
133 Authenticated clients can provide the token as `Authorization: Bearer TOKEN`,
134 `X-DeepSeek-Runtime-Token: TOKEN`, or `?token=TOKEN` for EventSource-style
135 clients that cannot set custom headers.
136
137 ### Endpoints
138
139 **Health**
140 - `GET /health`
141
142 **Sessions** (legacy session manager)
143 - `GET /v1/sessions?limit=50&search=<substring>`
144 - `GET /v1/sessions/{id}`
145 - `DELETE /v1/sessions/{id}`
146 - `POST /v1/sessions/{id}/resume-thread`
147
148 **Threads** (durable runtime data model)
149 - `GET /v1/threads?limit=50&include_archived=false&archived_only=false`
150 - `GET /v1/threads/summary?limit=50&search=<optional>&include_archived=false&archived_only=false`
151 - `POST /v1/threads`
152 - `GET /v1/threads/{id}`
153 - `PATCH /v1/threads/{id}` (see body shape below)
154 - `POST /v1/threads/{id}/resume`
155 - `POST /v1/threads/{id}/fork`
156
157 `archived_only=true` returns archived threads only (mutually overrides
158 `include_archived`). Default behavior is unchanged: `include_archived=false`
159 and `archived_only=false` returns active threads. Added in v0.8.10 (#563).
160
161 `PATCH /v1/threads/{id}` body — every field is optional, missing means
162 "no change". At least one field must be present. `title` and `system_prompt`
163 accept an empty string to clear a previously-set value. Added in v0.8.10 (#562):
164
165 ```json
166 {
167 "archived": true,
168 "allow_shell": false,
169 "trust_mode": false,
170 "auto_approve": false,
171 "model": "deepseek-v4-pro",
172 "mode": "agent",
173 "title": "User-set thread title",
174 "system_prompt": "You are a useful assistant."
175 }
176 ```
177
178 **Turns** (within a thread)
179 - `POST /v1/threads/{id}/turns`
180 - `POST /v1/threads/{id}/turns/{turn_id}/steer`
181 - `POST /v1/threads/{id}/turns/{turn_id}/interrupt`
182 - `POST /v1/threads/{id}/compact` (manual compaction)
183
184 **Events** (SSE replay + live stream)
185 - `GET /v1/threads/{id}/events?since_seq=<u64>`
186
187 **Compatibility stream** (one-shot, backwards-compatible)
188 - `POST /v1/stream`
189
190 **Tasks** (durable background work)
191 - `GET /v1/tasks`
192 - `POST /v1/tasks`
193 - `GET /v1/tasks/{id}`
194 - `POST /v1/tasks/{id}/cancel`
195
196 **Automations** (scheduled recurring work)
197 - `GET /v1/automations`
198 - `POST /v1/automations`
199 - `GET /v1/automations/{id}`
200 - `PATCH /v1/automations/{id}`
201 - `DELETE /v1/automations/{id}`
202 - `POST /v1/automations/{id}/run`
203 - `POST /v1/automations/{id}/pause`
204 - `POST /v1/automations/{id}/resume`
205 - `GET /v1/automations/{id}/runs?limit=20`
206
207 **Introspection**
208 - `GET /v1/workspace/status`
209 - `GET /v1/skills`
210 - `GET /v1/apps/mcp/servers`
211 - `GET /v1/apps/mcp/tools?server=<optional>`
212
213 **Usage** (token/cost aggregation across threads)
214 - `GET /v1/usage?since=<rfc3339>&until=<rfc3339>&group_by=<day|model|provider|thread>`
215
216 `since` / `until` are inclusive RFC 3339 timestamps and may be omitted (no
217 bound). `group_by` defaults to `day`. Buckets are sorted by ascending key.
218 Empty time ranges produce empty `buckets` (never a 404). Cost is computed via
219 the model→pricing map; turns whose model has no pricing entry contribute
220 tokens but `0.0` cost. Added in v0.8.10 (#564).
221
222 ```json
223 {
224 "since": "2026-04-01T00:00:00Z",
225 "until": "2026-04-30T23:59:59Z",
226 "group_by": "day",
227 "totals": {
228 "input_tokens": 12345,
229 "output_tokens": 6789,
230 "cached_tokens": 0,
231 "reasoning_tokens": 0,
232 "cost_usd": 0.012,
233 "turns": 42
234 },
235 "buckets": [
236 {
237 "key": "2026-04-30",
238 "input_tokens": 1234,
239 "output_tokens": 678,
240 "cached_tokens": 0,
241 "reasoning_tokens": 0,
242 "cost_usd": 0.001,
243 "turns": 3
244 }
245 ]
246 }
247 ```
248
249 ## Runtime data model
250
251 The runtime uses a durable Thread/Turn/Item lifecycle.
252
253 - **ThreadRecord** — `id`, `created_at`, `updated_at`, `model`, `workspace`,
254 `mode`, `task_id`, `coherence_state`, `system_prompt`, `latest_turn_id`,
255 `latest_response_bookmark`, `archived`
256 - **TurnRecord** — `id`, `thread_id`, `status` (`queued|in_progress|completed|
257 failed|interrupted|canceled`), timestamps, duration, usage, error summary
258 - **TurnItemRecord** — `id`, `turn_id`, `kind` (`user_message|agent_message|
259 tool_call|file_change|command_execution|context_compaction|status|error`),
260 lifecycle `status`, `metadata`
261
262 Events are append-only with a global monotonic `seq` for replay/resume.
263
264 ### Restart semantics
265
266 - If the process restarts while a turn or item is `queued` or `in_progress`,
267 the recovered record is marked `interrupted` with an `"Interrupted by
268 process restart"` error.
269 - Task execution performs its own recovery on top of the same persisted
270 thread/turn store.
271
272 ### Approval model
273
274 - The `auto_approve` flag applies to the runtime approval bridge and engine
275 tool context. When enabled for a thread/turn/task, approval-required tools
276 are auto-approved in the non-interactive runtime path, shell safety checks
277 run in auto-approved mode, and spawned sub-agents inherit that setting.
278 - When omitted, `auto_approve` defaults to `false`.
279
280 ### SSE event stream
281
282 The SSE event payload shape:
283
284 ```json
285 {
286 "seq": 42,
287 "timestamp": "2026-02-11T20:18:49.123Z",
288 "thread_id": "thr_1234abcd",
289 "turn_id": "turn_5678efgh",
290 "item_id": "item_90ab12cd",
291 "event": "item.delta",
292 "payload": {
293 "delta": "partial output",
294 "kind": "agent_message"
295 }
296 }
297 ```
298
299 Common event names: `thread.started`, `thread.forked`, `turn.started`,
300 `turn.lifecycle`, `turn.steered`, `turn.interrupt_requested`,
301 `turn.completed`, `item.started`, `item.delta`, `item.completed`,
302 `item.failed`, `item.interrupted`, `approval.required`, `sandbox.denied`,
303 `coherence.state`.
304
305 ## Security boundary
306
307 - **Localhost only**. The server binds to `127.0.0.1` by default. Set
308 `--host 0.0.0.0` only when you have a reverse-proxy / VPN that
309 authenticates. The runtime does not provide user isolation or TLS.
310 - **Optional token guard**. `--auth-token` or `DEEPSEEK_RUNTIME_TOKEN`
311 requires a matching bearer token for `/v1/*` routes. This is a local
312 convenience guard, not a replacement for TLS, VPN, or a trusted reverse
313 proxy on public networks.
314 - **No provider-token custody**. The server never returns the API key. The
315 `api_key.source` capability field reports `env`, `config`, or `missing` —
316 never the key itself.
317 - **No hosted relay**. The app-server is a local process under the user's
318 control. There is no cloud component.
319 - **Capability responses** never leak secrets, file contents, or session
320 message bodies. They report *metadata*: presence, counts, status flags.
321
322 ### CORS allow-list
323
324 The runtime API ships with a built-in dev-origin allow-list:
325 `http://localhost:3000`, `http://127.0.0.1:3000`, `http://localhost:1420`,
326 `http://127.0.0.1:1420`, `tauri://localhost`. To add additional origins (e.g.
327 when developing a UI on Vite's default `:5173`), use any of:
328
329 - CLI flag (repeatable): `deepseek serve --http --cors-origin http://localhost:5173`
330 - Env var (comma-separated): `DEEPSEEK_CORS_ORIGINS="http://localhost:5173,http://localhost:8080"`
331 - Config (`~/.deepseek/config.toml`):
332 ```toml
333 [runtime_api]
334 cors_origins = ["http://localhost:5173"]
335 ```
336
337 User-supplied origins **stack on top of** the built-in defaults; they do not
338 replace them. Wildcard origins are not supported — the explicit allow-list
339 model is preserved. Added in v0.8.10 (#561).
340
341 ## Session lifecycle (native UI supervision)
342
343 | Operation | Endpoint |
344 |---|---|
345 | List sessions | `GET /v1/sessions` |
346 | Get session | `GET /v1/sessions/{id}` |
347 | Delete session | `DELETE /v1/sessions/{id}` |
348 | Resume into thread | `POST /v1/sessions/{id}/resume-thread` |
349 | Create thread | `POST /v1/threads` |
350 | List threads | `GET /v1/threads` |
351 | Attach to events | `GET /v1/threads/{id}/events?since_seq=0` |
352 | Send message | `POST /v1/threads/{id}/turns` |
353 | Steer | `POST /v1/threads/{id}/turns/{turn_id}/steer` |
354 | Interrupt | `POST /v1/threads/{id}/turns/{turn_id}/interrupt` |
355 | Compact | `POST /v1/threads/{id}/compact` |
356
357 ## Compatibility tests
358
359 Contract snapshots live in `crates/protocol/tests/`. Run:
360
361 ```bash
362 cargo test -p deepseek-protocol --test parity_protocol --locked
363 ```
364
365 This validates that the app-server's event schema hasn't drifted from the
366 documented contract. CI runs this on every push to `main` and on release tags.
367
367 lines MARKDOWN