// Package chat 承载 Chat 模块的领域类型、Service 接口与对外暴露给其他业务模块的只读 Reader 接口。 // // 跨模块约束(Platform spec §4.3):其他业务模块(如 mcp)只能通过本文件暴露的 // MessageReader / ConversationReader 等 interface 横向依赖;不允许直接 import // repository.go / service_*.go。 package chat import ( "context" "time" "github.com/google/uuid" "github.com/yan1h/agent-coding-workflow/internal/infra/llm" ) // MessageRole 与 DB CHECK 约束一致。 type MessageRole string const ( RoleUser MessageRole = "user" RoleAssistant MessageRole = "assistant" ) // MessageStatus 是 messages.status 枚举。 type MessageStatus string const ( StatusPending MessageStatus = "pending" StatusOK MessageStatus = "ok" StatusError MessageStatus = "error" StatusCancelled MessageStatus = "cancelled" ) // TemplateScope 区分系统/用户模板。 type TemplateScope string const ( TemplateScopeSystem TemplateScope = "system" TemplateScopeUser TemplateScope = "user" ) // LLMProvider 与 DB CHECK 一致。 type LLMProvider string const ( ProviderAnthropic LLMProvider = "anthropic" ProviderOpenAI LLMProvider = "openai" ProviderGemini LLMProvider = "gemini" ) // ModelCapabilities 描述模型能力,用于附件 capability 校验与前端能力提示。 type ModelCapabilities struct { Image bool `json:"image"` PDF bool `json:"pdf"` Audio bool `json:"audio"` Reasoning bool `json:"reasoning"` Tools bool `json:"tools"` } // LLMEndpoint 是一个 LLM 端点(provider + base_url + 加密 api_key)。 type LLMEndpoint struct { ID uuid.UUID Provider LLMProvider DisplayName string BaseURL string APIKeyEncrypted []byte CreatedBy uuid.UUID CreatedAt, UpdatedAt time.Time } // LLMModel 是 endpoint 下的具体模型。 type LLMModel struct { ID uuid.UUID EndpointID uuid.UUID ModelID string DisplayName string Capabilities ModelCapabilities ContextWindow int MaxOutputTokens int PromptPricePerMillionUSD float64 CompletionPricePerMillionUSD float64 ThinkingPricePerMillionUSD float64 IsTitleGenerator bool Enabled bool SortOrder int CreatedAt, UpdatedAt time.Time } // PromptTemplate 是 system prompt 模板。 type PromptTemplate struct { ID uuid.UUID Name, Content string Scope TemplateScope OwnerID *uuid.UUID CreatedAt, UpdatedAt time.Time } // Conversation 是对话聚合根。Mounting 字段全部 NULLABLE 实现双轨制。 type Conversation struct { ID uuid.UUID UserID uuid.UUID ProjectID *uuid.UUID WorkspaceID *uuid.UUID IssueID *uuid.UUID ModelID uuid.UUID SystemPrompt string Title *string TitleGeneratedAt *time.Time CreatedAt, UpdatedAt time.Time DeletedAt *time.Time } // Attachment 是 message 的附件(user_id 必填,message_id 上传后未关联时为 nil)。 type Attachment struct { ID uuid.UUID UserID uuid.UUID MessageID *int64 // nil = orphan Filename string MimeType string SizeBytes int64 SHA256 string StoragePath string CreatedAt time.Time } // Message 是一轮对话消息。BIGSERIAL ID。 type Message struct { ID int64 ConversationID uuid.UUID Role MessageRole Content string Thinking *string ToolCalls []byte // JSONB raw bytes; mcp 预留,MVP 始终 nil Status MessageStatus ErrorMessage *string PromptTokens *int CompletionTokens *int ThinkingTokens *int Attachments []Attachment // 用户可读路径填充;composeHistory 不依赖此字段 CreatedAt, UpdatedAt time.Time } // UsageRecord 是 llm_usage 表一行。 type UsageRecord struct { ID int64 ConversationID uuid.UUID MessageID int64 UserID uuid.UUID EndpointID uuid.UUID ModelID uuid.UUID PromptTokens int CompletionTokens int ThinkingTokens int CostUSD float64 CreatedAt time.Time } // ===== 横向暴露给其他业务模块的只读接口(mcp 用) ===== // MessageReader 暴露只读消息查询,供 mcp 模块按用户上下文返回最近消息。 type MessageReader interface { ListRecentMessages(ctx context.Context, userID uuid.UUID, limit int) ([]Message, error) } // ConversationReader 暴露只读会话查询。 type ConversationReader interface { ListConversationsByUser(ctx context.Context, userID uuid.UUID, limit int) ([]Conversation, error) } // ===== Service 接口(在 service_*.go 实现) ===== // ConversationService — 会话 CRUD + 标题生成 hook。 type ConversationService interface { Create(ctx context.Context, userID uuid.UUID, in CreateConversationInput) (*Conversation, error) List(ctx context.Context, userID uuid.UUID, filter ConversationFilter) ([]Conversation, error) Get(ctx context.Context, userID, id uuid.UUID, limit int) (*Conversation, []Message, error) UpdateTitle(ctx context.Context, userID, id uuid.UUID, title string) error SoftDelete(ctx context.Context, userID, id uuid.UUID) error } // CreateConversationInput 是创建会话的入参。 type CreateConversationInput struct { ModelID uuid.UUID TemplateID *uuid.UUID SystemPrompt *string // 优先级:自定义 > 模板内容 > 空字符串 ProjectID *uuid.UUID WorkspaceID *uuid.UUID IssueID *uuid.UUID } // ConversationFilter 列表过滤参数。 type ConversationFilter struct { ProjectID *uuid.UUID WorkspaceID *uuid.UUID IssueID *uuid.UUID TitleQuery string BeforeID *uuid.UUID // cursor Limit int } // MessageService — 发消息 + 流接收 + 取消 + 重试。 type MessageService interface { Send(ctx context.Context, userID, conversationID uuid.UUID, content string, attachmentIDs []uuid.UUID) (userMsgID, assistantMsgID int64, err error) Stream(ctx context.Context, userID, conversationID uuid.UUID, sinceMessageID int64) (<-chan SSEEvent, error) Cancel(ctx context.Context, userID uuid.UUID, messageID int64) error Retry(ctx context.Context, userID uuid.UUID, messageID int64) (newAssistantMsgID int64, conversationID uuid.UUID, err error) ListMessages(ctx context.Context, userID, conversationID uuid.UUID, beforeID *int64, limit int) ([]Message, error) } // SSEEvent 是 chat handler 下行的 SSE 事件中间表示。 type SSEEvent struct { ID int64 // 自增;客户端用作 ?since= Event string // "message_meta" | "text_delta" | "thinking_delta" | "usage" | "done" | "error" Data any // 序列化为 JSON 的 payload } // TemplateService — prompt_templates CRUD。 type TemplateService interface { List(ctx context.Context, userID uuid.UUID) ([]PromptTemplate, error) Create(ctx context.Context, userID uuid.UUID, name, content string, scope TemplateScope) (*PromptTemplate, error) Update(ctx context.Context, userID, id uuid.UUID, name, content string) error Delete(ctx context.Context, userID, id uuid.UUID) error } // EndpointService — admin LLM endpoints / models CRUD(管理员守卫上层处理)。 type EndpointService interface { ListEndpoints(ctx context.Context) ([]LLMEndpoint, error) CreateEndpoint(ctx context.Context, userID uuid.UUID, in CreateEndpointInput) (*LLMEndpoint, error) UpdateEndpoint(ctx context.Context, id uuid.UUID, in UpdateEndpointInput) error DeleteEndpoint(ctx context.Context, id uuid.UUID) error TestEndpoint(ctx context.Context, id uuid.UUID) error // ping 上游 ListModels(ctx context.Context, onlyEnabled bool) ([]LLMModel, error) CreateModel(ctx context.Context, in CreateModelInput) (*LLMModel, error) UpdateModel(ctx context.Context, id uuid.UUID, in UpdateModelInput) error DeleteModel(ctx context.Context, id uuid.UUID) error } // CreateEndpointInput 创建 endpoint 入参。 type CreateEndpointInput struct { Provider LLMProvider DisplayName string BaseURL string APIKey string // 明文,service 内 AES-GCM 加密 } // UpdateEndpointInput 更新 endpoint 入参;nil 字段保持不变。 type UpdateEndpointInput struct { DisplayName *string BaseURL *string APIKey *string // nil = 不修改 } // CreateModelInput 创建 model 入参。 type CreateModelInput struct { EndpointID uuid.UUID ModelID, DisplayName string Capabilities ModelCapabilities ContextWindow, MaxOutputTokens int PromptPrice, CompletionPrice, ThinkingPrice float64 IsTitleGenerator bool Enabled bool SortOrder int } // UpdateModelInput 更新 model 入参;nil 字段保持不变。 type UpdateModelInput struct { DisplayName *string Capabilities *ModelCapabilities ContextWindow *int MaxOutputTokens *int PromptPrice *float64 CompletionPrice *float64 ThinkingPrice *float64 IsTitleGenerator *bool Enabled *bool SortOrder *int } // UsageService — 写入 + 汇总查询。 type UsageService interface { Insert(ctx context.Context, in UsageRecord) error SummarizeByUser(ctx context.Context, from, to time.Time) ([]UserUsageRow, error) SummarizeByModel(ctx context.Context, from, to time.Time) ([]ModelUsageRow, error) SummarizeByDay(ctx context.Context, from, to time.Time) ([]DayUsageRow, error) UserDaily(ctx context.Context, userID uuid.UUID, from, to time.Time) ([]DayUsageRow, error) } // UserUsageRow 按用户聚合的用量。 type UserUsageRow struct { UserID uuid.UUID PromptTokens, CompletionTokens, ThinkingTokens int CostUSD float64 } // ModelUsageRow 按模型聚合的用量。 type ModelUsageRow struct { ModelID uuid.UUID PromptTokens, CompletionTokens, ThinkingTokens int CostUSD float64 } // DayUsageRow 按天聚合的用量。 type DayUsageRow struct { Day time.Time PromptTokens, CompletionTokens, ThinkingTokens int CostUSD float64 } // ===== 流式控制接口(service_message 用) ===== // StreamerHub 暴露给 message service 的流控制能力。 type StreamerHub interface { Start(ctx context.Context, p StreamParams) error Cancel(messageID int64) bool Subscribe(messageID int64) (Subscription, bool) } // StreamParams 是启动一条流所需的所有上下文。 type StreamParams struct { UserID, ConversationID, EndpointID, ModelID uuid.UUID AssistantMessageID int64 Request llm.Request ModelMeta LLMModel } // Subscription 是订阅者读取 ring buffer 的句柄。 type Subscription interface { Next(ctx context.Context, nextEventID int64) (events []SSEEvent, closed bool, err error) }