8.7 KiB
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
enabledstate. - 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
officialCatalogServiceintoV1RouteDeps. -
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 preservingrecommended. -
In
speechGeneration, validate model and voice against catalog after resolvingauto. -
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:
autoalias disabled rejects; missing alias rejects; enabledautoresolves to a real router model. -
Keep v1 client-visible model as alias (
autoby 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/aliasesPOST /api/admin/official-catalog/aliases/syncPATCH /api/admin/official-catalog/aliases/:idPATCH /api/admin/official-catalog/aliases/:id/routesGET /api/admin/official-catalog/tts/modelsPOST /api/admin/official-catalog/tts/models/syncPATCH /api/admin/official-catalog/tts/models/:idGET /api/admin/official-catalog/tts/models/:id/voicesPOST /api/admin/official-catalog/tts/models/:id/voices/syncPATCH /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
autoalias 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 typecheckpnpm -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.