openapi: 3.1.0
info:
  title: 4SToken AI Gateway
  version: 1.0.0
  description: |
    4SToken 企业级 AI 模型网关对外 API。OpenAI / Anthropic 兼容；鉴权：Authorization: Bearer sk-...；
    模型价格见 Portal /models；接入教程见 /tools。点击下方左侧接口导航查看各端点详情。
  contact:
    name: 4SToken
    url: https://www.4stoken.cn/
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: https://api.4stoken.cn
    description: Production

security:
  - ApiKeyAuth: []

tags:
  - name: OpenAI 兼容
    description: OpenAI API 格式
  - name: Anthropic 兼容
    description: Claude Messages API
  - name: 统一外部能力
    description: 异步生成类能力统一入口

paths:
  /v1/models:
    get:
      tags: [OpenAI 兼容]
      summary: 模型列表
      operationId: listModels
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                    example: list
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ModelObject'
              example:
                object: list
                data:
                  - id: gpt-4.1
                    object: model
                    owned_by: ai-gateway
        '401':
          $ref: '#/components/responses/Unauthorized'

  /v1/chat/completions:
    post:
      tags: [OpenAI 兼容]
      summary: 对话补全
      description: 支持同步 JSON 返回；`stream=true` 时返回 SSE 流式响应。不支持异步任务返回或 callbackUrl。
      operationId: chatCompletions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChatCompletionRequest'
            example:
              model: your-model-code
              messages:
                - role: user
                  content: Hello
              max_tokens: 1024
              stream: false
      responses:
        '200':
          description: OK（非流式 JSON / 流式 SSE）
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatCompletionResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'

  /v1/embeddings:
    post:
      tags: [OpenAI 兼容]
      summary: 向量 Embedding
      description: 同步返回 embedding 结果。不支持异步任务返回或 callbackUrl。
      operationId: embeddings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmbeddingRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmbeddingResponse'
              example:
                object: list
                model: text-embedding-3-small
                data:
                  - object: embedding
                    index: 0
                    embedding: [0.0123, -0.0456, 0.0789]
                usage:
                  prompt_tokens: 8
                  total_tokens: 8
        '401':
          $ref: '#/components/responses/Unauthorized'

  /v1/images/generations:
    post:
      tags: [OpenAI 兼容]
      summary: 文生图（OpenAI 标准）
      description: |
        OpenAI 标准文生图：JSON 仅 `model` + `prompt`（及 `n`、`size` 等）。**不支持**参考图字段；
        传 `image_urls` 或参考图 `image` 将返回 **400**，请改用 `POST /v1/images/edits`。

        **同步（默认，OpenAI 标准）**：不传 `async`、无 `callbackUrl`，HTTP 等待 `{ created, taskId, task_id, data }`；
        Gateway 先将图片落 COS，再默认返回 `data[].url`，避免在响应体中传输大体积 base64 图片。
        `taskId` / `task_id` 为返回给下游使用的 Gateway 本地任务 ID，同步/异步均返回，可用于轮询 `GET /v1/images/tasks/{taskId}`。

        **网关扩展**：`async=true` 或 `callbackUrl` 时立即返回 `{ taskId, task_id, status: pending }`，轮询 `GET /v1/images/tasks/{taskId}`。
        Gateway 后台执行图片任务；上游同步/异步差异由 Gateway 内部适配。

        **callbackUrl 回调**：任务成功后 Gateway 会向 `callbackUrl` 发起 `POST`，`Content-Type: application/json`；
        请求体与 `GET /v1/images/tasks/{taskId}` 的成功响应一致，顶层直接包含 `task_id`、`taskId`、`status`、`data`、`usage`、`error` 等字段。
      operationId: imageGenerations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImageGenerationRequest'
            examples:
              textToImage:
                summary: 文生图
                value:
                  model: your-model-code
                  prompt: A cute cat in watercolor style
                  n: 1
                  size: '1:1'
                  image_size: 1K
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ImageGenerationResponse'
                  - $ref: '#/components/schemas/ImageTaskSubmitResponse'
              examples:
                sync:
                  summary: 同步返回图片结果
                  value:
                    created: 1710000000
                    task_id: img-202406010001
                    taskId: img-202406010001
                    usage:
                      input_tokens: 7148
                      output_tokens: 7024
                      total_tokens: 14172
                    data:
                      - url: https://example.com/generated/image.png
                async:
                  summary: async=true 或 callbackUrl 时返回任务
                  value:
                    task_id: gw-img-202406010001
                    taskId: gw-img-202406010001
                    status: pending
        '401':
          $ref: '#/components/responses/Unauthorized'

  /v1/images/edits:
    post:
      tags: [OpenAI 兼容]
      summary: 图生图 / 图片编辑（OpenAI 标准）
      description: |
        OpenAI 标准参考图与编辑：`multipart/form-data`，必填 `image`（可多张）+ `prompt`，可选 `mask` 局部重绘。
        参考图生图、多图融合、改图均使用本端点，**不要**在 `/v1/images/generations` 传 `image_urls`。

        Grsai 线路在网关内适配为上游 `POST /v1/images/generations`（JSON `image[]`）；Rivo 等支持原生 `edits`。
        带 `mask` 时须命中支持 mask 的上游线路，否则 **400**。
        同步成功时 Gateway 先将图片落 COS，再默认返回 `data[].url`，并返回 Gateway 本地 `taskId` / `task_id`；异步任务终态同样返回 `data[].url`。
        传 `async=true` 或 `callbackUrl` 时立即返回 `{ taskId, task_id, status: pending }`。

        **callbackUrl 回调**：任务成功后 Gateway 会向 `callbackUrl` 发起 `POST`，`Content-Type: application/json`；
        请求体与 `GET /v1/images/tasks/{taskId}` 的成功响应一致，顶层直接包含 `task_id`、`taskId`、`status`、`data`、`usage`、`error` 等字段。
      operationId: imageEdits
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [prompt, model]
              properties:
                model:
                  type: string
                prompt:
                  type: string
                image:
                  type: string
                  format: binary
                  description: 参考图，至少 1 张
                mask:
                  type: string
                  format: binary
                  description: 可选局部重绘 mask（部分上游不支持）
                image_size:
                  type: string
                  description: 网关扩展。分辨率档位 1K / 2K / 4K
                n:
                  type: integer
                size:
                  type: string
                  description: 宽高比或像素，如 1:1
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ImageGenerationResponse'
                  - $ref: '#/components/schemas/ImageTaskSubmitResponse'
              examples:
                sync:
                  summary: 同步返回图片结果
                  value:
                    created: 1710000000
                    task_id: img-202406010001
                    taskId: img-202406010001
                    usage:
                      input_tokens: 7148
                      output_tokens: 7024
                      total_tokens: 14172
                    data:
                      - url: https://example.com/generated/image.png
                async:
                  summary: async=true 或 callbackUrl 时返回任务
                  value:
                    task_id: gw-img-202406010002
                    taskId: gw-img-202406010002
                    status: pending
        '401':
          $ref: '#/components/responses/Unauthorized'

  /v1/images/tasks/{taskId}:
    get:
      tags: [OpenAI 兼容]
      summary: 图片异步任务查询
      description: |
        查询 Gateway 异步图片任务终态。任务完成后默认返回已落库图片 URL 与 usage；
        客户端无需依赖同步阶段的 `b64_json` 或回读完整原始 body。
      operationId: imageTask
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageTaskResponse'
              examples:
                pending:
                  summary: 处理中
                  value:
                    task_id: gw-img-202406010001
                    taskId: gw-img-202406010001
                    status: pending
                succeeded:
                  summary: 已完成
                  value:
                    created: 1710000000
                    task_id: gw-img-202406010001
                    taskId: gw-img-202406010001
                    status: succeeded
                    usage:
                      input_tokens: 7148
                      output_tokens: 7024
                      total_tokens: 14172
                    data:
                      - url: https://example.com/generated/image.png
                failed:
                  summary: 失败
                  value:
                    task_id: gw-img-202406010001
                    taskId: gw-img-202406010001
                    status: failed
                    error:
                      message: upstream task failed
                      type: upstream_error
                      code: image_generation_failed
        '401':
          $ref: '#/components/responses/Unauthorized'

  /v1/videos:
    post:
      tags: [OpenAI 兼容]
      summary: 视频生成（Seedance 官方协议）
      description: |
        Seedance 官方标准协议兼容入口。客户端提交 `model`、`prompt`、`duration`、`aspect_ratio`、
        `resolution`、`image_url`、`reference_images` 等字段，Gateway 根据模型路由到上游。

        **异步交互**：视频生成统一按任务语义返回 Gateway 本地 `vid-...`。`async=true` 或
        `callbackUrl` 为 Gateway 扩展字段，只控制下游立即返回与回调投递，不透传到 Seedance 官方上游。

        **结果查询**：轮询 `GET /v1/videos/{taskId}`。成功后返回 `data[].video_url`；如上游返回临时异步链接，
        Gateway 会先上传视频到 COS，再保存并返回我们的 COS URL。
      operationId: videoGenerations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VideoGenerationRequest'
            examples:
              textToVideo:
                summary: 文生视频
                value:
                  model: seedance-2-0-pro
                  prompt: 海浪拍打礁石，电影感推镜
                  duration: 5
                  aspect_ratio: '16:9'
                  resolution: 1080p
                  async: true
              imageToVideo:
                summary: 图生视频
                value:
                  model: seedance-2-0-pro
                  prompt: 让画面中的人物向镜头微笑挥手
                  image_url: https://example.com/reference.png
                  duration: 5
                  aspect_ratio: '16:9'
                  resolution: 1080p
                  callbackUrl: https://your-app.com/hooks/gateway
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/VideoTaskSubmitResponse'
                  - $ref: '#/components/schemas/VideoTaskResponse'
              examples:
                submitted:
                  summary: 已提交
                  value:
                    task_id: vid-202406010001
                    taskId: vid-202406010001
                    status: pending
                succeeded:
                  summary: 已完成
                  value:
                    created: 1710000000
                    task_id: vid-202406010001
                    taskId: vid-202406010001
                    status: succeeded
                    data:
                      - video_url: https://cos.example.com/gateway/video/result.mp4
        '401':
          $ref: '#/components/responses/Unauthorized'

  /v1/videos/{taskId}:
    get:
      tags: [OpenAI 兼容]
      summary: 视频任务查询
      description: |
        查询 Gateway 视频任务终态。支持使用 `POST /v1/videos` 返回的 Gateway 本地 `vid-...`；
        部分场景也兼容上游任务 ID。任务完成后返回 `data[].video_url`，其中临时链接会被 Gateway 镜像到 COS。
      operationId: videoTask
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
          example: vid-202406010001
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoTaskResponse'
              examples:
                pending:
                  summary: 处理中
                  value:
                    task_id: vid-202406010001
                    taskId: vid-202406010001
                    status: pending
                succeeded:
                  summary: 已完成
                  value:
                    created: 1710000000
                    task_id: vid-202406010001
                    taskId: vid-202406010001
                    status: succeeded
                    data:
                      - video_url: https://cos.example.com/gateway/video/result.mp4
                failed:
                  summary: 失败
                  value:
                    task_id: vid-202406010001
                    taskId: vid-202406010001
                    status: failed
                    error:
                      message: upstream task failed
                      type: upstream_error
                      code: video_generation_failed
        '401':
          $ref: '#/components/responses/Unauthorized'

  /v1/messages:
    post:
      tags: [Anthropic 兼容]
      summary: Claude Messages API
      description: 同步返回 Claude message 结果。不支持异步任务返回或 callbackUrl。
      operationId: anthropicMessages
      parameters:
        - name: x-api-key
          in: header
          required: false
          description: 可选，与 Authorization Bearer 二选一（均为 Client Key）
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnthropicMessagesRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnthropicMessagesResponse'
              example:
                id: msg_01HExample
                type: message
                role: assistant
                model: claude-3-5-sonnet
                content:
                  - type: text
                    text: Hello! How can I help?
                stop_reason: end_turn
                usage:
                  input_tokens: 12
                  output_tokens: 8
        '401':
          $ref: '#/components/responses/Unauthorized'

  /v1/abilities/generations:
    post:
      tags: [统一外部能力]
      summary: 统一生成提交
      description: |
        图/视频等异步能力。请轮询 `/v1/abilities/tasks/{taskId}` 获取结果（不支持网关 callbackUrl）。
      operationId: abilitySubmit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UnifiedGenerateRequest'
      responses:
        '200':
          description: 任务已提交
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AbilityTaskSubmitResponse'
              example:
                task_id: ability-202406010001
                status: pending
                provider: example-provider
        '401':
          $ref: '#/components/responses/Unauthorized'

  /v1/abilities/tasks/{taskId}:
    get:
      tags: [统一外部能力]
      summary: 统一任务查询
      operationId: abilityQuery
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
        - name: provider
          in: query
          required: false
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AbilityTaskResponse'
              examples:
                pending:
                  summary: 处理中
                  value:
                    task_id: ability-202406010001
                    status: pending
                    provider: example-provider
                succeeded:
                  summary: 已完成
                  value:
                    task_id: ability-202406010001
                    status: succeeded
                    provider: example-provider
                    outputAssets:
                      - https://example.com/output/video.mp4
                failed:
                  summary: 失败
                  value:
                    task_id: ability-202406010001
                    status: failed
                    provider: example-provider
                    error:
                      message: provider task failed
                      type: upstream_error
                      code: ability_generation_failed
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: Client Key
      description: Portal Client Key，格式 sk-...（历史 Key 可能为 sk-gw-...）

  responses:
    Unauthorized:
      description: Client Key 无效或未提供
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OpenAiError'
    PaymentRequired:
      description: 余额不足或配额超限
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OpenAiError'

  schemas:
    ModelObject:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
          example: model
        owned_by:
          type: string
          example: ai-gateway

    ChatCompletionRequest:
      type: object
      required: [model, messages]
      properties:
        model:
          type: string
          description: 逻辑模型编码（Portal 模型列表 modelCode）
        messages:
          type: array
          items:
            type: object
            required: [role, content]
            properties:
              role:
                type: string
                enum: [system, user, assistant]
              content:
                type: string
        stream:
          type: boolean
          default: false
        temperature:
          type: number
        top_p:
          type: number
        max_tokens:
          type: integer
        user:
          type: string

    ChatCompletionResponse:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
          example: chat.completion
        choices:
          type: array
          items:
            type: object
        usage:
          $ref: '#/components/schemas/UsageObject'

    EmbeddingRequest:
      type: object
      required: [model, input]
      properties:
        model:
          type: string
        input:
          oneOf:
            - type: string
            - type: array
              items:
                type: string

    EmbeddingResponse:
      type: object
      properties:
        object:
          type: string
          example: list
        model:
          type: string
        data:
          type: array
          items:
            type: object
            properties:
              object:
                type: string
                example: embedding
              index:
                type: integer
              embedding:
                type: array
                items:
                  type: number
                  format: float
        usage:
          $ref: '#/components/schemas/UsageObject'

    ImageGenerationRequest:
      type: object
      required: [model, prompt]
      properties:
        model:
          type: string
        prompt:
          type: string
        n:
          type: integer
        size:
          type: string
        response_format:
          type: string
        image_size:
          type: string
          description: 网关扩展。nano-banana 系分辨率档位，如 1K / 2K / 4K
        async:
          type: boolean
          description: |
            网关扩展。true 时 HTTP 立即返回 task_id，轮询 GET /v1/images/tasks/{task_id}。
            Gateway 后台执行图片任务；上游是否支持 async=true 由 Gateway 内部适配。
            未传/false 时为同步等待出图（OpenAI 兼容）。
        callbackUrl:
          type: string
          format: uri
          description: |
            可选。成功后异步 POST 图片任务结果，回调 body 与 `GET /v1/images/tasks/{task_id}` 成功响应一致。
            与 async=true 相同，HTTP 立即返回 task_id，由 Gateway 后台完成任务。

    ImageGenerationResponse:
      type: object
      required: [task_id, taskId]
      properties:
        created:
          type: integer
          format: int64
        task_id:
          type: string
          description: Gateway 本地任务 ID；同步/异步均返回，可用于 GET /v1/images/tasks/{task_id} 查询。
        taskId:
          type: string
          description: 同 task_id，返回给下游使用的 Gateway 本地任务 ID。
        usage:
          $ref: '#/components/schemas/UsageObject'
        data:
          type: array
          items:
            type: object
            properties:
              url:
                type: string
                format: uri
                description: |
                  生成图片 URL；同步结果默认先上传 COS 后返回该字段。
              b64_json:
                type: string
                description: |
                  仅兼容保留；同步结果默认不返回该字段，客户端应优先使用 url。
              revised_prompt:
                type: string

    ImageTaskSubmitResponse:
      type: object
      required: [task_id, taskId, status]
      properties:
        task_id:
          type: string
        taskId:
          type: string
          description: 同 task_id，返回给下游使用的 Gateway 本地任务 ID。
        status:
          type: string
          enum: [pending]

    ImageTaskResponse:
      type: object
      required: [task_id, taskId, status]
      properties:
        created:
          type: integer
          format: int64
        task_id:
          type: string
        taskId:
          type: string
          description: 同 task_id，返回给下游使用的 Gateway 本地任务 ID。
        status:
          type: string
          enum: [pending, running, succeeded, failed, canceled]
        usage:
          $ref: '#/components/schemas/UsageObject'
        data:
          type: array
          items:
            type: object
            properties:
              url:
                type: string
                format: uri
                description: 图片任务终态默认返回该字段
              b64_json:
                type: string
                description: 仅兼容保留；图片任务终态通常不返回该字段
              revised_prompt:
                type: string
        error:
          $ref: '#/components/schemas/OpenAiErrorObject'

    VideoGenerationRequest:
      type: object
      required: [model, prompt]
      properties:
        model:
          type: string
          description: Portal 发布的逻辑模型编码，需支持 videos.generations。
        prompt:
          type: string
        duration:
          type: integer
          description: 视频时长，单位秒；按 Seedance 官方协议透传。
          example: 5
        aspect_ratio:
          type: string
          description: 宽高比，如 16:9、9:16、1:1。
          example: '16:9'
        resolution:
          type: string
          description: 分辨率档位，如 720p、1080p。
          example: 1080p
        image_url:
          type: string
          format: uri
          description: 图生视频参考图 URL。
        reference_images:
          type: array
          description: 多参考图 URL；字段按 Seedance 官方协议透传。
          items:
            type: string
            format: uri
        seed:
          type: integer
        watermark:
          type: boolean
        async:
          type: boolean
          description: |
            Gateway 扩展。true 时 HTTP 立即返回 task_id，轮询 GET /v1/videos/{task_id}。
            该字段不透传到 Seedance 官方上游。
        callbackUrl:
          type: string
          format: uri
          description: |
            Gateway 扩展。成功后异步 POST 视频任务结果，回调 body 与 `GET /v1/videos/{task_id}` 成功响应一致。
            该字段不透传到 Seedance 官方上游。
      additionalProperties: true

    VideoTaskSubmitResponse:
      type: object
      required: [task_id, taskId, status]
      properties:
        task_id:
          type: string
          description: Gateway 本地视频任务 ID，通常为 vid-...。
        taskId:
          type: string
          description: 同 task_id。
        status:
          type: string
          enum: [pending]

    VideoTaskResponse:
      type: object
      required: [task_id, taskId, status]
      properties:
        created:
          type: integer
          format: int64
        task_id:
          type: string
          description: Gateway 本地视频任务 ID，通常为 vid-...。
        taskId:
          type: string
          description: 同 task_id。
        status:
          type: string
          enum: [pending, running, succeeded, failed, canceled]
        data:
          type: array
          items:
            type: object
            properties:
              video_url:
                type: string
                format: uri
                description: 视频结果 URL；临时链接会先镜像到 COS 后返回。
        usage:
          $ref: '#/components/schemas/UsageObject'
        error:
          $ref: '#/components/schemas/OpenAiErrorObject'

    AnthropicMessagesRequest:
      type: object
      required: [model, max_tokens, messages]
      properties:
        model:
          type: string
        max_tokens:
          type: integer
        system:
          type: string
        stream:
          type: boolean
        messages:
          type: array
          items:
            type: object
            properties:
              role:
                type: string
              content:
                oneOf:
                  - type: string
                  - type: array

    AnthropicMessagesResponse:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
          example: message
        role:
          type: string
          example: assistant
        model:
          type: string
        content:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
                example: text
              text:
                type: string
        stop_reason:
          type: string
        usage:
          type: object
          properties:
            input_tokens:
              type: integer
            output_tokens:
              type: integer

    UnifiedGenerateRequest:
      type: object
      required: [capability, model, prompt]
      properties:
        capability:
          type: string
          description: 能力类型枚举
        provider:
          type: string
        model:
          type: string
        prompt:
          type: string
        inputAssets:
          type: array
          items:
            type: string
        options:
          type: object
          additionalProperties: true

    AbilityTaskSubmitResponse:
      type: object
      required: [task_id, status]
      properties:
        task_id:
          type: string
        status:
          type: string
          enum: [pending]
        provider:
          type: string

    AbilityTaskResponse:
      type: object
      required: [task_id, status]
      properties:
        task_id:
          type: string
        status:
          type: string
          enum: [pending, running, succeeded, failed, canceled]
        provider:
          type: string
        outputAssets:
          type: array
          items:
            type: string
            format: uri
        result:
          type: object
          additionalProperties: true
        error:
          $ref: '#/components/schemas/OpenAiErrorObject'

    CallbackWebhookPayload:
      oneOf:
        - $ref: '#/components/schemas/ImageTaskResponse'
        - $ref: '#/components/schemas/VideoTaskResponse'
      description: |
        图片 / 视频接口向 `callbackUrl` POST 的 JSON body（业务回传，非 OpenAI 标准）。

        - 请求方式：`POST`，`Content-Type: application/json`
        - 触发时机：图片 / 视频异步任务成功后投递；原 API 请求已返回 task_id
        - 请求体：与对应任务查询成功响应一致，图片为 `GET /v1/images/tasks/{taskId}`，视频为 `GET /v1/videos/{taskId}`
        - 顶层直接包含 `task_id`、`taskId`、`status`、`data`、`usage`、`error` 等字段；视频结果使用 `data[].video_url`
        - 接收约定：建议调用方返回 HTTP 2xx 表示接收成功
        - 幂等建议：使用 `task_id` 去重，同一 `task_id` 重复收到时直接返回成功
        - 安全建议：使用 HTTPS 私有回调地址，并在业务侧校验来源、路径随机串或自定义 token

    OpenAiError:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/OpenAiErrorObject'

    OpenAiErrorObject:
      type: object
      properties:
        message:
          type: string
        type:
          type: string
        code:
          type: string

    UsageObject:
      type: object
      properties:
        prompt_tokens:
          type: integer
        completion_tokens:
          type: integer
        total_tokens:
          type: integer
