openapi: 3.0.3
info:
    title: AI 眼镜云端 API
    version: "2026-08-11"
    description: |
        面向账号、已绑定客户端和管理后台的 HTTP API。

        每个自然人只对应一个全局账号 `account_id`；手机号、密码、Google、Apple、微信只是绑定到该账号的登录身份，不是五套账号。登录与绑定是两条独立流程：匿名登录只能进入该身份已归属的账号；新增身份必须先登录目标账号后显式绑定。身份已属于其他账号时返回 409，绝不静默切号、按同邮箱自动合并或覆盖归属。

        用户资料、密码、登录身份、密钥和设备归属等控制面接口只接受登录会话（`ag_session_...`）；客户端数据面使用可撤销 access key（`ag_live_...`）。服务端只保存令牌 HMAC，不保存明文。
        DashScope / Qwen 的工作空间密钥仅在服务端配置，绝不能放入 Apifox 环境、浏览器或客户端请求。

        相机、录音和眼镜控制不是本 HTTP 服务的能力：它们属于前台客户端 MCP runtime。
        题词器的脚本、分页、渲染和 ASR 文本对齐也全部在前台客户端完成；云端只提供不携带脚本的 Qwen-Audio 流式实时 ASR 网关。
        HTTP 的 `/v1/agent/plan` 只返回受控客户端工具建议，不会打开设备；`/v1/agent/execute` 会为已绑定设备持久化命令，必须经过客户端领取、前台确认、MCP 执行和结果回传。MCP stdio 协议在文档末尾以 `x-mcp` 说明，不作为虚假的 HTTP 路径导入。
    contact:
        name: AI 眼镜 API
servers:
    - url: http://127.0.0.1:8080
      description: 本地开发环境
    - url: https://{host}
      description: 已部署 API 地址
      variables:
        host:
            default: api.example.com
tags:
    - name: 系统
    - name: 账号
    - name: 智能体
    - name: 多模态
    - name: 翻译
    - name: 实时识别
    - name: 支付
    - name: 记忆
    - name: 设备
    - name: 客户端统计
    - name: 访问密钥
    - name: 管理后台
security:
    - bearerAuth: []
