openapi: 3.0.3
info:
  title: Agents API v1
  version: "1.0"
  description: |
    多工作区 + 后台任务 + HTTP 轮询进度 + 文件上传/下载。

    - 鉴权：Authorization: Bearer <token>（token 为自定义字符串，服务端存 SHA256）
    - 事件流：与网页端 WebSocket 同粒度（text_chunk、tool_start 等）
    - 多工作区：所有对话/消息/文件都在指定 workspace 下进行
servers:
  - url: https://agent.cyjai.com

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: token
  schemas:
    ErrorResponse:
      type: object
      properties:
        success: { type: boolean, example: false }
        error: { type: string, example: "无效的 Token" }
      required: [success, error]

    CreateConversationRequest:
      type: object
      description: 可选参数；用于在创建对话时绑定 prompt/personalization
      properties:
        prompt_name:
          type: string
          nullable: true
          description: 自定义主 prompt 名称（用户级共享，shared/prompts/<name>.txt）
        personalization_name:
          type: string
          nullable: true
          description: 个性化配置名称（用户级共享，shared/personalization/<name>.json）
      additionalProperties: false

    CreateConversationResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        workspace_id: { type: string, example: "ws1" }
        conversation_id: { type: string, example: "conv_20260123_234245_036" }
      required: [success, workspace_id, conversation_id]

    SendMessageRequest:
      type: object
      properties:
        message:
          type: string
          description: 用户消息文本（message 与 images 至少其一不为空）
        images:
          type: array
          items: { type: string }
          description: 图片路径列表（相对 project 根目录）
        conversation_id:
          type: string
          description: 对话 ID；不传则自动创建
        model_key:
          type: string
          nullable: true
          description: 模型 key（可选，取决于服务端配置）
        run_mode:
          type: string
          nullable: true
          description: 运行模式：fast | thinking | deep；若传入则优先使用
        thinking_mode:
          type: boolean
          nullable: true
          description: 是否开启思考模式（true/false）
        max_iterations:
          type: integer
          nullable: true
          description: 最大迭代次数（最多允许多少轮模型调用/工具循环）
        prompt_name:
          type: string
          nullable: true
          description: 自定义主 prompt 名称（用户级共享，shared/prompts/<name>.txt）
        personalization_name:
          type: string
          nullable: true
          description: 个性化配置名称（用户级共享，shared/personalization/<name>.json）
      additionalProperties: false

    PersonalizationConfig:
      type: object
      description: 个性化配置结构（content），服务端会规范化与回落默认值
      properties:
        enabled: { type: boolean, default: false }
        self_identify: { type: string, default: "" }
        user_name: { type: string, default: "" }
        profession: { type: string, default: "" }
        tone: { type: string, default: "" }
        considerations:
          type: array
          items: { type: string }
          default: []
          description: 最多 10 条，每条最多 50 字符
        thinking_interval:
          type: integer
          nullable: true
          description: 1~50；非法值会被置空
        disabled_tool_categories:
          type: array
          items: { type: string }
          default: []
        default_run_mode:
          type: string
          nullable: true
          description: fast/thinking/deep 或 null
        auto_generate_title: { type: boolean, default: true }
        tool_intent_enabled: { type: boolean, default: true }
        default_model:
          type: string
          default: kimi
          description: kimi/deepseek/qwen3-vl-plus/minimax-m2.5
      additionalProperties: true
      example:
        enabled: true
        user_name: "Jojo"
        tone: "企业商务"
        considerations: ["优先给出结论，再补充依据"]

    PromptUpsertRequest:
      type: object
      properties:
        name: { type: string, example: "prompt_strict" }
        content: { type: string, example: "严格模式：..." }
      required: [name, content]
      additionalProperties: false

    PromptGetResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        name: { type: string, example: "prompt_strict" }
        content: { type: string, example: "严格模式：..." }
      required: [success, name, content]

    PromptListItem:
      type: object
      properties:
        name: { type: string, example: "prompt_strict" }
        size: { type: integer, example: 123 }
        updated_at: { type: number, format: double, example: 1769182550.59 }
      required: [name, size, updated_at]

    PromptListResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        items:
          type: array
          items: { $ref: "#/components/schemas/PromptListItem" }
      required: [success, items]

    PersonalizationUpsertRequest:
      type: object
      properties:
        name: { type: string, example: "biz_mobile" }
        content: { $ref: "#/components/schemas/PersonalizationConfig" }
      required: [name, content]
      additionalProperties: false

    PersonalizationGetResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        name: { type: string, example: "biz_mobile" }
        content: { $ref: "#/components/schemas/PersonalizationConfig" }
      required: [success, name, content]

    PersonalizationListItem:
      type: object
      properties:
        name: { type: string, example: "biz_mobile" }
        size: { type: integer, example: 345 }
        updated_at: { type: number, format: double, example: 1769182550.59 }
      required: [name, size, updated_at]

    PersonalizationListResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        items:
          type: array
          items: { $ref: "#/components/schemas/PersonalizationListItem" }
      required: [success, items]

    ConversationListItem:
      type: object
      properties:
        workspace_id: { type: string, example: "ws1" }
        id: { type: string, example: "conv_20260124_023218_677" }
        title: { type: string, example: "新对话" }
        created_at: { type: string, example: "2026-01-24T02:32:18.677Z" }
        updated_at: { type: string, example: "2026-01-24T02:33:10.123Z" }
        run_mode: { type: string, nullable: true, example: "fast" }
        model_key: { type: string, nullable: true, example: "kimi" }
        custom_prompt_name: { type: string, nullable: true, example: "custom_a" }
        personalization_name: { type: string, nullable: true, example: "biz_mobile" }
        messages_count: { type: integer, example: 6 }
      required: [workspace_id, id, title, created_at, updated_at, messages_count]

    ConversationListResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        data:
          type: array
          items: { $ref: "#/components/schemas/ConversationListItem" }
        total: { type: integer, example: 42 }
      required: [success, data, total]

    ConversationGetResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        workspace_id: { type: string, example: "ws1" }
        data:
          type: object
          additionalProperties: true
      required: [success, workspace_id, data]

    DeleteConversationResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        workspace_id: { type: string, example: "ws1" }
        message: { type: string, example: "对话已删除: conv_..." }
      required: [success, workspace_id, message]

    ModelsResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        items:
          type: array
          items:
            type: object
            properties:
              model_key: { type: string, example: "kimi" }
              name: { type: string, example: "Kimi" }
              supports_thinking: { type: boolean, example: true }
              fast_only: { type: boolean, example: false }
            required: [model_key, name, supports_thinking, fast_only]
      required: [success, items]

    HealthResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        status: { type: string, example: "ok" }
      required: [success, status]

    SendMessageResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        task_id: { type: string, example: "60322db3-f884-4a1e-a9b3-6eeb07fbab47" }
        conversation_id: { type: string, example: "conv_20260123_234245_036" }
        workspace_id: { type: string, example: "ws1" }
        status: { type: string, example: "running" }
        created_at: { type: number, format: double, example: 1769182965.3038778 }
      required: [success, task_id, conversation_id, workspace_id, status, created_at]

    TaskEvent:
      type: object
      properties:
        idx: { type: integer, example: 12 }
        type: { type: string, example: "text_chunk" }
        data:
          type: object
          additionalProperties: true
        ts: { type: number, format: double, example: 1769182968.154797 }
      required: [idx, type, data, ts]

    TaskPollResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        data:
          type: object
          properties:
            task_id: { type: string }
            workspace_id: { type: string, nullable: true }
            status: { type: string, example: "running" }
            created_at: { type: number, format: double }
            updated_at: { type: number, format: double }
            error: { type: string, nullable: true }
            events:
              type: array
              items: { $ref: "#/components/schemas/TaskEvent" }
            next_offset: { type: integer, example: 3 }
          required: [task_id, status, created_at, updated_at, error, events, next_offset]
      required: [success, data]

    CancelResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
      required: [success]

    UploadResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        workspace_id: { type: string, example: "ws1" }
        path: { type: string, example: "inputs/hello.txt" }
        filename: { type: string, example: "hello.txt" }
        size: { type: integer, example: 16 }
        sha256: { type: string, example: "0f5bd6..." }
      required: [success, workspace_id, path, filename]

    ListFilesItem:
      type: object
      properties:
        name: { type: string, example: "hello.txt" }
        is_dir: { type: boolean, example: false }
        size: { type: integer, example: 16 }
        modified_at: { type: number, format: double, example: 1769182550.594278 }
        path: { type: string, example: "inputs/hello.txt" }
      required: [name, is_dir, size, modified_at, path]

    ListFilesResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        workspace_id: { type: string, example: "ws1" }
        base: { type: string, example: "." }
        items:
          type: array
          items: { $ref: "#/components/schemas/ListFilesItem" }
      required: [success, workspace_id, base, items]

    WorkspaceCreateRequest:
      type: object
      properties:
        workspace_id:
          type: string
          description: 仅允许字母/数字/._-，长度1-40
      required: [workspace_id]
      additionalProperties: false

    WorkspaceInfo:
      type: object
      properties:
        workspace_id: { type: string, example: "ws1" }
        project_path: { type: string }
        data_dir: { type: string }
        has_conversations: { type: boolean }
      required: [workspace_id, project_path, data_dir, has_conversations]

    WorkspacesListResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        items:
          type: array
          items: { $ref: "#/components/schemas/WorkspaceInfo" }
      required: [success, items]

    WorkspaceCreateResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        workspace_id: { type: string, example: "ws1" }
        project_path: { type: string }
        data_dir: { type: string }
      required: [success, workspace_id, project_path, data_dir]

    ToolsCategory:
      type: object
      properties:
        id: { type: string, example: "network" }
        label: { type: string, example: "网络检索" }
        default_enabled: { type: boolean, example: true }
        silent_when_disabled: { type: boolean, example: false }
        tools:
          type: array
          items: { type: string }
      required: [id, label, default_enabled, silent_when_disabled, tools]

    ToolsResponse:
      type: object
      properties:
        success: { type: boolean, example: true }
        categories:
          type: array
          items: { $ref: "#/components/schemas/ToolsCategory" }
      required: [success, categories]

