# REST API

Daemon 只監聽 loopback 位址——`127.0.0.1:17989` 與 `[::1]:17989`——區網 client 無法連到。Port 固定，不可設定。

同一個 daemon 在 `/` 提供 web 儀表板。儀表板在編譯時內嵌進執行檔，因此 `http://127.0.0.1:17989` 就是全部介面——沒有託管在外的前端。開發時可用 `AGENVOY_PAGE_DIR` 把內嵌版本換成磁碟上的檔案（`make dev`）。

自 v1.0.1 起儀表板可離線運作。`GET /sw.js` 提供 service worker，把內嵌資產預先快取到帶版本號的 cache；`GET /vendor/*` 則從 `~/.config/agenvoy/vendor/` 提供第三方資產（QuickUI、Font Awesome、Material Symbols，以及 nanomd／voice 腳本），該目錄由 daemon 啟動時下載一次，執行檔版本變動時重新下載。頁面載入不會向 CDN 取任何東西。在 `AGENVOY_PAGE_DIR` 模式下 `/sw.js` 改為提供一支自我卸載的 worker，清掉自己的 cache 並解除註冊，避免開發版被舊快取蓋掉。

標記 **local** 的端點另外要求請求來自 `127.0.0.1`/`::1`（`localhostOnly()` 守門）。這些端點管理憑證、設定檔或行程生命週期，設計對象是同機儀表板，不是遠端 client。未匹配的路由也套用同一道守門：未知的 `/v1/` 路徑回 JSON 404，其他未知路徑則回退到儀表板首頁。

v1.3.11 起，`Origin` header 不是 loopback 來源（`localhost`、`127.0.0.1`、`::1`）的 `POST`、`PUT`、`PATCH`、`DELETE` 在路由前即回 403 `[Denied] origin: ...`；`POST /v1/send` 與 `POST /v1/chat/completions` 的 `Content-Type` 不是 `application/json` 時回 415 `[Invalid] Content-Type: ...`。沒有 `Origin` header 的請求（例如 `curl`）不受前一項檢查影響。

## Agent 執行

| Method | 路徑 | 說明 |
|---|---|---|
| `POST` | `/v1/send` | 執行一次 agent 請求 |
| `POST` | `/v1/chat/completions` | 無狀態、相容 OpenAI 的 chat completion |
| `GET` | `/v1/info/version` | 編譯時寫入的版本（`{version, dev}`）；未打 tag 的組建 `dev` 為 true |
| `GET` | `/v1/log` | SSE 串流。不帶 query 時只送 daemon `slog` 記錄（`EventDaemonLog` frame，`source` 即 level）——與 TUI 標題列同一路資料。其中包含新對話驗證碼，因此 daemon frame 只提供給 loopback 呼叫端。`?sessions=a,b` 在同一條連線附加這些 session 的事件；`replay=0` 跳過回補、`daemon=0` 去掉 daemon frame。遠端呼叫端或帶 `daemon=0` 時必須帶 `sessions`（否則回 400）。v1.3.8 起 session 事件附上後端格式化好的文字——`trace`（思考或工具呼叫行，例如 `⏵ API(Weather)`）、工具呼叫與確認的 `tool_display`，以及確認事件的 `confirm_title`、`restricted_lines`、`restricted_note`——各介面顯示的用詞一致 |
| `GET` | `/v1/daemon` | **local** — `{content}`：最近 28 天的 `daemon.log` 行。`?from=` / `?to=`（`yyyy-MM-dd-HH-mm`，本地時間）縮限時間範圍，`?keyword=` 只保留包含該字串的行（不分大小寫）；時間格式錯誤回 400 |
| `GET` | `/v1/system/update` | **local** — v1.0.11 新增。回 `{version, latest, update_available}`；`latest` 以追蹤 GitHub `releases/latest/download/x` 轉址取得（查不到時回 502 並附目前執行中的 `version`）|
| `POST` | `/v1/system/update` | **local** — v1.0.11 新增。開一個終端機視窗執行 `agen update`，回 `202 {status:"opened"}`；更新本身跑在該終端機內，不在 daemon 內。macOS 用 Terminal.app、WSL 啟動 Windows 終端機、Linux 依序嘗試 `xdg-terminal-exec`、`x-terminal-emulator`、`gnome-terminal`、`konsole`、`xfce4-terminal`、`kitty`、`alacritty`、`wezterm`、`xterm`。找不到可用終端機回 501 |
| `GET` | `/v1/mcp/tools` | 列出已連線 MCP server 註冊進來的工具（`mcp__*`） |

### `POST /v1/send` 語意

