feat(agent): align HM to AM's coding_a2a_agent contract

Adapt HM's template-agent integration to AM's actual CODING_A2A API (per their
doc), keeping it isolated in agent_template_runtime.go:

- start payload -> AM's POST /agents { name, template:"coding_a2a_agent",
  framework:"A2A", config:{user_id,...}, env } with the template .md folded into
  env.AGENT_INSTRUCTION_TEXT, template_key -> AGENT_ROLE_NAME, model gateway via
  OPENAI_BASE_URL + MODEL_NAME (OPENAI_API_KEY left to the client per A2A request).
- response parse -> access_info.domain/external_ip -> subdomain, namespace/name
  -> runtime_id; AM issues no access_token (client uses A2A api_key).
- env names aligned to AM: GIT_DEFAULT_BRANCH, POSTGRES_* (was PG_*),
  AZURE_BLOB_ACCOUNT_NAME/CONTAINER/ACCOUNT_KEY (was BLOB_*); source keys aligned
  to the resource-binding form (db_name/username/database_password/access_key).
  Only AM-supported types (git/mysql/postgres/azure-blob); vm/redis/mongo/bucket
  now rejected as unsupported until AM adds them.
- frontend: resources page splits DB into MySQL/PostgreSQL (correct provider),
  drops vm; deploy page hides unsupported resource types.
- docs: AM contract + client doc updated to the real env names, payload, and the
  A2A direct-connect (message/send · message/stream) + api_key auth.
