docs/superpowers/plans/2026-07-01-official-provider-catalog.md
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.
@moeru/std / existing error helpers for structured errors.enabled state.Files:
apps/server/src/schemas/official-catalog.tsapps/server/src/schemas/index.tsapps/server/src/services/domain/official-catalog/index.tsapps/server/src/services/domain/official-catalog/index.test.tsapps/server/drizzle/0016_official_provider_catalog.sqlInterfaces:
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.
Files:
apps/server/src/routes/openai/v1/types.tsapps/server/src/routes/openai/v1/operations/speech-catalog/index.tsapps/server/src/routes/openai/v1/operations/speech-generation/index.tsapps/server/src/app.tsapps/server/src/routes/openai/v1/route.test.tsInterfaces:
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.
Files:
apps/server/src/routes/openai/v1/operations/chat-completions/index.tsapps/server/src/routes/openai/v1/route.test.tsInterfaces:
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.
Files:
apps/server/src/routes/admin/official-catalog/index.tsapps/server/src/routes/admin/official-catalog/route.test.tsapps/server/src/app.tsInterfaces:
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/:idAdd 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.
Files:
apps/ui-admin/src/modules/api.tsapps/ui-admin/src/pages/ProvidersPage.vueapps/ui-admin/src/pages/TtsCatalogPage.vueapps/ui-admin/src/main.tsapps/ui-admin/src/App.vueapps/ui-admin/src/pages/ProvidersPage.test.tsapps/ui-admin/src/pages/TtsCatalogPage.test.tsInterfaces:
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.
Files:
apps/ui-admin/src/pages/VoicePackFormPage.vueapps/ui-admin/src/pages/VoicePackFormPage.test.tsInterfaces:
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.
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.