feat(#133): add workspace-scoped LLM configuration

Implement per-workspace LLM provider and personality configuration
with proper hierarchy (workspace > user > system fallback).

Schema:
- Add WorkspaceLlmSettings model with provider/personality FKs
- One-to-one relation with Workspace
- JSON settings field for extensibility

Service:
- getSettings: Retrieves/creates workspace settings
- updateSettings: Updates with null value support
- getEffectiveLlmProvider: Hierarchy-based provider selection
- getEffectivePersonality: Hierarchy-based personality selection

Endpoints:
- GET /workspaces/:id/settings/llm - Get settings
- PATCH /workspaces/:id/settings/llm - Update settings
- GET /workspaces/:id/settings/llm/effective-provider
- GET /workspaces/:id/settings/llm/effective-personality

Configuration hierarchy:
1. Workspace-configured provider/personality
2. User-specific provider (for providers)
3. System default fallback

Tests: 34 passing with 100% coverage

Fixes #133

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 13:15:36 -06:00
parent b8805cee50
commit 0c78923138
9 changed files with 959 additions and 3 deletions

View File

@@ -214,6 +214,7 @@ model Workspace {
knowledgeTags KnowledgeTag[]
cronSchedules CronSchedule[]
personalities Personality[]
llmSettings WorkspaceLlmSettings?
@@index([ownerId])
@@map("workspaces")
@@ -942,7 +943,8 @@ model Personality {
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
llmProviderInstance LlmProviderInstance? @relation("PersonalityLlmProvider", fields: [llmProviderInstanceId], references: [id], onDelete: SetNull)
llmProviderInstance LlmProviderInstance? @relation("PersonalityLlmProvider", fields: [llmProviderInstanceId], references: [id], onDelete: SetNull)
workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspacePersonality")
@@unique([id, workspaceId])
@@unique([workspaceId, name])
@@ -969,8 +971,9 @@ model LlmProviderInstance {
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
user User? @relation("UserLlmProviders", fields: [userId], references: [id], onDelete: Cascade)
personalities Personality[] @relation("PersonalityLlmProvider")
user User? @relation("UserLlmProviders", fields: [userId], references: [id], onDelete: Cascade)
personalities Personality[] @relation("PersonalityLlmProvider")
workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspaceLlmProvider")
@@index([userId])
@@index([providerType])
@@ -978,3 +981,25 @@ model LlmProviderInstance {
@@index([isEnabled])
@@map("llm_provider_instances")
}
// ============================================
// WORKSPACE LLM SETTINGS
// ============================================
model WorkspaceLlmSettings {
id String @id @default(uuid()) @db.Uuid
workspaceId String @unique @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
defaultLlmProviderId String? @map("default_llm_provider_id") @db.Uuid
defaultLlmProvider LlmProviderInstance? @relation("WorkspaceLlmProvider", fields: [defaultLlmProviderId], references: [id], onDelete: SetNull)
defaultPersonalityId String? @map("default_personality_id") @db.Uuid
defaultPersonality Personality? @relation("WorkspacePersonality", fields: [defaultPersonalityId], references: [id], onDelete: SetNull)
settings Json? @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@index([workspaceId])
@@index([defaultLlmProviderId])
@@index([defaultPersonalityId])
@@map("workspace_llm_settings")
}