// Package chat orchestrates conversations with the agent gateway, including // synchronous and streaming chat, scheduled triggers, messages, and memory storage. package chat import ( "encoding/json" "strings" "time" ) // Chat kind constants. const ( KindDirect = "direct" KindGroup = "group" KindThread = "thread" ) // Participant role constants. const ( RoleOwner = "owner" RoleAdmin = "admin" RoleMember = "member" ) // Chat list access mode constants. const ( AccessModeParticipant = "participant" AccessModeChannelIdentityObserved = "channel_identity_observed" ) // Chat is the first-class conversation container. type Chat struct { ID string `json:"id"` BotID string `json:"bot_id"` Kind string `json:"kind"` ParentChatID string `json:"parent_chat_id,omitempty"` Title string `json:"title,omitempty"` CreatedBy string `json:"created_by"` Metadata map[string]any `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // ChatListItem is a chat entry with access context for list rendering. type ChatListItem struct { ID string `json:"id"` BotID string `json:"bot_id"` Kind string `json:"kind"` ParentChatID string `json:"parent_chat_id,omitempty"` Title string `json:"title,omitempty"` CreatedBy string `json:"created_by"` Metadata map[string]any `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` AccessMode string `json:"access_mode"` ParticipantRole string `json:"participant_role,omitempty"` LastObservedAt *time.Time `json:"last_observed_at,omitempty"` } // ChatReadAccess is the resolved access context for reading chat content. type ChatReadAccess struct { AccessMode string ParticipantRole string LastObservedAt *time.Time } // Participant represents a chat member. type Participant struct { ChatID string `json:"chat_id"` UserID string `json:"user_id"` Role string `json:"role"` JoinedAt time.Time `json:"joined_at"` } // Settings holds per-chat configuration. type Settings struct { ChatID string `json:"chat_id"` EnableChatMemory bool `json:"enable_chat_memory"` EnablePrivateMemory bool `json:"enable_private_memory"` EnablePublicMemory bool `json:"enable_public_memory"` ModelID string `json:"model_id,omitempty"` Metadata map[string]any `json:"metadata,omitempty"` } // Route maps external channel conversations to a chat. type Route struct { ID string `json:"id"` ChatID string `json:"chat_id"` BotID string `json:"bot_id"` Platform string `json:"platform"` ChannelConfigID string `json:"channel_config_id,omitempty"` ConversationID string `json:"conversation_id"` ThreadID string `json:"thread_id,omitempty"` ReplyTarget string `json:"reply_target,omitempty"` Metadata map[string]any `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // Message represents a single persisted chat message. type Message struct { ID string `json:"id"` ChatID string `json:"chat_id"` BotID string `json:"bot_id"` RouteID string `json:"route_id,omitempty"` SenderChannelIdentityID string `json:"sender_channel_identity_id,omitempty"` SenderUserID string `json:"sender_user_id,omitempty"` Platform string `json:"platform,omitempty"` ExternalMessageID string `json:"external_message_id,omitempty"` Role string `json:"role"` Content json.RawMessage `json:"content"` Metadata map[string]any `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at"` } // CreateChatRequest is the input for creating a chat. type CreateChatRequest struct { Kind string `json:"kind"` Title string `json:"title,omitempty"` ParentChatID string `json:"parent_chat_id,omitempty"` Metadata map[string]any `json:"metadata,omitempty"` } // UpdateSettingsRequest is the input for updating chat settings. type UpdateSettingsRequest struct { EnableChatMemory *bool `json:"enable_chat_memory,omitempty"` EnablePrivateMemory *bool `json:"enable_private_memory,omitempty"` EnablePublicMemory *bool `json:"enable_public_memory,omitempty"` ModelID *string `json:"model_id,omitempty"` } // ResolveChatResult is returned by ResolveChat. type ResolveChatResult struct { ChatID string RouteID string Created bool } // ModelMessage is the canonical message format exchanged with the agent gateway. // Aligned with Vercel AI SDK ModelMessage structure. type ModelMessage struct { Role string `json:"role"` Content json.RawMessage `json:"content,omitempty"` ToolCalls []ToolCall `json:"tool_calls,omitempty"` ToolCallID string `json:"tool_call_id,omitempty"` Name string `json:"name,omitempty"` } // TextContent extracts the plain text from the message content. // If content is a string, it returns it directly. // If content is an array of parts, it joins all text-type parts. func (m ModelMessage) TextContent() string { if len(m.Content) == 0 { return "" } var s string if err := json.Unmarshal(m.Content, &s); err == nil { return s } var parts []ContentPart if err := json.Unmarshal(m.Content, &parts); err == nil { texts := make([]string, 0, len(parts)) for _, p := range parts { if strings.TrimSpace(p.Text) != "" { texts = append(texts, p.Text) } } return strings.Join(texts, "\n") } return "" } // ContentParts parses the content as an array of ContentPart. // Returns nil if the content is a plain string or not parseable. func (m ModelMessage) ContentParts() []ContentPart { if len(m.Content) == 0 { return nil } var parts []ContentPart if err := json.Unmarshal(m.Content, &parts); err != nil { return nil } return parts } // HasContent reports whether the message carries non-empty content or tool calls. func (m ModelMessage) HasContent() bool { if strings.TrimSpace(m.TextContent()) != "" { return true } if len(m.ContentParts()) > 0 { return true } return len(m.ToolCalls) > 0 } // NewTextContent creates a json.RawMessage from a plain string. func NewTextContent(text string) json.RawMessage { data, _ := json.Marshal(text) return data } // ContentPart represents one element of a multi-part message content. type ContentPart struct { Type string `json:"type"` Text string `json:"text,omitempty"` URL string `json:"url,omitempty"` Styles []string `json:"styles,omitempty"` Language string `json:"language,omitempty"` ChannelIdentityID string `json:"channel_identity_id,omitempty"` Emoji string `json:"emoji,omitempty"` Metadata map[string]any `json:"metadata,omitempty"` } // HasValue reports whether the content part carries a meaningful value. func (p ContentPart) HasValue() bool { return strings.TrimSpace(p.Text) != "" || strings.TrimSpace(p.URL) != "" || strings.TrimSpace(p.Emoji) != "" } // ToolCall represents a function/tool invocation in an assistant message. type ToolCall struct { ID string `json:"id,omitempty"` Type string `json:"type"` Function ToolCallFunction `json:"function"` } // ToolCallFunction holds the name and serialized arguments of a tool call. type ToolCallFunction struct { Name string `json:"name"` Arguments string `json:"arguments"` } // ChatRequest is the input for Chat and StreamChat. type ChatRequest struct { BotID string `json:"-"` ChatID string `json:"-"` Token string `json:"-"` ChannelIdentityID string `json:"-"` ContainerID string `json:"-"` DisplayName string `json:"-"` RouteID string `json:"-"` ChatToken string `json:"-"` ExternalMessageID string `json:"-"` Query string `json:"query"` Model string `json:"model,omitempty"` Provider string `json:"provider,omitempty"` MaxContextLoadTime int `json:"max_context_load_time,omitempty"` Language string `json:"language,omitempty"` Channels []string `json:"channels,omitempty"` CurrentChannel string `json:"current_channel,omitempty"` Messages []ModelMessage `json:"messages,omitempty"` Skills []string `json:"skills,omitempty"` AllowedActions []string `json:"allowed_actions,omitempty"` } // ChatResponse is the output of a non-streaming chat call. type ChatResponse struct { Messages []ModelMessage `json:"messages"` Skills []string `json:"skills,omitempty"` Model string `json:"model,omitempty"` Provider string `json:"provider,omitempty"` } // StreamChunk is a raw JSON chunk from the streaming response. type StreamChunk = json.RawMessage // AssistantOutput holds extracted assistant content for downstream consumers. type AssistantOutput struct { Content string Parts []ContentPart }