Body 為 `{content, stream?, session_id?, persist?, model?, reasoning?, skill?, work_dir?, rule?, task_hash?, exclude_tools?, enable_edit?, enable_sandbox?, enable_markdown?}`，`content` 必填。v1.3.9 起以 `stream`、`rule`、`task_hash` 取代 `sse`、`system_prompt`、`pending_task`；舊名稱仍視為別名接受，之後會移除。v1.3.5 起 `model` 與 `reasoning` 只作用於這次請求，不改 session 預設：未帶 `model` 用 session 模型，`auto` 即使 session 已固定模型仍交給 dispatcher，未帶 `reasoning` 用 session 等級。Web 對話的選擇存在瀏覽器 cookie，隨請求送到這裡。`work_dir` 預設為家目錄，必須解析到既有的目錄（否則回 400）。`rule` 為該次執行附加額外系統指示；`enable_markdown:false`（v1.3.9，預設 `true`）會再附加一條規則，要求不含 markdown 語法的純文字回覆。`task_hash` 在該 pending 任務仍有背景 job 時接續它。若 session 仍在執行中，新請求會以 steer 訊息附加到該次執行，而不是另起一次，並立即回 `{session_id}`（`steer:true` 欄位移除於 v1.3.9）。其餘情況 `stream:true` 以串流送出執行事件；未帶時於執行結束後回 `{text, model, usage, session_id}`，被取消時回 `{session_id, canceled:true}`（失敗回 500 並帶 `error`）。

v1.3.9 起，帶 `enable_edit` 或 `enable_sandbox`，或抵達時沒有開啟中的網頁確認 listener 的請求，以 API auto mode 執行：一律不跳確認；未設 `enable_edit:true` 時唯讀（擋寫入與非唯讀指令）；未設 `enable_sandbox:false` 時留在沙箱（擋 `$HOME` 外路徑、`sudo`、`sandbox: false`）；不提供 `ask_user`、`store_secret` 與背景工具；請求開始時清除該 session 的路徑授權。被擋的呼叫回 `[Denied]` 並指出可放行的參數。v1.3.9 之前，這類無人值守的請求會直接放行寫入與受限存取。v1.3.11 起 `enable_sandbox:false` 只接受 loopback 連線，其他位址的請求回 403 `[Denied] enable_sandbox: false is accepted only from this machine`。

| `persist` | `session_id` | 結果 |
|---|---|---|
| `false`（預設） | 空 | 建立 `temp-<uuid>`，30 分鐘無變動後刪除 |
| `true` | 空 | 建立 `chat-<uuid>`，保留 |
| 任意 | 有給 | 使用指定的 `session_id`（忽略 `persist`） |

`chat` 旗標與 `http-` 前綴已於 v1.0.11 移除：以 HTTP 建立的持久 session 改用儀表板同樣的 `chat-` 前綴，所有非 CLI 的持久 session 統一成同一種前綴。既有的 `http-` session 仍可透過 `session_id` 指名繼續使用。

```bash
curl --fail-with-body -sS \
  -H 'Content-Type: application/json' \
  -d '{"content":"List the available tools","persist":false}' \
  http://127.0.0.1:17989/v1/send
```

### `POST /v1/chat/completions`：OpenAI 相容的路由端點

`/v1/chat/completions` 為無狀態：需要延續脈絡時，每次請求都要帶上先前訊息。`reasoning_effort` 接受 `none` `low` `medium` `high` `xhigh` `max`（以及別名 `minimal` `extra` `ultra`）；省略或無法辨識的值會回退到模型選擇器為該請求挑選的等級（未挑選時為 `medium`）。`model` 預設為 `auto`。

預設情況下 completion 不帶任何 Agenvoy 自身的工具：只提供 client 送來的 `tools`。帶上標頭 `X-Agenvoy-Agent: 1`（或 `true` / `yes`）時改以 Agenvoy agent 執行——使用 Agenvoy 的 system prompt 與 TUI 專用以外的全部工具。不論哪種模式，此端點都會略過工具確認。

任何 OpenAI client 都能把 Agenvoy 當成模型路由器：把 SDK 的 base URL 設為 `http://127.0.0.1:17989/v1`。此端點不檢查 API key，填任意值即可。`GET /v1/models` 列出所有已註冊模型與 `auto`（見模型端點）。

| `model` | 由哪個模型回應 | Fallback |
|---|---|---|
| `auto`（預設） | dispatcher 模型依請求的工作類型挑選 worker 模型（見模型路由） | 送出失敗時，由優先序中下一個不同供應商、且通過 10 秒健康檢查的模型接手，最多四輪 |
| `<provider>@<model>` | 直接使用該已註冊模型；未註冊的名稱回 `[Not found] model` | 無 |

逾時與 rate limit 會先在同一個模型重試最多三次，之後才 fallback。未帶 `stream` 時回傳單一 `chat.completion` 物件，fallback 後仍失敗則回 `502`、`type: upstream_error`；`stream: true` 時以 SSE 回傳 `chat.completion.chunk`，最後一筆為 `data: [DONE]`。`usage` 回報 `prompt_tokens`（含快取讀取）、`completion_tokens` 與 `prompt_tokens_details.cached_tokens`。client 送了 `tools` 且模型呼叫其中之一時，回應帶 `tool_calls` 與 `finish_reason: "tool_calls"`，由 client 自行執行工具。system prompt 來自 Zed 時，以其中列出的專案根目錄為工作目錄，否則使用家目錄。

LiteLLM 是把多家供應商包成單一 OpenAI 格式 API 的 proxy server，OpenRouter 是在多家供應商間轉送請求的雲端 API。Agenvoy 的端點跑在你自己的電腦上，在你於 Agenvoy 註冊的模型之間路由，包含 Claude Code 與 Codex 訂閱供應商，並依工作類型挑選模型。
