Files
agentic-coding-workflow/internal/chat/domain.go
T
2026-06-10 15:02:58 +08:00

332 lines
11 KiB
Go

// 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)
}