# 智能体 · 生成与发布 API

Base URL：`http://127.0.0.1:8180`（网关）

需求 / 能力说明见：[智能体-生成发布-能力说明.md](./智能体-生成发布-能力说明.md)

> 业务用语：**模块**。API 路径仍为 `/api/v1/apps/...`。  
> **智能体默认无需配置 `app_slugs`**：启用「生成发布」后可自由发布自建模块；`app_slugs` 仅在需要白名单限制时填写。  
> **禁止**在已发布模块的业务库查用户 / 权限。账号绑定只走「首次登记」。

---

## 总览

| 步骤 | 方法 | 路径 | 鉴权 |
|------|------|------|------|
| 首次登记 | POST | `/api/v1/auth/agent/register` | 公开 + `register_secret` |
| 换票 | POST | `/api/v1/auth/token` | 公开（client_credentials） |
| 列模块 | GET | `/api/v1/apps` | Bearer + 「读取模块」（管理=全部；智能体默认不限制） |
| 登记在建 | PUT | `/api/v1/apps/{slug}/draft` | Bearer + 「发布模块」 |
| 读蓝图 | GET | `/api/v1/apps/{slug}/blueprint` | Bearer + 「读取模块」 |
| 列模型 | GET | `/ai/api/v1/llm/providers` | 公开 |
| 生成 | POST | `/api/v1/apps/generate` | 网关可公开 |
| 发布 | POST | `/api/v1/apps/{slug}/publish` | Bearer + 「发布模块」 |
| 公开读（加密路径） | GET | `/api/v1/public/m/{token}/blueprint` | 公开 |
| 导入 | POST | `/api/v1/apps/{slug}/{resource}/import` | Bearer + `row.import` |
| 抽查 | GET | `/api/v1/apps/{slug}/{resource}` | Bearer + `row.read` |

决策树：

```text
GET /apps（本账号可见模块）
  ├─ 选中已有模块 slug
  │     → GET blueprint（避开已有 page id/route）
  │     → generate（生成新页面）
  │     → publish mode=add_pages|auto
  │     → import
  └─ 无合适模块
        → generate（完整多页蓝图）
        → publish mode=create|auto（自定新 slug，无需预授权）
        → import
```

---

## 0. 首次登记

`POST /api/v1/auth/agent/register`（公开）

宿主第一次接入时调用。必须上传：

| 宿主侧 | API 字段 | 必填 | 说明 |
|--------|----------|------|------|
| 宇恒 ID | `host_key` | 是 | 稳定唯一标识，幂等绑定 |
| 名称 | `name` | 是 | 控制台显示名 |
| — | `tenant_id` | 否 | 默认 `1` |
| — | `register_secret` | 是 | 与平台注册密钥一致 |

```json
{
  "name": "宇恒节点显示名",
  "host_key": "<宇恒ID>",
  "tenant_id": 1,
  "register_secret": "dev-only-change-me"
}
```

响应含 `client_id` / `client_secret`（只返回一次），`status` 为 `pending`。  
管理员在控制台选角色「生成发布」并启用后即可换票。**可访问模块可留空**（留空=可自由发布自建模块）。

同一 `host_key`：pending 重连会轮换 secret；已 active 再 register → 409。

---

## 1. 换票

`POST /api/v1/auth/token`（公开）

```json
{
  "grant_type": "client_credentials",
  "client_id": "agt_xxx",
  "client_secret": "明文密钥"
}
```

取 `access_token`。后续列模块 / 读蓝图 / 发布 / 导入须：

`Authorization: Bearer <access_token>`

### 权限与模块授权

| 权限 | 用途 |
|------|------|
| 「发布模块」 | 发布模块（必选） |
| 「读取模块」 | 列模块、读蓝图（必选） |
| `row.import` | 灌数（必选） |
| `row.read` | 抽查（建议） |
| `storage.write` / `storage.read` | 素材（建议） |

推荐角色：**生成发布（`publisher`）**。

