feat(sdk): enumerate provider models of every modality in the model catalog - #4676
Conversation
…atalog The SDK model catalog (/api/sdk/v1/models) built its list from getAllModels, which only enumerates language models from configured providers. Image, TTS, music, ASR, video, and embedding models appeared only when they happened to be in RECOMMENDED_MODELS — an SDK client asking for image_model compatibility got exactly one entry (GPT Image 2) while the web editor's picker, which goes through availableForKind, showed the full provider lists. Add collectProviderCatalogModels: one pass per configured provider calling each getAvailable*Models once, no task filtering, so text_to_image and image_to_image capable models are both included. Per-list failures degrade to an empty list instead of dropping the provider. The catalog service merges these into the existing gathering behind a 60s per-user TTL cache. Only the remote provider enumeration is cached — local download state (HF cache scan, download manager) stays fresh on every call, so a finished download still flips to ready_local immediately. Worker-scoped catalogs are unchanged and never enumerate providers. getAllModels and availableForKind are untouched; existing consumers keep their exact behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR expands the SDK model catalog endpoint (/api/sdk/v1/models) to include provider-enumerated models for non-LLM modalities (image, TTS, music, ASR, video, embeddings) so SDK consumers see the same breadth of models as the editor’s provider pickers, while keeping provider enumeration behind a short per-user TTL cache.
Changes:
- Add
collectProviderCatalogModels()to enumerate all non-language-model modalities per configured provider (one call pergetAvailable*Modelsper provider, no task filtering). - Cache provider enumeration results for 60s per user in
getSdkV1ModelCatalog, with an optional injection hook for tests/callers. - Add Vitest coverage for inclusion, dedupe behavior, caching, and worker-scope behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/websocket/src/trpc/routers/models.ts | Adds collectProviderCatalogModels() to enumerate provider models across modalities for the SDK catalog. |
| packages/websocket/src/sdk/sdk-model-catalog-service.ts | Merges provider-enumerated models into the SDK catalog with a per-user TTL cache and test hook. |
| packages/websocket/tests/sdk-model-catalog-provider-models.test.ts | Adds tests validating provider-enumerated entries, dedupe, caching, injection, and worker-scope behavior. |
| [availableModels, providerCatalogModels, providerIds] = await Promise.all([ | ||
| getAllModels(args.userId), | ||
| (args.getProviderCatalogModels ?? getCachedProviderCatalogModels)( | ||
| args.userId | ||
| ), |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
packages/websocket/src/sdk/sdk-model-catalog-service.ts:182
providerCatalogCacheis a process-globalMapkeyed by userId but entries are never pruned. In a long-lived server handling many users, this can grow unbounded even though values are treated as TTL’d, since expired entries remain resident until overwritten for the same user.
providerCatalogCache.delete(key);
}
}
}
packages/websocket/src/sdk/sdk-model-catalog-service.ts:227
- This code path ends up re-running provider configuration checks multiple times per request:
getAllModels()callsgetAvailableProviderIds()internally (models.ts:784),collectProviderCatalogModels()calls it again (models.ts:942), andgetSdkV1ModelCatalog()also calls it here to buildconfiguredProviderIds. On instances with many registered providers/secret backends, this adds avoidable latency and load.
Consider fetching providerIds once in getSdkV1ModelCatalog and threading it into provider enumeration (e.g., add an optional providerIds parameter to collectProviderCatalogModels / cache helper), so at least the catalog-specific enumeration doesn’t re-check configuration.
let providerIds: readonly string[];
if (args.query.scope === "worker") {
if (!args.getWorkerModels) {
throw new SdkModelCatalogServiceError(
"Worker model catalogs are not available through this server."
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
packages/websocket/src/sdk/sdk-model-catalog-service.ts:240
getSdkV1ModelCatalognow callsgetAvailableProviderIdsdirectly, butcollectProviderCatalogModels(invoked viagetCachedProviderCatalogModels) also callsgetAvailableProviderIdsinternally, andgetAllModelsdoes the same. That means each catalog request can re-run provider configuration checks multiple times and can (rarely) observe different provider-id snapshots inside one response.
Consider fetching providerIds once in getSdkV1ModelCatalog and threading that list into the provider-catalog enumeration (and ideally into getAllModels) so the request uses a consistent set and avoids repeated secret/config checks.
[availableModels, providerCatalogModels, providerIds] = await Promise.all([
getAllModels(args.userId),
(args.getProviderCatalogModels ?? getCachedProviderCatalogModels)(
args.userId
),
getAvailableProviderIds(args.userId)
]);
The SDK model catalog (/api/sdk/v1/models) built its list from getAllModels, which only enumerates language models from configured providers. Image, TTS, music, ASR, video, and embedding models appeared only when they happened to be in RECOMMENDED_MODELS — an SDK client asking for image_model compatibility got exactly one entry (GPT Image 2) while the web editor's picker, which goes through availableForKind, showed the full provider lists.
Add collectProviderCatalogModels: one pass per configured provider calling each getAvailable*Models once, no task filtering, so text_to_image and image_to_image capable models are both included. Per-list failures degrade to an empty list instead of dropping the provider.
The catalog service merges these into the existing gathering behind a 60s per-user TTL cache. Only the remote provider enumeration is cached — local download state (HF cache scan, download manager) stays fresh on every call, so a finished download still flips to ready_local immediately. Worker-scoped catalogs are unchanged and never enumerate providers.
getAllModels and availableForKind are untouched; existing consumers keep their exact behavior.