22 KiB
csc serve — HTTP API 接口规范
csc serve启动一个 HTTP 服务器,为 IDE 插件和 Web 客户端提供 REST API。 内部架构为 子进程模式:每个会话 spawn 一个csc --print --input-format stream-json --output-format stream-json子进程,HTTP 层作为 NDJSON 协议的桥梁。
设计原则
- API 路径兼容 opencode serve:cs-cloud 通过路由表映射,客户端无需感知后端差异
- 子进程隔离:1 session = 1 子进程,无进程内多会话
- 子进程复用:同一 session 的多次 prompt 复用同一子进程(bridge 模式已验证)
- JSONL 持久化:会话数据沿用 csc 现有的 transcript JSONL 文件
- 文件系统操作由 cs-cloud 自有接口处理:
/api/v1/runtime/files等不经过 csc serve
CLI 入口
csc serve [options]
Options:
--port <port> 监听端口(默认 0 = 自动分配)
--host <host> 监听地址(默认 127.0.0.1)
--auth-token <token> Bearer token 认证
--workspace <dir> 默认工作目录
--max-sessions <n> 最大并发会话数(默认 32)
--idle-timeout <ms> 空闲会话超时(默认 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:
{
"status": "ok",
"version": "4.0.1",
"uptime_ms": 12345,
"active_sessions": 3
}
2. Info
GET /path
获取路径信息。
Response 200:
{
"home": "/Users/user",
"state": "/Users/user/.claude",
"config": "/Users/user/.claude",
"directory": "/path/to/workspace"
}
GET /vcs
获取 VCS 信息。
Response 200:
{
"branch": "main"
}
GET /command
列出可用 slash 命令。
Response 200:
[
{ "name": "compact", "description": "Compact conversation" },
{ "name": "clear", "description": "Clear conversation" },
{ "name": "help", "description": "Show help" }
]
数据来源:子进程 initialize response 中的 commands 字段。
GET /agent
列出可用的 agent 模式。
Response 200:
[
{ "id": "main", "name": "Main Agent" },
{ "id": "plan", "name": "Plan Mode" }
]
数据来源:子进程 initialize response 中的 agents 字段。
GET /mcp
获取 MCP 服务器状态。通过子进程 mcp_status control-request 获取。
Response 200:
{
"servers": [
{ "name": "filesystem", "status": "connected", "tools": 5 },
{ "name": "github", "status": "disconnected", "error": "Connection refused" }
]
}
3. Session
POST /session
创建新会话。spawn 一个 csc --print 子进程。
Request Body:
{
"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:
{
"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:
{
"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:
{
"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:
{ "error": "session not found" }
PATCH /session/:sessionID
更新会话属性。
Request Body:
{
"title": "New title",
"model": "claude-sonnet-4-20250514",
"permission_mode": "bypassPermissions"
}
model 和 permission_mode 的变更通过子进程的 control_request 实现(set_model / set_permission_mode)。
Response 200:
{
"session_id": "uuid-1",
"title": "New title",
"model": "claude-sonnet-4-20250514",
"permission_mode": "bypassPermissions"
}
DELETE /session/:sessionID
删除会话。kill 子进程 + 清理持久化索引。
Response 200:
{ "deleted": true }
GET /session/status
批量获取所有会话的状态。
Response 200:
{
"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:
{
"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:
{ "aborted": true }
POST /session/:sessionID/shell
在会话上下文中执行 shell 命令。转换为 prompt 发送到子进程。
Request Body:
{
"command": "npm test"
}
Response 200 (streaming): 同 prompt 的 SSE 流。
POST /session/:sessionID/command
执行 slash 命令。转换为对应 prompt 发送到子进程。
Request Body:
{
"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:
{
"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):
{
"behavior": "allow",
"updated_input": { "command": "rm -rf node_modules" }
}
Request Body (deny):
{
"behavior": "deny",
"message": "Command not allowed"
}
Response 200:
{ "resolved": true }
6. Question
csc 通过 control_request { subtype: "elicitation" } 实现 MCP elicitation 交互,映射为 question 端点。
GET /question
列出所有待处理的问题请求。
Response 200:
{
"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:
{
"action": "accept",
"content": { "key": "sk-xxx" }
}
Response 200:
{ "resolved": true }
POST /question/:requestID/reject
拒绝问题请求。
Request Body:
{
"action": "decline"
}
Response 200:
{ "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:
{
"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: </session/uuid-1/message?before=msg-uuid-first>; rel="prev"
X-Next-Cursor: msg-uuid-first
实现方式:解析 ~/.claude/projects/<project>/sessions/<sessionID>.jsonl,按 parentUuid 构建消息链。
GET /session/:sessionID/todo
获取会话中的 todo/plan 列表。从 transcript 中提取 tool_use[name=TodoWrite/TodoRead] 消息。
Response 200:
{
"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:
{
"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:
{
"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 配置 - 通过子进程的
initializeresponse 中的models字段获取模型列表
GET /provider/capabilities
精简的模型能力列表(供模型选择 UI 使用)。
Response 200:
{
"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:
["src/login.ts", "src/logout.ts", "tests/login.test.ts"]
错误格式
所有错误响应使用统一格式:
{
"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>
未携带或 token 不匹配返回 401。
子进程生命周期
POST /session
→ spawn: csc --print --input-format stream-json --output-format stream-json
[--session-id <id>] [--resume <id>] [--permission-mode <mode>]
[--model <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 |