`app_slugs`：**可选白名单**。  
- **留空（推荐默认）**：不限制，智能体可自定 slug 生成/发布  
- 填写若干 slug 或 `*`：仅允许列表内（`*`=全部）  

生成接口经网关可公开；发布与导入须鉴权（角色权限），**不强制**后台预授权模块。

---

## 2. 列出模块（先选目标）

`GET /api/v1/apps`  
需 Bearer + 「读取模块」。

| 调用方 | 可见范围（`scope`） |
|--------|---------------------|
| 管理账号（非 agent） | `all`：本租户全部（含在建） |
| 智能体（`app_slugs` 空或含 `*`） | `open`：不限制 |
| 智能体（配置了白名单） | `granted`：仅白名单 |

```bash
curl -s http://127.0.0.1:8180/api/v1/apps \
  -H "Authorization: Bearer <access_token>"
```

```json
{
  "scope": "all",
  "items": [
    {
      "app_id": "...",
      "slug": "settlement",
      "name": "沉降观测",
      "status": "draft",
      "status_label": "在建",
      "building": true,
      "page_count": 4,
      "entity_count": 2,
      "updated_at": "2026-07-24T08:00:00Z",
      "created_at": "2026-07-24T08:00:00Z"
    }
  ]
}
```

| 情况 | 下一步 |
|------|--------|
| 有目标模块（含在建） | 记下 `slug` → 读蓝图 / 继续生成 → `publish` |
| 没有 | 选定新 `slug` → generate → `PUT .../draft` 登记在建 → `publish` 上线 |

### 2.1 登记在建模块

生成蓝图后、正式发布前：

`PUT /api/v1/apps/{slug}/draft`  
Body：`{ "blueprint": { ... } }`  
需 「发布模块」。不跑 DDL；状态为 `draft`（在建）。已发布模块不可用本接口覆盖。

---

## 3. 读取已发布/在建蓝图

`GET /api/v1/apps/{slug}/blueprint`  
需 Bearer + 「读取模块」 + 该模块已授权。

用途：

- 生成新页面前，收集已有 `pages[].id` / `route`，避免冲突  
- 发布后自检  

---

## 4. 列出大模型（可选）

`GET /ai/api/v1/llm/providers`（公开）  
兼容：`http://127.0.0.1:8180/ai/api/v1/llm/providers`

```json
{
  "default": "deepseek",
  "providers": [
    {
      "id": "deepseek",
      "label": "DeepSeek",
      "configured": true,
      "default_model": "deepseek-chat",
      "models": [{ "id": "deepseek-chat", "label": "deepseek-chat" }]
    }
  ]
}
```

---

## 5. 生成蓝图 / 生成新页面

`POST /api/v1/apps/generate`  
（网关转发 AI；兼容 `POST /ai/api/v1/apps/generate`）

`Content-Type: multipart/form-data`

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `prompt` | string | 建议 | 需求描述。**已有模块**时写清：目标 slug、要新增哪些页面、勿复用已有 id/route |
| `storage_mode` | string | 否 | `schema_per_app`（默认）或 `database_per_app` |
| `llm_provider` | string | 否 | 默认 `deepseek` |
| `llm_model` | string | 否 | 空则用厂商默认 |
| `excel` | file | 否 | xlsx / csv / json |
| `data_file` | file | 否 | 同 excel |
| `images` | file[] | 否 | 界面截图 |
| `layout_files` | file[] | 否 | 页面 HTML/MHTML |

```bash
# 示例：为已有模块生成新页面
curl -s http://127.0.0.1:8180/api/v1/apps/generate \
  -F "prompt=目标模块 settlement。已有 page id: dash_main, list_records。请新生成「测点筛选」列表+新建表单，id/route 勿冲突。" \
  -F "storage_mode=schema_per_app" \
  -F "llm_provider=deepseek" \
  -F "llm_model=deepseek-chat" \
  -F "excel=@./settlement.xlsx" \
  -F "images=@./board_a.png"
```

成功响应要点：

