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

@@ -0,0 +1,58 @@
import { Controller, Get, Patch, Body, Param, Request, UseGuards } from "@nestjs/common";
import { WorkspaceSettingsService } from "./workspace-settings.service";
import { UpdateWorkspaceSettingsDto } from "./dto";
import { AuthGuard } from "../auth/guards/auth.guard";
import type { AuthenticatedRequest } from "../common/types/user.types";
/**
* Controller for workspace LLM settings endpoints
* All endpoints require authentication
*/
@Controller("workspaces/:workspaceId/settings/llm")
@UseGuards(AuthGuard)
export class WorkspaceSettingsController {
constructor(private readonly workspaceSettingsService: WorkspaceSettingsService) {}
/**
* GET /api/workspaces/:workspaceId/settings/llm
* Get workspace LLM settings
*/
@Get()
async getSettings(@Param("workspaceId") workspaceId: string) {
return this.workspaceSettingsService.getSettings(workspaceId);
}
/**
* PATCH /api/workspaces/:workspaceId/settings/llm
* Update workspace LLM settings
*/
@Patch()
async updateSettings(
@Param("workspaceId") workspaceId: string,
@Body() dto: UpdateWorkspaceSettingsDto
) {
return this.workspaceSettingsService.updateSettings(workspaceId, dto);
}
/**
* GET /api/workspaces/:workspaceId/settings/llm/effective-provider
* Get effective LLM provider for workspace
*/
@Get("effective-provider")
async getEffectiveProvider(
@Param("workspaceId") workspaceId: string,
@Request() req: AuthenticatedRequest
) {
const userId = req.user?.id;
return this.workspaceSettingsService.getEffectiveLlmProvider(workspaceId, userId);
}
/**
* GET /api/workspaces/:workspaceId/settings/llm/effective-personality
* Get effective personality for workspace
*/
@Get("effective-personality")
async getEffectivePersonality(@Param("workspaceId") workspaceId: string) {
return this.workspaceSettingsService.getEffectivePersonality(workspaceId);
}
}