Files
moeka-project/docs/superpowers/plans/2026-07-01-official-provider-catalog.md
T

169 lines
8.7 KiB
Markdown

# Official Provider Catalog Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build an official catalog layer that controls AIRI's official LLM aliases, TTS models, TTS voices, and ASR aliases for both public listing and gateway request authorization.
**Architecture:** Add database-backed catalog tables and a focused `official-catalog` domain service. Public routes and gateway operations read the same service so UI visibility and handwritten request authorization cannot drift. Admin UI writes catalog state; `LLM_ROUTER_CONFIG` remains the source for real provider/key routing.
**Tech Stack:** TypeScript, Hono, Drizzle ORM, Valibot, Vue 3 `<script setup>`, Pinia-light admin API module, Vitest, pnpm workspace filters.
## Global Constraints
- Do not create commits during implementation.
- Preserve existing dirty user changes.
- Use `@moeru/std` / existing error helpers for structured errors.
- Public listing and request execution must both enforce catalog `enabled` state.
- Existing router config and encrypted keys remain separate from catalog.
- Existing configured LLM/TTS/ASR models synced from runtime config default to enabled.
- TTS voices synced from providers default to disabled.
- Voice preview generation is out of scope for v1; store only provider/manual preview URLs.
---
### Task 1: Catalog Schema and Service
**Files:**
- Create: `apps/server/src/schemas/official-catalog.ts`
- Modify: `apps/server/src/schemas/index.ts`
- Create: `apps/server/src/services/domain/official-catalog/index.ts`
- Test: `apps/server/src/services/domain/official-catalog/index.test.ts`
- Create: `apps/server/drizzle/0016_official_provider_catalog.sql`
**Interfaces:**
- Produces: `createOfficialCatalogService(db, deps)` with methods:
- `syncAliasesFromRouterConfig(input: { surface: 'llm' | 'asr', modelIds: string[] }): Promise<OfficialProviderAlias[]>`
- `syncTtsModelsFromRouterConfig(input: { models: Record<string, { provider: string }> }): Promise<OfficialTtsModel[]>`
- `syncTtsVoices(input: { routerModelId: string, voices: OfficialTtsVoiceSyncInput[] }): Promise<OfficialTtsVoice[]>`
- `listEnabledTtsModels(): Promise<OfficialTtsModel[]>`
- `listEnabledTtsVoices(routerModelId: string): Promise<OfficialTtsVoice[]>`
- `resolveEnabledAlias(surface, aliasId): Promise<OfficialProviderAliasWithRoutes>`
- `assertTtsModelEnabled(routerModelId): Promise<OfficialTtsModel>`
- `assertTtsVoiceEnabled(routerModelId, providerVoiceId): Promise<OfficialTtsVoice>`
- [ ] Write service tests first for sync defaults, repeated sync preservation, enabled listing, alias lookup, and TTS voice gate errors.
- [ ] Add schema tables for aliases, alias routes, TTS models, and TTS voices.
- [ ] Implement service with explicit methods instead of leaking Drizzle query details into routes.
- [ ] Add manual migration SQL matching the schema.
- [ ] Run `pnpm exec vitest run apps/server/src/services/domain/official-catalog/index.test.ts`.
### Task 2: Public TTS Listing and Request Gate
**Files:**
- Modify: `apps/server/src/routes/openai/v1/types.ts`
- Modify: `apps/server/src/routes/openai/v1/operations/speech-catalog/index.ts`
- Modify: `apps/server/src/routes/openai/v1/operations/speech-generation/index.ts`
- Modify: `apps/server/src/app.ts`
- Test: `apps/server/src/routes/openai/v1/route.test.ts`
**Interfaces:**
- Consumes: `OfficialCatalogService`
- Produces: public TTS model and voice lists filtered by enabled catalog rows.
- [ ] Add failing route tests: disabled model hidden, disabled voice hidden, disabled model rejected in speech generation, disabled voice rejected in speech generation.
- [ ] Inject `officialCatalogService` into `V1RouteDeps`.
- [ ] In `listSpeechModels`, sync runtime TTS models then return enabled catalog rows in display order.
- [ ] In `listVoices`, fetch provider voices for sync, sync them as disabled-by-default, then return only enabled catalog voices while preserving `recommended`.
- [ ] In `speechGeneration`, validate model and voice against catalog after resolving `auto`.
- [ ] Run focused server route tests.
### Task 3: LLM Alias Gate
**Files:**
- Modify: `apps/server/src/routes/openai/v1/operations/chat-completions/index.ts`
- Test: `apps/server/src/routes/openai/v1/route.test.ts`
**Interfaces:**
- Consumes: `officialCatalogService.resolveEnabledAlias('llm', aliasId)`
- Produces: chat requests use client-visible alias and route to enabled alias primary target.
- [ ] Add failing tests: `auto` alias disabled rejects; missing alias rejects; enabled `auto` resolves to a real router model.
- [ ] Keep v1 client-visible model as alias (`auto` by default).
- [ ] Resolve alias before billing telemetry uses the real router model, while product analytics may retain alias in metadata.
- [ ] Run focused route tests.
### Task 4: Admin API
**Files:**
- Create: `apps/server/src/routes/admin/official-catalog/index.ts`
- Create: `apps/server/src/routes/admin/official-catalog/route.test.ts`
- Modify: `apps/server/src/app.ts`
**Interfaces:**
- Consumes: `OfficialCatalogService`, `LlmRouterService`, `ConfigKVService`
- Produces:
- `GET /api/admin/official-catalog/aliases`
- `POST /api/admin/official-catalog/aliases/sync`
- `PATCH /api/admin/official-catalog/aliases/:id`
- `PATCH /api/admin/official-catalog/aliases/:id/routes`
- `GET /api/admin/official-catalog/tts/models`
- `POST /api/admin/official-catalog/tts/models/sync`
- `PATCH /api/admin/official-catalog/tts/models/:id`
- `GET /api/admin/official-catalog/tts/models/:id/voices`
- `POST /api/admin/official-catalog/tts/models/:id/voices/sync`
- `PATCH /api/admin/official-catalog/tts/voices/:id`
- [ ] Add auth/admin guard tests following existing voice-pack route tests.
- [ ] Implement Valibot schemas for patch bodies.
- [ ] Implement sync endpoints from runtime config and provider voice catalog.
- [ ] Mount route under `/api/admin/official-catalog`.
- [ ] Run admin route tests.
### Task 5: Admin API Client and Forms
**Files:**
- Modify: `apps/ui-admin/src/modules/api.ts`
- Create: `apps/ui-admin/src/pages/ProvidersPage.vue`
- Create: `apps/ui-admin/src/pages/TtsCatalogPage.vue`
- Modify: `apps/ui-admin/src/main.ts`
- Modify: `apps/ui-admin/src/App.vue`
- Test: `apps/ui-admin/src/pages/ProvidersPage.test.ts`
- Test: `apps/ui-admin/src/pages/TtsCatalogPage.test.ts`
**Interfaces:**
- Consumes: admin official catalog endpoints.
- Produces: operator can sync aliases/models/voices, toggle enabled state, edit names/order/preview URLs.
- [ ] Add TypeScript interfaces and admin API methods.
- [ ] Add Providers page for v1 `auto` alias and route pool visibility.
- [ ] Add TTS page for model list and selected model voices.
- [ ] Add sidebar nav entries: Providers and TTS; keep Voice Packs separate.
- [ ] Add focused Vue tests around sync and toggle calls.
- [ ] Run `pnpm exec vitest run apps/ui-admin/src/pages/ProvidersPage.test.ts apps/ui-admin/src/pages/TtsCatalogPage.test.ts`.
### Task 6: Voice Pack Candidate Filtering
**Files:**
- Modify: `apps/ui-admin/src/pages/VoicePackFormPage.vue`
- Test: `apps/ui-admin/src/pages/VoicePackFormPage.test.ts`
**Interfaces:**
- Consumes: enabled official TTS models and voices from public/admin catalog.
- Produces: Voice Pack admin cannot create new packs from disabled catalog rows.
- [ ] Add tests showing disabled voices are not offered as candidates.
- [ ] Update catalog loading to use filtered public endpoints or admin enabled list.
- [ ] Keep editing existing packs resilient if a historical voice was later disabled.
- [ ] Run focused Voice Pack tests.
### Task 7: Verification Sweep
**Files:**
- All touched files.
- [ ] Run server focused tests:
`pnpm exec vitest run apps/server/src/services/domain/official-catalog/index.test.ts apps/server/src/routes/admin/official-catalog/route.test.ts apps/server/src/routes/openai/v1/route.test.ts`
- [ ] Run admin focused tests:
`pnpm exec vitest run apps/ui-admin/src/pages/ProvidersPage.test.ts apps/ui-admin/src/pages/TtsCatalogPage.test.ts apps/ui-admin/src/pages/VoicePackFormPage.test.ts`
- [ ] Run typechecks:
`pnpm -F @proj-airi/server typecheck`
`pnpm -F @proj-airi/ui-admin typecheck`
- [ ] Run targeted eslint on changed files.
- [ ] Report any unrelated pre-existing failures separately.
## Self-Review
- Spec coverage: LLM alias, TTS catalog, ASR-compatible alias structure, admin pages, strict request gate, sync defaults, and v2 preview generation deferral are represented.
- Placeholder scan: no TBD/TODO implementation placeholders are required by this plan; task details use exact paths and behavior.
- Type consistency: service names and route dependencies are consistent across tasks.