Skip to main content

README.md

# CMDC Gateway

HTTP + SSE + WebSocket 协议网关,让任何语言的外部系统接入 CMDC Agent 能力。

## 安装

```elixir
# mix.exs
def deps do
  [{:cmdc_gateway, "~> 0.6"}]
end
```

## 快速开始

### 1. 配置

```elixir
# config/config.exs
config :cmdc_gateway, CMDCGateway.Plugs.Auth,
  api_keys: %{
    "my-api-key" => "default-tenant"
  }

# 可选:限流配置
config :cmdc_gateway, CMDCGateway.RateLimiter,
  rpm: 60,          # 每分钟最大请求数
  enabled: true
```

### 2. 启动服务器

```elixir
# 在你的 Application 中添加 Cowboy
children = [
  {Plug.Cowboy, scheme: :http, plug: CMDCGateway.Router, options: [port: 4000]}
]
```

### 3. 调用 API

```bash
# 创建 Session
curl -X POST http://localhost:4000/v1/sessions \
  -H "X-API-Key: my-api-key" \
  -H "Content-Type: application/json" \
  -d '{"model": "deepseek:deepseek-chat", "systemPrompt": "你是助手"}'

# 发送 Prompt(异步返回 202)
curl -X POST http://localhost:4000/v1/sessions/{session_id}/prompt \
  -H "X-API-Key: my-api-key" \
  -H "Content-Type: application/json" \
  -d '{"text": "你好"}'

# 订阅 SSE 事件流(实时接收 Agent 回复)
curl -N http://localhost:4000/v1/sessions/{session_id}/events \
  -H "X-API-Key: my-api-key"
```

## 架构

```
外部系统 (Python / Node.js / Go / 浏览器)
  │  HTTP + SSE / WebSocket
  ▼
CMDCGateway.Router
  ├── Auth Plug          API Key → tenant_id
  ├── RateLimiter Plug   令牌桶限流
  ├── SessionStore       ETS session_id ↔ Agent pid
  ├── EventTranslator    内部事件 → 对外 JSON + RAG/GraphRAG trace events
  ├── SSEHandler         chunked SSE 推送 + 心跳
  ├── WSHandler          双向 WebSocket
  ├── A2A                JSON-RPC task + webhook lifecycle
  ├── WorkflowReplay     只读 AgentOps run event replay
  ├── Meter              per-key 用量计量
  └── CallbackTool       HTTP 回调工具代理
  │
  │  Elixir in-process 调用
  ▼
CMDC 核心库 (EventBus 驱动)
```

