Files
th-hotel-simple/docs/project/integrations/superagent-api-contract.md
2026-07-11 14:11:18 +08:00

783 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TH Hotel SuperAgent API 对接契约
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.7 |
| 日期 | 2026-07-11 |
| 状态 | 当前代码契约已支持 V2 `ai_task_results[]` 兼容、结构化 S10/S99、V3 业务根基础解析、旧 S000/S999 兼容和单酒店 hotel_id 后端解析 |
| 适用范围 | SuperAgent 调用本系统查询上下文、查询邮件会话、提交 AI 任务结果 |
| 主要读者 | SuperAgent 对接方、后端、测试、运维 |
## 联调参数区
以下参数供 SuperAgent 联调时手动修改。该区块只用于 dev/test 联调,不作为生产 Secret 管理方式。
| 参数 | 当前联调值 | 中文说明 |
| --- | --- | --- |
| `TH_HOTEL_API_BASE_URL` | `http://8.138.234.141:18087` | 本系统后端基础地址;本地联调用 8080部署环境改为实际网关或服务地址。 |
| `SYSTEM_HOTEL` | 后端平台酒店表唯一 `ACTIVE` 酒店 | SuperAgent 不需要配置或传入 `hotel_id`;单酒店阶段由 TH Hotel 后端从 `platform_hotel` 解析。 |
| `SUPERAGENT_CLIENT_ID` | `superagent-debug` | SuperAgent 调用方 ID对应 Header `X-TH-Hotel-SuperAgent-Client-Id`。 |
| `SUPERAGENT_HMAC_SECRET` | `th-hotel-superagent-debug-20260709-change-before-prod` | dev/test 联调临时 HMAC 密钥;生产上线前必须更换为新的高强度随机密钥。 |
生产注意:
- 生产环境必须更换 `SUPERAGENT_HMAC_SECRET`,不得继续使用上述联调临时密钥。
- 生产密钥不得写入 SuperAgent skill 文件、仓库文档、前端代码、镜像或普通日志,只能通过部署 Secret / 环境变量注入。
- 本系统后端按 profile 优先读取环境专属密钥dev 使用 `SUPERAGENT_DEV_TASK_RESULT_HMAC_SECRET`test 使用 `SUPERAGENT_TEST_TASK_RESULT_HMAC_SECRET`prod 使用 `SUPERAGENT_PROD_TASK_RESULT_HMAC_SECRET`;旧通用变量 `SUPERAGENT_TASK_RESULT_HMAC_SECRET` 仅作为兼容兜底。当前查询接口和任务结果通知接口共用同一个 HMAC secret。
## 1. 基础约定
请求地址先使用占位符:
```text
{TH_HOTEL_API_BASE_URL}
```
上线或联调时由环境提供实际域名,例如 UAT、生产内网域名或 API Gateway 地址。本文所有接口均为 SuperAgent 到本系统的服务到服务调用,不给前端直接调用。
## 2. 通用 HMAC 鉴权
查询接口和任务结果通知接口使用同一套 HMAC-SHA256 规则。
### 2.1 通用 Header
| Header | 是否必填 | 中文说明 |
| --- | --- | --- |
| `Content-Type` | 是 | 查询接口固定 `application/json`;任务结果通知接口支持 `application/json``text/plain` |
| `X-TH-Hotel-SuperAgent-Client-Id` | 是 | SuperAgent 调用方客户端 ID |
| `X-TH-Hotel-SuperAgent-Timestamp` | 是 | UTC ISO-8601 时间,例如 `2026-07-08T01:30:00Z` |
| `X-TH-Hotel-SuperAgent-Nonce` | 是 | 每次请求唯一随机值,用于防重放 |
| `X-TH-Hotel-SuperAgent-Signature` | 是 | HMAC 签名,格式 `sha256=<lowercase-hex>` |
| `X-TH-Hotel-Request-Id` | 否 | 调用方请求 ID用于日志串联 |
| `X-TH-Hotel-AI-Trace-Id` | 否 | AI 运行链路 ID查询接口会原样带回 `trace_id` |
### 2.2 签名串
签名串使用接口 path不包含域名、query string 或 fragment。
```text
POST
<request_path>
<X-TH-Hotel-SuperAgent-Timestamp>
<X-TH-Hotel-SuperAgent-Nonce>
<X-TH-Hotel-SuperAgent-Client-Id>
<lowercase-hex-sha256-of-raw-body>
```
签名算法:
```text
signature = HMAC_SHA256(SUPERAGENT_HMAC_SECRET, canonical_string)
```
Header 写法:
```text
X-TH-Hotel-SuperAgent-Signature: sha256=<lowercase-hex-signature>
```
### 2.3 服务端校验顺序
1. 校验请求体大小。
2. 查询接口校验 `Content-Type` 是否为 `application/json`;任务结果通知接口校验是否为 `application/json``text/plain`,不支持的媒体类型不消耗 nonce。
3. 校验 HMAC 相关 Header 是否存在。
4. 校验 timestamp 是否在允许时间窗口内。
5. 计算原始请求体 SHA-256。
6. 使用共享 secret 重新计算 HMAC。
7. 常量时间比较签名。
8. 校验并记录 `client_id + nonce`,防止重放。
9. 鉴权和协议校验通过后再解析业务 JSON、结构化 S10/S99、V3 业务根或 S000/S999 文本结果。
## 3. SourceMessage ID 口径
本系统存在两个容易混淆的 ID
| 名称 | 中文说明 | 使用位置 |
| --- | --- | --- |
| 外部来源消息 ID | AgentBus 邮件 payload 中的 `source.external_message_id`SuperAgent / Main Agent 在最终 JSON 中原样带回为 `source_message_id` | SuperAgent 任务结果通知接口入参和响应回显 |
| 内部 SourceMessage Inbox ID | `platform_source_message_inbox.id`,本系统数据库内部主键 | `workflow_*` 表的 `source_message_id` 外键、前端和运维排查 |
SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通知接口收到外部 `source_message_id` 后,后端先解析系统酒店,再使用 `hotel_id + provider + channel + external_message_id` 反查内部 Inbox 记录,最后用内部 ID 写入业务表。
查询接口 1、2 在 SuperAgent 查询阶段不依赖当前邮件是否已经入库。若请求体兼容旧契约传入 `source_message_id``source_event_index`,第一版后端会接收但忽略,不校验它们的格式,也不把它们作为查询边界。
查询接口 3、4 面向已经入库的邮件会话:缺省酒店由后端解析,`external_conversation_id` 最终仍按 `hotel_id + source_provider + source_channel + external_conversation_id` 查询;`source_message_id` 表示外部来源消息 ID可作为锚点反查该邮件所属会话。
## 3.1 M002 V3 迁移提醒
2026-07-11 起,项目需求基线已确认采用 `docs/project/requirements/M002-order-task-workflow-v3.md`
- 新入口结果将从旧文本 `S000/S999` 迁移为结构化 `S10/S99`
- 新业务输出将从旧 `ai_task_results[]` 迁移为 `source_message + message_events[] + case_candidates[] + extraction_warnings[] + unhandled_current_intents[]`
- 后端会完整保存 AI 三元组、`route_code` 和系统处理分类;`S10/S99` 仍以只读源邮件通知卡展示,任务列表可见,订单列表不可见。
-`S000/S999` 数据继续兼容展示,语义上分别映射到 `S10/S99`
当前后端已完成 M002 V3 CP1-CP2
- 已建立 42 条 P0 路由枚举 / 稳定配置。
- 已支持结构化 `S10/S99` 入站,创建只读 `SOURCE_MESSAGE_ONLY` 任务。
- 已支持 V3 业务根 `source_message + message_events[]` 的基础解析;可派生到现有任务模型的 event 会创建业务任务,无法派生、显式契约错误或基础 manual_review / parent split 结构不完整的 event 只落 `adapter_contract_error` transition不创建业务任务。
- 已支持 `unhandled_current_intents[]` 最小落库:只写 `UNHANDLED_CURRENT_INTENT` transition不创建业务任务也不按 adapter 契约错误返回。
- 已在 `workflow_reservation_ai_transition` 保存 `route_code``system_process_category``adapter_error_code``adapter_error_message`
尚未完成type-known manual review 同卡解阻、`manual_review.missing_fields[]` 到任务卡可编辑字段白名单的完整映射校验、typed `infrastructure_input_error` 响应、复核场景订单归属确认、真实 OPERA / OHIP、普通任务切换订单、任务列表 / 详情接口完整透出所有 V3 路由字段。
## 4. 接口 1查询订单上下文
### 4.1 请求
| 项目 | 内容 |
| --- | --- |
| Method | `POST` |
| URL | `{TH_HOTEL_API_BASE_URL}/api/ai-query/v1/case-context` |
| request_path | `/api/ai-query/v1/case-context` |
| Content-Type | `application/json` |
| 业务动作 | 只读查询,不创建任务、不修改订单、不写 OPERA |
### 4.2 请求体
```json
{
"group_code": "GRP-001",
"confirmation_number": null,
"reservation_no": null,
"object_type_hint": "group_block",
"target_key_source": "body_current",
"body_thread_used_only_as_evidence": false
}
```
字段说明:
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | 否 | 酒店上下文 IDSuperAgent 默认不传,后端按平台酒店表唯一 `ACTIVE` 酒店解析。若兼容旧契约传入,单酒店阶段必须与系统酒店一致。 |
| `group_code` | 条件必填 | Group / Allotment 查询 key |
| `confirmation_number` | 条件必填 | FIT Confirmation Number 查询 key |
| `reservation_no` | 条件必填 | OPERA reservation no当前系统无可靠表源只传该字段时会返回人工复核原因 |
| `object_type_hint` | 否 | 调用方推测的对象类型,只作为提示 |
| `target_key_source` | 否 | key 来源,例如 `body_current``body_thread_evidence` |
| `body_thread_used_only_as_evidence` | 否 | 历史线程 key 是否仅作为证据 |
`group_code``confirmation_number``reservation_no` 至少一个非空。当前稳定查询能力优先支持 `group_code``confirmation_number`
全局上下文查询最终依赖“后端解析出的酒店 ID + 业务 key”`source_message_id``source_event_index` 不作为查询边界,传入时也不会影响查询结果。
### 4.3 成功响应
```json
{
"success": true,
"request_id": "req-001",
"trace_id": "trace-001",
"data": {
"matched_order_records": [],
"pending_or_open_tasks": [],
"active_workflows": [],
"terminated_records": [],
"target_object_validation": {
"status": "none",
"matched_object_id": null,
"matched_object_type": null,
"can_create_new_booking_task": true,
"can_create_update_task": false,
"can_create_cancel_task": false,
"can_attach_voucher": false,
"can_attach_rooming_list": false,
"needs_manual_review_reason": null
},
"key_relationships": {
"group_code_and_confirmation_same_object": null,
"relationship_evidence": ""
}
},
"warnings": [],
"error": null
}
```
### 4.4 主要数据来源
| 返回字段 | 来源 |
| --- | --- |
| `matched_order_records[]` | `workflow_reservation_order` |
| `pending_or_open_tasks[]` | `workflow_reservation_task` + `workflow_reservation_ai_transition` |
| `terminated_records[]` | 订单 `ENDED` / `LOGIC_DELETED`,任务 `FAILED` / `COMPLETED` |
| `active_workflows[]` | 当前无独立 workflow 表,固定空数组 |
## 5. 接口 2查询对象详情
### 5.1 请求
| 项目 | 内容 |
| --- | --- |
| Method | `POST` |
| URL | `{TH_HOTEL_API_BASE_URL}/api/ai-query/v1/object-detail` |
| request_path | `/api/ai-query/v1/object-detail` |
| Content-Type | `application/json` |
| 业务动作 | 只读查询对象详情,不创建任务、不修改订单、不写 OPERA |
### 5.2 请求体
```json
{
"object_id": "ORDER:1900000000000000100",
"object_type": "group_block"
}
```
字段说明:
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | 否 | 酒店上下文 IDSuperAgent 默认不传,后端解析系统酒店。 |
| `object_id` | 是 | 查询对象 ID第一版只支持 `ORDER:{order_id}` |
| `object_type` | 否 | 调用方对象类型提示,第一版不作为强校验 |
### 5.3 成功响应
```json
{
"success": true,
"request_id": "req-002",
"trace_id": "trace-001",
"data": {
"object_id": "ORDER:1900000000000000100",
"object_type": "group_block",
"order_id": "1900000000000000100",
"order_key_type": "GROUP_CODE",
"group_code": "GRP-001",
"confirmation_number": null,
"reservation_no": null,
"block_id": null,
"temporary_order_code": "TMP-1900000000000000100",
"display_name": "GRP-001",
"status": "ACTIVE",
"source_message_id": "1900000000000000001",
"created_from_task_id": null,
"created_at": "2026-07-08T01:00:00Z",
"last_updated_at": "2026-07-08T01:10:00Z",
"arrival_date": null,
"departure_date": null,
"nights": null,
"guest_count": null,
"room_items": [],
"rate_code": null,
"rate_code_price": null,
"reservation_type": null,
"cancel_status": "not_cancelled",
"can_update": true,
"can_cancel": true,
"hard_validation_warnings": [
{
"code": "OPERA_PROJECTION_UNAVAILABLE",
"message": "当前系统尚未接入 OPERA 对象投影,日期、房型、房价等字段无法确认。"
}
]
},
"warnings": [],
"error": null
}
```
说明:接口 2 响应中的 `source_message_id` 当前是本系统内部 SourceMessage Inbox ID用于对象溯源和排查不要把该字段当作 SuperAgent 任务结果通知接口的外部 `source_message_id` 使用。
## 6. 接口 3查询邮件会话下所有任务
### 6.1 请求
| 项目 | 内容 |
| --- | --- |
| Method | `POST` |
| URL | `{TH_HOTEL_API_BASE_URL}/api/ai-query/v1/message-conversation/tasks` |
| request_path | `/api/ai-query/v1/message-conversation/tasks` |
| Content-Type | `application/json` |
| 业务动作 | 只读查询邮件会话下任务,不创建任务、不修改订单、不写 OPERA |
### 6.2 请求体
按外部邮件会话 ID 查询:
```json
{
"source_provider": "AGENTBUS",
"source_channel": "EMAIL",
"external_conversation_id": "thread-20260708-0001"
}
```
按外部来源消息 ID 作为锚点反查会话:
```json
{
"source_provider": "AGENTBUS",
"source_channel": "EMAIL",
"source_message_id": "mail-20260708-0001"
}
```
字段说明:
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | 否 | 酒店上下文 IDSuperAgent 默认不传,后端解析系统酒店。 |
| `source_provider` | 否 | 来源提供方,按会话 ID 查询和按 `source_message_id` 反查时都参与隔离,缺省为 `AGENTBUS` |
| `source_channel` | 否 | 来源渠道,按会话 ID 查询和按 `source_message_id` 反查时都参与隔离,缺省为 `EMAIL` |
| `external_conversation_id` | 条件必填 | 外部邮件会话 ID对应 AgentBus `source.external_conversation_id` |
| `source_message_id` | 条件必填 | 外部来源消息 ID对应 AgentBus `source.external_message_id`,不是内部 Inbox ID |
`external_conversation_id``source_message_id` 至少一个非空。两者同时传入时,第一版以后端直接按 `external_conversation_id` 查询为准。
### 6.3 成功响应
```json
{
"success": true,
"request_id": "req-003",
"trace_id": "trace-001",
"data": {
"hotel_id": "HOTEL-DEV",
"external_conversation_id": "thread-20260708-0001",
"task_count": 2,
"tasks": [
{
"task_id": "1900000000000000400",
"order_id": "1900000000000000300",
"external_source_message_id": "mail-20260708-0001",
"external_conversation_id": "thread-20260708-0001",
"source_received_at": "2026-07-08T01:00:00Z",
"source_event_index": 1,
"catalog_code": "S02",
"skill_id": "update_booking_amendment_skill",
"result_type": "normal_task",
"task_type": "Update Booking",
"system_task_type": "UPDATE_BOOKING",
"task_card_type": "UPDATE_BOOKING",
"task_subtype": "update_stay_dates",
"task_status": "PENDING_CONFIRM",
"queue_participation": true,
"execution_order": 1,
"parent_task_id": null,
"parent_source_event_index": null,
"linked_task_group_id": null,
"blocked_until_parent_completed": false,
"completed_at": null,
"task_created_at": "2026-07-08T01:01:00Z",
"task_updated_at": "2026-07-08T01:01:00Z"
}
]
},
"warnings": [],
"error": null
}
```
排序规则:
1. 先按邮件 `received_at` 正序。
2. 同一封邮件下,再按任务 `created_at` 正序。
3. 若时间相同,再按 `task_id` 正序稳定排序。
## 7. 接口 4查询邮件会话下所有受控正文
### 7.1 请求
| 项目 | 内容 |
| --- | --- |
| Method | `POST` |
| URL | `{TH_HOTEL_API_BASE_URL}/api/ai-query/v1/message-conversation/messages` |
| request_path | `/api/ai-query/v1/message-conversation/messages` |
| Content-Type | `application/json` |
| 业务动作 | 只读查询邮件会话受控正文,不返回附件 URL 或原始未清洗 HTML |
### 7.2 请求体
请求体字段与接口 3 相同,可按 `external_conversation_id` 查询,也可按外部 `source_message_id` 锚点反查会话。
```json
{
"source_provider": "AGENTBUS",
"source_channel": "EMAIL",
"source_message_id": "mail-20260708-0001"
}
```
### 7.3 成功响应
```json
{
"success": true,
"request_id": "req-004",
"trace_id": "trace-001",
"data": {
"hotel_id": "HOTEL-DEV",
"external_conversation_id": "thread-20260708-0001",
"message_count": 2,
"messages": [
{
"external_source_message_id": "mail-20260708-0001",
"external_conversation_id": "thread-20260708-0001",
"sender_summary": "guest@example.test",
"subject": "Booking update",
"received_at": "2026-07-08T01:00:00Z",
"source_sent_at": "2026-07-08T00:59:00Z",
"text_body": "Please update arrival date...",
"html_body_sanitized": "<html><body>Please update arrival date...</body></html>",
"html_sanitize_required": true,
"html_render_mode": "SANITIZED_HTML"
}
]
},
"warnings": [],
"error": null
}
```
安全边界:
- `messages[]` 按邮件 `received_at` 正序返回。
- 不返回 `html_body` 原始未清洗 HTML。
- 不返回 `attachments``inline_images``external_url`、附件 URL 或 HTML 中的 `href/src` 外链属性。
- 后端读取正文时会写入 SourceMessage 原文访问审计。
## 8. 接口 5SuperAgent 通知 AI 任务结果
### 8.1 请求
| 项目 | 内容 |
| --- | --- |
| Method | `POST` |
| URL | `{TH_HOTEL_API_BASE_URL}/api/integrations/superagent/task-results` |
| request_path | `/api/integrations/superagent/task-results` |
| Content-Type | `application/json``text/plain` |
| 业务动作 | 接收 AI 任务结果V3 JSON 支持结构化 S10/S99 和业务根V2 JSON 继续兼容,旧 S000/S999 文本创建只读特殊任务 |
### 8.2 V3 S10/S99 结构化请求体
S10 示例:
```json
{
"source_message": {
"source_message_id": "mail-20260708-0001",
"subject": null,
"from": null,
"cc": [],
"received_at": null,
"source_channel": "Email"
},
"route_code": "S10",
"handler_type": "main_agent_outcome",
"result_type": "source_message_review_notification",
"current_or_history": "current",
"agent_assessment": {
"status": "no_booking_action_detected",
"reason_code": "no_booking_action_detected",
"automation_action": "none"
},
"notification": {
"required": true,
"notification_type": "source_message_review",
"show_source_message": true,
"requires_user_decision": true
},
"manual_review": null
}
```
S99 与 S10 使用相同结构,但 `route_code=S99``agent_assessment.status=material_package_unavailable`,且 `manual_review` 必须是完整入口复核对象。
### 8.3 V3 业务根请求体
```json
{
"source_message": {
"source_message_id": "mail-20260708-0002",
"subject": "New booking",
"from": null,
"cc": [],
"received_at": null,
"source_channel": "Email"
},
"message_events": [
{
"event_type": "New Booking",
"source_event_index": "E1",
"current_or_history": "current",
"case_keys": {
"group_code": null,
"confirmation_number": "CNF-001"
},
"extracted_fields": {
"booking_object_type": "FIT Reservation"
},
"manual_review": null
}
],
"case_candidates": [],
"extraction_warnings": [],
"unhandled_current_intents": []
}
```
V3 字段说明:
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `source_message.source_message_id` | 是 | 外部来源消息 ID对应 SourceMessage Inbox 的 `external_message_id`;缺失时返回技术错误且不落库 |
| `route_code` | S10/S99 必填 | 只允许 `S10``S99`,用于区分入口通知结果 |
| `result_type` | S10/S99 必填 | 固定为 `source_message_review_notification` |
| `message_events[]` | 业务根必填 | SuperAgent 最终业务事件列表,本系统逐 event 派生路由 |
| `message_events[].event_type` | 是 | V3 active event 或 `Need Manual Review` |
| `message_events[].source_event_index` | 建议必填 | 可为 `E1` 或数字;后端会归一为数字序号 |
| `message_events[].case_keys` | 否 | 订单关联候选键 |
| `message_events[].extracted_fields` | 否 | 业务字段主体和 subtype 判别字段 |
| `message_events[].manual_review` | 否 | `null` 表示普通任务;对象表示 type-known manual review |
| `unhandled_current_intents[]` | 否 | 第一版只保存 `UNHANDLED_CURRENT_INTENT` transition不自动创建业务任务 |
当前已支持的 V3 行为:
- 42 条 P0 路由进入后端枚举 / 稳定配置。
- 结构化 `S10/S99` 创建只读 `SOURCE_MESSAGE_ONLY` 任务,任务列表可见,订单列表不可见。
- 业务 event 能派生到稳定路由时,复用现有订单 / 任务 / 任务卡创建链路。
- event 判别字段不完整、显式携带 `contract_errors`、根 `missing_fields`、不完整 `manual_review` 或不完整 parent split 候选时,写入 `adapter_contract_error` transition不创建订单和任务同一邮件其他 sibling event 继续处理。
- `unhandled_current_intents[]` 写入 `UNHANDLED_CURRENT_INTENT` transition不返回 `adapter_error_code`
### 8.4 V2 JSON 兼容请求体
```json
{
"source_message_id": "mail-20260708-0001",
"ai_task_results": [
{
"source_event_index": 1,
"catalog_code": "S01",
"skill_id": "S01_new_booking_skill",
"result_type": "normal_task",
"task_type": "New Booking",
"task_subtype": "new_fit_reservation",
"current_or_history": "current",
"case_keys": {
"group_code": null,
"confirmation_number": null
},
"visible_reason": "邮件正文包含新建预订请求。",
"relevant_message_excerpt": "Please create a new booking...",
"attachments": [],
"file_references": [],
"context_used": {},
"extracted_fields": {},
"manual_review": null,
"informational_message": null,
"additional_operations": [],
"idempotency_key": null
}
],
"extraction_warnings": []
}
```
字段说明:
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | 否 | 酒店上下文 IDSuperAgent 默认不传,后端解析系统酒店后用于反查 SourceMessage Inbox 幂等键。若兼容旧契约传入,单酒店阶段必须与系统酒店一致。 |
| `source_message_id` | 是 | SuperAgent / Main Agent 原样带回的外部来源消息 ID对应 AgentBus `source.external_message_id`,一次请求只能有一个 |
| `source_provider` | 否 | 来源提供方,第一版缺省为 `AGENTBUS` |
| `source_channel` | 否 | 来源渠道,第一版缺省为 `EMAIL` |
| `ai_task_results[]` | 是 | AI 拆分出的任务结果列表,必须保留数组顺序 |
| `ai_task_results[].source_event_index` | 是 | AI current 事件序号 |
| `ai_task_results[].catalog_code` | 是 | Skill 目录代码 |
| `ai_task_results[].skill_id` | 是 | Skill 标识 |
| `ai_task_results[].result_type` | 是 | 当前代码契约只接受 `normal_task``manual_review``informational_message` 仅历史兼容 |
| `ai_task_results[].task_type` | 是 | AI 原始任务类型 |
| `ai_task_results[].task_subtype` | 否 | 业务动作 subtype |
| `ai_task_results[].case_keys` | 否 | 订单关联候选键 |
| `ai_task_results[].extracted_fields` | 否 | 业务字段主体 |
正式联调时SuperAgent 不需要传 `hotel_id`。后端通过系统酒店 `hotel_id + source_provider(默认 AGENTBUS) + source_channel(默认 EMAIL) + source_message_id` 查找 `platform_source_message_inbox.external_message_id`。如果没有找到,返回 `SOURCE_MESSAGE_NOT_FOUND`。本地旧夹具允许在缺少 `hotel_id` 时使用内部数字 SourceMessage ID但该兼容路径不作为 SuperAgent 正式契约。
`informational_message` 结构化任务仅用于历史兼容。新数据如果是纯信息类邮件或无法形成业务素材包,应优先使用 V3 结构化 `S10/S99`;旧联调或兼容场景仍可使用下面的 `S000/S999` 文本请求体。
### 8.5 S000/S999 文本请求体
纯信息类邮件:
```text
S000,mail-20260708-0001
```
无法形成业务素材包:
```text
S999,mail-20260708-0001
```
字段规则:
| 片段 | 中文说明 |
| --- | --- |
| `S000` | 纯信息类邮件,不需要形成业务任务。 |
| `S999` | 入口阶段无法形成业务素材包,不需要进入业务执行。 |
| `mail-20260708-0001` | 外部来源消息 ID对应 SourceMessage Inbox 的 `external_message_id`。 |
S000/S999 不在 body 里传 `hotel_id`,后端使用平台酒店表唯一 `ACTIVE` 酒店查询 SourceMessage Inbox。命中后创建 `SOURCE_MESSAGE_ONLY` 只读特殊任务:任务列表可见,订单列表不可见,不允许编辑、确认、转换订单、执行 OPERA 或重试 OPERA也不参与同订单任务执行顺序阻塞。该文本格式仅为兼容路径新数据优先使用结构化 `S10/S99`
### 8.6 成功响应
```json
{
"request_id": "req-003",
"source_message_id": "mail-20260708-0001",
"batch_id": "1900000000000000200",
"idempotent_replay": false,
"accepted_count": 1,
"items": [
{
"source_event_index": 1,
"array_index": 1,
"ai_transition_id": "1900000000000000250",
"route_code": "R01_NEW_FIT_RESERVATION_NORMAL",
"system_process_category": "BUSINESS_TASK",
"adapter_error_code": null,
"order_id": "1900000000000000300",
"task_id": "1900000000000000400",
"system_task_type": "NEW_BOOKING",
"task_card_type": "NEW_BOOKING",
"task_status": "PENDING_CONFIRM",
"order_status": "TEMPORARY",
"execution_order": 1
}
],
"warnings": []
}
```
S000/S999 成功响应示例:
```json
{
"request_id": "req-004",
"source_message_id": "mail-20260708-0001",
"batch_id": "1900000000000000500",
"idempotent_replay": false,
"accepted_count": 1,
"items": [
{
"source_event_index": 1,
"array_index": 1,
"ai_transition_id": "1900000000000000550",
"route_code": "S10",
"system_process_category": "SOURCE_MESSAGE_NOTIFICATION",
"adapter_error_code": null,
"order_id": "1900000000000000600",
"task_id": "1900000000000000700",
"system_task_type": "SOURCE_MESSAGE_ONLY",
"task_card_type": "SOURCE_MESSAGE_ONLY",
"task_status": "COMPLETED",
"order_status": "TEMPORARY",
"execution_order": 1
}
],
"warnings": []
}
```
V3 `adapter_contract_error` 响应中的 `items[]` 不会包含 `order_id` / `task_id`
```json
{
"source_event_index": 2,
"array_index": 1,
"ai_transition_id": "1900000000000000800",
"route_code": null,
"system_process_category": "ADAPTER_CONTRACT_ERROR",
"adapter_error_code": "EVENT_ROUTE_UNSUPPORTED",
"order_id": null,
"task_id": null,
"system_task_type": "ADAPTER_CONTRACT_ERROR",
"task_card_type": "ADAPTER_CONTRACT_ERROR",
"task_status": null,
"order_status": null,
"execution_order": null
}
```
## 9. 错误响应
### 9.1 查询接口错误响应
```json
{
"success": false,
"request_id": "req-001",
"trace_id": "trace-001",
"data": null,
"warnings": [],
"error": {
"code": "AUTH_SIGNATURE_INVALID",
"message": "签名校验失败。",
"details": {}
}
}
```
### 9.2 任务结果通知接口错误响应
```json
{
"request_id": null,
"error_code": "AUTH_SIGNATURE_INVALID",
"message": "签名校验失败。",
"details": []
}
```
### 9.3 常见错误码
| 错误码 | HTTP 状态 | 中文说明 |
| --- | --- | --- |
| `AUTH_HEADER_MISSING` | 401 | HMAC 必要 Header 缺失 |
| `AUTH_HEADER_INVALID` | 401 | HMAC Header 格式或长度无效 |
| `AUTH_TIMESTAMP_INVALID` | 401 | timestamp 格式错误或超出时间窗口 |
| `AUTH_SIGNATURE_INVALID` | 401 | 签名不匹配或服务端未配置 secret |
| `AUTH_NONCE_REPLAY` | 409 | nonce 已被使用 |
| `REQUEST_BODY_TOO_LARGE` | 413 | 请求体超过大小限制 |
| `REQUEST_CONTENT_TYPE_UNSUPPORTED` | 415 | 查询接口 `Content-Type` 不是 `application/json`,或任务结果通知接口不是 `application/json` / `text/plain` |
| `REQUEST_BODY_INVALID` | 400 | JSON 不合法,或 S000/S999 文本格式不符合 `结果码,source_message_id` |
| `QUERY_KEY_REQUIRED` | 400 | 查询接口缺少可用业务 key |
| `MESSAGE_CONVERSATION_QUERY_KEY_REQUIRED` | 400 | 会话查询缺少 `external_conversation_id``source_message_id` |
| `OBJECT_NOT_FOUND` | 404 | 对象详情查询目标不存在 |
| `MESSAGE_CONVERSATION_NOT_FOUND` | 404 | 外部邮件会话尚未写入 SourceMessage Inbox |
| `SYSTEM_HOTEL_NOT_CONFIGURED` | 409 | 平台酒店表没有可用 `ACTIVE` 酒店 |
| `SYSTEM_HOTEL_AMBIGUOUS` | 409 | 单酒店阶段平台酒店表存在多家 `ACTIVE` 酒店 |
| `HOTEL_ACCESS_DENIED` | 403 | 显式传入的 `hotel_id` 与系统酒店或当前用户授权酒店不一致 |
| `SOURCE_MESSAGE_NOT_FOUND` | 404 | 任务结果通知或会话锚点引用的外部来源消息尚未写入 SourceMessage Inbox |
## 10. HMAC 上线配置
上线需要配置:
| 配置项 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `SUPERAGENT_DEV_TASK_RESULT_HMAC_SECRET` | dev 必填 | dev HMAC 共享密钥;查询接口和任务结果通知接口共用 |
| `SUPERAGENT_TEST_TASK_RESULT_HMAC_SECRET` | test 必填 | test HMAC 共享密钥;查询接口和任务结果通知接口共用 |
| `SUPERAGENT_PROD_TASK_RESULT_HMAC_SECRET` | prod 必填 | prod HMAC 共享密钥;生产不能为空,只能通过 Secret 注入 |
| `SUPERAGENT_TASK_RESULT_HMAC_SECRET` | 兼容兜底 | 旧通用 HMAC 变量;优先使用环境专属变量 |
| `SUPERAGENT_TASK_RESULT_CLOCK_SKEW_SECONDS` | 否 | 请求时间允许偏移,默认 `300` 秒 |
| `SUPERAGENT_TASK_RESULT_NONCE_TTL_SECONDS` | 否 | nonce 防重放保存时间,默认 `600` 秒 |
| `SUPERAGENT_TASK_RESULT_MAX_BODY_BYTES` | 否 | 请求体最大字节数,默认 `1048576` |
上线注意事项:
- 本系统和 SuperAgent 必须配置同一个 HMAC secret。
- 生产 secret 只能放在部署平台 Secret 或环境变量中,不能写入仓库、镜像、前端配置或普通文档。
- SuperAgent 必须使用原始请求体计算 SHA-256不能使用格式化后 JSON。
- HMAC canonical string 的第二行必须使用 `request_path`,例如 `/api/ai-query/v1/case-context`
- SuperAgent 每次请求必须生成全新的 nonce同一个 `client_id + nonce` 在 TTL 窗口内不能重复使用。
- 调用双方服务器时间必须同步,建议使用 NTP。
- 建议所有接口只暴露在 HTTPS 和可信网络边界内。
- 轮换 secret 时需要安排双写或短窗口切换;当前第一版后端只支持一个 secret轮换窗口内需要协调发布顺序。