ZettCode server
zettcode-server 把 ZettCode 编程代理放到 HTTP 后面。它跑的是和终端、SDK 同一个代理 —— 同样的工具、插件、MCP 服务、 skills、项目指令和会话存储 —— 所以从 HTTP 开始的对话可以在终端里接着开,反过来也一样。
它只是一层薄壳:每个端点都驱动 zettcode.sdk,再由 SDK 驱动已发布的 zett-agent 运行时。服务器自己没有 模型循环、工具和会话格式。
安装与运行
uv tool install zettcode
zettcode-server --workspace ~/projects/api --port 3290一个包,两个命令:uv tool install zettcode 会装上终端的 zettcode 和这里的 zettcode-server,所以服务器不用另装, 而且永远和 agent 是同一个版本。
zettcode-server 0.1.11 · http://127.0.0.1:3290 · workspace /home/you/projects/api · permission deny · token off默认只绑定 127.0.0.1。在 --host 指定之前,别的机器访问不到;一旦对外开放,就应该同时给出 token。
| 选项 | 含义 |
|---|---|
-w, --workspace DIR | 代理工作的目录。一个进程服务一个工作区,因为 SDK 使用进程的工作目录。 |
--host, --port | 监听地址;默认 127.0.0.1:3290。 |
--token | 除 /health 外每个端点要求的 Bearer token;默认取 $ZETTCODE_SERVER_TOKEN。 |
--permission {deny,ask,safe,full} | 运行开始时的 shell 权限,默认 deny。safe 把每条命令放进只能写工作区和 /tmp 的沙箱里跑,full 用你自己的权限跑;full 的旧名 auto 仍然可用;客户端可以按请求更改。 |
--harness | 启动时就打开 just-in-time harness:模型可以写并运行小的 Python 工具。默认关闭;PATCH /settings 发 {"harness": true} 可以改。 |
--model, --effort | 第一次运行使用的模型和推理强度。 |
--config FILE | 使用指定的配置文件代替 ~/.zettcode/config.toml。 |
--log-level | uvicorn 的日志级别。 |
一个工作区,一个进程
服务器只服务启动时所在的工作区。一个进程里跑两个工作区是冲突而不是特性:代理的工具按进程工作目录解析路径。 第二个项目就再起一个服务器。
一台新机器
服务器在还没有任何模型时也能启动。/health 说 ready: false,/models 是空的,任何需要代理的端点回答 409 no_model —— 而模型端点照常工作,所以正在用这个服务器的编辑器可以把模型配好:
curl -X POST http://127.0.0.1:3290/models \
-H 'content-type: application/json' \
-d '{"model": "gpt-5-mini", "token": "sk-…", "display_model": "Fast"}'第一个模型会构建出代理,/health 之后就是 ready: true。条目写进配置文件,键名和 /config add 一致,所以终端也能看到。 可以继续添加、修改(没给新 token 就保留旧的)或删除 —— 最后一个不能删,因为没有模型的配置无法创建 client。 PATCH /settings {"model": "Fast"} 就是从中选择,选择对下一次 prompt 生效。
第一个请求
curl http://127.0.0.1:3290/health{"status": "ok", "version": "0.1.0", "workspace": "/home/you/projects/api", "session": "01a15…", "busy": false}发一个 prompt 并观察它:
curl -N -X POST http://127.0.0.1:3290/prompts \
-H 'content-type: application/json' \
-d '{"text": "总结一下 README", "approvals": {"mode": "deny"}}'event: event
data: {"type":"text_delta","text":"README 说明了"}
event: event
data: {"type":"tool_started","call_id":"call-1","name":"read_file","arguments":{"file_path":"README.md"}}
event: result
data: {"text":"README 说明了 …","session_id":"01a15…","usage":{…},"tools":[…],"stop":null}每一帧的含义见事件流,端点清单见 API 参考, TypeScript 客户端把这一切包好,供编辑器或 Node 程序使用。 VS Code 扩展 就是基于该客户端的完整编辑器界面: 串联的工具调用、可点击的文件、审批和最近会话。
客户端能做什么
- 运行 prompt 并流式读取事件;从中断的地方继续一个请求。
- 在运行中回答代理:批准或拒绝 shell 命令、回答模型提出的问题,或者用新指令 steer 这次请求。
- 管理会话:列出、打开、新建、重命名、fork、压缩、导出成网页、读取历史。
- 在运行之间切换模型、推理强度和权限级别。
- 测量:token 用量、上下文报告、已加载的插件。
可交互这一点才让它和终端对等:需要回答的运行处于挂起状态,而不是被猜一个答案。见 回答一个运行。
让别的工具连上来
API 就是普通的 HTTP 和 JSON,编辑器扩展、CI 任务或脚本都可以直接使用。不只在 loopback 上时给它一个 token,并按 text/event-stream 读取:
const response = await fetch("http://127.0.0.1:3290/prompts", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ text: "修复失败的测试", approvals: { mode: "ask" } }),
});
const reader = response.body!.getReader();
// 每个 SSE 块以空行结束;从中解析 `event:` 和 `data:`。安全
- 默认只监听 loopback,启动服务器不会把 shell 暴露到网络上。
- shell 命令默认
deny:prompt 不说明怎么回答命令之前,什么都跑不了。 - token 只有一行:
--token或$ZETTCODE_SERVER_TOKEN,常量时间比较,除/health外都要它。它是明文 HTTP 上的 Bearer token,客户端不在同一台机器时请走隧道或终止 TLS 的反向代理。 - 一个会话里同时只有一轮运行:同一个会话里运行进行中再发 prompt 会得到
409 busy,而不是把两条请求交错进同一个会话。 另一个会话就是另一轮运行:运行中新建或打开别的会话不会中断它,GET /sessions会告诉你哪些会话在跑。