## 端点概览

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/healthz` | 健康检查(无需认证) |
| GET | `/.well-known/agent.json` | Agent Card / A2A 能力发现(无需认证) |
| POST | `/v1/a2a/tasks/send` | A2A JSON-RPC 同步 task |
| POST | `/v1/a2a/tasks/sendSubscribe` | A2A JSON-RPC SSE task |
| POST | `/v1/a2a/tasks/sendWithWebhook` | A2A JSON-RPC webhook task |
| GET | `/v1/a2a/tasks/:task_id` | A2A task 短期状态兜底查询 |
| POST | `/v1/sessions` | 创建 Session |
| GET | `/v1/sessions/:id` | 查询 Session 状态 |
| GET | `/v1/sessions/:id/status` | 完整状态:队列、审批、event cursor |
| DELETE | `/v1/sessions/:id` | 删除 Session |
| POST | `/v1/sessions/:id/prompt` | 发送 prompt(异步 202) |
| GET | `/v1/sessions/:id/events` | SSE 事件流,支持 `since/types` session replay |
| GET | `/v1/groups/:group_id/events` | group SSE live stream |
| GET | `/v1/workflows/runs/:run_id/events` | AgentOps workflow event replay |
| WS | `/v1/sessions/:id/ws` | WebSocket 双向通信 |
| POST | `/v1/sessions/:id/approve` | 审批通过 |
| POST | `/v1/sessions/:id/reject` | 审批拒绝 |
| POST | `/v1/sessions/:id/respond` | 回答 Agent 提问 |
| POST | `/v1/sessions/:id/switch_model` | 异步切换模型,支持严格 `providerOpts` |
| POST | `/v1/sessions/:id/tools/batch` | 批量 attach/detach/replace allowlisted Tool |
| PATCH | `/v1/sessions/:id/plugins/:plugin/opts` | allowlisted Plugin opts 热更新 |
| POST | `/v1/sessions/:id/steer` | 中段注入 steering |
| POST | `/v1/sessions/:id/abort` | 异步中止,可选 kill tools / clear queue |
| POST | `/v1/sessions/:id/checkpoints` | 保存 checkpoint |
| POST | `/v1/sessions/resume` | 从 checkpoint 恢复 |
| GET | `/v1/provider_profiles` | admin-only Provider Profile 列表 |
| POST | `/v1/provider_profiles` | admin-only Provider Profile 注册 |
| DELETE | `/v1/provider_profiles/:name` | admin-only Provider Profile 删除 |
| GET | `/v1/sessions/:id/stats` | 用量统计 |
| GET | `/v1/sessions/:id/messages` | 对话历史 |
| POST | `/v1/sessions/:id/tools` | 注册回调工具 |

> **完整 API 文档**(每个端点的详细输入/输出/字段说明/示例)见 **[API.md](API.md)**。

## 0.6 边界

- `POST /v1/sessions` 支持 core 0.6 安全白名单字段:`groupId`、`eventBufferSize`、`maxSteeringQueue`、`hibernateAfterMs`、`messages` 等。
- `messages` 历史导入只接受 `user` / `assistant` / `tool_result` JSON,禁止 `system`、`__struct__` 和任意 module 字段。
- `skillSelector`、checkpoint backend、provider resolver、plugin module 注入不通过 public JSON;这些必须由宿主 Elixir app 服务端配置。
- `?mode=audit` 只投影当前 EventBus 事件;system-wide telemetry audit 由宿主 app 自行 attach。
- group SSE 不提供 replay;断线补帧使用 session SSE 的 `since/types`。
- `CMDC.monitor/1` / `demonitor/2` 是进程内集成能力,不提供 HTTP endpoint。

## SDK 兼容

- `cmdc_gateway 0.6` 对既有 REST/SSE/WebSocket SDK 是加性升级:旧字段不改名,新字段可忽略。
- `POST /v1/sessions/:id/prompt` 仍返回 202,旧客户端继续通过 SSE/WS 读取 `agent_end`。
- `GET /v1/sessions/:id/events` 不带 `since/types` 时仍是实时流;需要断线补帧时启用 `eventBufferSize` 并保存 `lastEventIndex`。
- `POST /v1/sessions/:id/tools` 仍是 HTTP callback tool 注册;core Tool 批量控制使用 `/tools/batch`。
- `workingDir`、`providerOpts`、Plugin opts、Provider Profile opts 都经过服务端边界校验;SDK 不应发送任意模块名、resolver 函数或动态 atom key。

## 环境变量

| 变量 | 说明 | 默认值 |
|------|------|--------|
| `GATEWAY_PORT` | HTTP 监听端口 | `4000` |
| `GATEWAY_API_KEYS` | 逗号分隔的 API Key 列表 | — |

## Docker 部署

```dockerfile
FROM elixir:1.17-otp-27-alpine AS build
WORKDIR /app
COPY . .
RUN mix deps.get --only prod && MIX_ENV=prod mix release

FROM alpine:3.19
COPY --from=build /app/_build/prod/rel/cmdc_gateway /app
ENV GATEWAY_PORT=4000
EXPOSE 4000
CMD ["/app/bin/cmdc_gateway", "start"]
```

```bash
docker build -t cmdc-gateway .
docker run -p 4000:4000 -e GATEWAY_API_KEYS=my-key cmdc-gateway
```

## 许可证

Apache-2.0 + Commons Clause — 详见 [LICENSE](../LICENSE)。