依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類 共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git), 讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等 per-branch 筆記。 - 02-prd/ 21 個檔(PRD、features、market-analysis 等) - 03-design/ 18 個檔(design-spec、wireframes、flows 等) - 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等) - 07-delivery/ 3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup) 合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv, 但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
336 lines
8.7 KiB
Markdown
336 lines
8.7 KiB
Markdown
# API Spec — 對前端的 REST + WebSocket 端點
|
||
|
||
> **base URL**:`https://api.visiona.cloud`(Phase 1)/ `http://localhost:3001`(雛形)
|
||
> **認證**:`Authorization: Bearer <JWT>`(雛形可省略,走 `StaticAuthService`)
|
||
> **通用回應格式**:
|
||
> ```json
|
||
> { "success": true, "data": {...} }
|
||
> { "success": false, "error": { "code": "ERR_CODE", "message": "..." } }
|
||
> ```
|
||
|
||
---
|
||
|
||
## 1. Auth(雛形 stub)
|
||
|
||
### POST `/api/auth/login`
|
||
- 雛形:回 `501 { code: "NOT_IMPLEMENTED" }`
|
||
- Phase 1:`{ email, password }` → `{ user, access_token, refresh_token }`
|
||
|
||
### POST `/api/auth/register`
|
||
- 同上
|
||
|
||
### POST `/api/auth/logout`
|
||
- Phase 1:清 refresh token
|
||
|
||
### GET `/api/auth/me`
|
||
- 雛形:回 `demo-user` hard-coded
|
||
- Phase 1:從 JWT 取
|
||
|
||
---
|
||
|
||
## 2. Pairing
|
||
|
||
### POST `/api/pairing/token`
|
||
- Auth required(雛形:靜默通過)
|
||
- 雛形 Response:
|
||
```json
|
||
{ "success": false, "error": { "code": "NOT_IMPLEMENTED", "message": "Dev uses env VISIONA_PAIRING_TOKEN" } }
|
||
```
|
||
- Phase 1 Response:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"token": "pk_AbCd1234...",
|
||
"expires_at": "2026-04-21T13:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### GET `/api/pairing/status`
|
||
- 查詢當前 user 的 tunnel 連線狀態
|
||
- Response:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"connected": true,
|
||
"connected_at": "2026-04-21T12:00:00Z",
|
||
"last_seen_at": "2026-04-21T12:34:56Z",
|
||
"device_id": "dev-xxx",
|
||
"agent_version": "local-tool 1.2.3"
|
||
}
|
||
}
|
||
```
|
||
|
||
### GET `/api/pairing/tokens`
|
||
- List 當前 user 的所有 tokens
|
||
- Phase 1:回 array of `{ id, device_id, kind, created_at, last_seen_at }`
|
||
|
||
### DELETE `/api/pairing/tokens/:id`
|
||
- 撤銷指定 token
|
||
- Phase 1 實作;雛形 501
|
||
|
||
---
|
||
|
||
## 3. Devices
|
||
|
||
以下大部分端點**會被轉發到 local agent**。api-server 行為:
|
||
1. 檢查 user 有 tunnel 連線
|
||
2. 若 device_id 有傳,檢查 ownership
|
||
3. 透過 tunnel forward 請求到 local agent(沿用 POC `handleProxy`)
|
||
4. 回傳 local agent 的 response
|
||
|
||
路徑與回應格式**與 local-tool 相同**,前端改 base URL 即可。
|
||
|
||
### GET `/api/devices` — 列出當前本地掃到的裝置
|
||
### POST `/api/devices/scan` — 觸發重掃
|
||
### GET `/api/devices/:id` — 單一裝置
|
||
### POST `/api/devices/:id/connect`
|
||
### POST `/api/devices/:id/disconnect`
|
||
### POST `/api/devices/:id/flash` — 燒韌體(透過 tunnel)
|
||
### POST `/api/devices/:id/inference/start`
|
||
### POST `/api/devices/:id/inference/stop`
|
||
|
||
**雲端特有(非 tunnel forward)**:
|
||
|
||
### GET `/api/cloud/devices` — 列出「我在雲端綁過的 Device records」
|
||
- 與 `GET /api/devices` 不同:這個是查雲端 DB,不問 local agent
|
||
- 雛形:從 `InMemoryDeviceRepository` 回
|
||
- Response:`[{ id, name, device_type, serial_number, status, last_seen_at }]`
|
||
|
||
### POST `/api/cloud/devices/:id/rename`
|
||
- 改雲端上的 device name
|
||
|
||
---
|
||
|
||
## 4. Models
|
||
|
||
### GET `/api/models` — 列出 user 的 model
|
||
- 雲端模型(存 storage)+ preset models(硬編碼)
|
||
- Response:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"id": "abc-123",
|
||
"name": "YOLOv5 Face",
|
||
"target_chip": "kl520",
|
||
"file_size": 12345678,
|
||
"source": "uploaded",
|
||
"created_at": "..."
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### GET `/api/models/:id`
|
||
- Model 詳情
|
||
|
||
### POST `/api/models/init` — 初始化上傳
|
||
- Request: `{ name, file_size, checksum, target_chip, description? }`
|
||
- Response:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"model_id": "new-id",
|
||
"upload_url": "https://...presigned-put-url...",
|
||
"upload_expires_at": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
### POST `/api/models/:id/finalize`
|
||
- 在 presigned PUT 成功後呼叫
|
||
- api-server 驗證檔案已存在、size / checksum 對 → status 改 "ready"
|
||
|
||
### DELETE `/api/models/:id`
|
||
|
||
### POST `/api/models/:id/load-to-device`
|
||
- Body:`{ device_id }`
|
||
- api-server 產 presigned GET URL → 透過 tunnel 送 local agent 「下載並載入」
|
||
- 回傳 job status
|
||
|
||
---
|
||
|
||
## 5. Clusters(從 POC 搬)
|
||
|
||
### GET `/api/clusters`
|
||
### POST `/api/clusters`
|
||
- Body: `{ name, device_ids: [...] }`
|
||
### GET `/api/clusters/:id`
|
||
### DELETE `/api/clusters/:id`
|
||
### POST `/api/clusters/:id/devices`
|
||
### DELETE `/api/clusters/:id/devices/:deviceId`
|
||
### PUT `/api/clusters/:id/devices/:deviceId/weight`
|
||
### POST `/api/clusters/:id/flash`
|
||
### POST `/api/clusters/:id/inference/start`
|
||
### POST `/api/clusters/:id/inference/stop`
|
||
|
||
---
|
||
|
||
## 6. Camera / Media
|
||
|
||
與 local-tool 相同,全部透過 tunnel forward:
|
||
|
||
### GET `/api/camera/list`
|
||
### POST `/api/camera/start`
|
||
### POST `/api/camera/stop`
|
||
### GET `/api/camera/stream` — MJPEG(透過 tunnel streaming)
|
||
### POST `/api/media/upload/image`
|
||
### POST `/api/media/upload/video`
|
||
### POST `/api/media/upload/batch-images`
|
||
### GET `/api/media/batch-images/:index`
|
||
### POST `/api/media/seek`
|
||
|
||
---
|
||
|
||
## 7. System
|
||
|
||
### GET `/api/system/health`
|
||
- 雲端側:回 api-server 自己的健康 + tunnel 連線狀態
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"api_server": "ok",
|
||
"tunnel_connected": true,
|
||
"agent_last_seen_at": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
### GET `/api/system/info`
|
||
- 版本資訊
|
||
|
||
---
|
||
|
||
## 8. Converter
|
||
|
||
### 8.1 Phase 1 stub(既有,保留)
|
||
|
||
> 雛形 stub 路由;Phase 0.8 的真實整合改走 §8.2 `/api/conversion/*`,下列路由保留為 placeholder 待 Phase 1 視需要 supersede。
|
||
|
||
#### POST `/api/converter/jobs`
|
||
- Body:`{ source_model_key, target_chip, params? }`
|
||
- Response:`{ job_id, status: "queued" }`
|
||
|
||
#### GET `/api/converter/jobs`
|
||
- List user 的 jobs
|
||
|
||
#### GET `/api/converter/jobs/:id`
|
||
- Job 狀態
|
||
|
||
#### GET `/api/converter/jobs/:id/download`
|
||
- 下載產物(presigned URL redirect)
|
||
|
||
**詳細契約** → [`api-converter-contract.md`](api-converter-contract.md)
|
||
|
||
### 8.2 Phase 0.8 — `/api/conversion/*`(轉檔功能整合)
|
||
|
||
正式對接 kneron_model_converter scheduler + FAA delegated download:
|
||
|
||
- `POST /api/conversion/init` — multipart streaming proxy 到 converter,建 job
|
||
- `GET /api/conversion/{job_id}` — 查狀態(HTTP polling,frontend 間隔 2s)
|
||
- `POST /api/conversion/{job_id}/promote-to-models` — 「加到模型庫」
|
||
- `POST /api/conversion/{job_id}/download-token` — 換 browser 直連 FAA 的 delegated URL
|
||
- `GET /api/conversion/active` — 查當前 user 是否有 active job
|
||
|
||
**詳細契約** → [`api-conversion.md`](api-conversion.md)
|
||
**內部設計** → [`../conversion.md`](../conversion.md)
|
||
**ADR** → [`../adr/adr-014-conversion-integration.md`](../adr/adr-014-conversion-integration.md)
|
||
|
||
---
|
||
|
||
## 9. WebSocket
|
||
|
||
### WS `/ws/devices/events`
|
||
- 訂閱「裝置上下線」事件
|
||
- Server push:
|
||
```json
|
||
{ "type": "device.connected", "device_id": "xxx", "at": "..." }
|
||
{ "type": "device.disconnected", "device_id": "xxx", "at": "..." }
|
||
```
|
||
|
||
### WS `/ws/devices/:id/flash-progress`
|
||
- 燒錄進度(透過 tunnel 從 local agent 取)
|
||
|
||
### WS `/ws/devices/:id/inference`
|
||
- 推論結果串流
|
||
|
||
### WS `/ws/server-logs`
|
||
- log broadcast(沿用 local-tool 的 broadcaster)
|
||
|
||
### WS `/ws/system`
|
||
- 系統事件(server:shutdown-imminent 等)
|
||
|
||
### WS `/ws/clusters/:id/inference`
|
||
### WS `/ws/clusters/:id/flash-progress`
|
||
|
||
### WS `/ws/pairing/status`(新)
|
||
- 訂閱 tunnel 連線狀態變化
|
||
- Server push:
|
||
```json
|
||
{ "type": "tunnel.connected", "connected_at": "..." }
|
||
{ "type": "tunnel.disconnected", "reason": "network_error", "at": "..." }
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Storage(雛形 LocalFS 代理)
|
||
|
||
### GET `/storage/*filepath?expires=...&signature=...`
|
||
- LocalFS 的假 presigned GET
|
||
- 驗簽後讀檔回傳
|
||
|
||
### PUT `/storage/*filepath?expires=...&signature=...`
|
||
- LocalFS 的假 presigned PUT
|
||
- 驗簽後收 body 寫檔
|
||
|
||
**Phase 1**:直接由 S3 提供,不走 api-server。
|
||
|
||
---
|
||
|
||
## 11. 錯誤碼清單
|
||
|
||
| Code | HTTP | 說明 |
|
||
|------|------|------|
|
||
| `UNAUTHORIZED` | 401 | 未認證或 token 無效 |
|
||
| `FORBIDDEN` | 403 | 權限不足 |
|
||
| `NOT_FOUND` | 404 | 資源不存在 |
|
||
| `VALIDATION_FAILED` | 400 | 輸入驗證失敗 |
|
||
| `TUNNEL_DISCONNECTED` | 502 | Local agent 未連線 |
|
||
| `TUNNEL_ERROR` | 502 | Tunnel 傳輸錯誤 |
|
||
| `NOT_IMPLEMENTED` | 501 | 雛形尚未實作 |
|
||
| `RATE_LIMITED` | 429 | 請求過快(Phase 1)|
|
||
| `INTERNAL_ERROR` | 500 | 未預期錯誤 |
|
||
|
||
---
|
||
|
||
## 12. Pagination
|
||
|
||
對會變大的 list(models、devices、jobs)用 cursor-based:
|
||
|
||
```
|
||
GET /api/models?limit=50&cursor=...
|
||
Response:
|
||
{ "data": [...], "next_cursor": "..." | null }
|
||
```
|
||
|
||
雛形可先簡單回全部(in-memory);Phase 1 接 DB 時實作 cursor。
|
||
|
||
---
|
||
|
||
**雛形 MVP 清單**(必須有):
|
||
- `GET /api/system/health`
|
||
- `GET /api/pairing/status`
|
||
- `GET /api/devices` + 透過 tunnel forward
|
||
- `GET /api/models` + `POST /api/models/init` + `/finalize`(LocalFS)
|
||
- `/storage/*` 代理
|
||
- WS `/ws/devices/events`
|
||
- WS `/ws/pairing/status`
|
||
|
||
其他可以先 501 或 stub。
|