# csc serve — HTTP API 接口规范 > `csc serve` 启动一个 HTTP 服务器,为 IDE 插件和 Web 客户端提供 REST API。 > 内部架构为 **子进程模式**:每个会话 spawn 一个 `csc --print --input-format stream-json --output-format stream-json` 子进程,HTTP 层作为 NDJSON 协议的桥梁。 ## 设计原则 1. **API 路径兼容 opencode serve**:cs-cloud 通过路由表映射,客户端无需感知后端差异 2. **子进程隔离**:1 session = 1 子进程,无进程内多会话 3. **子进程复用**:同一 session 的多次 prompt 复用同一子进程(bridge 模式已验证) 4. **JSONL 持久化**:会话数据沿用 csc 现有的 transcript JSONL 文件 5. **文件系统操作由 cs-cloud 自有接口处理**:`/api/v1/runtime/files` 等不经过 csc serve ## CLI 入口 ```bash csc serve [options] Options: --port 监听端口(默认 0 = 自动分配) --host 监听地址(默认 127.0.0.1) --auth-token Bearer token 认证 --workspace 默认工作目录 --max-sessions 最大并发会话数(默认 32) --idle-timeout 空闲会话超时(默认 1800000 = 30 分钟,0 = 永不超时) ``` stdout 输出端口信息(供 cs-cloud 解析): ``` csc server listening on http://127.0.0.1:{port} ``` --- ## API 端点总览 | 分类 | 端点数 | 说明 | |---|---|---| | Server | 1 | 健康检查 | | Info | 5 | 路径、VCS、命令、agent 模式、MCP 状态 | | Session | 11 | 会话 CRUD、prompt、abort | | Event | 1 | SSE 事件流 | | Permission | 2 | 权限请求列表、回复 | | Question | 3 | 问题请求列表、回复、拒绝 | | Message | 3 | 消息历史、todo、diff | | Provider | 2 | 模型/Provider 信息 | | **合计** | **28** | | --- ## 1. Server ### `GET /health` 服务健康检查。 **Response 200:** ```json { "status": "ok", "version": "4.0.1", "uptime_ms": 12345, "active_sessions": 3 } ``` --- ## 2. Info ### `GET /path` 获取路径信息。 **Response 200:** ```json { "home": "/Users/user", "state": "/Users/user/.claude", "config": "/Users/user/.claude", "directory": "/path/to/workspace" } ``` ### `GET /vcs` 获取 VCS 信息。 **Response 200:** ```json { "branch": "main" } ``` ### `GET /command` 列出可用 slash 命令。 **Response 200:** ```json [ { "name": "compact", "description": "Compact conversation" }, { "name": "clear", "description": "Clear conversation" }, { "name": "help", "description": "Show help" } ] ``` 数据来源:子进程 `initialize` response 中的 `commands` 字段。 ### `GET /agent` 列出可用的 agent 模式。 **Response 200:** ```json [ { "id": "main", "name": "Main Agent" }, { "id": "plan", "name": "Plan Mode" } ] ``` 数据来源:子进程 `initialize` response 中的 `agents` 字段。 ### `GET /mcp` 获取 MCP 服务器状态。通过子进程 `mcp_status` control-request 获取。 **Response 200:** ```json { "servers": [ { "name": "filesystem", "status": "connected", "tools": 5 }, { "name": "github", "status": "disconnected", "error": "Connection refused" } ] } ``` --- ## 3. Session ### `POST /session` 创建新会话。spawn 一个 `csc --print` 子进程。 **Request Body:** ```json { "cwd": "/path/to/project", "permission_mode": "default", "model": "claude-sonnet-4-20250514", "system_prompt": "optional system prompt", "resume_session_id": "optional-uuid-to-resume" } ``` | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `cwd` | string | 否 | 工作目录,默认为 serve 的 `--workspace` | | `permission_mode` | string | 否 | 权限模式:`default` / `bypassPermissions` / `plan` | | `model` | string | 否 | 初始模型 | | `system_prompt` | string | 否 | 自定义 system prompt | | `resume_session_id` | string | 否 | 恢复已有会话的 ID | **Response 201:** ```json { "session_id": "550e8400-e29b-41d4-a716-446655440000", "status": "starting", "cwd": "/path/to/project", "created_at": 1713001234567 } ``` 子进程启动完成后通过 SSE 推送 `session.ready` 事件。 ### `GET /session` 列出所有会话。 **Query Parameters:** | 参数 | 类型 | 说明 | |---|---|---| | `limit` | number | 分页大小(默认 50) | | `offset` | number | 分页偏移 | | `search` | string | 搜索会话标题 | **Response 200:** ```json { "sessions": [ { "session_id": "uuid-1", "status": "running", "cwd": "/path/to/project", "title": "Fix authentication bug", "model": "claude-sonnet-4-20250514", "permission_mode": "default", "created_at": 1713001234567, "last_active_at": 1713001299999, "cost_usd": 0.0523, "api_duration_ms": 12345 } ] } ``` 数据来源:内存中的活跃会话 + `~/.claude/server-sessions.json` 持久化索引 + 各 session 的 transcript JSONL 头部。 ### `GET /session/:sessionID` 获取单个会话详情。 **Response 200:** ```json { "session_id": "uuid-1", "status": "running", "cwd": "/path/to/project", "title": "Fix authentication bug", "model": "claude-sonnet-4-20250514", "permission_mode": "default", "created_at": 1713001234567, "last_active_at": 1713001299999, "cost_usd": 0.0523, "api_duration_ms": 12345, "message_count": 12, "usage": { "input_tokens": 15000, "output_tokens": 5000, "cache_read_input_tokens": 10000, "cache_creation_input_tokens": 2000 } } ``` **Response 404:** ```json { "error": "session not found" } ``` ### `PATCH /session/:sessionID` 更新会话属性。 **Request Body:** ```json { "title": "New title", "model": "claude-sonnet-4-20250514", "permission_mode": "bypassPermissions" } ``` `model` 和 `permission_mode` 的变更通过子进程的 control_request 实现(`set_model` / `set_permission_mode`)。 **Response 200:** ```json { "session_id": "uuid-1", "title": "New title", "model": "claude-sonnet-4-20250514", "permission_mode": "bypassPermissions" } ``` ### `DELETE /session/:sessionID` 删除会话。kill 子进程 + 清理持久化索引。 **Response 200:** ```json { "deleted": true } ``` ### `GET /session/status` 批量获取所有会话的状态。 **Response 200:** ```json { "sessions": { "uuid-1": { "status": "running", "has_pending_permission": false }, "uuid-2": { "status": "idle", "has_pending_permission": true } } } ``` ### `POST /session/:sessionID/prompt` 发送 prompt 并流式返回响应。对应子进程 stdin 写入 `SDKUserMessage`。 **Request Body:** ```json { "content": "Fix the authentication bug in login.ts", "files": ["src/login.ts"], "images": [] } ``` | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `content` | string | 是 | 用户输入内容 | | `files` | string[] | 否 | 附带的文件路径(作为 context) | | `images` | object[] | 否 | 图片附件(base64 或 URL) | **Response 200 (streaming):** Content-Type: `text/event-stream` ``` event: message data: {"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"I'll fix the"}]}} event: message data: {"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":" authentication bug."}]}} event: message data: {"type":"tool_progress","tool_name":"Edit","elapsed_time_seconds":1} event: message data: {"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_xxx","name":"Edit","input":{...}}]}} event: result data: {"type":"result","subtype":"success","cost_usd":0.0123,"duration_ms":5432,"usage":{"input_tokens":5000,"output_tokens":2000}} ``` 事件类型直接映射子进程 stdout NDJSON 的 `SDKMessage.type`: | NDJSON type | SSE event | 说明 | |---|---|---| | `assistant` | `message` | assistant 流式内容 | | `partial_assistant` | `message` | 原始 API 流式事件 | | `tool_progress` | `message` | 工具执行进度 | | `result` | `result` | 最终结果(含 cost/usage) | | `result_success` | `result` | 成功结果 | | `system` | `system` | 系统消息(init、compact_boundary 等) | | `status` | `system` | 状态变更 | | `control_request` | `control_request` | 权限/交互请求 | | `permission_denial` | `system` | 权限被拒绝 | ### `POST /session/:sessionID/prompt_async` 异步 prompt。立即返回,后台执行。通过 SSE 推送事件。 **Request Body:** 同 `prompt`。 **Response 204:** No Content ### `POST /session/:sessionID/abort` 中止当前正在执行的 prompt。向子进程发送 `control_request { subtype: "interrupt" }`,如果无响应则 SIGTERM。 **Response 200:** ```json { "aborted": true } ``` ### `POST /session/:sessionID/shell` 在会话上下文中执行 shell 命令。转换为 prompt 发送到子进程。 **Request Body:** ```json { "command": "npm test" } ``` **Response 200 (streaming):** 同 `prompt` 的 SSE 流。 ### `POST /session/:sessionID/command` 执行 slash 命令。转换为对应 prompt 发送到子进程。 **Request Body:** ```json { "command": "/compact" } ``` **Response 200 (streaming):** 同 `prompt` 的 SSE 流。 --- ## 4. Event ### `GET /event` 全局 SSE 事件流。聚合所有会话的事件。 **Query Parameters:** | 参数 | 类型 | 说明 | |---|---|---| | `session_id` | string | 可选,只订阅特定会话的事件 | **Response 200 (SSE):** ``` event: connected data: {"type":"server.connected"} event: session.created data: {"session_id":"uuid-1","status":"starting"} event: session.ready data: {"session_id":"uuid-1","status":"running","model":"claude-sonnet-4-20250514"} event: session.message data: {"session_id":"uuid-1","type":"assistant","message":{...}} event: session.result data: {"session_id":"uuid-1","type":"result","cost_usd":0.0123,"duration_ms":5432} event: session.control_request data: {"session_id":"uuid-1","request_id":"req-1","request":{"subtype":"can_use_tool","tool_name":"Bash","input":{...}}} event: session.status_changed data: {"session_id":"uuid-1","status":"idle"} event: session.deleted data: {"session_id":"uuid-1"} event: heartbeat data: {"type":"server.heartbeat","ts":1713001234567} ``` 所有 session 相关事件都带 `session_id` 字段。心跳间隔 10 秒。 --- ## 5. Permission ### `GET /permission` 列出所有待处理的权限请求。 **Response 200:** ```json { "permissions": [ { "request_id": "req-1", "session_id": "uuid-1", "tool_name": "Bash", "tool_use_id": "toolu_xxx", "input": { "command": "rm -rf node_modules" }, "title": "Bash: rm -rf node_modules", "description": "Execute bash command" } ] } ``` 数据来源:拦截子进程 stdout 的 `control_request { subtype: "can_use_tool" }` 消息,缓存在内存。 ### `POST /permission/:requestID/reply` 回复权限请求。向对应子进程 stdin 写入 `control_response`。 **Request Body (allow):** ```json { "behavior": "allow", "updated_input": { "command": "rm -rf node_modules" } } ``` **Request Body (deny):** ```json { "behavior": "deny", "message": "Command not allowed" } ``` **Response 200:** ```json { "resolved": true } ``` --- ## 6. Question csc 通过 `control_request { subtype: "elicitation" }` 实现 MCP elicitation 交互,映射为 question 端点。 ### `GET /question` 列出所有待处理的问题请求。 **Response 200:** ```json { "questions": [ { "request_id": "req-2", "session_id": "uuid-1", "mcp_server_name": "my-server", "message": "Please provide your API key", "mode": "form", "requested_schema": { "type": "object", "properties": { "key": { "type": "string" } } } } ] } ``` ### `POST /question/:requestID/reply` 回复问题请求。 **Request Body:** ```json { "action": "accept", "content": { "key": "sk-xxx" } } ``` **Response 200:** ```json { "resolved": true } ``` ### `POST /question/:requestID/reject` 拒绝问题请求。 **Request Body:** ```json { "action": "decline" } ``` **Response 200:** ```json { "resolved": true } ``` --- ## 7. Message ### `GET /session/:sessionID/message` 获取会话消息历史。读取 transcript JSONL 文件。 **Query Parameters:** | 参数 | 类型 | 说明 | |---|---|---| | `limit` | number | 返回条数(默认 50) | | `before` | string | 游标:返回此 UUID 之前的消息 | | `include_system` | boolean | 是否包含 system 消息(默认 false) | **Response 200:** ```json { "messages": [ { "uuid": "msg-uuid-1", "type": "user", "role": "user", "content": "Fix the auth bug", "timestamp": 1713001234567, "parent_uuid": null }, { "uuid": "msg-uuid-2", "type": "assistant", "role": "assistant", "content": [ { "type": "text", "text": "I'll fix the authentication bug." }, { "type": "tool_use", "id": "toolu_xxx", "name": "Edit", "input": { "file_path": "src/login.ts", "old_string": "...", "new_string": "..." } } ], "timestamp": 1713001234999, "parent_uuid": "msg-uuid-1", "usage": { "input_tokens": 5000, "output_tokens": 2000 } } ] } ``` Headers: ``` Link: ; rel="prev" X-Next-Cursor: msg-uuid-first ``` 实现方式:解析 `~/.claude/projects//sessions/.jsonl`,按 `parentUuid` 构建消息链。 ### `GET /session/:sessionID/todo` 获取会话中的 todo/plan 列表。从 transcript 中提取 `tool_use[name=TodoWrite/TodoRead]` 消息。 **Response 200:** ```json { "todos": [ { "id": "todo-1", "content": "Fix authentication bug", "status": "in_progress", "priority": "high" }, { "id": "todo-2", "content": "Add unit tests", "status": "pending", "priority": "medium" } ] } ``` ### `GET /session/:sessionID/diff` 获取会话中的文件变更。读取 csc 的 file-history 快照目录。 **Query Parameters:** | 参数 | 类型 | 说明 | |---|---|---| | `messageID` | string | 可选,指定消息 ID 的变更 | **Response 200:** ```json { "diffs": [ { "file": "src/login.ts", "status": "modified", "additions": 5, "deletions": 2, "patch": "--- a/src/login.ts\n+++ b/src/login.ts\n@@ -10,3 +10,6 @@..." } ] } ``` 注意:csc 的 file-history 没有索引,需要按 messageID 关联快照目录,实现复杂度中等。如果 messageID 未指定则返回全部变更。 --- ## 8. Provider ### `GET /provider` 列出所有可用的 AI providers 和模型。 **Response 200:** ```json { "connected": ["anthropic"], "default_model": "claude-sonnet-4-20250514", "providers": [ { "id": "anthropic", "name": "Anthropic", "connected": true, "models": [ { "id": "claude-sonnet-4-20250514", "name": "Claude Sonnet 4", "context_window": 200000, "max_output_tokens": 64000, "supports_images": true, "supports_streaming": true } ] } ] } ``` 数据来源: - 读取 `~/.claude/settings.json` 中的 API key 配置 - 通过子进程的 `initialize` response 中的 `models` 字段获取模型列表 ### `GET /provider/capabilities` 精简的模型能力列表(供模型选择 UI 使用)。 **Response 200:** ```json { "connected": [ { "provider_id": "anthropic", "provider_name": "Anthropic", "models": [ { "model_id": "claude-sonnet-4-20250514", "model_name": "Claude Sonnet 4", "context_window": 200000, "max_output_tokens": 64000, "supports_images": true, "input_cost_per_million": 3.0, "output_cost_per_million": 15.0 } ] } ] } ``` --- ## 9. Find ### `GET /find/file` 按文件名搜索文件。 **Query Parameters:** | 参数 | 类型 | 说明 | |---|---|---| | `query` | string | 搜索模式(必填) | | `dirs` | string | 是否包含目录("true" / "false") | | `limit` | number | 结果数量限制 | **Response 200:** ```json ["src/login.ts", "src/logout.ts", "tests/login.test.ts"] ``` --- ## 错误格式 所有错误响应使用统一格式: ```json { "error": "ERROR_CODE", "message": "Human readable error message" } ``` | HTTP Status | Code | 说明 | |---|---|---| | 400 | `BAD_REQUEST` | 请求参数错误 | | 401 | `UNAUTHORIZED` | 认证失败(需要 auth-token) | | 404 | `NOT_FOUND` | 会话/资源不存在 | | 409 | `CONFLICT` | 会话已在执行中(重复 prompt) | | 429 | `TOO_MANY_SESSIONS` | 超过 max-sessions 限制 | | 500 | `INTERNAL` | 内部错误 | | 503 | `SESSION_ERROR` | 子进程崩溃或无响应 | --- ## 认证 如果启动时指定了 `--auth-token`,所有请求需要携带: ``` Authorization: Bearer ``` 未携带或 token 不匹配返回 401。 --- ## 子进程生命周期 ``` POST /session → spawn: csc --print --input-format stream-json --output-format stream-json [--session-id ] [--resume ] [--permission-mode ] [--model ] → 状态: starting 子进程输出 system.init → 状态: running POST /session/:id/prompt → stdin: SDKUserMessage → stdout → SSE: assistant/result/control_request → 状态: running → active → idle POST /session/:id/abort → stdin: control_request { subtype: "interrupt" } → 如果无响应: SIGTERM idle-timeout 到期 → SIGTERM 子进程 → 状态: stopped DELETE /session/:id → SIGKILL 子进程 → 清理 SessionIndex ``` --- ## cs-cloud 路由映射 cs-cloud 作为反向代理,将客户端 API 映射到 csc serve: | cs-cloud 路由 | → csc serve 路由 | 客户端调用 | |---|---|---| | `POST /api/v1/conversations` | `POST /session` | `conversation.create` | | `GET /api/v1/conversations` | `GET /session` | `conversation.list` | | `GET /api/v1/conversations/{id}` | `GET /session/{id}` | `conversation.get` | | `PATCH /api/v1/conversations/{id}` | `PATCH /session/{id}` | `conversation.update` | | `DELETE /api/v1/conversations/{id}` | `DELETE /session/{id}` | `conversation.delete` | | `POST /api/v1/conversations/{id}/prompt` | `POST /session/{id}/prompt` | `conversation.prompt` | | `POST /api/v1/conversations/{id}/prompt/async` | `POST /session/{id}/prompt_async` | `conversation.promptAsync` | | `POST /api/v1/conversations/{id}/abort` | `POST /session/{id}/abort` | `conversation.abort` | | `GET /api/v1/conversations/{id}/messages` | `GET /session/{id}/message` | `conversation.messages` | | `GET /api/v1/conversations/{id}/todo` | `GET /session/{id}/todo` | `conversation.todo` | | `GET /api/v1/conversations/{id}/diff` | `GET /session/{id}/diff` | `conversation.diff` | | `POST /api/v1/conversations/{id}/shell` | `POST /session/{id}/shell` | `conversation.shell` | | `POST /api/v1/conversations/{id}/command` | `POST /session/{id}/command` | `conversation.command` | | `GET /api/v1/conversations/status` | `GET /session/status` | `conversation.status` | | `GET /api/v1/events` | `GET /event` | `event.stream` | | `GET /api/v1/permissions` | `GET /permission` | `interaction.permissions` | | `POST /api/v1/permissions/{id}/reply` | `POST /permission/{id}/reply` | `interaction.permissionRespond` | | `GET /api/v1/questions` | `GET /question` | `interaction.questions` | | `POST /api/v1/questions/{id}/reply` | `POST /question/{id}/reply` | `interaction.questionReply` | | `POST /api/v1/questions/{id}/reject` | `POST /question/{id}/reject` | `interaction.questionReject` | | `GET /api/v1/agents/models` | `GET /provider/capabilities` | `runtime.modelCapabilities` | | `GET /api/v1/agents/session-modes` | `GET /agent` | `runtime.sessionModes` | | `GET /api/v1/agents` | `GET /provider` | `runtime.agentRuntimes` | 以下 cs-cloud 路由不代理到 csc serve,由 cs-cloud 自有接口处理: | cs-cloud 自有路由 | 客户端调用 | 说明 | |---|---|---| | `GET /api/v1/runtime/health` | `runtime.health` | cs-cloud 进程自身健康检查 | | `GET /api/v1/runtime/files` | `runtime.fileList` | cs-cloud 直接读文件系统 | | `GET /api/v1/runtime/files/content` | `runtime.fileRead` | cs-cloud 直接读文件内容 | 以下透传路由不经过 `/api/v1` 前缀,cs-cloud 直接代理: | cs-cloud 透传路由 | → csc serve 路由 | 客户端调用 | |---|---|---| | `GET /path` | `GET /path` | `runtime.targetContext` | | `GET /vcs` | `GET /vcs` | `runtime.vcs` | | `GET /command` | `GET /command` | `runtime.commands` | | `GET /mcp` | `GET /mcp` | `runtime.mcpStatus` | | `GET /find/file` | `GET /find/file` | `runtime.findFiles` | | `POST /instance/dispose` | `GET /health`(带 shutdown 标记) | `runtime.instanceDispose` | --- ## 与 opencode serve 的差异 | 能力 | opencode serve | csc serve | 说明 | |---|---|---|---| | 进程模型 | 1 进程多会话 | 1 进程 = 1 子进程 | csc 单例架构限制 | | 会话存储 | SQLite 数据库 | JSONL 文件 + 内存索引 | csc 沿用现有 transcript | | `POST /session/:id/revert` | ✅ | ❌ | csc 有 `rewind_files` control-request 可部分替代,但无完整 revert | | `POST /session/:id/unrevert` | ✅ | ❌ | 同上 | | `POST /session/:id/summarize` | ✅ | ❌ | csc 有 compact 但无独立 summarize | | `POST /session/:id/fork` | ✅ | ❌ | 需 JSONL 文件复制 + UUID 重映射 | | `POST /session/:id/share` | ✅ | ❌ | csc 无 share 功能 | | `DELETE /session/:id/share` | ✅ | ❌ | 同上 | | `GET /session/:id/diff` | ✅(DB 索引) | ⚠️(读 file-history) | 无索引,性能较差 | | `GET /project` | ✅ | ❌ | csc 无项目数据库 | | `GET /pty` | ✅ | ❌ | PTY 在子进程内部,无法外部操作 | | `GET /tui/*` | ✅(14 个端点) | ❌ | 仅 TUI 模式有意义 | | `GET /experimental/*` | ✅(11 个端点) | ❌ | 高级功能,暂不支持 | | 文件操作 | csc serve 处理 | cs-cloud 自有接口 | `/api/v1/runtime/files` 不经过 csc |