```json
{
  "draft": {
    "version": "1.0",
    "meta": { "slug": "settlement", "name": "..." },
    "entities": [],
    "apis": {},
    "pages": [],
    "storage": {},
    "security": {}
  },
  "warnings": [],
  "confidence": 0.86,
  "require_confirm": true,
  "llm_provider": "deepseek",
  "llm_model": "deepseek-chat",
  "fidelity": { "skipped": false, "target": 95, "final_score": 96, "passed": true },
  "generate_log": { "run_id": "...", "lines": [] }
}
```

| 字段 | 说明 |
|------|------|
| `draft` | 蓝图；发布时提交（可微调） |
| `draft.meta.slug` | 新建模块时用此 slug；已有模块发布时以路径 `{slug}` 为准 |
| `draft.pages` | **禁止只生成 1 个 page**；数量不设上限 |
| `draft == null` | 生成失败，看 `warnings` / `generate_log` |

### 页面约定

| type | 用途 |
|------|------|
| `list` | 列表（建议 `actions` 含 `import`） |
| `form_create` | 新建表单 |
| `form_edit` | 编辑表单 |
| `dashboard` | 看板 |
| `detail` | 详情 |

每个 page 必须有独立 `id` / `title` / `route` / `entity`。

```json
"layout": {
  "columns": ["sku", "qty", "status"],
  "actions": ["create", "edit", "delete", "export", "import", "refresh"]
}
```

**已有模块**：`draft` 应主要是**新生成的 pages**（及对应 entities / apis），不要把旧页面再写一遍。  
**新建模块**：`draft` 为完整多页蓝图。

---

## 6. 发布（宿主 → 建站平台）

宿主（宇恒）侧「发布」应把建站蓝图与展示元数据 **POST 到本建站平台**；平台落库建站，并**按用户/智能体 ID 加密出文件路径**，回执给宿主表格展示。

`POST /api/v1/apps/{slug}/publish`

Headers：

- `Authorization: Bearer <access_token>`
- `Content-Type: application/json`

Path `{slug}` = 选定的目标**模块**（新建时为新 slug）。`blueprint.meta.slug` 会对齐到路径 slug。  
默认无需预授权；仅当账号配置了 `app_slugs` 白名单时才校验。

```json
{
  "mode": "auto",
  "blueprint": { },
  "host_meta": {
    "module_name": "whm123",
    "publish_style": "immediate",
    "host_base_url": "https://whm123.yuheng.com"
  }
}
```

| 字段 | 说明 |
|------|------|
| `mode` | 见下表 |
| `blueprint` | 建站蓝图（必填） |
| `host_meta.module_name` | 宿主表格「模块名称」 |
| `host_meta.publish_style` | `immediate` → 立即发布上线 |
| `host_meta.host_base_url` | 宿主访问域名；有则作为回执 `access_url` |

| `mode` | 含义 |
|--------|------|
| `auto`（默认） | 模块已存在 → 发布新生成的页面；不存在 → 新建模块 |
| `add_pages` | 必须已存在；发布草稿中的**新页面** |
| `create` | 必须不存在；新建模块 |
| `replace` | 整份覆盖（慎用） |

日常用 `auto` 或显式 `add_pages` / `create`。

### 加密访问路径

平台用当前账号的 **用户/智能体 ID（owner_id）** + 租户 + 模块 slug，加密生成逻辑文件路径：

- `access_path`：如 `m/ajzm1_...`（路径中**无明文 slug / 用户 id**）  
- `access_url`：优先宿主 `host_base_url`；否则 `{PublicBaseURL}/api/v1/public/{access_path}/blueprint`  

公开读取蓝图（无需登录）：

`GET /api/v1/public/m/{token}/blueprint`  
其中 `{token}` 为 `access_path` 去掉前缀 `m/` 的段。

### 宿主「AI 表格数据」字段映射

| 宿主表格 | 取自发布响应 |
|----------|----------------|
| 模块名称 | `module_name` |
| 发布方式 | `publish_style`（`immediate` → 立即发布上线） |
| 访问地址 | `access_url`（或宿主自拼域名 + `access_path`） |
| 发布时间 | `published_at` |
| 状态 | `status` = `published` → 已发布 |

