This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/models/models.go
Jeffrey Smith c3e9dcf731 step 3: gut store interfaces and models
- interfaces.go: 1449 → 437 lines (13 kernel interfaces from 33)
- models.go: 1137 → 397 lines (17 kernel types from 64)
- Deleted 28 store impl files per dialect (PG + SQLite)
- Deleted 3 model files (git, memory, workspace)
- Deleted 3 store iface files (project, memory, tree_types)
- Rewrote stores.go constructors for both PG and SQLite
- Stores struct: 20 fields (from 40+, includes separate iface stores)

66 files changed, ~19K lines removed.
Build will break — expected, fixed in steps 4-5.
2026-03-25 19:58:14 -04:00

398 lines
14 KiB
Go

package models
import (
"database/sql"
"encoding/json"
"strings"
"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"
)
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"`
AuthSource string `json:"auth_source" db:"auth_source"`
ExternalID *string `json:"external_id,omitempty" db:"external_id"`
Handle string `json:"handle" db:"handle"`
}
// =========================================
// 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
MyRole string `json:"my_role,omitempty"` // computed, user context
}
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)
// HasKey returns true if an encrypted API key is stored.
func (p *ProviderConfig) HasKey() bool {
return len(p.APIKeyEnc) > 0
}
// MODEL CATALOG (replaces model_configs)
func (c ModelCapabilities) HasProviderData() bool {
return c.ToolCalling || c.Vision || c.Thinking || c.Reasoning ||
c.CodeOptimized || c.WebSearch || c.MaxContext > 0 || c.MaxOutputTokens > 0
}
// PERSONAS (replaces ModelPreset)
// =========================================
// 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
// HiddenEntry identifies a model+provider pair for bulk visibility operations.
// CompositeModelKey builds the composite key used for per-provider model preferences.
func CompositeModelKey(providerConfigID, modelID string) string {
return providerConfigID + ":" + modelID
}
// CHANNELS
// SESSION PARTICIPANTS (v0.24.3)
// SessionParticipant is an ephemeral identity for anonymous workflow
// channel visitors. Scoped to a single channel, no users row required.
// MESSAGES
// CHANNEL PARTICIPANTS, MODELS, CURSORS
// PERSONA GROUPS (v0.23.0)
// HandleFromName generates a URL-safe @mention handle from a display name.
// "Veronica Sharpe" → "veronica-sharpe"
func HandleFromName(name string) string {
h := strings.ToLower(strings.TrimSpace(name))
h = strings.Map(func(r rune) rune {
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
return r
}
if r == ' ' || r == '_' {
return '-'
}
return -1
}, h)
// Collapse multiple hyphens
for strings.Contains(h, "--") {
h = strings.ReplaceAll(h, "--", "-")
}
h = strings.Trim(h, "-")
if len(h) > 50 {
h = h[:50]
}
return h
}
// ORGANIZATION
// ProjectPatch holds optional fields for updating a project.
// ProjectChannel represents a channel's membership in a project.
// ProjectKB represents a KB's association with a project.
// ProjectNote represents a note's association with a project.
// NOTES
// NoteLink represents a directed link extracted from [[wikilink]] syntax.
// ExportNoteLink is a note_link with source_note_id included (for export).
// NoteLinkResult represents a backlink — a note that links to a given note.
// NoteGraphNode is a lightweight note representation for graph display.
// NoteGraphEdge is a resolved link between two notes.
// NoteGraphDangling is an unresolved [[link]] reference.
// NoteGraph is the full graph topology for a user's notes.
// ATTACHMENTS
// FILES
// FileOrigin constants
// =========================================
// AUDIT LOG
// =========================================
type AuditEntry struct {
ID string `json:"id" db:"id"`
ActorID *string `json:"actor_id,omitempty" db:"actor_id"`
ActorName *string `json:"actor_name,omitempty" db:"-"` // resolved via JOIN, not a column
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
// MODEL PRICING
// VIEW MODELS (computed, not stored)
// UserModel is the view model returned by the capability resolver.
// Combines catalog entries + Personas for the frontend.
// =========================================
// 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
}
// Extension tier constants
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,omitempty" db:"created_by"`
Source string `json:"source" db:"source"` // manual (default), oidc, system
Permissions []string `json:"permissions" db:"permissions"`
TokenBudgetDaily *int64 `json:"token_budget_daily,omitempty" db:"token_budget_daily"`
TokenBudgetMonthly *int64 `json:"token_budget_monthly,omitempty" db:"token_budget_monthly"`
AllowedModels []string `json:"allowed_models,omitempty" db:"allowed_models"` // nil = unrestricted
MemberCount int `json:"member_count,omitempty"` // computed, not a DB column
}
// GroupPatch holds optional fields for updating a group.
type GroupPatch struct {
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Permissions *[]string `json:"permissions,omitempty"`
TokenBudgetDaily *int64 `json:"token_budget_daily,omitempty"`
TokenBudgetMonthly *int64 `json:"token_budget_monthly,omitempty"`
AllowedModels *[]string `json:"allowed_models,omitempty"` // &[]string{} = restrict to none; nil = no change
ClearBudgetDaily bool `json:"clear_budget_daily,omitempty"`
ClearBudgetMonthly bool `json:"clear_budget_monthly,omitempty"`
ClearAllowedModels bool `json:"clear_allowed_models,omitempty"`
}
// 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"`
}
// KnowledgeBase is a named collection of documents with vector embeddings.
// PersonaKB binds a knowledge base to a persona.
// KBDocument is a single uploaded file within a knowledge base.
// KBChunk is a text segment from a document with its embedding vector.
// KBSearchResult is a single result from similarity search.
// ChannelKB represents a knowledge base linked to a channel.
// PROVIDER HEALTH (v0.22.0)
// ProviderStatus represents the derived health state.
// AvgLatencyMs returns the average latency, or 0 if no requests.
func (w *ProviderHealthWindow) AvgLatencyMs() int {
if w.RequestCount == 0 {
return 0
}
return int(w.TotalLatencyMs / int64(w.RequestCount))
}
// ErrorRate returns the error fraction, or 0 if no requests.
func (w *ProviderHealthWindow) ErrorRate() float64 {
if w.RequestCount == 0 {
return 0
}
return float64(w.ErrorCount) / float64(w.RequestCount)
}
// ProviderHealthSummary is the API response for a provider's current health.
// ToolHealthWindow tracks health of built-in tools (web_search, url_fetch, etc.)
// in hourly buckets, analogous to ProviderHealthWindow.
// ToolHealthSummary is the API response for a tool's health.
// CAPABILITY OVERRIDES (v0.22.0)
// CapabilityOverride is an admin correction for a model's capabilities.
// ROUTING POLICIES (v0.22.2)
// RoutingPolicy is one routing rule that controls how requests are
// dispatched to provider configs. Stored in the routing_policies table.