# 外部工作流 API 参考

当需要从其他应用或新的集成层调用 agent-management 工作流时，按本参考实现服务调用。

## 契约概要

- 基础路径：`http(s)://<host>:<port>/api/v1`
- 默认后端端口：`2438`
- 鉴权：每个 `/api/v1/**` 请求需携带 `X-API-Key: <key>`
- JSON 成功响应包：

```json
{ "code": "OK", "message": "", "data": {} }
```

- 错误响应包：

```json
{ "code": "VALIDATION_FAILED", "message": "可读错误信息", "data": null }
```

- 主要错误码：`UNAUTHORIZED`、`VALIDATION_FAILED`、`WORKFLOW_NOT_FOUND`、`RUN_NOT_FOUND`、`GRAPH_INVALID`、`RUN_ALREADY_STARTED`、`INTERNAL_ERROR`

## 工作流标识

外部 API 的所有 `{workflowName}` 路径段同时接受两种值，**优先使用 `name`**：

- **`name`（推荐）**：kebab-case 字符串，正则 `^[a-z0-9]+(-[a-z0-9]+)*$`，最大 64 字符，全局唯一。例：`military-analyze-agent`、`report-pipeline`。
- **`id`（兼容兜底）**：纯数字字符串。当传入值不是 `name` 且为纯数字时，按内部主键 `id` 查找。仅用于兼容历史调用方，新集成不要依赖。

解析顺序：先 `findByName(nameOrId)`，未命中且为纯数字时回退 `findById(Long.valueOf(nameOrId))`。

响应中的标识字段约定：

| 字段 | 类型 | 含义 |
|---|---|---|
| `workflowId` | number | 内部数字主键，仅作为元数据返回，**不用于 URL 路径** |
| `workflowName` | string | kebab-case 唯一标识，所有 links、downloadUrl、logsUrl 都用它拼接 |
| `displayName` | string | 中文展示名，仅用于 UI 展示 |

工作流列表响应还会返回 `description`、`inputs`、`outputs`（从 `userInput` / `output` 节点自动提取的字段定义）。

## 端点

| 方法 | 路径 | 用途 |
|---|---|---|
| `GET` | `/api/v1/workflows` | 列出当前 API key 可访问的工作流。响应项含 `id`、`name`、`displayName`、`description`、`inputs`、`outputs`；当前实现返回所有可访问工作流，分页/搜索查询参数仅作前向兼容保留。 |
| `POST` | `/api/v1/workflows/{workflowName}/runs` | 创建一次运行，可携带变量与可选文件。`{workflowName}` 接受 kebab-case `name` 或纯数字 `id`。 |
| `POST` | `/api/v1/workflows/{workflowName}/runs/{runId}/start` | 启动一个已创建的异步运行 |
| `GET` | `/api/v1/workflows/{workflowName}/runs/{runId}/stream` | 订阅 SSE 节点/运行事件 |
| `GET` | `/api/v1/workflows/{workflowName}/runs/{runId}` | 查询运行状态、输出、节点和工作空间信息 |
| `GET` | `/api/v1/workflows/{workflowName}/runs/{runId}/nodes/{nodeId}/logs` | 查询某节点的持久化日志 |
| `GET` | `/api/v1/workflows/{workflowName}/runs/{runId}/workspace` | 下载工作空间 zip |

## 推荐的异步流程

1. `POST /workflows/{workflowName}/runs`，带 `async: true`。`{workflowName}` 推荐使用 kebab-case `name`。
2. 读取 `data.runId` 和 `data.links`。响应中的 `workflowId`（数字主键）和 `workflowName`（kebab-case）可分别作为元数据保存。
3. `POST /workflows/{workflowName}/runs/{runId}/start`。
4. 打开 `GET /workflows/{workflowName}/runs/{runId}/stream`，请求头 `Accept: text/event-stream`。
5. 收到 `workflow_complete` 或 `workflow_error` 后，关闭流或标记为终态。
6. 查询 `GET /workflows/{workflowName}/runs/{runId}` 获取持久化输出与节点摘要。
7. 将 `GET /workflows/{workflowName}/runs/{runId}/workspace` 作为二进制下载入口提供给用户。

仅当期望"单次阻塞请求直接返回 SSE"时才使用 `async: false`。

## 创建运行

纯 JSON 请求：

```http
POST /api/v1/workflows/military-analyze-agent/runs
X-API-Key: demo-key
Content-Type: application/json

{
  "variables": {
    "topic": "大语言模型在企业中的应用",
    "maxTokens": 2048,
    "options": { "lang": "zh", "verbose": false }
  },
  "async": true,
  "failStrategy": "abort",
  "ttlHours": 720
}
```

Multipart 请求：

```http
POST /api/v1/workflows/military-analyze-agent/runs
X-API-Key: demo-key
Content-Type: multipart/form-data

payload: {"variables":{"topic":"汇总报告"},"async":true}
files: report.md
files: inputs/2024/data.csv
```

字段说明：

- `variables`：自定义工作流输入变量；对象；可选。
- `async`：默认 `true`。为 `true` 时仅创建；为 `false` 时立即启动并以 SSE 形式返回。
- `failStrategy`：取值 `abort` 或 `skip`；可选且前向兼容。当前 agent-management 实现中该字段虽被 DTO 接收，但执行仍依赖 `graphData` 中节点级别的 `failStrategy`；除非目标版本显式支持请求级覆盖，否则不要依赖此字段。
- `ttlHours`：工作空间保留小时数；默认 `720`。
- `files`：可重复的 multipart 二进制 part；可选。
- `X-Relative-Path`：multipart part 的可选 header，用于保留目录路径。若客户端技术栈不支持，则退化为仅使用文件名并明确记录该限制。