- tests updated for the new env names + AM payload/response shape. All green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 15:30:33 +08:00
co-authored by Claude Opus 4.8
parent e21cc1e81e
commit 074a3cc7e7
9 changed files with 213 additions and 165 deletions
+34 -34
View File
@@ -16,41 +16,40 @@
HM 用 `Authorization: Bearer <service_token>` 调 AM。统一响应信封建议 `{ "success": true, "data": {...} }`(HM 会从 `data` 取值;失败给 `{ "success": false, "message": "..." }`)。
### 1.1 启动 agent(核心)
`POST {AM}/api/agent/agents/start`
### 1.1 启动 agent(核心,已对齐你们的 `POST /agents`)
`POST {AM}/agents`
**请求体(HM 发):**
**请求体(HM 实际发送的,已按你们 CODING_A2A §2 对齐):**
```json
{
"manager_deployment_id": "dep_b5fab27e9255", // HM 侧记录 id,回调/排查用
"template_key": "architect", // 模板标识
"agent_definition": "---\nname: architect\nmodel: opus\n...\n---\n<Agent_Prompt>...</Agent_Prompt>",
"model": "opus", // 从模板 frontmatter 解析(可空)
"env": { // 资源配置,注入 agent 的 .env(见 §2)
"GIT_REPO_URL": "https://github.com/owner/repo",
"GIT_TOKEN": "ghp_…",
"VM_HOST": "1.2.3.4", "VM_USER": "deploy", "VM_PASSWORD": "…"
"name": "dep-b5fab27e9255", // 由 HM 部署 id 派生(DNS 安全)
"template": "coding_a2a_agent",
"framework": "A2A",
"config": {
"user_id": "22",
"manager_deployment_id": "dep_b5fab27e9255",
"callback_url": "https://code.xinghanlab.com/api/agent/callbacks/runtime-events"
},
"callback_url": "https://code.xinghanlab.com/api/agent/callbacks/runtime-events"
"env": {
"AGENT_ROLE_NAME": "architect",
"AGENT_INSTRUCTION_TEXT": "---\nname: architect\n---\n<Agent_Prompt>...</Agent_Prompt>",
"OPENAI_BASE_URL": "https://code.xinghanlab.com/v1",
"MODEL_NAME": "gpt-5.4",
"GIT_PROVIDER": "github", "GIT_REPO_URL": "...", "GIT_TOKEN": "…",
"MYSQL_HOST": "...", "MYSQL_PASSWORD": "…"
}
}
```
**响应 data(AM 返回):**
```json
{ "runtime_id": "rt_xxx", "subdomain": "https://abc123.agents.example.com", "access_token": "…", "status": "running" }
```
- `runtime_id`:AM 侧实例 id(HM 存,用于后续 status/stop/delete)。HM 也接受 `agent_id`/`id`/`deployment_id` 作为别名。
- `subdomain`:agent 对外**唯一可直连地址**(完整 URL 最佳)。HM 也接受 `address`/`url` 别名。
- `access_token`:客户端连 agent 时出示的令牌(见 §3)。HM 也接受 `token` 别名。
- `status`:`running`/`starting`/… HM 也接受 `runtime_status` 别名。
**响应(HM 解析你们返回的字段):**
- `runtime_id` ← `runtime_id`/`agent_id`/`id`/`name`/`namespace`(取到的实例标识,用于 status/stop/delete)。
- `subdomain` ← `subdomain` 或 `access_info.domain` / `access_info.external_ip`(agent 直连地址)。
- `access_token` ← `access_token`/`token`(你们当前不返回 → HM 留空,客户端用 A2A `api_key`,见 §3)。
- `status` ← `status`/`runtime_status`(取不到默认 `running`)。
**AM 启动时必须做的:**
1. 用 `agent_definition`(.md:frontmatter + 系统提示)作为该 agent 的**角色/行为定义**(等价 Claude Code subagent)。
2. 把 `env` 注入 agent 的 `.env`/进程环境,**agent 只读 env 即知道有哪些资源**。
3. 分配唯一子域名 + 生成 access_token,**agent 端自行校验该 token**(§3)。
4. 该 agent 用模型时**调 HM 的 `/v1/*`**(带用户身份,HM 计费)——不要直连上游模型。
**AM 启动时做的(你们已实现):**用 `env.AGENT_INSTRUCTION_TEXT`+`AGENT_ROLE_NAME` 设角色;注入 env;agent 用模型走 `OPENAI_BASE_URL`(=HM `/v1`)。
> ⚠️ **provisioning 是同步的**:HM 启动调用超时默认 60s(`AGENT_RUNTIME_START_TIMEOUT_SECONDS`),请在该时间内完成起容器+分配地址并返回。
> ⚠️ provisioning 同步:HM 超时默认 60s(`AGENT_RUNTIME_START_TIMEOUT_SECONDS`)。
### 1.2 取 agent 状态
`GET {AM}/api/agent/agents/{runtime_id}`
@@ -68,19 +67,20 @@ HM 用 `Authorization: Bearer <service_token>` 调 AM。统一响应信封建议
HM 把用户绑定的资源按**资源类型 + provider**解成固定 env 名(代码 `controller/agent_template_env.go`)。**AM 的 agent 模板按这些名字读 env**;用户在 HM 只填业务字段、看不到 env 名。
> **2026-06-04 已对齐 AM 的 `coding_a2a_agent`**:env 名按你们文档实现。当前**只支持 AM 已支持的 4 类**(git / mysql / postgres / azure blob);vm / redis / mongo / 对象桶等 HM 暂不下发,待 AM 支持再加。
| 资源类型 | provider | 注入的 env(非密 + 密钥) |
|---|---|---|
| git | github / gitea / gitlab | `GIT_PROVIDER` `GIT_REPO_URL` `GIT_BRANCH` `GIT_API_BASE` · `GIT_TOKEN`(密) |
| vm | (ssh) | `VM_HOST` `VM_PORT` `VM_USER` · `VM_PASSWORD` / `VM_SSH_KEY`(密) |
| git | github / gitea / gitlab | `GIT_PROVIDER` `GIT_REPO_URL` `GIT_DEFAULT_BRANCH` · `GIT_TOKEN`(密) |
| database | mysql | `MYSQL_HOST` `MYSQL_PORT` `MYSQL_DATABASE` `MYSQL_USER` · `MYSQL_PASSWORD`(密) |
| database | pg(postgres) | `PG_HOST` `PG_PORT` `PG_DATABASE` `PG_USER` · `PG_PASSWORD`(密) |
| database | redis | `REDIS_HOST` `REDIS_PORT` `REDIS_DB` · `REDIS_PASSWORD`(密) |
| database | mongo | `MONGO_HOST` `MONGO_PORT` `MONGO_DATABASE` `MONGO_USER` · `MONGO_PASSWORD`(密) |
| storage(blob) | azure blob | `BLOB_ACCOUNT` `BLOB_CONTAINER` · `BLOB_KEY`(密) |
| storage(bucket) | s3/oss/minio | `BUCKET_ENDPOINT` `BUCKET_REGION` `BUCKET_NAME` · `BUCKET_ACCESS_KEY_ID` `BUCKET_SECRET_ACCESS_KEY`(密) |
| database | postgres | `POSTGRES_HOST` `POSTGRES_PORT` `POSTGRES_DATABASE` `POSTGRES_USER` · `POSTGRES_PASSWORD`(密) |
| storage | azure blob | `AZURE_BLOB_ACCOUNT_NAME` `AZURE_BLOB_CONTAINER` · `AZURE_BLOB_ACCOUNT_KEY`(密) |
另外随启动注入(模型网关):`OPENAI_BASE_URL`(= HM `/v1`)、`MODEL_NAME`。**`OPENAI_API_KEY` 不在启动时注入** —— 由客户端在 A2A 请求里带 `api_key`(你们文档 §7);后续若走 V2 解密路径再调整。
- 缺失的可选字段不会出现在 env 里(agent 自行容错)。
- 同类型资源 HM 限制只挂一个(避免 env 名冲突)。
- 角色/指令注入:HM 把模板 .md 放进 `env.AGENT_INSTRUCTION_TEXT`、模板 key 放进 `env.AGENT_ROLE_NAME`(对齐你们 §4)。
---
@@ -114,7 +114,7 @@ HM 这些路径都可用环境变量覆盖(默认值见 §1),AM 若用别的路
|---|---|---|
| `AGENT_RUNTIME_BASE_URL` | — | AM 基址(必须配,HTTPS/私网) |
| `AGENT_RUNTIME_SERVICE_TOKEN` | — | HM 调 AM 的 Bearer |
| `AGENT_RUNTIME_AGENT_START_PATH` | `/api/agent/agents/start` | 启动 |
| `AGENT_RUNTIME_AGENT_START_PATH` | `/agents` | 启动(你们的 create) |
| `AGENT_RUNTIME_AGENT_PATH` | `/api/agent/agents/{agent_id}` | 取状态(GET)/删除(DELETE) |
| `AGENT_RUNTIME_AGENT_STOP_PATH` | `/api/agent/agents/{agent_id}/stop` | 停止 |
| `AGENT_RUNTIME_START_TIMEOUT_SECONDS` | `60` | 启动超时 |
+22 -8
View File
@@ -170,17 +170,31 @@ signature = base64( ed25519_sign( device_priv, sha256(canonical) ) )
---
## 6. 直连 Agent(客户端 ↔ agent,SSE)🔴
## 6. 直连 Agent(客户端 ↔ agent,A2A 协议)
> 这一段是 **客户端直接连 agent**,不经过 HM。HM 只负责把 `subdomain + access_token` 给你。
> 客户端**直接连 agent 子域名**(§5 的 `subdomain`),不经过 HM。agent 是 AM 的 `coding_a2a_agent`,走 **A2A 协议**。
- 从 §5 列表拿到某个 `status=running` agent 的 `subdomain` 和 `access_token`。
- 客户端**直连该子域名,SSE 双向通信**,请求携带 `access_token`(具体头部/握手以 AM 的 agent 端契约为准)。
- **agent 自己校验 token**;token 错/缺则拒绝(防公网裸奔)。
- agent 用 `.env` 里注入的资源(git/vm/db/blob 配置)自行干活;**用模型仍打 HM `/v1/*`**。
- agent 不会把 `.env`/密钥回显到对话或日志(由 AM 保证)。
- **同步**:`POST {subdomain}/message/send`
- **流式**:`POST {subdomain}/message/stream`(返回 `text/event-stream`)
- **发现/健康**:`GET {subdomain}/.well-known/agent.json`、`GET {subdomain}/health`
> 该直连契约(SSE 握手、token 头名、消息格式)由 AM 的 agent 端定义,定稿后补入本节。
请求体(JSON-RPC,A2A):
```json
{ "jsonrpc":"2.0", "id":"task-1", "method":"message/send",
"params": {
"api_key": "sk-…", // ★ 模型 key,agent 用它调 HM /v1 计费(客户端自带)
"model": "gpt-5.4",
"message": { "role":"user", "parts":[ {"kind":"text","text":"…"} ] },
"configuration": { "workspace": { "root_dir":"/workspace", "allowed_paths":["a.py"] } }
}
}
```
- **鉴权 = `params.api_key`**(模型 sk-,客户端自带的网关 key);agent 凭它调 HM `/v1/*` 计费。HM 当前列表里的 `access_token` 字段对该 agent**为空**(AM 不发专属 token);连 agent 用上面的 `api_key`。
- agent 用 `.env` 里注入的资源(git/mysql/postgres/azure-blob)自行干活;工具:`read_file/write_file/edit_file/run_command/git_*/run_database_query/list_blob_objects` 等。缺配置的资源工具调用会返回 `resource not configured`,不阻塞。
- 可在 `params.configuration.resources` 里按需覆盖资源(请求级覆盖启动级)。
> ⚠️ 安全:当前 agent 端鉴权仅为"持有效 api_key",非 per-agent 专属令牌。HM 侧规划**用 V2 解密接口**做按用户隔离(客户端加密请求→agent 解密拿 key),或由 AM 自行加门。上线公网前需确认该端点不被他人用任意 key 驱动(见 AM 契约 §3/§4)。
---