You've already forked agentic-coding-workflow
321 lines
12 KiB
Go
321 lines
12 KiB
Go
package chat
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ===== Request DTOs =====
|
|
|
|
// CreateConversationReq is the HTTP request body for POST /conversations.
|
|
type CreateConversationReq struct {
|
|
ModelID uuid.UUID `json:"model_id"`
|
|
TemplateID *uuid.UUID `json:"template_id,omitempty"`
|
|
SystemPrompt *string `json:"system_prompt,omitempty"`
|
|
ProjectID *uuid.UUID `json:"project_id,omitempty"`
|
|
WorkspaceID *uuid.UUID `json:"workspace_id,omitempty"`
|
|
IssueID *uuid.UUID `json:"issue_id,omitempty"`
|
|
RequirementID *uuid.UUID `json:"requirement_id,omitempty"`
|
|
}
|
|
|
|
// UpdateConversationTitleReq is the HTTP request body for PATCH /conversations/{id}.
|
|
type UpdateConversationTitleReq struct {
|
|
Title string `json:"title"`
|
|
}
|
|
|
|
// SendMessageReq is the HTTP request body for POST /conversations/{id}/messages.
|
|
type SendMessageReq struct {
|
|
Content string `json:"content"`
|
|
AttachmentIDs []uuid.UUID `json:"attachment_ids,omitempty"`
|
|
}
|
|
|
|
// CreateTemplateReq is the HTTP request body for POST /prompt-templates.
|
|
type CreateTemplateReq struct {
|
|
Name string `json:"name"`
|
|
Content string `json:"content"`
|
|
Scope string `json:"scope"` // "user" | "system"
|
|
}
|
|
|
|
// UpdateTemplateReq is the HTTP request body for PATCH /prompt-templates/{id}.
|
|
type UpdateTemplateReq struct {
|
|
Name string `json:"name"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
// CreateEndpointReq is the HTTP request body for POST /admin/llm-endpoints.
|
|
type CreateEndpointReq struct {
|
|
Provider string `json:"provider"`
|
|
DisplayName string `json:"display_name"`
|
|
BaseURL string `json:"base_url"`
|
|
APIKey string `json:"api_key"`
|
|
}
|
|
|
|
// UpdateEndpointReq is the HTTP request body for PATCH /admin/llm-endpoints/{id}.
|
|
type UpdateEndpointReq struct {
|
|
DisplayName *string `json:"display_name,omitempty"`
|
|
BaseURL *string `json:"base_url,omitempty"`
|
|
APIKey *string `json:"api_key,omitempty"`
|
|
}
|
|
|
|
// CreateModelReq is the HTTP request body for POST /admin/llm-models.
|
|
type CreateModelReq struct {
|
|
EndpointID uuid.UUID `json:"endpoint_id"`
|
|
ModelID string `json:"model_id"`
|
|
DisplayName string `json:"display_name"`
|
|
Capabilities ModelCapabilities `json:"capabilities"`
|
|
ContextWindow int `json:"context_window"`
|
|
MaxOutputTokens int `json:"max_output_tokens"`
|
|
PromptPricePerMillionUSD float64 `json:"prompt_price_per_million_usd"`
|
|
CompletionPricePerMillionUSD float64 `json:"completion_price_per_million_usd"`
|
|
ThinkingPricePerMillionUSD float64 `json:"thinking_price_per_million_usd"`
|
|
IsTitleGenerator bool `json:"is_title_generator"`
|
|
Enabled bool `json:"enabled"`
|
|
SortOrder int `json:"sort_order"`
|
|
}
|
|
|
|
// UpdateModelReq is the HTTP request body for PATCH /admin/llm-models/{id}.
|
|
type UpdateModelReq struct {
|
|
DisplayName *string `json:"display_name,omitempty"`
|
|
Capabilities *ModelCapabilities `json:"capabilities,omitempty"`
|
|
ContextWindow *int `json:"context_window,omitempty"`
|
|
MaxOutputTokens *int `json:"max_output_tokens,omitempty"`
|
|
PromptPricePerMillionUSD *float64 `json:"prompt_price_per_million_usd,omitempty"`
|
|
CompletionPricePerMillionUSD *float64 `json:"completion_price_per_million_usd,omitempty"`
|
|
ThinkingPricePerMillionUSD *float64 `json:"thinking_price_per_million_usd,omitempty"`
|
|
IsTitleGenerator *bool `json:"is_title_generator,omitempty"`
|
|
Enabled *bool `json:"enabled,omitempty"`
|
|
SortOrder *int `json:"sort_order,omitempty"`
|
|
}
|
|
|
|
// ===== Response DTOs =====
|
|
|
|
// ConversationDTO is the JSON representation of a Conversation.
|
|
type ConversationDTO struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
ModelID uuid.UUID `json:"model_id"`
|
|
SystemPrompt string `json:"system_prompt"`
|
|
Title *string `json:"title,omitempty"`
|
|
ProjectID *uuid.UUID `json:"project_id,omitempty"`
|
|
WorkspaceID *uuid.UUID `json:"workspace_id,omitempty"`
|
|
IssueID *uuid.UUID `json:"issue_id,omitempty"`
|
|
RequirementID *uuid.UUID `json:"requirement_id,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// MessageDTO is the JSON representation of a Message.
|
|
type MessageDTO struct {
|
|
ID int64 `json:"id"`
|
|
ConversationID uuid.UUID `json:"conversation_id"`
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
Thinking *string `json:"thinking,omitempty"`
|
|
Status string `json:"status"`
|
|
ErrorMessage *string `json:"error_message,omitempty"`
|
|
PromptTokens *int `json:"prompt_tokens,omitempty"`
|
|
CompletionTokens *int `json:"completion_tokens,omitempty"`
|
|
ThinkingTokens *int `json:"thinking_tokens,omitempty"`
|
|
Attachments []AttachmentDTO `json:"attachments"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// AttachmentDTO is the JSON representation of an Attachment.
|
|
type AttachmentDTO struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Filename string `json:"filename"`
|
|
MimeType string `json:"mime_type"`
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
SHA256 string `json:"sha256"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// SendMessageResp is the response body for POST /conversations/{id}/messages.
|
|
type SendMessageResp struct {
|
|
UserMessageID int64 `json:"user_message_id"`
|
|
AssistantMessageID int64 `json:"assistant_message_id"`
|
|
StreamURL string `json:"stream_url"`
|
|
}
|
|
|
|
// TemplateDTO is the JSON representation of a PromptTemplate.
|
|
type TemplateDTO struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
Content string `json:"content"`
|
|
Scope string `json:"scope"`
|
|
OwnerID *uuid.UUID `json:"owner_id,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// EndpointDTO is the JSON representation of a LLMEndpoint.
|
|
// APIKeyEncrypted is intentionally excluded to prevent leaking secrets.
|
|
type EndpointDTO struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Provider string `json:"provider"`
|
|
DisplayName string `json:"display_name"`
|
|
BaseURL string `json:"base_url"`
|
|
CreatedBy uuid.UUID `json:"created_by"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// ModelDTO is the JSON representation of a LLMModel.
|
|
type ModelDTO struct {
|
|
ID uuid.UUID `json:"id"`
|
|
EndpointID uuid.UUID `json:"endpoint_id"`
|
|
ModelID string `json:"model_id"`
|
|
DisplayName string `json:"display_name"`
|
|
Capabilities ModelCapabilities `json:"capabilities"`
|
|
ContextWindow int `json:"context_window"`
|
|
MaxOutputTokens int `json:"max_output_tokens"`
|
|
PromptPricePerMillionUSD float64 `json:"prompt_price_per_million_usd"`
|
|
CompletionPricePerMillionUSD float64 `json:"completion_price_per_million_usd"`
|
|
ThinkingPricePerMillionUSD float64 `json:"thinking_price_per_million_usd"`
|
|
IsTitleGenerator bool `json:"is_title_generator"`
|
|
Enabled bool `json:"enabled"`
|
|
SortOrder int `json:"sort_order"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// DayUsageDTO is the per-day usage row returned to both regular users and admins.
|
|
type DayUsageDTO struct {
|
|
Day time.Time `json:"day"`
|
|
PromptTokens int `json:"prompt_tokens"`
|
|
CompletionTokens int `json:"completion_tokens"`
|
|
ThinkingTokens int `json:"thinking_tokens"`
|
|
CostUSD float64 `json:"cost_usd"`
|
|
}
|
|
|
|
// UserUsageDTO is the per-user aggregate usage row returned to admins.
|
|
type UserUsageDTO struct {
|
|
UserID uuid.UUID `json:"user_id"`
|
|
PromptTokens int `json:"prompt_tokens"`
|
|
CompletionTokens int `json:"completion_tokens"`
|
|
ThinkingTokens int `json:"thinking_tokens"`
|
|
CostUSD float64 `json:"cost_usd"`
|
|
}
|
|
|
|
// ModelUsageDTO is the per-model aggregate usage row returned to admins.
|
|
type ModelUsageDTO struct {
|
|
ModelID uuid.UUID `json:"model_id"`
|
|
PromptTokens int `json:"prompt_tokens"`
|
|
CompletionTokens int `json:"completion_tokens"`
|
|
ThinkingTokens int `json:"thinking_tokens"`
|
|
CostUSD float64 `json:"cost_usd"`
|
|
}
|
|
|
|
// AdminUsageItem is a single aggregated usage row keyed by user/model/day.
|
|
type AdminUsageItem struct {
|
|
Key string `json:"key"`
|
|
PromptTokens int `json:"prompt_tokens"`
|
|
CompletionTokens int `json:"completion_tokens"`
|
|
ThinkingTokens int `json:"thinking_tokens"`
|
|
CostUSD float64 `json:"cost_usd"`
|
|
}
|
|
|
|
// AdminUsageResp is the response body for GET /admin/usage.
|
|
type AdminUsageResp struct {
|
|
Items []AdminUsageItem `json:"items"`
|
|
}
|
|
|
|
// ===== Conversion helpers =====
|
|
|
|
func toConversationDTO(c *Conversation) ConversationDTO {
|
|
return ConversationDTO{
|
|
ID: c.ID,
|
|
UserID: c.UserID,
|
|
ModelID: c.ModelID,
|
|
SystemPrompt: c.SystemPrompt,
|
|
Title: c.Title,
|
|
ProjectID: c.ProjectID,
|
|
WorkspaceID: c.WorkspaceID,
|
|
IssueID: c.IssueID,
|
|
RequirementID: c.RequirementID,
|
|
CreatedAt: c.CreatedAt,
|
|
UpdatedAt: c.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func toMessageDTO(m *Message) MessageDTO {
|
|
return MessageDTO{
|
|
ID: m.ID,
|
|
ConversationID: m.ConversationID,
|
|
Role: string(m.Role),
|
|
Content: m.Content,
|
|
Thinking: m.Thinking,
|
|
Status: string(m.Status),
|
|
ErrorMessage: m.ErrorMessage,
|
|
PromptTokens: m.PromptTokens,
|
|
CompletionTokens: m.CompletionTokens,
|
|
ThinkingTokens: m.ThinkingTokens,
|
|
Attachments: toAttachmentDTOs(m.Attachments),
|
|
CreatedAt: m.CreatedAt,
|
|
UpdatedAt: m.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
// toAttachmentDTOs maps a slice of Attachment to DTOs, always returning a
|
|
// non-nil (possibly empty) slice so the JSON field serializes as [].
|
|
func toAttachmentDTOs(atts []Attachment) []AttachmentDTO {
|
|
out := make([]AttachmentDTO, 0, len(atts))
|
|
for _, a := range atts {
|
|
out = append(out, AttachmentDTO{
|
|
ID: a.ID,
|
|
Filename: a.Filename,
|
|
MimeType: a.MimeType,
|
|
SizeBytes: a.SizeBytes,
|
|
SHA256: a.SHA256,
|
|
CreatedAt: a.CreatedAt,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func toTemplateDTO(t *PromptTemplate) TemplateDTO {
|
|
return TemplateDTO{
|
|
ID: t.ID,
|
|
Name: t.Name,
|
|
Content: t.Content,
|
|
Scope: string(t.Scope),
|
|
OwnerID: t.OwnerID,
|
|
CreatedAt: t.CreatedAt,
|
|
UpdatedAt: t.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func toEndpointDTO(ep *LLMEndpoint) EndpointDTO {
|
|
return EndpointDTO{
|
|
ID: ep.ID,
|
|
Provider: string(ep.Provider),
|
|
DisplayName: ep.DisplayName,
|
|
BaseURL: ep.BaseURL,
|
|
CreatedBy: ep.CreatedBy,
|
|
CreatedAt: ep.CreatedAt,
|
|
UpdatedAt: ep.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func toModelDTO(m *LLMModel) ModelDTO {
|
|
return ModelDTO{
|
|
ID: m.ID,
|
|
EndpointID: m.EndpointID,
|
|
ModelID: m.ModelID,
|
|
DisplayName: m.DisplayName,
|
|
Capabilities: m.Capabilities,
|
|
ContextWindow: m.ContextWindow,
|
|
MaxOutputTokens: m.MaxOutputTokens,
|
|
PromptPricePerMillionUSD: m.PromptPricePerMillionUSD,
|
|
CompletionPricePerMillionUSD: m.CompletionPricePerMillionUSD,
|
|
ThinkingPricePerMillionUSD: m.ThinkingPricePerMillionUSD,
|
|
IsTitleGenerator: m.IsTitleGenerator,
|
|
Enabled: m.Enabled,
|
|
SortOrder: m.SortOrder,
|
|
CreatedAt: m.CreatedAt,
|
|
UpdatedAt: m.UpdatedAt,
|
|
}
|
|
}
|