# 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=` | | `X-TH-Hotel-Request-Id` | 否 | 调用方请求 ID,用于日志串联 | | `X-TH-Hotel-AI-Trace-Id` | 否 | AI 运行链路 ID,查询接口会原样带回 `trace_id` | ### 2.2 签名串 签名串使用接口 path,不包含域名、query string 或 fragment。 ```text POST ``` 签名算法: ```text signature = HMAC_SHA256(SUPERAGENT_HMAC_SECRET, canonical_string) ``` Header 写法: ```text X-TH-Hotel-SuperAgent-Signature: sha256= ``` ### 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-CP6: - 已建立 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 同卡解阻、当前订单归属确认、P0 fixtures 回归测试和 V3 typed `infrastructure_input_error` 响应。 尚未完成:真实 OPERA / OHIP、普通任务切换订单、字段矩阵从当前扁平结构整体迁移到 0711 P0 新结构。 ## 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` | 否 | 酒店上下文 ID;SuperAgent 默认不传,后端按平台酒店表唯一 `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` | 否 | 酒店上下文 ID;SuperAgent 默认不传,后端解析系统酒店。 | | `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` | 否 | 酒店上下文 ID;SuperAgent 默认不传,后端解析系统酒店。 | | `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": "Please update arrival date...", "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. 接口 5:SuperAgent 通知 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, "visible_message": "未匹配到当前 Agent 支持的业务事件类型,请查看原邮件并决定是否需要回复或进行其他处理。" }, "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", "event_role": "travel_agent_request", "source_event_index": "E1", "current_or_history": "current", "case_keys": { "group_code": null, "confirmation_number": "CNF-001", "reservation_number": null, "block_code": null }, "relevant_message_excerpt": "Please create a new FIT reservation.", "attachments": [], "file_references": [], "context_used": {}, "extracted_fields": { "booking_object_type": "FIT Reservation", "arrival_date": "2026-09-01", "departure_date": "2026-09-03", "room_quantity": 2, "room_type": "Deluxe King", "pms_room_type_code": "RM2" }, "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[].event_role` | 是 | 事件来源角色,第一版必须是非空字符串 | | `message_events[].source_event_index` | 是 | 可为 `E1` 或数字;后端会归一为数字序号 | | `message_events[].current_or_history` | 是 | 第一版只接受 `current` | | `message_events[].case_keys` | 是 | 必须包含 `group_code`、`confirmation_number`、`reservation_number`、`block_code` 四个字段,值为 string 或 null | | `message_events[].relevant_message_excerpt` | 是 | 当前事件的邮件证据摘录,必须是字符串 | | `message_events[].attachments` | 是 | 当前事件引用附件数组,无附件传空数组 | | `message_events[].file_references` | 是 | 当前事件引用文件数组,无文件传空数组 | | `message_events[].context_used` | 是 | 当前事件使用的上下文对象,无上下文传空对象 | | `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`。 - V3 `message_events[].relevant_message_excerpt` 入站后会归一化到任务卡 AI payload 根路径,供旧字段矩阵读取证据字段;SuperAgent 仍只需要按 V3 event 契约提供该字段。 - type-known manual review 第一版在同一业务任务卡解阻;New Booking 房型字段主路径已迁移为 `room_items[0]`,例如 `/extracted_fields/room_items/0/pms_room_type_code`。旧扁平字段仍可作为过渡提交 key,解阻接口也支持提交 P0 主 `field_path` 或旧扁平 `field_path`,响应会归一化为 P0 主 `field_path`。 `source_message.source_message_id` 缺失时返回 `HTTP 400`,响应体不使用通用错误包装: ```json { "result_type": "infrastructure_input_error", "error_code": "missing_source_message_id", "retryable": true, "missing_fields": [ "source_message.source_message_id" ] } ``` ### 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` | 否 | 酒店上下文 ID;SuperAgent 默认不传,后端解析系统酒店后用于反查 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 | | `missing_source_message_id` | 400 | V3 请求缺少 `source_message.source_message_id`,响应体为 typed `infrastructure_input_error` | ## 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,轮换窗口内需要协调发布顺序。