paths:
    /:
        get:
            tags: [系统]
            operationId: openAPIRoot
            summary: 打开 OpenAPI 服务入口
            description: 浏览器访问服务根路径时，临时跳转至 `/openapi.yaml`。
            security: []
            responses:
                '307':
                    description: 临时跳转至 OpenAPI YAML 文件。
                    headers:
                        Location:
                            schema: {type: string, example: /openapi.yaml}
    /openapi.yaml:
        get:
            tags: [系统]
            operationId: getOpenAPISpec
            summary: 获取可供 Apifox 导入的 OpenAPI YAML
            description: 服务端从配置的规范文件路径读取并返回当前 API 合约。
            security: []
            responses:
                '200':
                    description: OpenAPI 3.0.3 YAML。
                    content:
                        application/yaml:
                            schema: {type: string}
                '503':
                    description: 当前部署未包含或无法读取 OpenAPI 规范文件。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/ErrorResponse'}
    /healthz:
        get:
            tags: [系统]
            operationId: healthz
            summary: 查询 API 与持久化存储的轻量健康状态
            description: 不访问模型、短信、OAuth、支付或容器运行时等外部依赖，适合负载均衡健康检查。完整依赖状态仅对管理员开放于 `/v1/admin/system/health`。
            security: []
            responses:
                '200':
                    description: 服务进程和持久化存储均可用。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/HealthResponse'
                            example:
                                ok: true
                                ready: true
                                storage: postgres
                '503': {$ref: '#/components/responses/ServiceUnavailable'}
    /livez:
        get:
            tags: [系统]
            operationId: liveness
            summary: 查询进程存活状态
            description: 仅证明 HTTP 进程仍在运行，不访问数据库，适合作为容器 liveness probe。
            security: []
            responses:
                '200':
                    description: 进程存活。
                    content:
                        application/json:
                            schema:
                                type: object
                                required: [ok, status]
                                properties:
                                    ok: {type: boolean, enum: [true]}
                                    status: {type: string, enum: [live]}
    /readyz:
        get:
            tags: [系统]
            operationId: readiness
            summary: 查询数据库依赖是否就绪
            description: 检查当前持久化存储连接，适合作为 readiness probe。
            security: []
            responses:
                '200':
                    description: 服务可接收业务请求。
                    content:
                        application/json:
                            schema:
                                type: object
                                required: [ok, status, storage]
                                properties:
                                    ok: {type: boolean, enum: [true]}
                                    status: {type: string, enum: [ready]}
                                    storage: {type: string}
                '503': {$ref: '#/components/responses/ServiceUnavailable'}
    /v1/capabilities:
        get:
            tags: [系统]
            operationId: getCapabilities
            summary: 查询每条业务链路是否真正可用及当前模型/适配器
            description: 用于客户端和控制台隐藏未配置功能，避免把固定返回 503 的可选集成展示成可用接口；不返回任何密钥或供应商凭据。
            security: []
            responses:
                '200':
                    description: 当前部署能力矩阵。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/CapabilitiesResponse'}
    /v1/auth/providers:
        get:
            tags: [账号]
            operationId: listLoginProviders
            summary: 查询当前部署启用的登录方式
            security: []
            responses:
                '200':
                    description: 手机号、Google、Apple、微信登录的启用状态，以及当前部署是否可完成手机号验证的公开密码注册；不返回任何 OAuth 或短信密钥。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/LoginProviderListResponse'}
    /v1/auth/phone/request:
        post:
            tags: [账号]
            operationId: requestPhoneLoginCode
            summary: 按指定用途向手机号发送一次性验证码
            security: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/PhoneCodeRequest'}
                        example: {phone: "+8613812345678", purpose: login}
            responses:
                '202':
                    description: 验证码已交由配置的短信供应商发送。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/PhoneCodeResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '503': {$ref: '#/components/responses/ServiceUnavailable'}
    /v1/auth/phone/verify:
        post:
            tags: [账号]
            operationId: verifyPhoneLogin
            summary: 用手机号验证码登录或自动注册账号
            description: 匿名账号入口。已有有效用户会话时返回 `account_switch_requires_logout`；新增手机号应改用当前账号的手机号绑定接口，避免意外创建或切换账号。若该全局账号存在管理员设置的临时密码，响应的 `password_change_required=true`，客户端必须进入改密引导。
            security: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/PhoneLoginRequest'}
                        example: {phone: "+8613812345678", code: "123456", display_name: "张三"}
            responses:
                '200':
                    description: 已签发可撤销会话；同时写入 HttpOnly Cookie。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/LoginResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/auth/password/register:
        post:
            tags: [账号]
            operationId: registerPassword
            summary: 使用手机号验证码注册用户名密码账号
            description: 完全匿名调用。验证码会发送至 body.phone；验证码验证成功后才创建账号、保存密码哈希并签发登录会话。无需管理员令牌或已有 Bearer Token；已有有效用户会话时必须先退出，不能借注册接口切换账号。用户自行选择的初始密码始终返回 `password_change_required=false`。
            security: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/PasswordRegistrationRequest'}
                        example:
                            phone: "+8613812345678"
                            code: "123456"
                            login_name: "zhangsan"
                            password: "Glasses#2026Secure"
                            display_name: "张三"
            responses:
                '200':
                    description: 已注册并签发普通用户会话。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/PasswordLoginResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/auth/password/reset:
        post:
            tags: [账号]
            operationId: resetPasswordByPhone
            summary: 通过手机号验证码重置密码
            description: 完全匿名调用。验证码会发送到 body.phone；验证成功后只查找既有手机号身份并重置该规范账号的密码。未知手机号不会创建账号。无需旧密码、管理员令牌或已有 Token；已有有效用户会话时应使用账号内改密接口或先退出。
            security: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/PhonePasswordResetRequest'}
                        example:
                            phone: "+8613812345678"
                            code: "123456"
                            password: "Glasses#2026Secure"
            responses:
                '200':
                    description: 密码已重置并签发新普通用户会话。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/PasswordLoginResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/auth/oauth/{provider}/authorize:
        get:
            tags: [账号]
            operationId: startOAuthLogin
            summary: 开始 Google、Apple 或微信授权码登录
            description: 服务端生成并保存一次性登录态 state/nonce，随后 302 跳转到身份提供商；仅已配置的提供商可用。此接口是匿名登录入口，不用于把提供商绑定到当前登录账号；绑定必须调用 `/v1/account/identities/oauth/{provider}/authorize`。
            security: []
            parameters:
                - $ref: '#/components/parameters/OAuthProvider'
            responses:
                '302': {description: 跳转至 OAuth 授权页。}
                '409': {$ref: '#/components/responses/Conflict'}
                '503': {$ref: '#/components/responses/ServiceUnavailable'}
    /v1/auth/oauth/{provider}/callback:
        get:
            tags: [账号]
            operationId: finishOAuthLoginGet
            summary: 接收 OAuth GET 回调
            security: []
            parameters:
                - $ref: '#/components/parameters/OAuthProvider'
            responses:
                '200':
                    description: 未设置前端回跳地址时返回登录会话，其中 password_change_required 是客户端改密引导的权威信号。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/LoginResponse'}
                '302': {description: 设置前端回跳地址时，写入 HttpOnly Cookie 后跳回前端；回跳 URL 附带 login=success 和 password_change_required=true|false。}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/auth/oauth/callback:
        post:
            tags: [账号]
            operationId: finishOAuthLoginPost
            summary: 接收 Apple form_post OAuth 回调
            security: []
            description: Apple 等提供商使用 form_post 时，服务端从一次性 state 对应的服务端记录中确定 provider；provider 不由 URL 或请求体决定。
            requestBody:
                required: true
                content:
                    application/x-www-form-urlencoded:
                        schema:
                            type: object
                            required: [state]
                            properties:
                                state: {type: string}
                                code: {type: string}
                                id_token: {type: string}
                                user: {type: string, description: Apple 首次授权时可能提供的 JSON 用户资料。}
                                error: {type: string}
            responses:
                '200':
                    description: 未设置前端回跳地址时返回登录会话，其中 password_change_required 是客户端改密引导的权威信号。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/LoginResponse'}
                '302': {description: 设置前端回跳地址时，写入 HttpOnly Cookie 后跳回前端；回跳 URL 附带 login=success 和 password_change_required=true|false。}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/auth/me:
        get:
            tags: [账号]
            operationId: getCurrentAccount
            summary: 获取当前登录账号资料与强制改密状态
            description: 客户端在冷启动、OAuth 回跳或恢复本地会话后调用。password_change_required=true 时，仅允许本接口、POST /v1/account/password 和 POST /v1/auth/logout。
            security: [{accountSessionAuth: []}]
            responses:
                '200':
                    description: 当前账号。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AccountSessionStateResponse'}
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/auth/logout:
        post:
            tags: [账号]
            operationId: logout
            summary: 撤销当前登录会话
            security: [{accountSessionAuth: []}]
            responses:
                '204': {description: 会话已撤销并清除 Cookie。}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/account:
        get:
            tags: [账号]
            operationId: getAccount
            summary: 获取当前账号资料与强制改密状态
            description: 与 GET /v1/auth/me 等价的会话状态入口；password_change_required=true 时仅可用于客户端改密引导。
            security: [{accountSessionAuth: []}]
            responses:
                '200':
                    description: 当前账号。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AccountSessionStateResponse'}
                '401': {$ref: '#/components/responses/Unauthorized'}
        post:
            tags: [账号]
            operationId: updateAccount
            summary: 更新当前账号显示名称或头像
            security: [{accountSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/AccountUpdateRequest'}
                        example: {display_name: "张三", avatar_url: "https://cdn.example.com/avatar.png"}
            responses:
                '200':
                    description: 已更新账号资料。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AccountProfile'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/account/close:
        post:
            tags: [账号]
            operationId: closeAccount
            summary: 注销当前账号并撤销全部会话和访问密钥
            security: [{accountSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/AccountCloseRequest'}
                        example: {confirm: true}
            responses:
                '204': {description: 账号已注销；设备已解绑，记忆和身份关联已删除。}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/account/identities:
        get:
            tags: [账号]
            operationId: listAccountIdentities
            summary: 获取当前账号绑定的登录方式
            security: [{accountSessionAuth: []}]
            responses:
                '200':
                    description: 登录身份列表。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AuthIdentityListResponse'}
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/account/identities/phone/link:
        post:
            tags: [账号]
            operationId: linkPhoneIdentity
            summary: 将已验证手机号绑定到当前全局账号
            description: |
                必须同时持有当前账号会话和目标手机号验证码。手机号尚未绑定时写入当前 account_id；
                已绑定当前账号时幂等成功；已绑定其他账号时返回 `identity_bound_elsewhere`，不迁移、不合并、不切换当前会话。
                验证码可先通过 `/v1/auth/phone/request` 发送。
            security: [{accountSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/PhoneIdentityLinkRequest'}
                        example: {phone: "+8613812345678", code: "123456"}
            responses:
                '200':
                    description: 已绑定到当前账号，或此前已经绑定当前账号。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/IdentityLinkResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/account/identities/oauth/{provider}/authorize:
        get:
            tags: [账号]
            operationId: startOAuthIdentityLink
            summary: 将 Google、Apple 或微信绑定到当前全局账号
            description: |
                需要当前账号会话。服务端把目标 account_id 写入一次性 OAuth state 后跳转到提供商。
                公共 callback 根据 state 执行绑定而非登录；身份属于其他账号时返回 409，绝不会把当前用户切换到那个账号。
            security: [{accountSessionAuth: []}]
            parameters:
                - $ref: '#/components/parameters/OAuthProvider'
            responses:
                '302': {description: 跳转至 OAuth 授权页。}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/Conflict'}
                '503': {$ref: '#/components/responses/ServiceUnavailable'}
    /v1/account/identities/unlink:
        post:
            tags: [账号]
            operationId: unlinkAccountIdentity
            summary: 解绑一个登录方式
            security: [{accountSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            required: [identity_id]
                            properties:
                                identity_id: {type: string, maxLength: 128}
                        example: {identity_id: identity_01K2A8EXAMPLE}
            responses:
                '204': {description: 已解绑。}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '404': {$ref: '#/components/responses/NotFound'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/agent/ask:
        post:
            tags: [智能体]
            operationId: askAgent
            summary: 向账号隔离的 Qwen 智能体提问
            description: ADK 智能体会在需要时检索一次当前账号的记忆；它不能访问设备硬件。
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/AgentAskRequest'
                        example:
                            question: 我有哪些翻译偏好？
                            enable_thinking: false
                            max_tokens: 512
            responses:
                '200':
                    description: 智能体回答。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AgentAskResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '502': {$ref: '#/components/responses/QwenError'}
                '503': {$ref: '#/components/responses/QwenError'}
    /v1/agent/plan:
        post:
            tags: [智能体]
            operationId: planClientTool
            summary: 规划一次前台客户端工具调用
            description: |
                仅返回建议，不执行操作。绑定客户端会独立检查能力、前台确认和设备 ACK。
                该低延迟接口的 `enable_thinking` 默认值为 `false`。
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/AgentPlanRequest'
                        example:
                            question: 拍一张照片
                            device_id: glasses_001
                            enable_thinking: false
                            max_tokens: 96
            responses:
                '200':
                    description: 普通回答或恰好一次建议的客户端工具调用。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AgentPlanResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '502': {$ref: '#/components/responses/QwenError'}
    /v1/agent/complex-plan:
        post:
            tags: [智能体]
            operationId: planComplexClientTools
            summary: 创建仅供审核的复合客户端计划
            description: |
                结果状态为 `ready`、`clarify` 或 `unsupported`。`clarify` 与 `unsupported` 始终返回零动作。
                `ready` 不是执行授权：客户端对每个确认后的动作返回完整结果，并等待前置动作 ACK。
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/ComplexPlanRequest'
                        example:
                            question: 先拍照，再开始录音
                            device_id: glasses_001
                            max_tokens: 160
            responses:
                '200':
                    description: 已校验的审核计划。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ComplexPlanResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '502': {$ref: '#/components/responses/QwenError'}
    /v1/agent/ingest:
        post:
            tags: [智能体, 多模态]
            operationId: ingestTextOrAudio
            summary: 识别文本或上传音频，并可选翻译
            description: |
                JSON 用于提交已识别文本；multipart 音频会发送给 Qwen-ASR-Realtime。
                支持 PCM16、原始 Opus、可转码为 Opus 的 WebM/OGG/MP4，以及 PCM16 单声道 WAV；最大 15 MiB。
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/IngestTextRequest'
                        example:
                            text: 你好，很高兴认识你
                            translate_to: English
                            source_lang: zh
                    multipart/form-data:
                        schema:
                            $ref: '#/components/schemas/IngestAudioRequest'
            responses:
                '200':
                    description: 识别文本及可选翻译结果。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/IngestResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '413': {$ref: '#/components/responses/PayloadTooLarge'}
                '502': {$ref: '#/components/responses/QwenError'}
    /v1/agent/vision:
        post:
            tags: [智能体, 多模态]
            operationId: analyzeVision
            summary: 使用 Qwen 视觉理解已获用户同意的图片
            description: 只能上传客户端持有且已单独前台确认的照片；仅支持 JPEG、PNG、WebP，最大 8 MiB。
            requestBody:
                required: true
                content:
                    multipart/form-data:
                        schema:
                            $ref: '#/components/schemas/VisionRequest'
            responses:
                '200':
                    description: 视觉理解回答。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ModelAnswer'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '502': {$ref: '#/components/responses/QwenError'}
    /v1/agent/execute:
        post:
            tags: [智能体]
            operationId: dispatchClientMCPTool
            summary: 为已绑定前台客户端创建受控 MCP 命令
            description: |
                仅允许服务端白名单动作。接口会校验目标设备属于当前账号，将标准 JSON-RPC `tools/call` 和 `ui_navigation` 持久化入队，并返回可查询的 command_id。
                客户端通过 `/v1/client/commands/claim` 领取，执行本地确认与 MCP 调用，再通过 result 接口回传完整结果。202 仅代表已入队，不代表已经执行。
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/ExecuteToolRequest'
                        example:
                            tool: translate
                            device_id: glass-001
                            payload:
                                text: 早上好
                                target_lang: English
            responses:
                '202':
                    description: 命令已持久化入队，等待目标客户端领取。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/ClientCommandCreateResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/DeviceConflict'}
    /v1/agent/commands/get:
        post:
            tags: [智能体]
            operationId: getClientCommand
            summary: 查询当前账号的一条客户端命令及完整执行结果
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            required: [command_id]
                            properties:
                                command_id: {type: string, maxLength: 128}
                        example: {command_id: command_01K2A8EXAMPLE}
            responses:
                '200':
                    description: 命令状态；成功或失败后包含客户端回传的完整 MCP result。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/ClientCommandResponse'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/tools/translate:
        post:
            tags: [翻译]
            operationId: translateText
            summary: 使用 Qwen-MT 翻译文本
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/TranslationRequest'
                        example:
                            text: 你好，世界
                            source_lang: zh
                            target_lang: English
            responses:
                '200':
                    description: 完整翻译结果。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/TranslationResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '502': {$ref: '#/components/responses/QwenError'}
    /v1/tools/translate/stream:
        post:
            tags: [翻译]
            operationId: translateTextStream
            summary: 通过 SSE 流式返回 Qwen-MT 增量翻译
            description: "第一个 `translation` 事件含有 `first: true` 和 `elapsed_ms`，用于记录服务首个翻译分片延迟。"
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/TranslationRequest'
                        example: {text: "早上好", source_lang: "zh", target_lang: "English"}
            responses:
                '200':
                    description: Server-Sent Events，事件名称为 `translation`、`done` 或 `error`。
                    content:
                        text/event-stream:
                            schema:
                                type: string
                            example: |
                                event: translation
                                data: {"delta":"Hello","first":true,"elapsed_ms":180}

                                event: translation
                                data: {"delta":" world"}

                                event: done
                                data: {"model":"qwen-mt-flash"}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/tools/live-translate:
        get:
            tags: [翻译]
            operationId: openLiveTranslateSession
            summary: 打开 Qwen3.5-LiveTranslate 实时同声传译会话
            description: 仅供已绑定的原生客户端发起 WebSocket 升级。服务端固定模型和鉴权；默认由客户端通过 `input_audio_buffer.commit` 控制语义边界。将 `turn_detection` 设为 `server_vad` 时由模型侧 VAD 自动判定语音段，适合实时同传和延迟对比。客户端只可发送 `input_audio_buffer.append`（Base64 Opus/PCM）、`input_audio_buffer.commit` 和 `session.finish`，不会得到百炼密钥。
            parameters:
                - name: target_lang
                  in: query
                  required: true
                  description: 目标语言；可使用 `en`、`zh` 等语言代码或常见语言名称。
                  schema: {type: string, minLength: 1, maxLength: 64}
                - name: source_lang
                  in: query
                  required: false
                  description: 源语言提示；省略时自动识别。
                  schema: {type: string, maxLength: 64}
                - name: audio_format
                  in: query
                  required: false
                  schema: {type: string, enum: [opus, pcm], default: opus}
                - name: sample_rate
                  in: query
                  required: false
                  schema: {type: integer, enum: [8000, 16000], default: 16000}
                - name: turn_detection
                  in: query
                  required: false
                  description: "`manual`（默认）由客户端 commit；`server_vad` 使用模型侧 VAD 自动切分语音段。"
                  schema: {type: string, enum: [manual, server_vad], default: manual}
            responses:
                '101': {description: 已升级为 WebSocket；服务端先发送 `session.ready`，随后透传 Qwen 的翻译事件。}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '502': {$ref: '#/components/responses/QwenError'}
    /v1/tools/asr/realtime:
        get:
            tags: [实时识别]
            operationId: openRealtimeASRSession
            summary: 打开用于本地题词器对齐的 Qwen-Audio 流式实时 ASR 会话
            description: |
                仅供已绑定的原生客户端发起 WebSocket 升级。服务端固定 `qwen-audio-3.0-asr-flash-streaming` 并持有百炼密钥；上游使用 `run-task`、二进制音频与 `finish-task` 协议。
                网关先返回本地 `session.ready`，随后透传上游的 `result-generated`、`task-finished` 或 `task-failed` 事件。客户端应优先发送二进制 Opus/PCM 音频帧，最后发送 `{"type":"session.finish"}`；兼容 `input_audio_buffer.append` 的 Base64 JSON 包装。`input_audio_buffer.commit` 被接受但不改变上游任务。
                题词器脚本、分页、显示状态和文本匹配均不得上传；客户端以返回的 partial/final sentence 在本地推进题词进度。
            parameters:
                - name: language
                  in: query
                  required: false
                  description: 可选语言提示，如 `zh` 或 `en`。
                  schema: {type: string, maxLength: 64}
                - name: audio_format
                  in: query
                  required: false
                  schema: {type: string, enum: [opus, pcm], default: opus}
                - name: sample_rate
                  in: query
                  required: false
                  schema: {type: integer, minimum: 8000, maximum: 48000, default: 16000}
            responses:
                '101': {description: 已升级为 WebSocket；服务端先发送 `session.ready`，随后透传 Qwen-Audio 流式转写事件。}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '502': {$ref: '#/components/responses/QwenError'}
    /v1/tools/payment/intents:
        post:
            tags: [支付]
            security: [{accountSessionAuth: []}]
            operationId: createPaymentIntent
            summary: 创建幂等的银联支付意图
            description: |
                创建状态为 `requires_confirmation` 的支付意图，不会直接扣款。
                相同 Idempotency-Key 只有请求体金额、币种、说明和商户引用完全一致时才会重放；内容不同返回 409，避免误复用旧订单。
                此资金操作必须使用交互式账号会话；客户端 access key 只能调度支付确认界面，不能创建或确认支付。
                在官方银联 SDK 适配器和商户证书安装前，确认接口会按设计返回 `503 payment_provider_unavailable`。
            parameters:
                - $ref: '#/components/parameters/IdempotencyKey'
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/PaymentCreateRequest'
                        example:
                            amount: 1234
                            currency: CNY
                            description: 会员订阅
                            merchant_ref: order_20260810_001
            responses:
                '201':
                    description: 新建支付意图。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/PaymentIntent'
                '200':
                    description: 幂等重试返回此前创建的支付意图。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/PaymentIntent'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/tools/payment/intents/confirm:
        post:
            tags: [支付]
            security: [{accountSessionAuth: []}]
            operationId: confirmPaymentIntent
            summary: 在用户明确确认后确认支付意图
            description: 必须使用交互式账号会话。客户端 access key 不具备确认资金操作的权限。
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/PaymentConfirmRequest'
                        example:
                            intent_id: pay_01K2A8EXAMPLE
                            confirm: true
            responses:
                '200':
                    description: 支付渠道确认后的支付意图。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/PaymentIntent'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
                '409': {$ref: '#/components/responses/Conflict'}
                '503': {$ref: '#/components/responses/PaymentUnavailable'}
    /v1/payments/unionpay/notify:
        post:
            tags: [支付]
            operationId: unionPayNotify
            summary: 接收已验证的银联支付回调
            description: 不使用 Bearer 鉴权。生产银联 SDK 适配器必须在改变状态前验证表单签名。
            security: []
            requestBody:
                required: true
                content:
                    application/x-www-form-urlencoded:
                        schema:
                            type: object
                            additionalProperties:
                                type: string
            responses:
                '204': {description: Verified callback was accepted.}
                '400': {$ref: '#/components/responses/BadRequest'}
                '503': {$ref: '#/components/responses/PaymentUnavailable'}
    /v1/memories:
        post:
            tags: [记忆]
            operationId: createMemory
            summary: 保存账号隔离的长期记忆
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/MemoryCreateRequest'
                        example:
                            kind: preference
                            content: 默认把粤语翻译成普通话。
            responses:
                '201':
                    description: 记忆文本已立即保存；启用 embedding 时向量在后台生成，不阻塞本次响应。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/Memory'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
        get:
            tags: [记忆]
            operationId: listMemories
            summary: 获取当前账号最近的记忆列表
            description: 最多返回最近 100 条，避免账号记忆增长后产生无界数据库读取；检索请使用 POST /v1/memories/search。
            responses:
                '200':
                    description: 记忆列表。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/MemoryListResponse'
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/memories/search:
        post:
            tags: [记忆]
            operationId: searchMemories
            summary: 使用已配置的 BM25、余弦或混合策略检索账号记忆
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/MemorySearchRequest'
                        example:
                            q: qwen3.7-flash 模型配置
                            limit: 6
            responses:
                '200':
                    description: 排序结果与延迟分解。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/MemorySearchResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/memories/remove:
        post:
            tags: [记忆]
            operationId: deleteMemory
            summary: 删除当前账号拥有的一条记忆
            security: [{accountSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            required: [memory_id]
                            properties:
                                memory_id: {type: string, maxLength: 128}
                        example: {memory_id: mem_01K2A8EXAMPLE}
            responses:
                '204': {description: Memory deleted.}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/devices/bind:
        post:
            tags: [设备]
            operationId: bindDevice
            summary: 将已预配设备绑定至当前账号
            security: [{accountSessionAuth: []}]
            description: 配对码由管理员创建、会过期且仅可使用一次。
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/DeviceBindRequest'
                        example:
                            device_id: glasses_001
                            name: Alice 的眼镜
                            pairing_code: pairing-code-issued-by-admin
            responses:
                '201':
                    description: 设备已绑定。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/Device'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '403': {$ref: '#/components/responses/InvalidPairingCode'}
                '409': {$ref: '#/components/responses/DeviceConflict'}
    /v1/devices:
        get:
            tags: [设备]
            operationId: listDevices
            summary: 获取当前账号已绑定设备
            security: [{accountSessionAuth: []}]
            responses:
                '200':
                    description: 账号已绑定设备；接口有意不返回租户与账号 ID。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/DeviceListResponse'
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/devices/unbind:
        post:
            tags: [设备]
            operationId: unbindDevice
            summary: 解绑当前账号的一台活跃设备
            security: [{accountSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            required: [device_id]
                            properties:
                                device_id: {type: string, maxLength: 128}
                        example: {device_id: glasses_001}
            responses:
                '204': {description: Device unbound.}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/client/heartbeat:
        post:
            tags: [客户端统计]
            operationId: reportClientHeartbeat
            summary: 上报已绑定客户端的健康与延迟快照
            security: [{clientAccessKeyAuth: []}]
            description: 只有当前绑定至调用账号的设备可上报遥测；不接受媒体、模型提示词、密钥或原始结果。
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/ClientHeartbeatRequest'
                        example:
                            device_id: glasses_001
                            name: Alice glasses client
                            platform: android/arm64
                            version: 1.2.0
                            capabilities: [capture_photo, record_audio, rfmlite_persistent_local_nlu]
                            resources:
                                heap_alloc_bytes: 10485760
                                system_bytes: 41943040
                                goroutines: 18
                            last_tool:
                                name: rfmlite_local_nlu
                                state: local_candidate
                                local_nlu_ms: 1
                                recorded_at: '2026-08-10T10:00:00Z'
            responses:
                '200':
                    description: 遥测已接收。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/HeartbeatResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/DeviceConflict'}
    /v1/client/commands/claim:
        post:
            tags: [客户端统计]
            operationId: claimClientCommand
            summary: 由已绑定客户端长轮询领取下一条 MCP 命令
            description: 此接口会把一条 queued 命令原子变更为 claimed，因此使用 POST。仅接受客户端访问密钥，不接受普通用户会话。
            security:
                - clientAccessKeyAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/ClientCommandClaimRequest'}
                        example: {device_id: glasses_001, wait_seconds: 20}
            responses:
                '200':
                    description: 返回已领取命令；超时且无命令时 command 为 null。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/ClientCommandClaimResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '409': {$ref: '#/components/responses/DeviceConflict'}
    /v1/client/commands/result:
        post:
            tags: [客户端统计]
            operationId: completeClientCommand
            summary: 客户端回传完整 MCP 执行结果
            security:
                - clientAccessKeyAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/ClientCommandResultRequest'}
                        example:
                            command_id: command_01K2A8EXAMPLE
                            device_id: glasses_001
                            claim_token: ag_key_one_time_claim_token
                            success: true
                            result:
                                content: [{type: text, text: '{"translation":"Good morning"}'}]
                                structuredContent: {translation: Good morning}
            responses:
                '200':
                    description: 已持久化最终结果。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/ClientCommandResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
                '409': {$ref: '#/components/responses/DeviceConflict'}
    /v1/clients:
        get:
            tags: [客户端统计]
            operationId: listClientAgents
            summary: 获取当前账号客户端遥测列表
            security: [{accountSessionAuth: []}]
            responses:
                '200':
                    description: 客户端快照。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ClientListResponse'
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/access-keys:
        get:
            tags: [访问密钥]
            operationId: listAccessKeys
            summary: 获取当前账号访问密钥元数据
            security: [{accountSessionAuth: []}]
            responses:
                '200':
                    description: 仅返回密钥元数据，不返回密钥明文。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AccessKeyListResponse'
                '401': {$ref: '#/components/responses/Unauthorized'}
        post:
            tags: [访问密钥]
            operationId: createAccessKey
            summary: 为当前账号签发新的访问密钥
            security: [{accountSessionAuth: []}]
            description: "`secret` 只会在本次成功响应中出现；请立即保存至安全的密钥管理系统。"
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/AccessKeyCreateRequest'
                        example: {name: production-client, device_id: glasses_001}
            responses:
                '201':
                    description: 密钥元数据和一次性返回的明文密钥。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AccessKeyIssueResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/DeviceConflict'}
    /v1/access-keys/revoke:
        post:
            tags: [访问密钥]
            operationId: revokeAccessKey
            summary: 吊销当前账号拥有的一把访问密钥
            security: [{accountSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            required: [key_id]
                            properties:
                                key_id: {type: string, maxLength: 128}
                        example: {key_id: key_01K2A8EXAMPLE}
            responses:
                '204': {description: Key revoked.}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/admin/devices:
        get:
            tags: [管理后台]
            operationId: listAllDevices
            summary: 获取全部已预配、已绑定、已解绑与已吊销设备
            security:
                - adminSessionAuth: []
            responses:
                '200':
                    description: 所有设备，包含绑定账号标识。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AdminDeviceListResponse'
                '403': {$ref: '#/components/responses/Forbidden'}
    /v1/admin/devices/provision:
        post:
            tags: [管理后台]
            operationId: provisionDevice
            summary: 为指定设备 ID 预配一次性配对码
            security:
                - adminSessionAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/DeviceProvisionRequest'
                        example: {device_id: glasses_001, expires_in_seconds: 600}
            responses:
                '201':
                    description: 已预配设备与一次性配对码。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/DeviceProvisionResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '409': {$ref: '#/components/responses/DeviceConflict'}
    /v1/admin/devices/revoke:
        post:
            tags: [管理后台]
            operationId: revokeDevice
            summary: 吊销设备并使其绑定失效
            security:
                - adminSessionAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            required: [device_id]
                            properties:
                                device_id: {type: string, maxLength: 128}
                        example: {device_id: glasses_001}
            responses:
                '200':
                    description: 已吊销设备记录。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AdminDevice'
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/admin/accounts/search:
        post:
            tags: [管理后台]
            operationId: listAccounts
            summary: 获取普通用户账号及密码配置元数据
            description: 返回登录名、是否要求首次改密和锁定时间等安全元数据；绝不返回密码、密码哈希、会话或访问令牌。
            security:
                - adminSessionAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            properties:
                                tenant_id: {type: string, maxLength: 128}
                        example: {tenant_id: default}
            responses:
                '200':
                    description: 当前租户或全部租户的普通用户账号列表。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AdminAccountListResponse'}
                '403': {$ref: '#/components/responses/Forbidden'}
    /v1/admin/service-accounts:
        post:
            tags: [管理后台]
            operationId: createServiceAccount
            summary: 创建仅供服务或设备自动化使用的 API 账号
            description: 不创建无法登录的“普通用户”。此账号没有人类登录入口，必须同时签发首把访问密钥；普通用户只能通过手机号、密码注册、OAuth 或后台密码账号接口创建。
            security:
                - adminSessionAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/AdminAccountCreateRequest'
                        example:
                            tenant_id: default
                            account_id: alice
                            display_name: Alice
                            initial_key_name: initial-client-key
            responses:
                '201':
                    description: 服务账号及一次性返回的初始访问密钥。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AdminAccountCreateResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
    /v1/admin/accounts/get:
        post:
            tags: [管理后台]
            operationId: getAccount
            summary: 获取一个账号资料
            security:
                - adminSessionAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            required: [account_id]
                            properties:
                                tenant_id: {type: string, maxLength: 128}
                                account_id: {type: string, maxLength: 128}
                        example: {tenant_id: default, account_id: acct_01K2A8EXAMPLE}
            responses:
                '200':
                    description: 账号资料。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AccountProfile'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/admin/accounts/update:
        post:
            tags: [管理后台]
            operationId: updateManagedAccount
            summary: 更新一个账号的公开资料
            security:
                - adminSessionAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/AdminAccountUpdateRequest'}
                        example: {tenant_id: "default", account_id: "acct_01K2A8EXAMPLE", display_name: "张三", avatar_url: "https://cdn.example.com/avatar.png"}
            responses:
                '200':
                    description: 已更新账号资料。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AccountProfile'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/admin/accounts/status:
        post:
            tags: [管理后台]
            operationId: setAccountStatus
            summary: 启用或禁用账号
            description: 禁用账号会立即吊销其 API Key 与登录会话；已删除账号不能重新启用。
            security:
                - adminSessionAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/AccountStatusRequest'}
                        example: {tenant_id: "default", account_id: "acct_01K2A8EXAMPLE", status: "disabled"}
            responses:
                '200':
                    description: 已更新账号状态。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AccountProfile'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/admin/accounts/access-keys/create:
        post:
            tags: [管理后台]
            operationId: createAccountAccessKey
            summary: 为指定账号签发访问密钥
            security:
                - adminSessionAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/AdminAccessKeyCreateRequest'
                        example:
                            tenant_id: default
                            account_id: acct_01K2A8EXAMPLE
                            name: device-bootstrap
                            device_id: glasses_001
            responses:
                '201':
                    description: 一次性返回的密钥明文及元数据。
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AccessKeyIssueResponse'
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
    /v1/admin/accounts/access-keys/revoke:
        post:
            tags: [管理后台]
            operationId: revokeAccountAccessKey
            summary: 管理员吊销指定账号的一把客户端访问密钥
            security:
                - adminSessionAuth: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/AdminAccessKeyRevokeRequest'}
                        example: {tenant_id: default, account_id: acct_01K2A8EXAMPLE, key_id: key_01K2A8EXAMPLE}
            responses:
                '204': {description: 访问密钥已吊销。}
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/admin/model-providers/list:
        post:
            tags: [管理后台]
            operationId: listManagedModelProviders
            summary: 列出受管模型 API Profile 与脱敏密钥元数据
            description: 仅 super_admin。任何响应、审计记录和列表都不会包含 API Key 明文或密文；无数据库 Profile 时会提供不含密钥的启动配置供创建表单预填。
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {type: object, additionalProperties: false}
                        example: {}
            responses:
                '200':
                    description: 当前唯一受管 Qwen Profile；Key 仅返回 key_masked。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/ManagedProviderListResponse'}
                '403': {$ref: '#/components/responses/Forbidden'}
    /v1/admin/model-providers/create:
        post:
            tags: [管理后台]
            operationId: createManagedQwenProvider
            summary: 创建唯一受管 Qwen API Profile
            description: 仅 super_admin。首期固定 type/code=qwen；创建后该 Profile 成为权威配置，未选择 active Key 前模型调用会失败关闭，不回退 YAML。
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/ManagedProviderCreateRequest'}
                        example: {name: "百炼生产工作区", enabled: true, config: {mt_base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1", embedding_base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1", agent_base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1", asr_websocket_url: "wss://dashscope.aliyuncs.com/api-ws/v1/realtime", teleprompter_asr_websocket_url: "wss://dashscope.aliyuncs.com/api-ws/v1/inference", live_translate_websocket_url: "wss://dashscope.aliyuncs.com/api-ws/v1/realtime", translation_model: "qwen-mt-flash", embedding_model: "qwen3.7-text-embedding", embedding_dimensions: 1024, agent_model: "qwen3.7-plus", asr_model: "qwen3-asr-flash-realtime", teleprompter_asr_model: "qwen-audio-3.0-asr-flash-streaming", live_translation_model: "qwen3.5-livetranslate-flash-realtime"}}
            responses:
                '201': {description: 已创建（不含密钥）。, content: {application/json: {schema: {$ref: '#/components/schemas/ManagedProvider'}}}}
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '409': {$ref: '#/components/responses/Conflict'}
                '503': {$ref: '#/components/responses/ServiceUnavailable'}
    /v1/admin/model-providers/update:
        post:
            tags: [管理后台]
            operationId: updateManagedQwenProvider
            summary: 更新受管 Qwen API Profile 的端点与模型
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/ManagedProviderUpdateRequest'}
                        example: {provider_id: "provider_01", name: "百炼生产工作区", enabled: true, config: {mt_base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1", embedding_base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1", agent_base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1", asr_websocket_url: "wss://dashscope.aliyuncs.com/api-ws/v1/realtime", teleprompter_asr_websocket_url: "wss://dashscope.aliyuncs.com/api-ws/v1/inference", live_translate_websocket_url: "wss://dashscope.aliyuncs.com/api-ws/v1/realtime", translation_model: "qwen-mt-flash", embedding_model: "qwen3.7-text-embedding", embedding_dimensions: 1024, agent_model: "qwen3.7-plus", asr_model: "qwen3-asr-flash-realtime", teleprompter_asr_model: "qwen-audio-3.0-asr-flash-streaming", live_translation_model: "qwen3.5-livetranslate-flash-realtime"}}
            responses:
                '200': {description: 已更新。, content: {application/json: {schema: {$ref: '#/components/schemas/ManagedProvider'}}}}
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/admin/model-providers/status:
        post:
            tags: [管理后台]
            operationId: setManagedQwenProviderStatus
            summary: 启用或停用受管 Qwen API Profile
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {type: object, additionalProperties: false, required: [provider_id, enabled], properties: {provider_id: {type: string, maxLength: 128}, enabled: {type: boolean}}}
                        example: {provider_id: "provider_01", enabled: false}
            responses:
                '200': {description: Profile 状态已更新。, content: {application/json: {schema: {$ref: '#/components/schemas/ManagedProvider'}}}}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/admin/model-providers/keys/create:
        post:
            tags: [管理后台]
            operationId: createManagedQwenProviderKey
            summary: 加入一把 Qwen API Key
            description: 仅 super_admin。secret 只接收写入请求：服务端 AES-GCM 加密后持久化，成功响应只返回 key_masked；activate=true 会原子选择它为当前 Key，并将旧 active Key 转为 standby。
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {type: object, additionalProperties: false, required: [provider_id, name, secret], properties: {provider_id: {type: string, maxLength: 128}, name: {type: string, minLength: 2, maxLength: 80}, secret: {type: string, format: password, writeOnly: true, minLength: 12, maxLength: 2048}, activate: {type: boolean, default: false}}}
                        example: {provider_id: "provider_01", name: "主密钥-2026Q3", secret: "replace_with_real_secret", activate: true}
            responses:
                '201': {description: 已保存；不返回 secret。, content: {application/json: {schema: {$ref: '#/components/schemas/ManagedProviderKey'}}}}
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
                '503': {$ref: '#/components/responses/ServiceUnavailable'}
    /v1/admin/model-providers/keys/activate:
        post:
            tags: [管理后台]
            operationId: activateManagedQwenProviderKey
            summary: 原子选择一把 standby Qwen API Key 为当前 Key
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {type: object, additionalProperties: false, required: [provider_id, key_id], properties: {provider_id: {type: string, maxLength: 128}, key_id: {type: string, maxLength: 128}}}
                        example: {provider_id: "provider_01", key_id: "provider_key_02"}
            responses:
                '200': {description: 当前 Key 已切换。, content: {application/json: {schema: {$ref: '#/components/schemas/ManagedProviderKey'}}}}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/admin/model-providers/keys/status:
        post:
            tags: [管理后台]
            operationId: setManagedQwenProviderKeyStatus
            summary: 将非当前 Key 切换为 standby、disabled 或不可逆 revoked
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {type: object, additionalProperties: false, required: [provider_id, key_id, status], properties: {provider_id: {type: string, maxLength: 128}, key_id: {type: string, maxLength: 128}, status: {type: string, enum: [standby, disabled, revoked]}}}
                        example: {provider_id: "provider_01", key_id: "provider_key_01", status: "revoked"}
            responses:
                '200': {description: 密钥状态已更新。, content: {application/json: {schema: {$ref: '#/components/schemas/ManagedProviderKey'}}}}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/admin/model-providers/probe:
        post:
            tags: [管理后台]
            operationId: probeManagedQwenProvider
            summary: 使用当前 active Key 做无推理连通性探测
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {type: object, additionalProperties: false, required: [provider_id], properties: {provider_id: {type: string, maxLength: 128}}}
                        example: {provider_id: "provider_01"}
            responses:
                '200': {description: 探测结果；失败消息不会透传上游正文或密钥。, content: {application/json: {schema: {$ref: '#/components/schemas/ManagedProviderProbeResponse'}}}}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
    /v1/admin/model-providers/realtime/probe:
        post:
            tags: [管理后台]
            operationId: probeManagedQwenRealtimeCapability
            summary: 人工验证一条 Qwen 实时会话控制链路
            description: 仅 super_admin。必须明确确认可能产生供应商费用。每次仅验证一个 capability，服务端只建立一次实时会话并以协议结束事件关闭；不会上传、提交或保存音频，不能据此证明识别、翻译、首字或首译质量。每个 Profile/能力 60 秒内最多一次，健康矩阵只读取该人工结果，绝不会自动发起此调用。
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/ManualRealtimeProbeRequest'}
                        example: {provider_id: "provider_01", capability: "qwen_live_translate", confirm_billable_probe: true}
            responses:
                '200':
                    description: 实时控制面验证结果；响应不包含 URL、密钥、会话 ID、音频或上游错误原文。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/ManualRealtimeProbeResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '403': {$ref: '#/components/responses/Forbidden'}
                '404': {$ref: '#/components/responses/NotFound'}
                '409': {$ref: '#/components/responses/Conflict'}
                '429':
                    description: 同一能力最近已验证，避免重复建立可能收费的实时会话。
                    headers:
                        Retry-After: {schema: {type: integer, minimum: 1}}
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/ErrorResponse'}
    /v1/auth/password/login:
        post:
            tags: [账号]
            operationId: passwordLogin
            summary: 普通账号密码登录
            description: 匿名账号入口。浏览器已有有效用户会话时必须先退出，服务端不会用另一账号覆盖当前会话。
            security: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            required: [login_name, password]
                            properties:
                                login_name: {type: string, example: alice@example.com}
                                password: {type: string, format: password}
                        example: {login_name: "alice@example.com", password: "Glasses#2026Secure"}
            responses:
                '200':
                    description: 已建立普通用户会话。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/PasswordLoginResponse'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/account/password:
        post:
            tags: [账号]
            operationId: setAccountPassword
            summary: 设置或修改当前普通用户密码
            security: [{accountSessionAuth: []}]
            description: 已有密码时必须提供 current_password；首次从手机号或 OAuth 会话设置密码时，在 JSON body 内提供 login_name。成功后撤销该账号全部旧会话并在响应中签发新的完整会话；临时密码状态随之解除。
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            required: [new_password]
                            properties:
                                current_password: {type: string, format: password}
                                new_password: {type: string, format: password}
                                login_name: {type: string, description: 仅首次从手机/OAuth 会话设置密码时必填。}
                        example: {current_password: "Old#Password2026", new_password: "Glasses#2026Secure"}
            responses:
                '200':
                    description: 密码已更新，旧会话已撤销，并签发 password_change_required=false 的新会话。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/PasswordLoginResponse'}
                '400': {$ref: '#/components/responses/BadRequest'}
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/admin/bootstrap:
        post:
            tags: [管理后台]
            operationId: bootstrapAdministrator
            summary: 通过一次性部署令牌创建首个超级管理员
            description: 仅管理员表为空且请求携带 X-Admin-Token 时可用；创建成功后该令牌不能授权任何其他后台接口。
            security: [{adminTokenAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/AdministratorCreateRequest'
                        example: {username: "platform_owner", display_name: "平台负责人", password: "Glasses#2026Secure"}
            responses:
                '201':
                    description: 已创建超级管理员。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/Administrator'}
                '409': {$ref: '#/components/responses/Conflict'}
    /v1/admin/auth/login:
        post:
            tags: [管理后台]
            operationId: adminPasswordLogin
            summary: 管理员账号密码登录
            security: []
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            required: [username, password]
                            properties:
                                username: {type: string}
                                password: {type: string, format: password}
                        example: {username: "platform_owner", password: "Glasses#2026Secure"}
            responses:
                '200':
                    description: 已建立可撤销管理员会话。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AdministratorLoginResponse'}
                '401': {$ref: '#/components/responses/Unauthorized'}
    /v1/admin/auth/me:
        get:
            tags: [管理后台]
            operationId: getCurrentAdministrator
            summary: 获取当前管理员及角色
            security: [{adminSessionAuth: []}]
            responses:
                '200':
                    description: 当前管理员。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/Administrator'}
    /v1/admin/auth/logout:
        post:
            tags: [管理后台]
            operationId: adminLogout
            summary: 撤销当前管理员会话
            security: [{adminSessionAuth: []}]
            responses:
                '204': {description: 已退出}
    /v1/admin/password:
        post:
            tags: [管理后台]
            operationId: changeOwnAdministratorPassword
            summary: 修改当前管理员密码
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            required: [current_password, new_password]
                            properties:
                                current_password: {type: string, format: password}
                                new_password: {type: string, format: password}
                        example: {current_password: "Old#Password2026", new_password: "Glasses#2026Secure"}
            responses:
                '204': {description: 密码已更新，其他管理员会话已撤销}
    /v1/admin/users:
        get:
            tags: [管理后台]
            operationId: listAdministrators
            summary: 列出管理员账号
            security: [{adminSessionAuth: []}]
            responses:
                '200':
                    description: 管理员列表。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AdministratorListResponse'}
        post:
            tags: [管理后台]
            operationId: createAdministrator
            summary: 创建管理员账号
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/AdministratorCreateRequest'}
                        example: {username: "audit_li", display_name: "李审计", role: "auditor", password: "Glasses#2026Secure", force_password_change: true}
            responses:
                '201':
                    description: 管理员已创建。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/Administrator'}
    /v1/admin/users/update:
        post:
            tags: [管理后台]
            operationId: updateAdministrator
            summary: 修改管理员显示名称或角色
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            required: [admin_id]
                            properties:
                                admin_id: {type: string, maxLength: 128}
                                display_name: {type: string}
                                role: {type: string, enum: [super_admin, account_admin, auditor]}
                        example: {admin_id: admin_01K2A8EXAMPLE, display_name: "李审计", role: "auditor"}
            responses:
                '200':
                    description: 管理员已更新。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/Administrator'}
    /v1/admin/users/status:
        post:
            tags: [管理后台]
            operationId: setAdministratorStatus
            summary: 启用或禁用管理员
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {type: object, additionalProperties: false, required: [admin_id, status], properties: {admin_id: {type: string, maxLength: 128}, status: {type: string, enum: [active, disabled]}}}
                        example: {admin_id: admin_01K2A8EXAMPLE, status: "disabled"}
            responses:
                '200':
                    description: 管理员状态已更新。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/Administrator'}
    /v1/admin/users/password:
        post:
            tags: [管理后台]
            operationId: resetAdministratorPassword
            summary: 管理员重置另一管理员密码
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {type: object, additionalProperties: false, required: [admin_id, password], properties: {admin_id: {type: string, maxLength: 128}, password: {type: string, format: password}, force_password_change: {type: boolean}}}
                        example: {admin_id: admin_01K2A8EXAMPLE, password: "Glasses#2026Secure", force_password_change: true}
            responses:
                '200':
                    description: 密码已重置且会话已撤销。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/Administrator'}
    /v1/admin/accounts/password/create:
        post:
            tags: [管理后台]
            operationId: createPasswordAccount
            summary: 创建普通用户账号和首个临时密码
            description: force_password_change 省略时默认为 true。客户端首次登录只能查询会话状态、改密或退出；只有显式传 false 才允许支持人员创建非临时密码。
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/UserPasswordAccountRequest'}
                        example: {tenant_id: "default", account_id: "zhangsan", display_name: "张三", login_name: "zhangsan", password: "Glasses#2026Secure", force_password_change: true}
            responses:
                '201':
                    description: 普通用户账号已创建。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/UserPasswordAccountResponse'}
    /v1/admin/accounts/password/reset:
        post:
            tags: [管理后台]
            operationId: resetAccountPassword
            summary: 管理员重置普通用户密码
            description: force_password_change 省略时默认为 true。重置会撤销旧会话和访问密钥可用的产品能力，直到客户端完成自助改密。
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema: {$ref: '#/components/schemas/UserPasswordResetRequest'}
                        example: {tenant_id: "default", account_id: "acct_01K2A8EXAMPLE", password: "Glasses#2026Secure", force_password_change: true}
            responses:
                '200':
                    description: 密码已重置且用户会话已撤销。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/UserPasswordAccountResponse'}
    /v1/admin/inspection/overview:
        get:
            tags: [管理后台]
            operationId: getInspectionOverview
            summary: 获取账号、会话、设备与失败登录巡查总览
            security: [{adminSessionAuth: []}]
            responses:
                '200':
                    description: 当前巡查聚合指标。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/InspectionOverview'}
    /v1/admin/system/health:
        get:
            tags: [管理后台]
            operationId: getAdministratorSystemHealth
            summary: 查询 API、数据库、容器与外部依赖健康矩阵
            description: 仅超级管理员和审计管理员可读。模型兼容接口仅执行无推理的 `/models` 检查；实时 WebSocket、OAuth 授权和支付交易不会被健康检查打开。实时项可合并最近的超级管理员人工无音频验证结果，但本接口本身从不打开实时会话。响应不包含 URL、连接串、密钥、令牌或上游响应正文。
            security: [{adminSessionAuth: []}]
            responses:
                '200':
                    description: 依赖检查已完成；`ok=false` 时仍返回完整矩阵以便定位问题。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AdministratorSystemHealthResponse'}
                '401': {$ref: '#/components/responses/Unauthorized'}
                '403': {$ref: '#/components/responses/Forbidden'}
    /v1/admin/audit/events/search:
        post:
            tags: [管理后台]
            operationId: listAuditEvents
            summary: 查询不可包含密码或令牌的审计事件
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            properties:
                                from: {type: string, format: date-time}
                                to: {type: string, format: date-time}
                                actor_type: {type: string}
                                action_prefix: {type: string}
                                tenant_id: {type: string, maxLength: 128}
                                account_id: {type: string, maxLength: 128}
                                admin_id: {type: string, maxLength: 128}
                                cursor: {type: string}
                                limit: {type: integer, minimum: 1, maximum: 500}
                        example: {tenant_id: default, action_prefix: account., limit: 50}
            responses:
                '200':
                    description: 倒序审计事件分页。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AuditEventListResponse'}
    /v1/admin/inspection/accounts/get:
        post:
            tags: [管理后台]
            operationId: inspectAccount
            summary: 巡查一个普通账号的身份、密码元数据与绑定设备
            security: [{adminSessionAuth: []}]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            additionalProperties: false
                            required: [account_id]
                            properties:
                                tenant_id: {type: string, maxLength: 128}
                                account_id: {type: string, maxLength: 128}
                        example: {tenant_id: default, account_id: acct_01K2A8EXAMPLE}
            responses:
                '200':
                    description: 账号巡查明细，不包含密码哈希。
                    content:
                        application/json:
                            schema: {$ref: '#/components/schemas/AccountInspectionResponse'}
                '404': {$ref: '#/components/responses/NotFound'}
components:
    securitySchemes:
        bearerAuth:
            type: http
            scheme: bearer
            bearerFormat: ag_live access key or ag_session session
            description: "账号隔离的访问密钥或登录会话，例如 `Authorization: Bearer ag_live_...` 或 `Bearer ag_session_...`。账号 password_change_required=true 时，全部产品能力（包括已有 access key）返回 403 password_change_required。"
        accountSessionAuth:
            type: http
            scheme: bearer
            bearerFormat: ag_session
            description: 普通用户登录会话；账号资料、密码、身份、访问密钥和设备归属管理必须使用此凭据。临时密码会话只允许 GET /v1/auth/me、GET /v1/account、POST /v1/account/password 和 POST /v1/auth/logout。
        clientAccessKeyAuth:
            type: http
            scheme: bearer
            bearerFormat: ag_live
            description: 设备数据面访问密钥；用于客户端心跳、命令领取/结果回传和获准的模型工具，不具备账号管理权限。所属账号强制改密期间同样返回 403 password_change_required。
        adminAuth:
            type: apiKey
            in: header
            name: X-Admin-Token
            description: 仅非 production 构建的本机验证码测试接口使用；普通后台接口不接受此令牌。
        adminTokenAuth:
            type: apiKey
            in: header
            name: X-Admin-Token
            description: 仅管理员表为空时创建首个超级管理员；初始化完成后不能授权任何后台接口。
        adminSessionAuth:
            type: http
            scheme: bearer
            bearerFormat: ag_admin session
            description: 管理员密码登录后返回的可撤销短期会话令牌；浏览器也可使用 HttpOnly Cookie。
        adminBrowserSessionCookie:
            type: apiKey
            in: cookie
            name: ag_admin_session
            description: 仅非生产测试验证码控制台使用的 HttpOnly 管理员会话 Cookie；默认必须为 Secure 且页面来自已配置 HTTPS Origin。仅当显式启用 192.168/16 LAN HTTP 测试模式时可为非 Secure，不能改为手工 Bearer 或部署初始化令牌。
        headerIdentity:
            type: apiKey
            in: header
            name: X-Account-ID
            description: 仅限开发迁移；只有 security.allow_header_identity=true 时可用，生产环境不可使用。
    parameters:
        OAuthProvider: {name: provider, in: path, required: true, schema: {type: string, enum: [google, apple, wechat]}}
        IdempotencyKey:
            name: Idempotency-Key
            in: header
            required: true
            description: 稳定重试键，长度为 16 至 128 个字符；同一账号复用会返回原支付意图。
            schema: {type: string, minLength: 16, maxLength: 128}
    schemas:
        ErrorResponse:
            type: object
            required: [error]
            properties:
                error:
                    type: object
                    required: [code, message]
                    properties:
                        code: {type: string, example: invalid_request}
                        message: {type: string, example: question is required and must not exceed 64 KiB}
        PasswordChangeRequiredResponse:
            allOf:
                - $ref: '#/components/schemas/ErrorResponse'
                - type: object
                  required: [password_change_required, password_change_endpoint, allowed_actions]
                  properties:
                    error:
                        type: object
                        required: [code, message]
                        properties:
                            code: {type: string, enum: [password_change_required]}
                            message: {type: string}
                    password_change_required: {type: boolean, enum: [true]}
                    password_change_endpoint: {type: string, enum: [/v1/account/password]}
                    allowed_actions:
                        type: array
                        items: {type: string, enum: [get_session, change_password, logout]}
                        example: [get_session, change_password, logout]
        AdministratorCreateRequest:
            type: object
            additionalProperties: false
            required: [username, password]
            properties:
                username: {type: string, minLength: 3, maxLength: 80}
                display_name: {type: string, maxLength: 80}
                role: {type: string, enum: [super_admin, account_admin, auditor], default: account_admin}
                password: {type: string, format: password, minLength: 12, maxLength: 128}
                force_password_change: {type: boolean, default: false}
        UserPasswordAccountRequest:
            type: object
            additionalProperties: false
            required: [account_id, login_name, password]
            properties:
                tenant_id: {type: string}
                account_id: {type: string, maxLength: 128}
                display_name: {type: string, maxLength: 80}
                login_name: {type: string, minLength: 3, maxLength: 80}
                password: {type: string, format: password, minLength: 12, maxLength: 128}
                force_password_change: {type: boolean, default: true, description: 省略时默认为 true；仅在经过明确支持流程时可显式传 false。}
        UserPasswordResetRequest:
            type: object
            additionalProperties: false
            required: [account_id, password]
            properties:
                tenant_id: {type: string}
                account_id: {type: string, maxLength: 128}
                login_name: {type: string, minLength: 3, maxLength: 80}
                password: {type: string, format: password, minLength: 12, maxLength: 128}
                force_password_change: {type: boolean, default: true, description: 省略时默认为 true；仅在经过明确支持流程时可显式传 false。}
        LoginProvider:
            type: object
            required: [provider, enabled]
            properties:
                provider: {type: string, enum: [phone, google, apple, wechat]}
                enabled: {type: boolean}
        LoginProviderListResponse:
            type: object
            required: [items, public_registration]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/LoginProvider'}
                public_registration:
                    $ref: '#/components/schemas/PublicRegistrationStatus'
        PublicRegistrationStatus:
            type: object
            required: [enabled, method, requires_phone_verification, phone_code_purpose]
            properties:
                enabled: {type: boolean, description: true 表示已配置短信或受控测试验证码，客户端可以展示公共注册入口。}
                method: {type: string, enum: [password]}
                requires_phone_verification: {type: boolean, enum: [true]}
                phone_code_purpose: {type: string, enum: [register]}
        PhoneCodeRequest:
            type: object
            additionalProperties: false
            required: [phone, purpose]
            properties:
                phone: {type: string, description: E.164 手机号，例如 +8613812345678。}
                purpose: {type: string, enum: [login, register, reset_password, link_phone], description: 验证码只可用于这里声明的单一业务，不能跨登录、注册、重置密码或绑定手机号复用。}
        PhoneCodeResponse:
            type: object
            required: [phone, purpose, expires_at]
            properties:
                phone: {type: string, description: 脱敏后的号码。}
                purpose: {type: string, enum: [login, register, reset_password, link_phone]}
                expires_at: {type: string, format: date-time}
        LocalPhoneCodeResponse:
            type: object
            description: 仅本机测试模式返回；不可在生产部署启用或记录。
            required: [phone, purpose, code, expires_at, testing_only]
            properties:
                phone: {type: string, description: E.164 手机号。}
                purpose: {type: string, enum: [login, register, reset_password, link_phone]}
                code: {type: string, minLength: 6, maxLength: 6, description: 仅测试模式的短时验证码。}
                expires_at: {type: string, format: date-time}
                testing_only: {type: boolean, enum: [true]}
        PhoneLoginRequest:
            type: object
            additionalProperties: false
            required: [phone, code]
            properties:
                phone: {type: string, description: E.164 手机号。}
                code: {type: string, minLength: 6, maxLength: 6}
                display_name: {type: string, maxLength: 80, description: 首次注册时可选。}
        PasswordRegistrationRequest:
            type: object
            additionalProperties: false
            required: [phone, code, login_name, password]
            properties:
                phone: {type: string, example: "+8613812345678", description: 验证码接收手机号，必须为 E.164 格式。}
                code: {type: string, minLength: 6, maxLength: 6, example: "123456"}
                login_name: {type: string, minLength: 3, maxLength: 80, example: zhangsan}
                password: {type: string, format: password, minLength: 12, maxLength: 128, example: "Glasses#2026Secure"}
                display_name: {type: string, maxLength: 80, example: 张三}
        PhonePasswordResetRequest:
            type: object
            additionalProperties: false
            required: [phone, code, password]
            properties:
                phone: {type: string, example: "+8613812345678", description: 账号绑定的 E.164 手机号。}
                code: {type: string, minLength: 6, maxLength: 6, example: "123456"}
                password: {type: string, format: password, minLength: 12, maxLength: 128, example: "Glasses#2026Secure"}
                login_name: {type: string, description: 仅手机号账号从未设置过密码时必填。}
        AccountProfile:
            type: object
            required: [account_id, display_name, status, created_at, updated_at]
            properties:
                tenant_id: {type: string}
                account_id: {type: string, description: 全局规范账号 ID。手机号、密码及全部 OAuth 身份都解析到此 ID；普通注册由服务端生成全局唯一值。}
                display_name: {type: string}
                email: {type: string, format: email}
                phone: {type: string}
                avatar_url: {type: string, format: uri}
                status: {type: string, enum: [active, disabled, deleted]}
                created_at: {type: string, format: date-time}
                updated_at: {type: string, format: date-time}
                last_login_at: {type: string, format: date-time}
        AccountSessionStateResponse:
            allOf:
                - $ref: '#/components/schemas/AccountProfile'
                - type: object
                  required: [password_change_required]
                  properties:
                    password_change_required: {type: boolean, description: true 时该会话只可查询状态、改密或退出。}
        LoginResponse:
            type: object
            required: [account, access_token, token_type, expires_at, password_change_required]
            properties:
                account: {$ref: '#/components/schemas/AccountProfile'}
                access_token: {type: string, description: 一次性返回的可撤销登录会话；浏览器同时获得 HttpOnly Cookie。}
                token_type: {type: string, enum: [Bearer]}
                expires_at: {type: string, format: date-time}
                password_change_required: {type: boolean, description: true 时客户端必须先完成 POST /v1/account/password。}
        PasswordLoginResponse:
            allOf:
                - $ref: '#/components/schemas/LoginResponse'
                - type: object
                  required: [password_change_required]
                  properties:
                    password_change_required: {type: boolean, description: true 时客户端应在当前会话内立即引导改密。}
        Administrator:
            type: object
            required: [id, username, display_name, role, status, must_change_password, password_changed_at, created_at, updated_at]
            properties:
                id: {type: string}
                username: {type: string}
                display_name: {type: string}
                role: {type: string, enum: [super_admin, account_admin, auditor]}
                status: {type: string, enum: [active, disabled]}
                must_change_password: {type: boolean}
                locked_until: {type: string, format: date-time, nullable: true}
                password_changed_at: {type: string, format: date-time}
                created_by: {type: string}
                created_at: {type: string, format: date-time}
                updated_at: {type: string, format: date-time}
                last_login_at: {type: string, format: date-time, nullable: true}
        AdministratorLoginResponse:
            type: object
            required: [administrator, access_token, token_type, expires_at, password_change_required]
            properties:
                administrator: {$ref: '#/components/schemas/Administrator'}
                access_token: {type: string, description: ag_admin 不透明管理员会话；浏览器同时获得 HttpOnly Cookie。}
                token_type: {type: string, enum: [Bearer]}
                expires_at: {type: string, format: date-time}
                password_change_required: {type: boolean}
        AdministratorListResponse:
            type: object
            required: [items]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/Administrator'}
        ManagedQwenConfig:
            type: object
            additionalProperties: false
            required: [mt_base_url, embedding_base_url, agent_base_url, asr_websocket_url, teleprompter_asr_websocket_url, live_translate_websocket_url, translation_model, embedding_model, embedding_dimensions, agent_model, asr_model, teleprompter_asr_model, live_translation_model]
            properties:
                mt_base_url: {type: string, format: uri}
                embedding_base_url: {type: string, format: uri}
                agent_base_url: {type: string, format: uri}
                asr_websocket_url: {type: string, format: uri}
                teleprompter_asr_websocket_url: {type: string, format: uri}
                live_translate_websocket_url: {type: string, format: uri}
                translation_model: {type: string, maxLength: 160}
                embedding_model: {type: string, maxLength: 160}
                embedding_dimensions: {type: integer, minimum: 1, maximum: 16384}
                agent_model: {type: string, maxLength: 160}
                asr_model: {type: string, maxLength: 160}
                teleprompter_asr_model: {type: string, maxLength: 160}
                live_translation_model: {type: string, maxLength: 160}
        ManagedProviderCreateRequest:
            type: object
            additionalProperties: false
            required: [name, config]
            properties:
                name: {type: string, minLength: 2, maxLength: 80}
                enabled: {type: boolean, default: true}
                config: {$ref: '#/components/schemas/ManagedQwenConfig'}
        ManagedProviderUpdateRequest:
            type: object
            additionalProperties: false
            required: [provider_id, name, config]
            properties:
                provider_id: {type: string, maxLength: 128}
                name: {type: string, minLength: 2, maxLength: 80}
                enabled: {type: boolean}
                config: {$ref: '#/components/schemas/ManagedQwenConfig'}
        ManagedProviderKey:
            type: object
            required: [id, provider_id, name, key_masked, status, created_at, updated_at]
            properties:
                id: {type: string}
                provider_id: {type: string}
                name: {type: string}
                key_masked: {type: string, description: 固定前后缀的不可逆展示掩码；不等于完整密钥。}
                status: {type: string, enum: [active, standby, disabled, revoked]}
                created_at: {type: string, format: date-time}
                updated_at: {type: string, format: date-time}
                last_used_at: {type: string, format: date-time}
                activated_at: {type: string, format: date-time}
        ManagedProvider:
            type: object
            required: [id, type, code, name, enabled, config, created_at, updated_at, keys]
            properties:
                id: {type: string}
                type: {type: string, enum: [qwen]}
                code: {type: string, enum: [qwen]}
                name: {type: string}
                enabled: {type: boolean}
                config: {$ref: '#/components/schemas/ManagedQwenConfig'}
                active_key_id: {type: string, nullable: true}
                keys: {type: array, items: {$ref: '#/components/schemas/ManagedProviderKey'}}
                created_at: {type: string, format: date-time}
                updated_at: {type: string, format: date-time}
        ManagedProviderListResponse:
            type: object
            required: [items, supported_types, managed_profile_code, bootstrap_fallback_available, key_store_configured, bootstrap_profile]
            properties:
                items: {type: array, items: {$ref: '#/components/schemas/ManagedProvider'}}
                supported_types: {type: array, items: {type: string, enum: [qwen]}}
                managed_profile_code: {type: string, enum: [qwen]}
                bootstrap_fallback_available: {type: boolean, description: true 时尚未创建数据库 Profile，配置文件/环境变量仍为运行时回退来源。}
                key_store_configured: {type: boolean, description: true 表示服务端已配置独立 PROVIDER_SECRET_MASTER_KEY，可安全保存受管 API Key。}
                bootstrap_profile: {allOf: [{$ref: '#/components/schemas/ManagedProviderBootstrap'}], nullable: true}
        ManagedProviderBootstrap:
            type: object
            description: 仅在尚无数据库受管 Profile 时提供的非机密启动配置预填值；不是持久化 Profile，不能用于更新或选择密钥。
            required: [type, code, name, enabled, config]
            properties:
                type: {type: string, enum: [qwen]}
                code: {type: string, enum: [qwen]}
                name: {type: string}
                enabled: {type: boolean}
                config: {$ref: '#/components/schemas/ManagedQwenConfig'}
        ManagedProviderProbeResponse:
            type: object
            required: [provider_id, ok, latency_ms]
            properties:
                provider_id: {type: string}
                ok: {type: boolean}
                latency_ms: {type: integer, minimum: 0}
                message: {type: string, description: 安全的通用失败描述，不含上游响应正文或密钥。}
        ManualRealtimeProbeRequest:
            type: object
            additionalProperties: false
            required: [provider_id, capability, confirm_billable_probe]
            properties:
                provider_id: {type: string, maxLength: 128}
                capability: {type: string, enum: [qwen_asr_realtime, qwen_teleprompter_asr, qwen_live_translate]}
                confirm_billable_probe: {type: boolean, enum: [true], description: 必须明确为 true；实时会话可能按供应商规则计费。}
        ManualRealtimeProbeResponse:
            type: object
            required: [provider_id, capability, status, model, mode, audio_bytes, inference_result_verified, connect_latency_ms, session_ready_latency_ms, total_latency_ms, checked_at, message]
            properties:
                provider_id: {type: string}
                capability: {type: string, enum: [qwen_asr_realtime, qwen_teleprompter_asr, qwen_live_translate]}
                status: {type: string, enum: [verified, failed]}
                model: {type: string}
                mode: {type: string, enum: [session_setup_no_audio]}
                audio_bytes: {type: integer, enum: [0], description: 固定为 0；本接口绝不上传或提交音频。}
                inference_result_verified: {type: boolean, enum: [false], description: 固定为 false；无音频探测不验证识别或翻译输出。}
                connect_latency_ms: {type: integer, minimum: 0, description: 建立 WebSocket 所用时间。}
                session_ready_latency_ms: {type: integer, minimum: 0, description: 收到 session.updated 或 task-started 所用时间。}
                total_latency_ms: {type: integer, minimum: 0, description: 收到 session.finished 或 task-finished 所用时间。}
                checked_at: {type: string, format: date-time}
                message: {type: string, description: 安全的通用状态说明，不含端点、会话 ID、密钥或上游错误原文。}
        AccountUpdateRequest:
            type: object
            additionalProperties: false
            properties:
                display_name: {type: string, minLength: 1, maxLength: 80}
                avatar_url: {type: string, format: uri, maxLength: 2048}
        AccountCloseRequest:
            type: object
            additionalProperties: false
            required: [confirm]
            properties:
                confirm: {type: boolean, enum: [true]}
        AuthIdentity:
            type: object
            required: [id, provider, subject, created_at, last_login_at]
            properties:
                id: {type: string}
                provider: {type: string, enum: [phone, google, apple, wechat]}
                subject: {type: string, description: 服务端提供商主体标识；不等同于密码或 OAuth Token。}
                email: {type: string, format: email}
                phone: {type: string}
                display_name: {type: string}
                avatar_url: {type: string, format: uri}
                created_at: {type: string, format: date-time}
                last_login_at: {type: string, format: date-time}
        PhoneIdentityLinkRequest:
            type: object
            additionalProperties: false
            required: [phone, code]
            properties:
                phone: {type: string, description: 待绑定的 E.164 手机号, example: "+8613812345678"}
                code: {type: string, minLength: 6, maxLength: 6, example: "123456"}
        IdentityLinkResponse:
            type: object
            required: [account, linked, provider]
            properties:
                account: {$ref: '#/components/schemas/AccountProfile'}
                linked: {type: boolean, description: true 表示本次新建绑定；false 表示此前已绑定当前账号。}
                provider: {type: string, enum: [phone, google, apple, wechat]}
                identity: {$ref: '#/components/schemas/AuthIdentity'}
                access_token: {type: string, description: OAuth 绑定 callback 直接返回 JSON 时签发的新账号会话。}
                token_type: {type: string, enum: [Bearer]}
                expires_at: {type: string, format: date-time}
        AuthIdentityListResponse:
            type: object
            required: [items]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/AuthIdentity'}
        AdminAccountListResponse:
            type: object
            required: [items]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/UserCredentialSummary'}
        UserPasswordMetadata:
            type: object
            description: 仅用于后台巡查；不包含密码或密码哈希。
            required: [login_name, must_change_password, password_changed_at, created_at, updated_at]
            properties:
                login_name: {type: string}
                must_change_password: {type: boolean}
                locked_until: {type: string, format: date-time}
                password_changed_at: {type: string, format: date-time}
                created_at: {type: string, format: date-time}
                updated_at: {type: string, format: date-time}
        UserCredentialSummary:
            type: object
            required: [account]
            properties:
                account: {$ref: '#/components/schemas/AccountProfile'}
                password: {$ref: '#/components/schemas/UserPasswordMetadata'}
        UserPasswordAccountResponse:
            type: object
            required: [account, password]
            properties:
                account: {$ref: '#/components/schemas/AccountProfile'}
                password: {$ref: '#/components/schemas/UserPasswordMetadata'}
        InspectionOverview:
            type: object
            required: [active_accounts, disabled_accounts, active_administrators, active_user_sessions, active_admin_sessions, bound_devices, login_failures_24h, generated_at]
            properties:
                active_accounts: {type: integer, minimum: 0}
                disabled_accounts: {type: integer, minimum: 0}
                active_administrators: {type: integer, minimum: 0}
                active_user_sessions: {type: integer, minimum: 0}
                active_admin_sessions: {type: integer, minimum: 0}
                bound_devices: {type: integer, minimum: 0}
                login_failures_24h: {type: integer, minimum: 0}
                generated_at: {type: string, format: date-time}
        AuditEvent:
            type: object
            required: [id, actor_type, action, outcome, created_at]
            properties:
                id: {type: string}
                actor_type: {type: string, enum: [admin, account, anonymous, system]}
                actor_id: {type: string}
                tenant_id: {type: string}
                account_id: {type: string}
                action: {type: string, example: account.password.login.succeeded}
                target_type: {type: string}
                target_id: {type: string}
                outcome: {type: string, enum: [success, failure]}
                request_id: {type: string}
                source_ip: {type: string}
                metadata: {type: object, additionalProperties: true, description: 仅非敏感业务元数据；不会包含密码、验证码或令牌。}
                created_at: {type: string, format: date-time}
        AuditEventListResponse:
            type: object
            required: [items, next_cursor]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/AuditEvent'}
                next_cursor: {type: string, description: 为空时没有下一页；原样传回 cursor 参数。}
        AccountInspectionResponse:
            type: object
            required: [account, identities, devices]
            properties:
                account: {$ref: '#/components/schemas/AccountProfile'}
                password: {allOf: [{$ref: '#/components/schemas/UserPasswordMetadata'}], nullable: true}
                identities: {type: array, items: {$ref: '#/components/schemas/AuthIdentity'}}
                devices: {type: array, items: {$ref: '#/components/schemas/Device'}}
        AdminAccountUpdateRequest:
            type: object
            additionalProperties: false
            required: [account_id]
            properties:
                tenant_id: {type: string, maxLength: 128}
                account_id: {type: string, maxLength: 128}
                display_name: {type: string, maxLength: 80}
                avatar_url: {type: string, format: uri, maxLength: 2048}
        AccountStatusRequest:
            type: object
            additionalProperties: false
            required: [account_id, status]
            properties:
                tenant_id: {type: string, maxLength: 128}
                account_id: {type: string, maxLength: 128}
                status: {type: string, enum: [active, disabled]}
        HealthResponse:
            type: object
            description: 面向负载均衡的轻量检查；不会访问任何外部 Provider。
            required: [ok, ready, storage]
            properties:
                ok: {type: boolean}
                ready: {type: boolean, description: 持久化存储可用时为 true。}
                storage: {type: string, example: postgres}
        AdministratorSystemHealthResponse:
            type: object
            description: 管理后台依赖健康矩阵。即使某项不可用也返回 HTTP 200 和完整项列表，调用方应读取 ok 与各项 status。
            required: [ok, checked_at, latency_ms, dependencies]
            properties:
                ok: {type: boolean, description: 数据库或任何已配置依赖处于 unavailable/stale 时为 false。}
                checked_at: {type: string, format: date-time}
                latency_ms: {type: integer, minimum: 0, description: 本次并发健康检查的总耗时。}
                dependencies:
                    type: array
                    items: {$ref: '#/components/schemas/SystemDependencyHealth'}
        SystemDependencyHealth:
            type: object
            description: 单个依赖状态。不会返回地址、数据库连接串、密钥、令牌、短信认证头或上游响应正文。
            required: [id, category, name, configured, status]
            properties:
                id:
                    type: string
                    enum: [api, database, database_container, qwen_agent, qwen_mt, qwen_embedding, qwen_asr_realtime, qwen_teleprompter_asr, qwen_live_translate, sms, oauth_google, oauth_apple, oauth_wechat, unionpay]
                category: {type: string, example: 模型}
                name: {type: string, example: Qwen-MT 翻译}
                configured: {type: boolean, description: 配置是否足以启用此依赖。}
                status:
                    type: string
                    enum: [healthy, verified, unavailable, unconfigured, disabled, unverified, stale]
                    description: healthy 表示安全探测成功；verified 表示最近的人工无音频实时会话控制面验证成功；unverified 表示已配置但未执行人工实时验证；stale 表示观察器或人工验证结果过期。
                checked_at: {type: string, format: date-time, description: 实际网络/存储探测执行完成时间；配置检查不会填充此字段。}
                configuration_checked_at: {type: string, format: date-time, description: 仅配置、模型名与 URL 结构检查完成时间；不是实时会话探测。}
                last_manual_probe_at: {type: string, format: date-time, description: 最近一次人工实时会话控制面验证时间；仅实时能力可能返回。}
                latency_ms: {type: integer, minimum: 0, description: 仅在执行实际探测时返回。}
                message: {type: string, description: 非敏感、面向管理员的状态说明。}
                details:
                    type: object
                    additionalProperties: true
                    description: 非敏感补充信息。例如数据库 migrations、容器 state/health、模型名称或测试模式；不包含地址或凭据。
        CapabilitiesResponse:
            type: object
            required: [client_dispatch, models, login, payment]
            properties:
                client_dispatch:
                    type: object
                    required: [enabled, transport, requires_bound_device]
                    properties:
                        enabled: {type: boolean}
                        transport: {type: string, enum: [durable_long_poll]}
                        requires_bound_device: {type: boolean}
                models:
                    type: object
                    additionalProperties:
                        type: object
                        required: [enabled, model]
                        properties:
                            enabled: {type: boolean}
                            model: {type: string}
                login:
                    type: object
                    additionalProperties:
                        type: object
                        required: [enabled]
                        properties:
                            enabled: {type: boolean}
                            mode: {type: string}
                payment:
                    type: object
                    required: [enabled, provider, reason]
                    properties:
                        enabled: {type: boolean}
                        provider: {type: string, enum: [unionpay]}
                        reason: {type: string, description: 启用时为空；禁用时为稳定机器码。}
        AgentAskRequest:
            type: object
            additionalProperties: false
            required: [question]
            properties:
                question: {type: string, minLength: 1, maxLength: 65536}
                max_tokens: {type: integer, minimum: 64, maximum: 4096}
                enable_thinking: {type: boolean}
        AgentAskResponse:
            type: object
            required: [model, answer, memory_mode, memory_hits]
            properties:
                model: {type: string, example: qwen3.7-plus}
                answer: {type: string}
                memory_mode: {type: string, example: bm25_fast_path}
                memory_hits: {type: integer, minimum: 0}
        AgentPlanRequest:
            type: object
            additionalProperties: false
            required: [question]
            properties:
                question: {type: string, minLength: 1, maxLength: 65536}
                device_id: {type: string, maxLength: 128, description: Optional bound client ID used only to select supported tools.}
                max_tokens: {type: integer, minimum: 32, maximum: 512}
                enable_thinking: {type: boolean, default: false}
        AgentPlanResponse:
            type: object
            required: [model, answer, tool_call, device, timing]
            properties:
                model: {type: string, example: qwen3.7-plus}
                answer: {type: string}
                tool_call:
                    nullable: true
                    allOf: [{$ref: '#/components/schemas/ClientToolCall'}]
                device: {$ref: '#/components/schemas/PlannerDeviceSummary'}
                timing:
                    type: object
                    required: [agent_tool_call_ms]
                    properties:
                        agent_tool_call_ms: {type: integer, format: int64, minimum: 0}
        ComplexPlanRequest:
            type: object
            additionalProperties: false
            required: [question]
            properties:
                question: {type: string, minLength: 1, maxLength: 65536}
                device_id: {type: string, maxLength: 128}
                max_tokens: {type: integer, minimum: 64, maximum: 512}
        ComplexPlanResponse:
            type: object
            required: [model, plan, device, timing]
            properties:
                model: {type: string, example: qwen3.7-plus}
                plan: {$ref: '#/components/schemas/ComplexPlan'}
                device: {$ref: '#/components/schemas/PlannerDeviceSummary'}
                timing:
                    type: object
                    required: [agent_complex_plan_ms]
                    properties:
                        agent_complex_plan_ms: {type: integer, format: int64, minimum: 0}
        PlannerDeviceSummary:
            type: object
            description: 账号隔离的设备能力摘要；省略或存在歧义时，不假定客户端具备任何能力。
            additionalProperties: true
        ClientToolCall:
            type: object
            required: [id, name, arguments]
            properties:
                id: {type: string}
                name:
                    type: string
                    enum: [capture_photo, record_audio, start_recording, stop_recording, translate_text]
                arguments: {type: object, additionalProperties: true}
        ComplexPlan:
            type: object
            required: [state, reply, actions]
            properties:
                state: {type: string, enum: [ready, clarify, unsupported]}
                reply: {type: string, maxLength: 240}
                reason: {type: string}
                actions:
                    type: array
                    maxItems: 6
                    items: {$ref: '#/components/schemas/ComplexPlanAction'}
        ComplexPlanAction:
            type: object
            required: [tool, purpose, wait_for, requires_confirmation]
            properties:
                tool:
                    type: string
                    enum: [capture_photo, analyze_captured_image, record_audio, start_recording, stop_recording, transcribe_recording, interpret_recording, translate_text, start_video, stop_video, start_app, stop_app]
                purpose: {type: string}
                duration_ms: {type: integer, minimum: 250, maximum: 60000, description: Only valid for record_audio.}
                wait_for:
                    type: array
                    description: 当前动作前必须完成的动作序号，从 1 开始计数。
                    items: {type: integer, minimum: 1}
                input_from: {type: integer, minimum: 1, description: One-based earlier action whose opaque result is used as input.}
                prompt: {type: string, description: Vision prompt for analyze_captured_image.}
                text: {type: string, description: Direct source text for translate_text.}
                source_lang: {type: string}
                target_lang: {type: string, description: Required by interpret_recording and translate_text.}
                package_name: {type: string, description: Android package for start_app or stop_app.}
                requires_confirmation: {type: boolean}
        IngestTextRequest:
            type: object
            additionalProperties: false
            required: [text]
            properties:
                text: {type: string, minLength: 1, maxLength: 65536}
                translate_to: {type: string, maxLength: 64}
                source_lang: {type: string, maxLength: 64}
        IngestAudioRequest:
            type: object
            required: [audio]
            properties:
                audio: {type: string, format: binary, description: Maximum 15 MiB.}
                audio_format: {type: string, enum: [pcm, opus, webm, ogg, mp4]}
                sample_rate: {type: integer, enum: [8000, 16000], default: 16000}
                language: {type: string, maxLength: 64}
                context_text: {type: string, maxLength: 65536}
                translate_to: {type: string, maxLength: 64}
                source_lang: {type: string, maxLength: 64}
        IngestResponse:
            type: object
            required: [input_type, recognized_text]
            properties:
                input_type: {type: string, enum: [text, audio]}
                recognized_text: {type: string}
                translation: {type: string}
                translation_model: {type: string, example: qwen3.5-livetranslate-flash-realtime}
        VisionRequest:
            type: object
            required: [image]
            properties:
                image: {type: string, format: binary, description: JPEG, PNG: '', or WebP; maximum 8 MiB.: ''}
                prompt: {type: string, maxLength: 65536}
        ModelAnswer:
            type: object
            required: [model, answer]
            properties:
                model: {type: string, example: qwen3.7-plus}
                answer: {type: string}
        ExecuteToolRequest:
            type: object
            additionalProperties: false
            required: [tool, device_id, payload]
            properties:
                tool: {type: string, enum: [translate, payment, capture_photo, record_audio, start_recording, stop_recording, analyze_captured_image, transcribe_recording, interpret_recording]}
                device_id: {type: string, minLength: 1, maxLength: 128, description: 当前账号内已激活绑定的目标客户端。}
                payload: {type: object, additionalProperties: true}
        ClientCommand:
            type: object
            required: [id, device_id, tool_name, state, ui_navigation, mcp_request, created_at, expires_at]
            properties:
                id: {type: string, example: command_0123456789abcdef}
                device_id: {type: string}
                tool_name: {type: string, enum: [translate_text, open_payment, capture_photo, record_audio, start_recording, stop_recording, analyze_captured_image, transcribe_recording, interpret_recording]}
                state: {type: string, enum: [queued, claimed, succeeded, failed, expired]}
                ui_navigation:
                    type: object
                    required: [type, screen, route, requires_foreground]
                    properties:
                        type: {type: string, enum: [native_route]}
                        screen: {type: string, enum: [translation, payment, camera, recorder, vision_confirmation, transcription_confirmation, interpretation_confirmation]}
                        route: {type: string, example: 'ai-glasses://translation'}
                        requires_foreground: {type: boolean, enum: [true]}
                mcp_request:
                    type: object
                    required: [jsonrpc, id, method, params]
                    properties:
                        jsonrpc: {type: string, enum: ['2.0']}
                        id: {type: string}
                        method: {type: string, enum: [tools/call]}
                        params:
                            type: object
                            required: [name, arguments]
                            properties:
                                name: {type: string, enum: [translate_text, open_payment, capture_photo, record_audio, start_recording, stop_recording, analyze_captured_image, transcribe_recording, interpret_recording]}
                                arguments: {type: object, additionalProperties: true}
                result: {type: object, nullable: true, additionalProperties: true, description: 客户端回传的完整 MCP result。}
                error_code: {type: string}
                error_message: {type: string}
                created_at: {type: string, format: date-time}
                claimed_at: {type: string, format: date-time, nullable: true}
                claimed_until: {type: string, format: date-time, nullable: true, description: 领取租约截止时间；逾期未回传时命令可重新投递。}
                claim_token: {type: string, description: 仅在 claimed/终态命令中返回的本次领取凭证，客户端回传结果时必须原样提交。}
                attempts: {type: integer, minimum: 0, maximum: 3}
                completed_at: {type: string, format: date-time, nullable: true}
                expires_at: {type: string, format: date-time}
        ClientCommandResponse:
            type: object
            required: [command]
            properties:
                command: {$ref: '#/components/schemas/ClientCommand'}
        ClientCommandCreateResponse:
            type: object
            required: [command, next]
            properties:
                command: {$ref: '#/components/schemas/ClientCommand'}
                next:
                    type: object
                    required: [client_claim, status, status_payload]
                    properties:
                        client_claim: {type: string, example: /v1/client/commands/claim}
                        status: {type: string, example: /v1/agent/commands/get}
                        status_payload:
                            type: object
                            required: [command_id]
                            properties:
                                command_id: {type: string, example: command_0123456789abcdef}
        ClientCommandClaimRequest:
            type: object
            additionalProperties: false
            required: [device_id]
            properties:
                device_id: {type: string, minLength: 1, maxLength: 128}
                wait_seconds: {type: integer, minimum: 0, maximum: 25, default: 20}
        ClientCommandClaimResponse:
            type: object
            required: [command]
            properties:
                command:
                    nullable: true
                    allOf: [{$ref: '#/components/schemas/ClientCommand'}]
        ClientCommandResultRequest:
            type: object
            additionalProperties: false
            required: [command_id, device_id, claim_token, success, result]
            properties:
                command_id: {type: string, minLength: 1, maxLength: 128}
                device_id: {type: string, minLength: 1, maxLength: 128}
                claim_token: {type: string, minLength: 1, maxLength: 256, description: 领取命令响应中的一次性租约凭证；相同凭证的完成请求可以安全重试。}
                success: {type: boolean}
                result: {type: object, additionalProperties: true, description: MCP tools/call 的完整 result，包括 content、structuredContent 和 isError。}
                error_code: {type: string, maxLength: 128}
                error_message: {type: string, maxLength: 2048}
        TranslationRequest:
            type: object
            additionalProperties: false
            required: [text, target_lang]
            properties:
                text: {type: string, minLength: 1, maxLength: 65536}
                source_lang: {type: string, maxLength: 64, default: auto}
                target_lang: {type: string, minLength: 1, maxLength: 64}
                translation_memory:
                    type: array
                    description: 仅允许已完成的上下文对；不可放入尚未完成的 ASR 分段。
                    maxItems: 6
                    items:
                        type: object
                        additionalProperties: false
                        required: [source, target]
                        properties:
                            source: {type: string, minLength: 1, maxLength: 512}
                            target: {type: string, minLength: 1, maxLength: 1024}
        TranslationResponse:
            type: object
            required: [model, source_lang, target_lang, translation]
            properties:
                model: {type: string, example: qwen-mt-flash}
                source_lang: {type: string, example: auto}
                target_lang: {type: string, example: English}
                translation: {type: string}
        PaymentCreateRequest:
            type: object
            additionalProperties: false
            required: [amount, currency]
            properties:
                amount: {type: integer, format: int64, minimum: 1, maximum: 100000000, description: Minor currency unit; 1234 means CNY 12.34.}
                currency: {type: string, minLength: 3, maxLength: 3, example: CNY}
                description: {type: string, maxLength: 65536}
                merchant_ref: {type: string, maxLength: 65536}
        PaymentConfirmRequest:
            type: object
            additionalProperties: false
            required: [intent_id, confirm]
            properties:
                intent_id: {type: string, minLength: 1, maxLength: 128}
                confirm: {type: boolean, description: Must be true only after displaying the exact merchant, amount: '', and currency.: ''}
        PaymentIntent:
            type: object
            required: [id, amount, currency, description, merchant_ref, provider, status, created_at, updated_at]
            properties:
                id: {type: string, example: pay_0123456789abcdef}
                amount: {type: integer, format: int64}
                currency: {type: string, example: CNY}
                description: {type: string}
                merchant_ref: {type: string}
                provider: {type: string, example: unionpay}
                status: {type: string, enum: [requires_confirmation, confirming, pending, paid, failed, cancelled], example: requires_confirmation}
                provider_ref: {type: string}
                provider_transaction_id: {type: string}
                payment_url: {type: string, format: uri}
                failure_code: {type: string}
                created_at: {type: string, format: date-time}
                embedding_status: {type: string, enum: [queued, disabled], description: 仅创建响应返回；向量生成是后台任务。}
                updated_at: {type: string, format: date-time}
        MemoryCreateRequest:
            type: object
            additionalProperties: false
            required: [content]
            properties:
                kind: {type: string, default: note, maxLength: 65536}
                content: {type: string, minLength: 1, maxLength: 65536}
        MemorySearchRequest:
            type: object
            additionalProperties: false
            required: [q]
            properties:
                q: {type: string, minLength: 1, maxLength: 65536}
                limit: {type: integer, minimum: 1, maximum: 50, default: 6}
        Memory:
            type: object
            required: [id, kind, content, created_at]
            properties:
                id: {type: string, example: mem_0123456789abcdef}
                kind: {type: string}
                content: {type: string}
                created_at: {type: string, format: date-time}
        MemoryListResponse:
            type: object
            required: [items]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/Memory'}
        MemorySearchResult:
            type: object
            required: [id, kind, content, score]
            properties:
                id: {type: string}
                kind: {type: string}
                content: {type: string}
                score: {type: number, format: float}
        MemorySearchResponse:
            type: object
            required: [query, items, embedding, retrieval, timing]
            properties:
                query: {type: string}
                items:
                    type: array
                    items: {$ref: '#/components/schemas/MemorySearchResult'}
                embedding: {type: string, example: bm25_fast_path}
                retrieval: {type: string, enum: [bm25, cosine, cosine_bm25]}
                timing:
                    type: object
                    required: [memory_load_ms, embedding_ms, ranking_ms, total_ms]
                    properties:
                        memory_load_ms: {type: integer, format: int64, minimum: 0}
                        embedding_ms: {type: integer, format: int64, minimum: 0}
                        ranking_ms: {type: integer, format: int64, minimum: 0}
                        total_ms: {type: integer, format: int64, minimum: 0}
        DeviceBindRequest:
            type: object
            additionalProperties: false
            required: [device_id, pairing_code]
            properties:
                device_id: {type: string, minLength: 1, maxLength: 128}
                name: {type: string, maxLength: 65536}
                pairing_code: {type: string, minLength: 1, maxLength: 65536}
        Device:
            type: object
            required: [id, name, status, updated_at]
            properties:
                id: {type: string}
                name: {type: string}
                status: {type: string, enum: [provisioned, active, unbound, revoked]}
                bound_at: {type: string, format: date-time}
                updated_at: {type: string, format: date-time}
        DeviceListResponse:
            type: object
            required: [items]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/Device'}
        ClientResources:
            type: object
            required: [heap_alloc_bytes, system_bytes, goroutines]
            properties:
                heap_alloc_bytes: {type: integer, format: int64, minimum: 0}
                system_bytes: {type: integer, format: int64, minimum: 0}
                goroutines: {type: integer, minimum: 0}
        ClientToolTiming:
            type: object
            required: [name, state]
            properties:
                name: {type: string, maxLength: 64}
                state: {type: string, maxLength: 64}
                agent_plan_ms: {type: integer, format: int64, minimum: 0}
                client_round_trip_ms: {type: integer, format: int64, minimum: 0}
                local_dispatch_ms: {type: integer, format: int64, minimum: 0}
                local_nlu_ms: {type: integer, format: int64, minimum: 0}
                device_open_ms: {type: integer, format: int64, minimum: 0}
                first_frame_ms: {type: integer, format: int64, minimum: 0}
                capture_ms: {type: integer, format: int64, minimum: 0}
                upload_ms: {type: integer, format: int64, minimum: 0}
                error_code: {type: string, maxLength: 128}
                recorded_at: {type: string, format: date-time}
        ClientHeartbeatRequest:
            type: object
            additionalProperties: false
            required: [device_id, name, platform, version, capabilities, resources, last_tool]
            properties:
                device_id: {type: string, minLength: 1, maxLength: 128}
                name: {type: string, maxLength: 128}
                platform: {type: string, maxLength: 64}
                version: {type: string, maxLength: 64}
                capabilities:
                    type: array
                    maxItems: 24
                    items: {type: string, minLength: 1, maxLength: 64}
                resources: {$ref: '#/components/schemas/ClientResources'}
                last_tool: {$ref: '#/components/schemas/ClientToolTiming'}
        ClientAgent:
            allOf:
                - $ref: '#/components/schemas/ClientHeartbeatRequest'
                - type: object
                  required: [last_seen_at]
                  properties:
                    last_seen_at: {type: string, format: date-time}
        HeartbeatResponse:
            type: object
            required: [ok, device_id, received_at]
            properties:
                ok: {type: boolean}
                device_id: {type: string}
                received_at: {type: string, format: date-time}
        ClientListResponse:
            type: object
            required: [items]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/ClientAgent'}
        AccessKeyCreateRequest:
            type: object
            additionalProperties: false
            required: [name, device_id]
            properties:
                name: {type: string, minLength: 1, maxLength: 48}
                device_id: {type: string, minLength: 1, maxLength: 128, description: 密钥只能代表这一台当前已绑定设备调用客户端数据面接口。}
        AccessKey:
            type: object
            required: [id, name, fingerprint, status, created_at]
            properties:
                id: {type: string, example: key_0123456789abcdef}
                device_id: {type: string, description: 设备客户端密钥的绑定设备；服务账号初始通用密钥为空。}
                name: {type: string}
                fingerprint: {type: string, example: ag_live_ab••••WXYZ}
                status: {type: string, enum: [active, revoked]}
                created_at: {type: string, format: date-time}
                last_used_at: {type: string, format: date-time, nullable: true}
        AccessKeyIssueResponse:
            type: object
            required: [access_key, secret]
            properties:
                access_key: {$ref: '#/components/schemas/AccessKey'}
                secret: {type: string, example: "ag_live_example"}
        AccessKeyListResponse:
            type: object
            required: [items]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/AccessKey'}
        AdminDevice:
            allOf:
                - $ref: '#/components/schemas/Device'
                - type: object
                  properties:
                    tenant_id: {type: string}
                    account_id: {type: string}
        AdminDeviceListResponse:
            type: object
            required: [items]
            properties:
                items:
                    type: array
                    items: {$ref: '#/components/schemas/AdminDevice'}
        DeviceProvisionRequest:
            type: object
            additionalProperties: false
            required: [device_id]
            properties:
                device_id: {type: string, minLength: 1, maxLength: 128}
                expires_in_seconds: {type: integer, minimum: 60, maximum: 3600, default: 600}
        DeviceProvisionResponse:
            type: object
            required: [device, pairing_code, expires_at]
            properties:
                device: {$ref: '#/components/schemas/AdminDevice'}
                pairing_code: {type: string, description: One-time secret; present it only to the intended account owner.}
                expires_at: {type: string, format: date-time}
        AdminAccountCreateRequest:
            type: object
            additionalProperties: false
            required: [account_id, initial_key_name]
            properties:
                tenant_id: {type: string, maxLength: 128, default: default}
                account_id: {type: string, minLength: 1, maxLength: 128}
                display_name: {type: string, maxLength: 65536}
                initial_key_name: {type: string, minLength: 1, maxLength: 48}
        AdminAccountCreateResponse:
            type: object
            required: [tenant_id, account_id]
            properties:
                tenant_id: {type: string}
                account_id: {type: string}
                initial_access_key: {$ref: '#/components/schemas/AccessKeyIssueResponse'}
        AdminAccessKeyCreateRequest:
            type: object
            additionalProperties: false
            required: [account_id, name, device_id]
            properties:
                tenant_id: {type: string, maxLength: 128, default: default}
                account_id: {type: string, minLength: 1, maxLength: 128}
                name: {type: string, minLength: 1, maxLength: 48}
                device_id: {type: string, minLength: 1, maxLength: 128}
        AdminAccessKeyRevokeRequest:
            type: object
            additionalProperties: false
            required: [account_id, key_id]
            properties:
                tenant_id: {type: string, maxLength: 128, default: default}
                account_id: {type: string, minLength: 1, maxLength: 128}
                key_id: {type: string, minLength: 1, maxLength: 128}
    responses:
        GenericJSON:
            description: JSON 响应；具体结构取决于选择的服务端白名单工具。
            content:
                application/json:
                    schema: {type: object, additionalProperties: true}
        BadRequest:
            description: 请求参数校验失败。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
        PayloadTooLarge:
            description: 文本或上传内容超过接口限制。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
        Unauthorized:
            description: Bearer 访问密钥缺失、无效或已被吊销。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
        Forbidden:
            description: 当前凭据类型无权执行该操作，例如客户端 access key 尝试修改账号或确认支付。若 error.code=password_change_required，客户端必须进入改密引导。
            content:
                application/json:
                    schema:
                        # PasswordChangeRequiredResponse extends ErrorResponse, so anyOf
                        # correctly represents both the generic and the richer 403 body.
                        anyOf:
                            - $ref: '#/components/schemas/ErrorResponse'
                            - $ref: '#/components/schemas/PasswordChangeRequiredResponse'
        PasswordChangeRequired:
            description: 当前账号使用管理员设置的临时密码，产品能力已暂停，直到客户端完成改密。
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/PasswordChangeRequiredResponse'
        NotFound:
            description: 未找到当前账号范围内的资源。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
        Conflict:
            description: 请求的状态转换不被允许。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
        DeviceConflict:
            description: 设备未预配、已绑定或当前未绑定。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
        InvalidPairingCode:
            description: 配对码无效、已过期或已使用。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
        QwenError:
            description: Qwen/DashScope 上游服务或配置错误。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
        ServiceUnavailable:
            description: 当前部署未配置或暂时无法访问所需的外部登录/短信服务。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
        PaymentUnavailable:
            description: 当前构建尚未配置官方银联 SDK 适配器或证书。
            content:
                application/json:
                    schema: {$ref: '#/components/schemas/ErrorResponse'}
x-apifox:
    schemaFolders:
        - 智能体
        - 多模态
        - 翻译
        - 账号
        - 设备
        - 管理后台
x-mcp:
    transport: stdio
    launch: MCP_MODE=stdio
    protocol_version: '2025-11-25'
    required_environment: [MCP_AUTH_TOKEN]
    session_authentication: MCP_AUTH_TOKEN is an active, revocable account-session token. The process validates it at startup, rejects sessions whose account has password_change_required=true, and does not accept account identifiers from environment or tool arguments.
    methods: [initialize, ping, tools/list, tools/call]
    controlled_tools:
        - ask_ai
        - analyze_captured_image
        - transcribe_recording
        - interpret_recording
        - translate_text
        - payment_create_intent
        - payment_confirm_intent
    client_tool_catalog: internal/capability/catalog.go
    note: Foreground client MCP owns capture_photo, record_audio, start_recording, stop_recording, and local RFMLite routing. Those local tools are intentionally absent from this cloud HTTP OpenAPI document.
