861 lines
33 KiB
Go
861 lines
33 KiB
Go
package models
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"time"
|
|
)
|
|
|
|
// ── Base ────────────────────────────────────
|
|
|
|
type BaseModel struct {
|
|
ID string `json:"id" db:"id"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// ── Scope Constants ─────────────────────────
|
|
|
|
const (
|
|
ScopeGlobal = "global"
|
|
ScopeTeam = "team"
|
|
ScopePersonal = "personal"
|
|
)
|
|
|
|
// ── Visibility Constants ────────────────────
|
|
|
|
const (
|
|
VisibilityVisible = "visible"
|
|
VisibilityHidden = "hidden"
|
|
)
|
|
|
|
// ── Role Constants ──────────────────────────
|
|
|
|
const (
|
|
UserRoleUser = "user"
|
|
UserRoleAdmin = "admin"
|
|
|
|
TeamRoleAdmin = "admin"
|
|
TeamRoleMember = "member"
|
|
)
|
|
|
|
// ── Grant Type Constants ────────────────────
|
|
|
|
const (
|
|
GrantTypeTool = "tool"
|
|
GrantTypeKnowledgeBase = "knowledge_base"
|
|
GrantTypeAPIEndpoint = "api_endpoint"
|
|
)
|
|
|
|
// ── Channel Type Constants ──────────────────
|
|
|
|
const (
|
|
ChannelTypeDirect = "direct"
|
|
ChannelTypeGroup = "group"
|
|
ChannelTypeChannel = "channel"
|
|
)
|
|
|
|
// ── Message Role Constants ──────────────────
|
|
|
|
const (
|
|
MessageRoleUser = "user"
|
|
MessageRoleAssistant = "assistant"
|
|
MessageRoleSystem = "system"
|
|
MessageRoleTool = "tool"
|
|
)
|
|
|
|
// =========================================
|
|
// USERS
|
|
// =========================================
|
|
|
|
type User struct {
|
|
BaseModel
|
|
Username string `json:"username" db:"username"`
|
|
Email string `json:"email" db:"email"`
|
|
PasswordHash string `json:"-" db:"password_hash"`
|
|
DisplayName string `json:"display_name,omitempty" db:"display_name"`
|
|
AvatarURL string `json:"avatar_url,omitempty" db:"avatar_url"`
|
|
Role string `json:"role" db:"role"`
|
|
IsActive bool `json:"is_active" db:"is_active"`
|
|
Settings JSONMap `json:"settings,omitempty" db:"settings"`
|
|
LastLoginAt *time.Time `json:"last_login_at,omitempty" db:"last_login_at"`
|
|
}
|
|
|
|
// =========================================
|
|
// TEAMS
|
|
// =========================================
|
|
|
|
type Team struct {
|
|
BaseModel
|
|
Name string `json:"name" db:"name"`
|
|
Description string `json:"description,omitempty" db:"description"`
|
|
CreatedBy string `json:"created_by" db:"created_by"`
|
|
IsActive bool `json:"is_active" db:"is_active"`
|
|
Settings JSONMap `json:"settings,omitempty" db:"settings"`
|
|
MemberCount int `json:"member_count,omitempty"` // computed
|
|
}
|
|
|
|
type TeamMember struct {
|
|
ID string `json:"id" db:"id"`
|
|
TeamID string `json:"team_id" db:"team_id"`
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
Role string `json:"role" db:"role"`
|
|
JoinedAt string `json:"joined_at" db:"joined_at"`
|
|
// Joined fields from users table
|
|
Email string `json:"email,omitempty"`
|
|
DisplayName string `json:"display_name,omitempty"`
|
|
Username string `json:"username,omitempty"`
|
|
UserRole string `json:"user_role,omitempty"`
|
|
}
|
|
|
|
// =========================================
|
|
// PROVIDER CONFIGS (replaces APIConfig)
|
|
// =========================================
|
|
|
|
type ProviderConfig struct {
|
|
BaseModel
|
|
Scope string `json:"scope" db:"scope"`
|
|
OwnerID *string `json:"owner_id,omitempty" db:"owner_id"`
|
|
Name string `json:"name" db:"name"`
|
|
Provider string `json:"provider" db:"provider"`
|
|
Endpoint string `json:"endpoint" db:"endpoint"`
|
|
APIKeyEnc []byte `json:"-" db:"api_key_enc"`
|
|
KeyNonce []byte `json:"-" db:"key_nonce"`
|
|
KeyScope string `json:"-" db:"key_scope"`
|
|
ModelDefault string `json:"model_default,omitempty" db:"model_default"`
|
|
Config JSONMap `json:"config,omitempty" db:"config"`
|
|
Headers JSONMap `json:"headers,omitempty" db:"headers"`
|
|
Settings JSONMap `json:"settings,omitempty" db:"settings"`
|
|
IsActive bool `json:"is_active" db:"is_active"`
|
|
IsPrivate bool `json:"is_private" db:"is_private"`
|
|
}
|
|
|
|
// HasKey returns true if an encrypted API key is stored.
|
|
func (p *ProviderConfig) HasKey() bool {
|
|
return len(p.APIKeyEnc) > 0
|
|
}
|
|
|
|
type ProviderConfigPatch struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Endpoint *string `json:"endpoint,omitempty"`
|
|
APIKeyEnc []byte `json:"-"`
|
|
KeyNonce []byte `json:"-"`
|
|
ModelDefault *string `json:"model_default,omitempty"`
|
|
Config JSONMap `json:"config,omitempty"`
|
|
Headers JSONMap `json:"headers,omitempty"`
|
|
Settings JSONMap `json:"settings,omitempty"`
|
|
IsActive *bool `json:"is_active,omitempty"`
|
|
IsPrivate *bool `json:"is_private,omitempty"`
|
|
}
|
|
|
|
// =========================================
|
|
// MODEL CATALOG (replaces model_configs)
|
|
// =========================================
|
|
|
|
type CatalogEntry struct {
|
|
BaseModel
|
|
ProviderConfigID string `json:"provider_config_id" db:"provider_config_id"`
|
|
ModelID string `json:"model_id" db:"model_id"`
|
|
DisplayName string `json:"display_name,omitempty" db:"display_name"`
|
|
ModelType string `json:"model_type" db:"model_type"` // "chat", "embedding", "image", etc.
|
|
Capabilities ModelCapabilities `json:"capabilities" db:"capabilities"`
|
|
Pricing *ModelPricing `json:"pricing,omitempty" db:"pricing"`
|
|
Visibility string `json:"visibility" db:"visibility"`
|
|
LastSyncedAt *time.Time `json:"last_synced_at,omitempty" db:"last_synced_at"`
|
|
}
|
|
|
|
type ModelCapabilities struct {
|
|
Streaming bool `json:"streaming"`
|
|
ToolCalling bool `json:"tool_calling"`
|
|
Vision bool `json:"vision"`
|
|
Thinking bool `json:"thinking"`
|
|
Reasoning bool `json:"reasoning"`
|
|
CodeOptimized bool `json:"code_optimized"`
|
|
WebSearch bool `json:"web_search"`
|
|
MaxContext int `json:"max_context"`
|
|
MaxOutputTokens int `json:"max_output_tokens"`
|
|
}
|
|
|
|
func (c ModelCapabilities) HasProviderData() bool {
|
|
return c.ToolCalling || c.Vision || c.Thinking || c.Reasoning ||
|
|
c.CodeOptimized || c.WebSearch || c.MaxContext > 0 || c.MaxOutputTokens > 0
|
|
}
|
|
|
|
type ModelPricing struct {
|
|
InputPerM float64 `json:"input_per_m,omitempty"`
|
|
OutputPerM float64 `json:"output_per_m,omitempty"`
|
|
Currency string `json:"currency,omitempty"`
|
|
}
|
|
|
|
// =========================================
|
|
// PERSONAS (replaces ModelPreset)
|
|
// =========================================
|
|
|
|
type Persona struct {
|
|
BaseModel
|
|
Name string `json:"name" db:"name"`
|
|
Description string `json:"description,omitempty" db:"description"`
|
|
Icon string `json:"icon,omitempty" db:"icon"`
|
|
Avatar string `json:"avatar,omitempty" db:"avatar"`
|
|
|
|
BaseModelID string `json:"base_model_id" db:"base_model_id"`
|
|
ProviderConfigID *string `json:"provider_config_id,omitempty" db:"provider_config_id"`
|
|
|
|
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
|
Temperature *float64 `json:"temperature,omitempty" db:"temperature"`
|
|
MaxTokens *int `json:"max_tokens,omitempty" db:"max_tokens"`
|
|
ThinkingBudget *int `json:"thinking_budget,omitempty" db:"thinking_budget"`
|
|
TopP *float64 `json:"top_p,omitempty" db:"top_p"`
|
|
|
|
Scope string `json:"scope" db:"scope"`
|
|
OwnerID *string `json:"owner_id,omitempty" db:"owner_id"`
|
|
CreatedBy string `json:"created_by" db:"created_by"`
|
|
|
|
IsActive bool `json:"is_active" db:"is_active"`
|
|
IsShared bool `json:"is_shared" db:"is_shared"`
|
|
|
|
// Loaded from persona_grants, not stored in personas table
|
|
Grants []Grant `json:"grants,omitempty" db:"-"`
|
|
|
|
// Loaded from persona_knowledge_bases, not stored in personas table
|
|
KBIDs []string `json:"kb_ids,omitempty" db:"-"`
|
|
|
|
// Memory configuration (v0.18.0)
|
|
MemoryEnabled bool `json:"memory_enabled" db:"memory_enabled"`
|
|
MemoryExtractionPrompt *string `json:"memory_extraction_prompt,omitempty" db:"memory_extraction_prompt"`
|
|
}
|
|
|
|
type PersonaPatch struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
Icon *string `json:"icon,omitempty"`
|
|
Avatar *string `json:"avatar,omitempty"`
|
|
BaseModelID *string `json:"base_model_id,omitempty"`
|
|
ProviderConfigID *string `json:"provider_config_id,omitempty"`
|
|
SystemPrompt *string `json:"system_prompt,omitempty"`
|
|
Temperature *float64 `json:"temperature,omitempty"`
|
|
MaxTokens *int `json:"max_tokens,omitempty"`
|
|
ThinkingBudget *int `json:"thinking_budget,omitempty"`
|
|
TopP *float64 `json:"top_p,omitempty"`
|
|
IsActive *bool `json:"is_active,omitempty"`
|
|
IsShared *bool `json:"is_shared,omitempty"`
|
|
MemoryEnabled *bool `json:"memory_enabled,omitempty"`
|
|
MemoryExtractionPrompt *string `json:"memory_extraction_prompt,omitempty"`
|
|
}
|
|
|
|
// =========================================
|
|
// GRANTS
|
|
// =========================================
|
|
|
|
type Grant struct {
|
|
ID string `json:"id" db:"id"`
|
|
PersonaID string `json:"persona_id" db:"persona_id"`
|
|
GrantType string `json:"grant_type" db:"grant_type"`
|
|
GrantRef string `json:"grant_ref" db:"grant_ref"`
|
|
Config JSONMap `json:"config,omitempty" db:"config"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// =========================================
|
|
// PLATFORM POLICIES
|
|
// =========================================
|
|
|
|
var PolicyDefaults = map[string]string{
|
|
"allow_user_byok": "false",
|
|
"allow_user_personas": "false",
|
|
"allow_raw_model_access": "false",
|
|
"allow_registration": "true",
|
|
"default_user_active": "false",
|
|
"allow_team_providers": "true",
|
|
"default_model": "",
|
|
}
|
|
|
|
// =========================================
|
|
// USER MODEL SETTINGS
|
|
// =========================================
|
|
|
|
type UserModelSetting struct {
|
|
BaseModel
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
ModelID string `json:"model_id" db:"model_id"`
|
|
Hidden bool `json:"hidden" db:"hidden"`
|
|
PreferredTemperature *float64 `json:"preferred_temperature,omitempty" db:"preferred_temperature"`
|
|
PreferredMaxTokens *int `json:"preferred_max_tokens,omitempty" db:"preferred_max_tokens"`
|
|
SortOrder int `json:"sort_order" db:"sort_order"`
|
|
}
|
|
|
|
type UserModelSettingPatch struct {
|
|
Hidden *bool `json:"hidden,omitempty"`
|
|
PreferredTemperature *float64 `json:"preferred_temperature,omitempty"`
|
|
PreferredMaxTokens *int `json:"preferred_max_tokens,omitempty"`
|
|
SortOrder *int `json:"sort_order,omitempty"`
|
|
}
|
|
|
|
// =========================================
|
|
// CHANNELS
|
|
// =========================================
|
|
|
|
type Channel struct {
|
|
BaseModel
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
Title string `json:"title" db:"title"`
|
|
Description string `json:"description,omitempty" db:"description"`
|
|
Type string `json:"type" db:"type"`
|
|
Model string `json:"model,omitempty" db:"model"`
|
|
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
|
ProviderConfigID *string `json:"provider_config_id,omitempty" db:"provider_config_id"`
|
|
IsArchived bool `json:"is_archived" db:"is_archived"`
|
|
IsPinned bool `json:"is_pinned" db:"is_pinned"`
|
|
FolderID *string `json:"folder_id,omitempty" db:"folder_id"`
|
|
TeamID *string `json:"team_id,omitempty" db:"team_id"`
|
|
ProjectID *string `json:"project_id,omitempty" db:"project_id"`
|
|
Settings JSONMap `json:"settings,omitempty" db:"settings"`
|
|
}
|
|
|
|
// =========================================
|
|
// MESSAGES
|
|
// =========================================
|
|
|
|
type Message struct {
|
|
BaseModel
|
|
ChannelID string `json:"channel_id" db:"channel_id"`
|
|
Role string `json:"role" db:"role"`
|
|
Content string `json:"content" db:"content"`
|
|
Model string `json:"model,omitempty" db:"model"`
|
|
TokensUsed int `json:"tokens_used,omitempty" db:"tokens_used"`
|
|
ToolCalls JSONMap `json:"tool_calls,omitempty" db:"tool_calls"`
|
|
Metadata JSONMap `json:"metadata,omitempty" db:"metadata"`
|
|
ParentID *string `json:"parent_id,omitempty" db:"parent_id"`
|
|
SiblingIndex int `json:"sibling_index" db:"sibling_index"`
|
|
ParticipantType string `json:"participant_type,omitempty" db:"participant_type"`
|
|
ParticipantID string `json:"participant_id,omitempty" db:"participant_id"`
|
|
DeletedAt *time.Time `json:"deleted_at,omitempty" db:"deleted_at"`
|
|
}
|
|
|
|
// =========================================
|
|
// CHANNEL MEMBERS, MODELS, CURSORS
|
|
// =========================================
|
|
|
|
type ChannelMember struct {
|
|
ID string `json:"id" db:"id"`
|
|
ChannelID string `json:"channel_id" db:"channel_id"`
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
Role string `json:"role" db:"role"`
|
|
JoinedAt time.Time `json:"joined_at" db:"joined_at"`
|
|
LastReadAt time.Time `json:"last_read_at" db:"last_read_at"`
|
|
}
|
|
|
|
type ChannelModel struct {
|
|
ID string `json:"id" db:"id"`
|
|
ChannelID string `json:"channel_id" db:"channel_id"`
|
|
ModelID string `json:"model_id" db:"model_id"`
|
|
ProviderConfigID string `json:"provider_config_id,omitempty" db:"provider_config_id"`
|
|
DisplayName string `json:"display_name,omitempty" db:"display_name"`
|
|
SystemPrompt string `json:"system_prompt,omitempty" db:"system_prompt"`
|
|
IsDefault bool `json:"is_default" db:"is_default"`
|
|
}
|
|
|
|
type ChannelCursor struct {
|
|
ID string `json:"id" db:"id"`
|
|
ChannelID string `json:"channel_id" db:"channel_id"`
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
ActiveLeafID *string `json:"active_leaf_id,omitempty" db:"active_leaf_id"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// =========================================
|
|
// ORGANIZATION
|
|
// =========================================
|
|
|
|
type Folder struct {
|
|
BaseModel
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
Name string `json:"name" db:"name"`
|
|
ParentID *string `json:"parent_id,omitempty" db:"parent_id"`
|
|
SortOrder int `json:"sort_order" db:"sort_order"`
|
|
}
|
|
|
|
type Project struct {
|
|
BaseModel
|
|
Name string `json:"name" db:"name"`
|
|
Description string `json:"description,omitempty" db:"description"`
|
|
Color *string `json:"color,omitempty" db:"color"`
|
|
Icon *string `json:"icon,omitempty" db:"icon"`
|
|
Scope string `json:"scope" db:"scope"` // personal, team, global
|
|
OwnerID string `json:"owner_id" db:"owner_id"`
|
|
TeamID *string `json:"team_id,omitempty" db:"team_id"`
|
|
IsArchived bool `json:"is_archived" db:"is_archived"`
|
|
Settings JSONMap `json:"settings,omitempty" db:"settings"`
|
|
|
|
// Computed fields (not DB columns)
|
|
ChannelCount int `json:"channel_count,omitempty"`
|
|
KBCount int `json:"kb_count,omitempty"`
|
|
NoteCount int `json:"note_count,omitempty"`
|
|
}
|
|
|
|
// ProjectPatch holds optional fields for updating a project.
|
|
type ProjectPatch struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
Color *string `json:"color,omitempty"`
|
|
Icon *string `json:"icon,omitempty"`
|
|
IsArchived *bool `json:"is_archived,omitempty"`
|
|
}
|
|
|
|
// ProjectChannel represents a channel's membership in a project.
|
|
type ProjectChannel struct {
|
|
ProjectID string `json:"project_id" db:"project_id"`
|
|
ChannelID string `json:"channel_id" db:"channel_id"`
|
|
Position int `json:"position" db:"position"`
|
|
Folder string `json:"folder,omitempty" db:"folder"`
|
|
AddedAt string `json:"added_at" db:"added_at"`
|
|
}
|
|
|
|
// ProjectKB represents a KB's association with a project.
|
|
type ProjectKB struct {
|
|
ProjectID string `json:"project_id" db:"project_id"`
|
|
KBID string `json:"kb_id" db:"kb_id"`
|
|
AutoSearch bool `json:"auto_search" db:"auto_search"`
|
|
AddedAt string `json:"added_at" db:"added_at"`
|
|
}
|
|
|
|
// ProjectNote represents a note's association with a project.
|
|
type ProjectNote struct {
|
|
ProjectID string `json:"project_id" db:"project_id"`
|
|
NoteID string `json:"note_id" db:"note_id"`
|
|
AddedAt string `json:"added_at" db:"added_at"`
|
|
}
|
|
|
|
// =========================================
|
|
// NOTES
|
|
// =========================================
|
|
|
|
type Note struct {
|
|
BaseModel
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
Title string `json:"title" db:"title"`
|
|
Content string `json:"content" db:"content"`
|
|
FolderPath string `json:"folder_path" db:"folder_path"`
|
|
Tags []string `json:"tags,omitempty" db:"tags"`
|
|
Metadata JSONMap `json:"metadata,omitempty" db:"metadata"`
|
|
SourceChannelID *string `json:"source_channel_id,omitempty" db:"source_channel_id"`
|
|
SourceMessageID *string `json:"source_message_id,omitempty" db:"source_message_id"`
|
|
TeamID *string `json:"team_id,omitempty" db:"team_id"`
|
|
}
|
|
|
|
// NoteLink represents a directed link extracted from [[wikilink]] syntax.
|
|
type NoteLink struct {
|
|
TargetNoteID *string `json:"target_note_id,omitempty"`
|
|
TargetTitle string `json:"target_title"`
|
|
DisplayText string `json:"display_text,omitempty"`
|
|
IsTransclusion bool `json:"is_transclusion"`
|
|
}
|
|
|
|
// NoteLinkResult represents a backlink — a note that links to a given note.
|
|
type NoteLinkResult struct {
|
|
SourceNoteID string `json:"id"`
|
|
Title string `json:"title"`
|
|
FolderPath string `json:"folder_path"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
DisplayText string `json:"display_text,omitempty"`
|
|
}
|
|
|
|
// NoteGraphNode is a lightweight note representation for graph display.
|
|
type NoteGraphNode struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
FolderPath string `json:"folder_path"`
|
|
Tags []string `json:"tags"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
LinkCount int `json:"link_count"`
|
|
}
|
|
|
|
// NoteGraphEdge is a resolved link between two notes.
|
|
type NoteGraphEdge struct {
|
|
Source string `json:"source"`
|
|
Target string `json:"target"`
|
|
Title string `json:"title"`
|
|
IsTransclusion bool `json:"is_transclusion"`
|
|
}
|
|
|
|
// NoteGraphDangling is an unresolved [[link]] reference.
|
|
type NoteGraphDangling struct {
|
|
Source string `json:"source"`
|
|
Title string `json:"title"`
|
|
}
|
|
|
|
// NoteGraph is the full graph topology for a user's notes.
|
|
type NoteGraph struct {
|
|
Nodes []NoteGraphNode `json:"nodes"`
|
|
Edges []NoteGraphEdge `json:"edges"`
|
|
Unresolved []NoteGraphDangling `json:"unresolved"`
|
|
}
|
|
|
|
// =========================================
|
|
// ATTACHMENTS
|
|
// =========================================
|
|
|
|
type Attachment struct {
|
|
ID string `json:"id" db:"id"`
|
|
ChannelID string `json:"channel_id" db:"channel_id"`
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
MessageID *string `json:"message_id,omitempty" db:"message_id"`
|
|
Filename string `json:"filename" db:"filename"`
|
|
ContentType string `json:"content_type" db:"content_type"`
|
|
SizeBytes int64 `json:"size_bytes" db:"size_bytes"`
|
|
StorageKey string `json:"-" db:"storage_key"` // never expose filesystem path
|
|
ExtractedText *string `json:"extracted_text,omitempty" db:"extracted_text"`
|
|
Metadata JSONMap `json:"metadata,omitempty" db:"metadata"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// =========================================
|
|
// AUDIT LOG
|
|
// =========================================
|
|
|
|
type AuditEntry struct {
|
|
ID string `json:"id" db:"id"`
|
|
ActorID *string `json:"actor_id,omitempty" db:"actor_id"`
|
|
Action string `json:"action" db:"action"`
|
|
ResourceType string `json:"resource_type" db:"resource_type"`
|
|
ResourceID string `json:"resource_id,omitempty" db:"resource_id"`
|
|
Metadata JSONMap `json:"metadata,omitempty" db:"metadata"`
|
|
IPAddress string `json:"ip_address,omitempty" db:"ip_address"`
|
|
UserAgent string `json:"user_agent,omitempty" db:"user_agent"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// =========================================
|
|
// USAGE TRACKING
|
|
// =========================================
|
|
|
|
type UsageEntry struct {
|
|
ID string `json:"id" db:"id"`
|
|
ChannelID *string `json:"channel_id,omitempty" db:"channel_id"`
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
ProviderConfigID *string `json:"provider_config_id,omitempty" db:"provider_config_id"`
|
|
ProviderScope string `json:"provider_scope" db:"provider_scope"`
|
|
ModelID string `json:"model_id" db:"model_id"`
|
|
Role *string `json:"role,omitempty" db:"role"` // null=chat, "utility", "embedding"
|
|
PromptTokens int `json:"prompt_tokens" db:"prompt_tokens"`
|
|
CompletionTokens int `json:"completion_tokens" db:"completion_tokens"`
|
|
CacheCreationTokens int `json:"cache_creation_tokens" db:"cache_creation_tokens"`
|
|
CacheReadTokens int `json:"cache_read_tokens" db:"cache_read_tokens"`
|
|
CostInput *float64 `json:"cost_input,omitempty" db:"cost_input"`
|
|
CostOutput *float64 `json:"cost_output,omitempty" db:"cost_output"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
type UsageAggregate struct {
|
|
GroupKey string `json:"group_key"`
|
|
Label string `json:"label"`
|
|
Requests int `json:"requests"`
|
|
InputTokens int `json:"input_tokens"`
|
|
OutputTokens int `json:"output_tokens"`
|
|
TotalCost float64 `json:"total_cost"`
|
|
}
|
|
|
|
type UsageTotals struct {
|
|
Requests int `json:"requests"`
|
|
InputTokens int `json:"input_tokens"`
|
|
OutputTokens int `json:"output_tokens"`
|
|
TotalCost float64 `json:"total_cost"`
|
|
Period string `json:"period"`
|
|
}
|
|
|
|
// =========================================
|
|
// MODEL PRICING
|
|
// =========================================
|
|
|
|
type PricingEntry struct {
|
|
ID string `json:"id" db:"id"`
|
|
ProviderConfigID string `json:"provider_config_id" db:"provider_config_id"`
|
|
ModelID string `json:"model_id" db:"model_id"`
|
|
InputPerM *float64 `json:"input_per_m" db:"input_per_m"`
|
|
OutputPerM *float64 `json:"output_per_m" db:"output_per_m"`
|
|
CacheCreatePerM *float64 `json:"cache_create_per_m" db:"cache_create_per_m"`
|
|
CacheReadPerM *float64 `json:"cache_read_per_m" db:"cache_read_per_m"`
|
|
Currency string `json:"currency" db:"currency"`
|
|
Source string `json:"source" db:"source"` // "catalog" | "manual"
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
UpdatedBy *string `json:"updated_by,omitempty" db:"updated_by"`
|
|
}
|
|
|
|
// =========================================
|
|
// VIEW MODELS (computed, not stored)
|
|
// =========================================
|
|
|
|
// UserModel is the view model returned by the capability resolver.
|
|
// Combines catalog entries + Personas for the frontend.
|
|
type UserModel struct {
|
|
ID string `json:"id"`
|
|
DisplayName string `json:"display_name"`
|
|
ModelID string `json:"model_id"`
|
|
ModelType string `json:"model_type"` // "chat", "embedding", "image", etc.
|
|
Source string `json:"source"` // "catalog", "persona", "live"
|
|
|
|
ProviderConfigID string `json:"provider_config_id"`
|
|
ConfigID string `json:"config_id"` // Alias of ProviderConfigID for frontend compat
|
|
ProviderName string `json:"provider_name"`
|
|
ProviderType string `json:"provider_type"`
|
|
|
|
Capabilities ModelCapabilities `json:"capabilities"`
|
|
|
|
// Preset fields — always emitted so frontend can branch on is_preset.
|
|
IsPreset bool `json:"is_preset"`
|
|
PresetID string `json:"preset_id,omitempty"`
|
|
PresetScope string `json:"preset_scope,omitempty"`
|
|
PresetAvatar string `json:"preset_avatar,omitempty"`
|
|
PresetTeamName string `json:"preset_team_name,omitempty"`
|
|
|
|
PersonaID string `json:"persona_id,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Icon string `json:"icon,omitempty"`
|
|
Avatar string `json:"avatar,omitempty"`
|
|
SystemPrompt string `json:"system_prompt,omitempty"`
|
|
Temperature *float64 `json:"temperature,omitempty"`
|
|
MaxTokens *int `json:"max_tokens,omitempty"`
|
|
ToolGrants []string `json:"tool_grants,omitempty"`
|
|
|
|
Pricing *ModelPricing `json:"pricing,omitempty"`
|
|
|
|
Scope string `json:"scope"`
|
|
OwnerID *string `json:"owner_id,omitempty"`
|
|
TeamName string `json:"team_name,omitempty"`
|
|
|
|
Hidden bool `json:"hidden"`
|
|
SortOrder int `json:"sort_order"`
|
|
}
|
|
|
|
// =========================================
|
|
// JSON HELPERS
|
|
// =========================================
|
|
|
|
// JSONMap scans from/to JSONB columns.
|
|
type JSONMap map[string]interface{}
|
|
|
|
func (m *JSONMap) Scan(src interface{}) error {
|
|
if src == nil {
|
|
*m = nil
|
|
return nil
|
|
}
|
|
var source []byte
|
|
switch v := src.(type) {
|
|
case []byte:
|
|
source = v
|
|
case string:
|
|
source = []byte(v)
|
|
default:
|
|
return nil
|
|
}
|
|
result := make(JSONMap)
|
|
if err := json.Unmarshal(source, &result); err != nil {
|
|
return err
|
|
}
|
|
*m = result
|
|
return nil
|
|
}
|
|
|
|
// ── Extensions ──────────────────────────────
|
|
|
|
// Extension tier constants
|
|
const (
|
|
ExtTierBrowser = "browser"
|
|
ExtTierStarlark = "starlark"
|
|
ExtTierSidecar = "sidecar"
|
|
)
|
|
|
|
// Extension represents an installed extension in the registry.
|
|
type Extension struct {
|
|
ID string `json:"id" db:"id"`
|
|
ExtID string `json:"ext_id" db:"ext_id"` // manifest id
|
|
Name string `json:"name" db:"name"`
|
|
Version string `json:"version" db:"version"`
|
|
Tier string `json:"tier" db:"tier"`
|
|
Description string `json:"description" db:"description"`
|
|
Author string `json:"author" db:"author"`
|
|
Manifest json.RawMessage `json:"manifest" db:"manifest"`
|
|
IsSystem bool `json:"is_system" db:"is_system"`
|
|
IsEnabled bool `json:"is_enabled" db:"is_enabled"`
|
|
Scope string `json:"scope" db:"scope"`
|
|
TeamID *string `json:"team_id,omitempty" db:"team_id"`
|
|
InstalledBy *string `json:"installed_by,omitempty" db:"installed_by"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// ExtensionUserSettings stores per-user overrides for an extension.
|
|
type ExtensionUserSettings struct {
|
|
ExtensionID string `json:"extension_id" db:"extension_id"`
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
Settings json.RawMessage `json:"settings" db:"settings"`
|
|
IsEnabled bool `json:"is_enabled" db:"is_enabled"`
|
|
}
|
|
|
|
// UserExtension combines extension info with user-specific settings for API responses.
|
|
type UserExtension struct {
|
|
Extension
|
|
UserEnabled *bool `json:"user_enabled,omitempty"`
|
|
UserSettings *json.RawMessage `json:"user_settings,omitempty"`
|
|
}
|
|
|
|
func NullString(s *string) sql.NullString {
|
|
if s == nil {
|
|
return sql.NullString{}
|
|
}
|
|
return sql.NullString{String: *s, Valid: true}
|
|
}
|
|
|
|
func NullFloat(f *float64) sql.NullFloat64 {
|
|
if f == nil {
|
|
return sql.NullFloat64{}
|
|
}
|
|
return sql.NullFloat64{Float64: *f, Valid: true}
|
|
}
|
|
|
|
func NullInt(i *int) sql.NullInt64 {
|
|
if i == nil {
|
|
return sql.NullInt64{}
|
|
}
|
|
return sql.NullInt64{Int64: int64(*i), Valid: true}
|
|
}
|
|
|
|
func StringPtr(s string) *string { return &s }
|
|
func Float64Ptr(f float64) *float64 { return &f }
|
|
func IntPtr(i int) *int { return &i }
|
|
func BoolPtr(b bool) *bool { return &b }
|
|
|
|
// =========================================
|
|
// GROUPS (v0.16.0)
|
|
// =========================================
|
|
|
|
// Group scope constants (reuses ScopeGlobal, ScopeTeam from above)
|
|
|
|
// ResourceType constants for resource_grants
|
|
const (
|
|
ResourceTypePersona = "persona"
|
|
ResourceTypeKnowledgeBase = "knowledge_base"
|
|
)
|
|
|
|
// GrantScope constants for resource_grants
|
|
const (
|
|
GrantScopeTeamOnly = "team_only"
|
|
GrantScopeGlobal = "global"
|
|
GrantScopeGroups = "groups"
|
|
)
|
|
|
|
// Group is an access-control list. Decouples resource visibility from teams.
|
|
type Group struct {
|
|
BaseModel
|
|
Name string `json:"name" db:"name"`
|
|
Description string `json:"description" db:"description"`
|
|
Scope string `json:"scope" db:"scope"` // global, team
|
|
TeamID *string `json:"team_id,omitempty" db:"team_id"`
|
|
CreatedBy string `json:"created_by" db:"created_by"`
|
|
MemberCount int `json:"member_count,omitempty"` // computed, not a DB column
|
|
}
|
|
|
|
// GroupMember links a user to a group.
|
|
type GroupMember struct {
|
|
ID string `json:"id" db:"id"`
|
|
GroupID string `json:"group_id" db:"group_id"`
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
AddedBy string `json:"added_by" db:"added_by"`
|
|
AddedAt time.Time `json:"added_at" db:"added_at"`
|
|
// Joined fields from users table (for list responses)
|
|
Username string `json:"username,omitempty"`
|
|
Email string `json:"email,omitempty"`
|
|
DisplayName string `json:"display_name,omitempty"`
|
|
}
|
|
|
|
// ResourceGrant controls who can USE a resource (personas, KBs) via groups.
|
|
// One row per resource. Not to be confused with persona_grants (what a Persona can DO).
|
|
type ResourceGrant struct {
|
|
BaseModel
|
|
ResourceType string `json:"resource_type" db:"resource_type"`
|
|
ResourceID string `json:"resource_id" db:"resource_id"`
|
|
GrantScope string `json:"grant_scope" db:"grant_scope"`
|
|
GrantedGroups []string `json:"granted_groups" db:"granted_groups"` // UUID array
|
|
CreatedBy string `json:"created_by" db:"created_by"`
|
|
}
|
|
|
|
// ── Knowledge Bases ────────────────────────────
|
|
|
|
// KnowledgeBase is a named collection of documents with vector embeddings.
|
|
type KnowledgeBase struct {
|
|
ID string `json:"id" db:"id"`
|
|
Name string `json:"name" db:"name"`
|
|
Description string `json:"description" db:"description"`
|
|
Scope string `json:"scope" db:"scope"` // global, team, personal
|
|
OwnerID *string `json:"owner_id,omitempty" db:"owner_id"`
|
|
TeamID *string `json:"team_id,omitempty" db:"team_id"`
|
|
EmbeddingConfig JSONMap `json:"embedding_config" db:"embedding_config"`
|
|
DocumentCount int `json:"document_count" db:"document_count"`
|
|
ChunkCount int `json:"chunk_count" db:"chunk_count"`
|
|
TotalBytes int64 `json:"total_bytes" db:"total_bytes"`
|
|
Discoverable bool `json:"discoverable" db:"discoverable"`
|
|
Status string `json:"status" db:"status"` // active, processing, error
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// PersonaKB binds a knowledge base to a persona.
|
|
type PersonaKB struct {
|
|
PersonaID string `json:"persona_id" db:"persona_id"`
|
|
KBID string `json:"kb_id" db:"kb_id"`
|
|
AutoSearch bool `json:"auto_search" db:"auto_search"`
|
|
AddedAt time.Time `json:"added_at" db:"added_at"`
|
|
|
|
// Joined fields (not in persona_knowledge_bases table)
|
|
KBName string `json:"kb_name,omitempty" db:"kb_name"`
|
|
DocumentCount int `json:"document_count,omitempty" db:"document_count"`
|
|
ChunkCount int `json:"chunk_count,omitempty" db:"chunk_count"`
|
|
}
|
|
|
|
// KBDocument is a single uploaded file within a knowledge base.
|
|
type KBDocument struct {
|
|
ID string `json:"id" db:"id"`
|
|
KBID string `json:"kb_id" db:"kb_id"`
|
|
Filename string `json:"filename" db:"filename"`
|
|
ContentType string `json:"content_type" db:"content_type"`
|
|
SizeBytes int64 `json:"size_bytes" db:"size_bytes"`
|
|
StorageKey string `json:"storage_key" db:"storage_key"`
|
|
ExtractedText *string `json:"-" db:"extracted_text"`
|
|
ChunkCount int `json:"chunk_count" db:"chunk_count"`
|
|
Status string `json:"status" db:"status"` // pending, chunking, embedding, ready, error
|
|
Error *string `json:"error,omitempty" db:"error"`
|
|
UploadedBy string `json:"uploaded_by" db:"uploaded_by"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// KBChunk is a text segment from a document with its embedding vector.
|
|
type KBChunk struct {
|
|
ID string `json:"id" db:"id"`
|
|
KBID string `json:"kb_id" db:"kb_id"`
|
|
DocumentID string `json:"document_id" db:"document_id"`
|
|
ChunkIndex int `json:"chunk_index" db:"chunk_index"`
|
|
Content string `json:"content" db:"content"`
|
|
TokenCount int `json:"token_count" db:"token_count"`
|
|
Embedding []float64 `json:"-" db:"embedding"` // never serialized to API
|
|
Metadata JSONMap `json:"metadata" db:"metadata"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// KBSearchResult is a single result from similarity search.
|
|
type KBSearchResult struct {
|
|
Content string `json:"content"`
|
|
Filename string `json:"source"`
|
|
KBName string `json:"kb"`
|
|
Similarity float64 `json:"similarity"`
|
|
Metadata JSONMap `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// ChannelKB represents a knowledge base linked to a channel.
|
|
type ChannelKB struct {
|
|
KBID string `json:"kb_id" db:"kb_id"`
|
|
KBName string `json:"kb_name" db:"name"`
|
|
Enabled bool `json:"enabled" db:"enabled"`
|
|
DocumentCount int `json:"document_count" db:"document_count"`
|
|
}
|