`async: true` 的响应：

```json
{
  "code": "OK",
  "data": {
    "runId": "a3f9b2c1",
    "workflowId": 12,
    "workflowName": "military-analyze-agent",
    "displayName": "军事分析助手",
    "status": "CREATED",
    "createdAt": "2026-07-01T08:30:00Z",
    "links": {
      "start": "/api/v1/workflows/military-analyze-agent/runs/a3f9b2c1/start",
      "stream": "/api/v1/workflows/military-analyze-agent/runs/a3f9b2c1/stream",
      "result": "/api/v1/workflows/military-analyze-agent/runs/a3f9b2c1",
      "workspace": "/api/v1/workflows/military-analyze-agent/runs/a3f9b2c1/workspace"
    }
  }
}
```

## SSE 流

打开连接（路径段使用 `workflowName`，即 kebab-case `name`）：

```http
GET /api/v1/workflows/{workflowName}/runs/{runId}/stream
X-API-Key: <key>
Accept: text/event-stream
```

断线重连时携带：

```http
Last-Event-ID: 42
```

每条事件遵循标准 SSE 格式：

```text
event: node_stream
id: 43
data: {"runId":"a3f9b2c1","type":"node_stream"}
```

事件类型：

- `run_started`：引擎已启动。
- `node_status`：节点状态变更。用于创建/更新节点卡片。
- `node_stream`：节点增量流。`kind` 取值为 `thinking`、`tool_call` 或 `tool_result`。
- `workflow_complete`：终态成功。包含最终输出和工作空间/结果 URL。
- `workflow_error`：终态失败。包含错误信息，通常带有失败节点 ID。

关键负载字段示例：

```json
{
  "runId": "a3f9b2c1",
  "nodeId": "node-2",
  "nodeType": "agent",
  "label": "研究助手",
  "status": "RUNNING",
  "kind": "thinking",
  "content": "首先我需要搜索相关资料...",
  "toolName": null,
  "timestamp": "2026-07-01T08:30:07Z"
}
```

客户端行为约定：

- 以 `nodeId` 为主键维护每个节点的状态。
- 将 `node_stream.kind === "thinking"` 的内容追加到该节点的思考缓冲区。
- 将 `tool_call` 与 `tool_result` 作为日志存储，保留工具名和内容。
- 把 `workflow_complete` 与 `workflow_error` 视为终态。
- 如需支持重连，持久化最近一条 SSE 的 `id`。

## 查询结果

```http
GET /api/v1/workflows/military-analyze-agent/runs/a3f9b2c1
X-API-Key: demo-key
```

预期的 data 形态：

```json
{
  "runId": "a3f9b2c1",
  "workflowId": 12,
  "workflowName": "military-analyze-agent",
  "displayName": "军事分析助手",
  "status": "SUCCESS",
  "startedAt": "2026-07-01T08:30:05Z",
  "completedAt": "2026-07-01T08:31:12Z",
  "error": null,
  "outputs": {
    "summary": "本报告探讨了...",
    "_workingDirFiles": ["report.md"],
    "_runId": "a3f9b2c1"
  },
  "nodes": [
    {
      "nodeId": "node-2",
      "nodeType": "agent",
      "label": "研究助手",
      "status": "SUCCESS",
      "output": { "research": "..." },
      "error": null,
      "logsCount": 124,
      "logsUrl": "/api/v1/workflows/military-analyze-agent/runs/a3f9b2c1/nodes/node-2/logs"
    }
  ],
  "workspace": {
    "fileCount": 12,
    "totalBytes": 456789,
    "downloadUrl": "/api/v1/workflows/military-analyze-agent/runs/a3f9b2c1/workspace"
  }
}
```

状态取值：`CREATED`、`RUNNING`、`SUCCESS`、`FAILED`。

## 日志与工作空间

节点日志：

```http
GET /api/v1/workflows/{workflowName}/runs/{runId}/nodes/{nodeId}/logs
```

日志类型通常为 `THINKING`、`TOOL_CALL`、`TOOL_RESULT`、`INFO`、`ERROR`。把思考过程抽到独立展示区，非思考日志放在可折叠的日志列表中。

工作空间下载：

```http
GET /api/v1/workflows/{workflowName}/runs/{runId}/workspace
```

响应是二进制 zip 内容，不要走 JSON 响应拦截器。

## 安全与集成注意事项

- 默认不要在前端代码中暴露 `X-API-Key`。优先使用后端代理，由服务端注入密钥。
- base URL 与 API key 应存放在环境/配置中，不要硬编码到源码。
- API Key 的访问范围（白名单）在 agent-management 后端 `ExternalApiProperties` 中配置，同时支持 `workflowNames`（推荐，按 kebab-case `name` 精确匹配）和 `workflowIds`（兼容，按数字主键匹配）；两者皆空表示不限制。新集成建议使用 `workflowNames` 与 URL 路径保持一致。
- 如实现代理上传，需校验路径，防止 `../` 路径穿越。
- 代理 SSE 时关闭缓冲，保留事件名、id 和 data 负载。
- 未经用户同意，不要悄悄把 SSE 降级为轮询。
- 使用 axios/fetch 等封装时，对 `text/event-stream` 和工作空间 zip 端点绕过 JSON 响应包解析。