security:
  - bearerAuth: []

paths:
  /api/v1/tools:
    get:
      summary: 获取工具分类与工具名称列表
      security: [{ bearerAuth: [] }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ToolsResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/workspaces:
    get:
      summary: 列出工作区
      security: [{ bearerAuth: [] }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WorkspacesListResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
    post:
      summary: 创建/确保工作区存在
      security: [{ bearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WorkspaceCreateRequest" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/WorkspaceCreateResponse" }
        "400":
          description: Bad Request
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/workspaces/{workspace_id}:
    get:
      summary: 查询单个工作区
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  workspace: { $ref: "#/components/schemas/WorkspaceInfo" }
                required: [success, workspace]
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
    delete:
      summary: 删除工作区（若有运行中任务则 409）
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  workspace_id: { type: string, example: "ws1" }
                required: [success, workspace_id]
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "409":
          description: Conflict (该工作区有运行中任务)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/workspaces/{workspace_id}/conversations:
    post:
      summary: 创建对话（工作区内）
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateConversationRequest" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CreateConversationResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
    get:
      summary: 获取对话列表（工作区内）
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
        - in: query
          name: limit
          required: false
          schema: { type: integer, default: 20, minimum: 1, maximum: 100 }
        - in: query
          name: offset
          required: false
          schema: { type: integer, default: 0, minimum: 0 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ConversationListResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/workspaces/{workspace_id}/conversations/{conversation_id}:
    get:
      summary: 获取单个对话（工作区内，可选 full=1 返回 messages）
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
        - in: path
          name: conversation_id
          required: true
          schema: { type: string }
        - in: query
          name: full
          required: false
          schema: { type: string, default: "0", enum: ["0", "1"] }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ConversationGetResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
    delete:
      summary: 删除对话（工作区内）
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
        - in: path
          name: conversation_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DeleteConversationResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/workspaces/{workspace_id}/messages:
    post:
      summary: 发送消息并创建后台任务（工作区内）
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SendMessageRequest" }
      responses:
        "202":
          description: Accepted
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SendMessageResponse" }
        "400":
          description: Bad Request
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found (prompt/personalization 不存在)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "409":
          description: Conflict (该工作区已有运行任务)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/prompts:
    get:
      summary: 列出 Prompt
      security: [{ bearerAuth: [] }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PromptListResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
    post:
      summary: 创建/覆盖 Prompt
      security: [{ bearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PromptUpsertRequest" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean, example: true }
                  name: { type: string, example: "prompt_strict" }
                required: [success, name]
        "400":
          description: Bad Request
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/prompts/{name}:
    get:
      summary: 获取 Prompt 内容
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: name
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PromptGetResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/personalizations:
    get:
      summary: 列出个性化配置
      security: [{ bearerAuth: [] }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PersonalizationListResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
    post:
      summary: 创建/覆盖个性化配置（服务端会 sanitize 并返回清洗后的 content）
      security: [{ bearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PersonalizationUpsertRequest" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PersonalizationGetResponse" }
        "400":
          description: Bad Request
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/personalizations/{name}:
    get:
      summary: 获取个性化配置内容（服务端会 sanitize 并可能回写）
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: name
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PersonalizationGetResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/models:
    get:
      summary: 列出服务端模型（公开）
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ModelsResponse" }

  /api/v1/health:
    get:
      summary: 健康检查（公开）
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HealthResponse" }

  /api/v1/tasks/{task_id}:
    get:
      summary: 轮询任务事件
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: task_id
          required: true
          schema: { type: string }
        - in: query
          name: from
          required: false
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TaskPollResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/tasks/{task_id}/cancel:
    post:
      summary: 请求停止任务
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: task_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CancelResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/workspaces/{workspace_id}/files/upload:
    post:
      summary: 上传文件到 user_upload
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                filename:
                  type: string
                dir:
                  type: string
                  description: user_upload 下子目录
              required: [file]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/UploadResponse" }
        "400":
          description: Bad Request
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/workspaces/{workspace_id}/files:
    get:
      summary: 列出 project 目录内容
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
        - in: query
          name: path
          required: false
          schema: { type: string, default: "" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ListFilesResponse" }
        "400":
          description: Bad Request
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /api/v1/workspaces/{workspace_id}/files/download:
    get:
      summary: 下载文件或目录（目录会打包成 zip）
      security: [{ bearerAuth: [] }]
      parameters:
        - in: path
          name: workspace_id
          required: true
          schema: { type: string }
        - in: query
          name: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: File content
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
            application/zip:
              schema:
                type: string
                format: binary
        "400":
          description: Bad Request
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Not Found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