下一步仍是：**灌数 → 打开模块**。

### 已有模块（`auto` / `add_pages`）

- 保留该模块已有 pages / entities / apis  
- 发布草稿中**新生成**的 page（`id`、`route` 不可冲突）  
- 新 entity / resource 一并加入；已有 entity 只追加新字段  
- 若没有任何新 page / entity / resource → 400  

### 新建模块时 blueprint 至少含

- `meta.slug` / `meta.name`
- `version`（如 `"1.0"`）
- `storage`：`{ "mode": "schema_per_app", "engine": "postgres" }`
- `security`：`{ "visibility": "private", "roles": [], "row_policies": [] }`
- `apis.base_path` = `/api/v1/apps/{slug}`
- `entities` / `apis` / `pages`

```bash
curl -s http://127.0.0.1:8180/api/v1/apps/settlement/publish \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"create","host_meta":{"module_name":"whm123","publish_style":"immediate","host_base_url":"https://whm123.yuheng.com"},"blueprint":{...}}'
```

响应示例：

```json
{
  "app_id": "...",
  "slug": "settlement",
  "schema_name": "app_t1_settlement",
  "status": "published",
  "publish_mode": "created",
  "module_name": "whm123",
  "publish_style": "immediate",
  "access_path": "m/ajzm1_...",
  "access_url": "https://whm123.yuheng.com",
  "published_at": "2026-07-24T02:00:00Z",
  "owner_id": 3,
  "endpoints": ["GET /api/v1/apps/settlement/blueprint", "..."],
  "ddl": [],
  "memory_mode": false
}
```

| `publish_mode` | 含义 |
|----------------|------|
| `created` | 新建了模块 |
| `pages_added` | 向已有模块发布了新页面 |
| `replaced` | 整份覆盖 |

| HTTP | 原因 |
|------|------|
| 401 | token 无效 |
| 403 | 缺 「发布模块」；或配置了白名单但不含该 slug |
| 400 | 蓝图非法 / page id 冲突 / mode 与是否存在不一致 |

---

## 7. 导入业务数据（发布后必做）

发布只建表，**不自动灌数**。

`POST /api/v1/apps/{slug}/{resource}/import`  
`Content-Type: multipart/form-data`  
需 Bearer + `row.import` + 模块授权。

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `file` | file | 是 | xlsx / csv / json |

`resource` = `apis.resources[].path`（无前导 `/`），如 `records`。

```bash
curl -s "http://127.0.0.1:8180/api/v1/apps/settlement/records/import" \
  -H "Authorization: Bearer <access_token>" \
  -F "file=@./settlement.xlsx"
```

```json
{ "inserted": 120, "skipped": 0, "errors": [] }
```

抽查：

`GET /api/v1/apps/{slug}/{resource}?page=1&page_size=5`（需 `row.read`）  
确认 `total > 0`，否则任务未完成。

---

## 8. 推荐调用顺序

```text
0. POST /api/v1/auth/agent/register     宇恒ID(host_key) + 名称(name)
1. （管理员）启用 + 角色 publisher（模块白名单可选，默认留空）
2. POST /api/v1/auth/token
3. GET  /api/v1/apps                    先选模块（仅本账号可见）
4. GET  /api/v1/apps/{slug}/blueprint   仅已有模块：避让已有 page
5. POST /api/v1/apps/generate           生成新页面 或 完整蓝图
6. POST /api/v1/apps/{slug}/publish     mode=auto | add_pages | create
7. POST /api/v1/apps/{slug}/{resource}/import
8. GET  /api/v1/apps/{slug}/{resource}?page=1&page_size=5
```

不要：查业务库用户表；行级增删改（除 import）；admin/audit；对已有模块默认 `replace`。  
语义：已有模块上是 **生成新页面再发布**；新建模块自定 slug 即可，无需后台预授权。
