openapi: 3.0.3
info:
  title: Domus Agent Chat API
  version: 1.0.0
  description: |
    Agent chat APIs expose family-scoped conversation, message, turn, and voice-agent
    integration workflows from `chat_handler.go`.

    Responses always use the `{code, message, data}` envelope defined under
    `components.schemas.ResponseEnvelope`. A `code` of `0` means success. Domain/business
    errors return `code != 0`, while unexpected failures surface as HTTP 500 with
    `code = ErrInternal`.
servers:
  - url: /api/v1
    description: Gateway base path
tags:
  - name: Chat
    description: Conversation, message, turn, title, and suggestion endpoints.
  - name: VoiceAgent
    description: Voice-agent bridge endpoints for LLMFrame message persistence and context building.
security:
  - bearerAuth: []
    userContext: []
    familyContext: []

paths:
  /chat/conversation/list:
    get:
      tags:
        - Chat
      operationId: listChatConversations
      summary: List chat conversations
      description: Returns active conversations for the authenticated user/family role.
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      responses:
        '200':
          description: Conversation list.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ListConversationsResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /chat/conversation/{id}/history:
    get:
      tags:
        - Chat
      operationId: getChatConversationHistory
      summary: Get conversation history
      description: |
        Returns UI messages for a conversation. Pagination mode controls which cursor
        fields are valid:
        `history` allows optional `offset` only; `latest` allows no cursor fields;
        `seq_back` requires `before_seq`; `seq_forward` requires `after_seq`.
      parameters:
        - $ref: '#/components/parameters/ConversationIDPath'
        - $ref: '#/components/parameters/HistoryType'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
        - $ref: '#/components/parameters/BeforeSequence'
        - $ref: '#/components/parameters/AfterSequence'
      responses:
        '200':
          description: Conversation messages.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/ConversationHistoryResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /chat/conversation/{id}/title:generate:
    post:
      tags:
        - Chat
      operationId: generateChatConversationTitle
      summary: Wait for generated conversation title
      description: Waits for the generated title and emoji for the conversation.
      parameters:
        - $ref: '#/components/parameters/ConversationIDPath'
      responses:
        '200':
          description: Generated title payload.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/GenerateTitleResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /chat/message/send:
    post:
      tags:
        - Chat
      operationId: sendChatMessage
      summary: Send a chat message
      description: |
        Sends user content to a conversation. If `conversation_id` is omitted, the backend
        creates a conversation. Supported user input content types are `text`, `image`,
        `file`, and `form`. `X-Timezone` is required by the handler.
      parameters:
        - $ref: '#/components/parameters/XTimezoneHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMessageRequest'
      responses:
        '200':
          description: Message accepted and turn created.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/SendMessageResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /chat/turn/{id}/interrupt:
    post:
      tags:
        - Chat
      operationId: interruptChatTurn
      summary: Interrupt a turn
      description: Interrupts the active generation turn. The request body carries the turn id used by the handler.
      parameters:
        - $ref: '#/components/parameters/TurnIDPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InterruptTurnRequest'
      responses:
        '200':
          description: Turn interrupted.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /chat/turn/{id}/suggestions:
    get:
      tags:
        - Chat
      operationId: getChatTurnSuggestions
      summary: Get turn suggestions
      description: Waits for generated follow-up suggestions for a completed turn.
      parameters:
        - $ref: '#/components/parameters/TurnIDPath'
        - $ref: '#/components/parameters/ConversationIDQuery'
      responses:
        '200':
          description: Generated suggestion items.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/TurnSuggestionsResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /voice-agent/get-or-create-conversation-id:
    get:
      tags:
        - VoiceAgent
      operationId: getOrCreateVoiceConversationID
      summary: Get or create voice conversation id
      description: Returns a recent conversation id or generates a new UUID when no recent conversation exists.
      responses:
        '200':
          description: Conversation id selection result.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/GetOrCreateConversationIDResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /voice-agent/get-messages:
    get:
      tags:
        - VoiceAgent
      operationId: getVoiceAgentMessages
      summary: Get voice-agent model messages
      description: Returns recent model messages for the requested conversation.
      parameters:
        - $ref: '#/components/parameters/VoiceConversationIDQuery'
        - $ref: '#/components/parameters/VoiceRoundsLimit'
      responses:
        '200':
          description: Recent model messages.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/GetMessagesResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /voice-agent/add-messages-llmf:
    post:
      tags:
        - VoiceAgent
      operationId: addVoiceAgentMessagesLLMF
      summary: Add LLMFrame messages
      description: Creates a turn and persists the supplied LLMFrame model messages.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddMessagesLLMFRequest'
      responses:
        '200':
          description: Persisted turn and message ids.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/AddMessagesLLMFResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /voice-agent/update-turn-messages-llmf:
    post:
      tags:
        - VoiceAgent
      operationId: updateVoiceAgentTurnMessagesLLMF
      summary: Append one LLMFrame model message
      description: Appends a single assistant or tool model message to an existing turn.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTurnMessagesLLMFRequest'
      responses:
        '200':
          description: Updated turn identifier.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/UpdateTurnMessagesLLMFResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /voice-agent/build-tool-cards:
    post:
      tags:
        - VoiceAgent
      operationId: buildVoiceAgentToolCards
      summary: Build tool cards
      description: Converts voice-agent `tool_call` and `tool_result` segments into renderable card content blocks.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BuildToolCardsRequest'
      responses:
        '200':
          description: Renderable card contents.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/BuildToolCardsResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

  /voice-agent/agent-context:
    post:
      tags:
        - VoiceAgent
      operationId: buildVoiceAgentContext
      summary: Build agent context
      description: Builds the environment payload consumed by downstream agent services.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BuildAgentContextRequest'
      responses:
        '200':
          description: Agent environment payload.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ResponseEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/BuildAgentContextResponse'
        default:
          $ref: '#/components/responses/ErrorEnvelope'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Domus access token issued by the auth service.
    userContext:
      type: apiKey
      in: header
      name: X-User-ID
      description: Domus user id extracted from the gateway JWT.
    familyContext:
      type: apiKey
      in: header
      name: X-Family-ID
      description: Target family id required by family-role middleware.

  parameters:
    ConversationIDPath:
      name: id
      in: path
      required: true
      description: Conversation id (`conversations.id`).
      schema:
        type: string
        format: uuid
    TurnIDPath:
      name: id
      in: path
      required: true
      description: Turn id (`turns.id`).
      schema:
        type: string
        format: uuid
    ConversationIDQuery:
      name: conversation_id
      in: query
      required: true
      description: Conversation id (`conversations.id`).
      schema:
        type: string
        format: uuid
    VoiceConversationIDQuery:
      name: conversation-id
      in: query
      required: true
      description: Conversation id used by the voice-agent bridge.
      schema:
        type: string
        format: uuid
    Limit:
      name: limit
      in: query
      required: false
      description: Page size. Defaults to 20.
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    Offset:
      name: offset
      in: query
      required: false
      description: Zero-based offset. Defaults to 0.
      schema:
        type: integer
        minimum: 0
        default: 0
    HistoryType:
      name: type
      in: query
      required: false
      description: Conversation history pagination mode.
      schema:
        type: string
        enum:
          - history
          - latest
          - seq_back
          - seq_forward
        default: history
    BeforeSequence:
      name: before_seq
      in: query
      required: false
      description: Required by `seq_back`; forbidden for `history`, `latest`, and `seq_forward`.
      schema:
        type: integer
        format: int64
    AfterSequence:
      name: after_seq
      in: query
      required: false
      description: Required by `seq_forward`; forbidden for `history`, `latest`, and `seq_back`.
      schema:
        type: integer
        format: int64
    VoiceRoundsLimit:
      name: limit
      in: query
      required: false
      description: Number of recent rounds to return. Defaults to 5.
      schema:
        type: integer
        minimum: 1
        default: 5
    XTimezoneHeader:
      name: X-Timezone
      in: header
      required: true
      description: IANA timezone name used by the chat send workflow.
      schema:
        type: string
        example: Asia/Shanghai

  responses:
    ErrorEnvelope:
      description: Error envelope. Business errors return `code != 0`; unexpected failures use HTTP 500 with `code = ErrInternal`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'

  schemas:
    ResponseEnvelope:
      type: object
      required:
        - code
      properties:
        code:
          type: integer
          format: int32
          description: Application-level status code. 0 means success.
        message:
          type: string
          nullable: true
          description: Optional error message for non-zero codes.
        data:
          nullable: true
          description: Operation-specific payload.
    ErrorEnvelope:
      allOf:
        - $ref: '#/components/schemas/ResponseEnvelope'
      description: Standard error envelope. `data` is always null.

    JsonValue:
      nullable: true
      description: Arbitrary JSON value.

    ContentBlock:
      type: object
      required:
        - id
        - type
        - content
      properties:
        id:
          type: string
          description: Client or server generated content block id.
        type:
          type: string
          enum:
            - text
            - image
            - file
            - form
            - card
        content:
          $ref: '#/components/schemas/JsonValue'
        metadata:
          type: object
          nullable: true
          additionalProperties: true

    Conversation:
      type: object
      required:
        - id
        - title
        - title_emoji
        - status
        - updated_at
      properties:
        id:
          type: string
          format: uuid
          description: Conversation id (`conversations.id`).
        title:
          type: string
        title_emoji:
          type: string
        status:
          type: string
          description: Conversation status.
        updated_at:
          type: integer
          format: int64
          description: Unix timestamp seconds.
        active_turn_id:
          type: string
          format: uuid
          nullable: true

    ListConversationsResponse:
      type: object
      required:
        - conversations
        - limit
        - offset
        - total_count
      properties:
        conversations:
          type: array
          items:
            $ref: '#/components/schemas/Conversation'
        limit:
          type: integer
        offset:
          type: integer
        total_count:
          type: integer
          format: int64

    SendMessageRequest:
      type: object
      required:
        - contents
        - metadata
        - stream_enabled
      properties:
        conversation_id:
          type: string
          format: uuid
          nullable: true
          description: Existing conversation id. Omit to create a conversation.
        contents:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/ContentBlock'
        metadata:
          $ref: '#/components/schemas/SendMessageRequestMetadata'
        stream_enabled:
          type: boolean
          description: Enables SSE persist mode when true.
        is_memory_mode:
          type: boolean
          description: Uses blocking memory mode when true and `stream_enabled` is false.

    SendMessageRequestMetadata:
      type: object
      required:
        - client_instance_id
        - source
      properties:
        client_instance_id:
          type: string
        source:
          type: string
        entry_point:
          type: string

    SendMessageResponse:
      type: object
      required:
        - conversation_id
        - turn_id
        - ui_message_id
        - ui_message_sequence
      properties:
        conversation_id:
          type: string
          format: uuid
        title:
          type: string
          description: Present when a new conversation title is available.
        title_emoji:
          type: string
          description: Present when a new conversation title emoji is available.
        turn_id:
          type: string
          format: uuid
        ui_message_id:
          type: string
          format: uuid
        ui_message_sequence:
          type: integer
          format: int64
        contents:
          type: array
          description: Assistant contents returned in blocking persist mode; omitted in SSE mode.
          items:
            $ref: '#/components/schemas/ContentBlock'
        usage_context:
          $ref: '#/components/schemas/SendMessageUsageContext'

    SendMessageUsageContext:
      type: object
      properties:
        show_free_trial_prompt:
          type: boolean
          nullable: true

    Message:
      type: object
      required:
        - id
        - status
        - role
        - sequence
        - contents
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
        role:
          type: string
          enum:
            - user
            - assistant
            - tool
        sequence:
          type: integer
          format: int64
        contents:
          type: array
          items:
            $ref: '#/components/schemas/ContentBlock'

    ConversationHistoryResponse:
      type: object
      required:
        - messages
        - limit
      properties:
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
        limit:
          type: integer
        offset:
          type: integer
          nullable: true
        total_count:
          type: integer
          format: int64
          nullable: true
        has_more:
          type: boolean
          nullable: true

    InterruptTurnRequest:
      type: object
      required:
        - conversation_id
        - turn_id
      properties:
        conversation_id:
          type: string
          format: uuid
        turn_id:
          type: string
          format: uuid
          description: Must identify the turn being interrupted.

    GenerateTitleResponse:
      type: object
      required:
        - title
        - title_emoji
      properties:
        title:
          type: string
        title_emoji:
          type: string

    TurnSuggestionsResponse:
      type: object
      required:
        - items
      properties:
        items:
          type: array
          items:
            type: string

    MessageSegment:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - text
            - tool_call
            - tool_result
        text:
          type: string
          nullable: true
        tool_call:
          $ref: '#/components/schemas/ToolCall'
        tool_result:
          $ref: '#/components/schemas/ToolResult'

    ToolCall:
      type: object
      required:
        - id
        - name
        - arguments
      properties:
        id:
          type: string
        name:
          type: string
          description: Voice-agent tool name, for example `task.create_tasks`.
        arguments:
          type: object
          additionalProperties: true

    ToolResult:
      type: object
      required:
        - tool_call_id
        - name
        - content
      properties:
        tool_call_id:
          type: string
        name:
          type: string
        content:
          type: object
          additionalProperties: true
          description: Tool result payload. `modelVisibleData` is persisted for model-visible content when present.

    ModelMessage:
      type: object
      required:
        - role
        - segments
      properties:
        role:
          type: string
          enum:
            - user
            - assistant
            - tool
        segments:
          type: array
          items:
            $ref: '#/components/schemas/MessageSegment'

    ModelMessageResponse:
      type: object
      required:
        - role
        - segments
      properties:
        role:
          type: string
          enum:
            - user
            - assistant
            - tool
        segments:
          type: array
          items:
            $ref: '#/components/schemas/JsonValue'
        created_at:
          type: string
          format: date-time

    GetOrCreateConversationIDResponse:
      type: object
      required:
        - conversation_id
        - is_new
      properties:
        conversation_id:
          type: string
          format: uuid
        is_new:
          type: boolean

    GetMessagesResponse:
      type: object
      required:
        - conversation_id
        - messages
        - count
        - conversation_exists
      properties:
        conversation_id:
          type: string
          format: uuid
        messages:
          type: array
          items:
            $ref: '#/components/schemas/ModelMessageResponse'
        count:
          type: integer
        conversation_exists:
          type: boolean

    AddMessagesLLMFRequest:
      type: object
      required:
        - conversation_id
        - source
        - title
        - model_messages
      properties:
        conversation_id:
          type: string
          format: uuid
        is_new:
          type: boolean
          description: Creates a new conversation using `conversation_id` when true.
        source:
          type: string
          description: Conversation source.
        title:
          type: string
        model_messages:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/ModelMessage'

    AddMessagesLLMFResponse:
      type: object
      required:
        - conversation_id
        - turn_id
        - message_ids
      properties:
        conversation_id:
          type: string
          format: uuid
        turn_id:
          type: string
          format: uuid
        message_ids:
          type: array
          items:
            type: string
            format: uuid

    UpdateTurnMessagesLLMFRequest:
      type: object
      required:
        - conversation_id
        - turn_id
        - model_message
      properties:
        conversation_id:
          type: string
          format: uuid
        turn_id:
          type: string
          format: uuid
        model_message:
          $ref: '#/components/schemas/ModelMessage'

    UpdateTurnMessagesLLMFResponse:
      type: object
      required:
        - conversation_id
        - turn_id
      properties:
        conversation_id:
          type: string
          format: uuid
        turn_id:
          type: string
          format: uuid

    BuildToolCardsRequest:
      type: object
      required:
        - segments
      properties:
        segments:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/MessageSegment'

    BuildToolCardsResponse:
      type: object
      required:
        - contents
      properties:
        contents:
          type: array
          items:
            $ref: '#/components/schemas/ToolCardContentBlock'

    ToolCardContentBlock:
      allOf:
        - $ref: '#/components/schemas/ContentBlock'
        - type: object
          required:
            - metadata
            - created_at
            - updated_at
          properties:
            metadata:
              type: object
              additionalProperties: true
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time

    BuildAgentContextRequest:
      type: object
      required:
        - user_id
        - family_id
        - timezone
      properties:
        user_id:
          type: string
          format: uuid
        family_id:
          type: string
          format: uuid
        timezone:
          type: string
          example: Asia/Shanghai

    BuildAgentContextResponse:
      type: object
      required:
        - environment
      properties:
        environment:
          $ref: '#/components/schemas/AgentEnvironment'

    AgentEnvironment:
      type: object
      properties:
        family_info:
          $ref: '#/components/schemas/AgentFamilyInfo'
        user_brief:
          $ref: '#/components/schemas/AgentUserBrief'
        chat_info:
          $ref: '#/components/schemas/AgentChatInfo'

    AgentChatInfo:
      type: object
      properties:
        conversation_id:
          type: string
        turn_id:
          type: string
        user_ui_message_id:
          type: string
        assistant_ui_message_id:
          type: string

    AgentFamilyInfo:
      type: object
      properties:
        family_id:
          type: string
          format: uuid
        name:
          type: string
        roles:
          type: array
          items:
            $ref: '#/components/schemas/AgentFamilyRole'
        location:
          type: string
        locale:
          type: string
          default: en-US

    AgentFamilyRole:
      type: object
      properties:
        family_role_id:
          type: string
          format: uuid
        role_name:
          type: string
          nullable: true
        role_nickname:
          type: string
        user_id:
          type: string
          format: uuid
          nullable: true
        birthday:
          type: string
          format: date-time
          nullable: true

    AgentUserBrief:
      type: object
      properties:
        task:
          type: object
          nullable: true
          additionalProperties: true
        calendar:
          type: object
          nullable: true
          additionalProperties: true
        shopping:
          type: object
          nullable: true
          additionalProperties: true
        user_current_location:
          type: string
