# The human agent handling the chat, if one has taken it over. None means
# the AI still has it — the inbox needs this to label the row.
user_id:Optional[UUID]=None
user_name:Optional[str]=None
session_id:UUID
classChatDetailResponse(BaseModel):
customer:CustomerInfo
agent:AgentInfo
messages:List[Message]
status:SessionStatus
channel:str='web'
# Which connected channel account this conversation arrived on; None for web.
channel_account_id:Optional[UUID]=None
group_id:Optional[UUID]
session_id:UUID
user_id:Optional[UUID]
user_name:Optional[str]
created_at:datetime
updated_at:datetime
classConfig:
from_attributes=True
json_encoders={
UUID:str,
datetime:lambdav:v.isoformat()
}
classSourceRef(BaseModel):
"""A knowledge-base source used to ground an answer (for citation chips)."""
name:str
type:str
classChatResponse(BaseModel):
message:str=Field(description="The response from the agent. IMPORTANT: When shopify_output is present, DO NOT include product images, URLs, prices, or product details in this field - all product info should ONLY go in the shopify_output field. Keep the message conversational.")
transfer_to_human:bool=Field(description="Whether to transfer the conversation to a human")
end_chat:bool=Field(description="Whether to end the chat and request rating")
transfer_reason:Optional[TransferReasonType]=Field(default=None,description="Transfer reason Type should be one of the following: UNABLE_TO_ANSWER, NEED_MORE_INFO, KNOWLEDGE_GAP, NEED_TO_CALL, NEED_TO_EMAIL, NEED_TO_MEET, FRUSTRATED, REPEAT_INSTRUCTIONS, DIRECT_REQUEST, HIGH_PRIORITY_ISSUE, COMPLIANCE_ISSUE")
transfer_description:Optional[str]=Field(default=None,description="Detailed description for the transfer")
end_chat_reason:Optional[EndChatReasonType]=Field(default=None,description="End chat reason Type should be one of the following: ISSUE_RESOLVED, CUSTOMER_REQUEST, CONFIRMATION_RECEIVED, FAREWELL, THANK_YOU, NATURAL_CONCLUSION, TASK_COMPLETED")
end_chat_description:Optional[str]=Field(default=None,description="Detailed description for ending the chat")
request_rating:bool=Field(description="Whether to request a rating from the customer")
# Ticket creation fields
create_ticket:bool=Field(description="Whether to create a ticket in the integrated system (Jira, Zendesk, etc.)")
ticket_summary:Optional[str]=Field(default=None,description="Summary/title for the ticket to be created")
ticket_description:Optional[str]=Field(default=None,description="Detailed description for the ticket to be created")
integration_type:Optional[str]=Field(default=None,description="Type of integration to use for ticket creation")
ticket_id:Optional[str]=Field(default=None,description="ID of the created ticket (filled after creation)")
ticket_status:Optional[str]=Field(default=None,description="Status of the created ticket (filled after creation)")
ticket_priority:Optional[str]=Field(default=None,description="Priority level for the ticket (e.g., 'High', 'Medium', 'Low')")
# Shopify Output Field - Uses LLM model (no products field)
shopify_output:Optional[ShopifyOutputDataLLM]=Field(default=None,description="Shopify product cache info. ONLY include: product_cache_key, product_ids, shop_domain, total_count. DO NOT include products array.")
# Knowledge-base citations (system-managed). The LLM must NOT populate this; it is
# filled automatically from the knowledge search tool after the response is generated.
sources:Optional[List[SourceRef]]=Field(default=None,description="System-managed knowledge citations. Do not populate — set automatically.")
# System-managed: set when a handoff/ticket happens so the widget shows the contact form
# (even when no agents are available and transfer_to_human ends up False).
request_contact:Optional[bool]=Field(default=False,description="System-managed. Do not populate — set automatically on handoff.")
# LLM-set (only when lead capture is enabled): the agent collects details
# conversationally and reports them here, mirroring how transfer_to_human works.
request_lead_capture:bool=Field(default=False,description="Set to true ONLY when you have collected the visitor's contact details (at least a valid email in lead_email) and — if consent is required — they have explicitly agreed to be contacted. This records the lead.")
# Explicit scalar fields for the standard contact details. These are used INSTEAD of a
# free-form dict because OpenAI strict structured outputs cannot emit an open-ended
# object (additionalProperties) — a Dict field always comes back empty. Named scalar
# fields populate reliably. The widget records the lead from these.
lead_email:Optional[str]=Field(default=None,description="The visitor's email address exactly as they gave it, e.g. 'jane@acme.com'. Fill this as soon as they share it.")
lead_name:Optional[str]=Field(default=None,description="The visitor's name, if they share it.")
lead_company:Optional[str]=Field(default=None,description="The visitor's company/organization, if they share it.")
lead_phone:Optional[str]=Field(default=None,description="The visitor's phone number, if they share it.")
lead_data:Optional[Dict[str,Any]]=Field(default=None,description="System-managed. Do not populate — use lead_email/lead_name/lead_company/lead_phone instead.")
lead_summary:Optional[str]=Field(default=None,description="A 1-2 sentence qualification summary of this lead from the whole conversation (e.g. '40-person fintech replacing Intercom, ~800 Shopify orders/week, wants a demo this week').")
lead_consent:bool=Field(default=False,description="Set to true only when the visitor has explicitly agreed to be contacted / share their details.")
# Keys a model might wrap a choice in when it returns objects, not strings
_CHOICE_TEXT_KEYS=("title","text","label")
classChannelChatResponse(ChatResponse):
"""The reply schema for agents on a channel that can show tappable
choices — the only ones whose schema offers them, so every other agent's
output contract is unchanged."""
# A list of plain strings: strict structured outputs handle it on every
# provider, unlike a list of objects.
quick_replies:Optional[List[str]]=Field(default=None,description="Leave empty unless your instructions tell you to offer choices. The short choices the customer can tap, in order.")