You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[llm-pi-ai] OpenRouter model picker ignores the key's guardrails: list models from GET /models/user
#12355
OpenRouter route configured with apiKeyEnv only (no models list)
What happens
My OpenRouter API key is governed by a guardrail whose model allowlist contains 10 models. The DSH model picker shows the bundled catalog instead (pi-ai/dist/providers/data/openrouter.json, 396 chat models), which is wrong in both directions:
it lists 389 models the key may not use; picking one can only fail at request time, since OpenRouter rejects allowlist violations with a 403;
it is missing 3 of the 10 models the key may use (openai/gpt-6.1-terra, moonshotai/kimi-k3.1-pro, deepseek/deepseek-v4.1-flash-1017), because the bundled catalog predates them.
Claude Code and pi, on the same key, show exactly the 10 allowed models.
Measured today with the same key:
Endpoint
Models
GET /api/v1/models (public)
454
GET /api/v1/models/user (with the key)
10
DSH picker (bundled catalog)
396, of which 7 are allowed
Why
Nothing between the settings document and the picker asks OpenRouter which models this key may use:
resolveRouteModels: a route that declares no models serves the installed catalog unchanged.
discoverModels ("Fetch available models"): for a provider pi-ai ships, it returns the installed catalog without any network call.
OpenRouter already exposes the right list
GET /api/v1/models/user lists models "filtered by user provider preferences, privacy settings, and guardrails". It takes the ordinary API key, returns the same Model schema as /models (context_length, top_provider, architecture.input_modalities, reasoning.supported_efforts, supported_parameters, endpoints), and on a regional hostname also applies in-region filtering.
pi-ai 1.3.0 already exports a helper for it, personalizeOpenRouterCatalog(catalog, { credential, baseUrl, signal }) in @earendil-works/pi-ai/providers/openrouter, but nothing in dsh-llm-pi-ai calls it. On its own it would not be enough: it only filters the static catalog, so with my key it would show 7 models, not 10.
Proposal
In discoverModels, when the provider is openrouter and a credential resolves, answer from /models/user instead of the installed catalog. Use the installed catalog entry where one exists, and build the rest from the response (top_provider.context_length / context_length, top_provider.max_completion_tokens, architecture.input_modalities, reasoning.supported_efforts). Fall back to the installed catalog on 401/403/404 or a network failure. "Fetch available models" then offers exactly the key's models for adoption, and cordis.patch.yml stays the only thing that decides what the route serves, which matches the design note in discovery.js.
Optionally, when an OpenRouter route declares no models, apply the same list when materializing the route catalog, falling back to the static list if the request fails.
Workaround today
Declare a models list on the OpenRouter route. A configured list replaces the installed catalog, and entries the catalog knows inherit its metadata by id. Entries it does not know must spell out their limits, and their reasoning levels too (see #4071):
#5691, #6307 and #4071 ask for a live catalog. This is the per-key side of the same gap: even a live /models list would still show models the key's guardrails forbid. /models/user addresses both.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Environment
@deepseek-ai/dsh-llm-pi-ai0.2.2-rc.1,@earendil-works/pi-ai1.3.0apiKeyEnvonly (nomodelslist)What happens
My OpenRouter API key is governed by a guardrail whose model allowlist contains 10 models. The DSH model picker shows the bundled catalog instead (
pi-ai/dist/providers/data/openrouter.json, 396 chat models), which is wrong in both directions:openai/gpt-6.1-terra,moonshotai/kimi-k3.1-pro,deepseek/deepseek-v4.1-flash-1017), because the bundled catalog predates them.Claude Code and pi, on the same key, show exactly the 10 allowed models.
Measured today with the same key:
GET /api/v1/models(public)GET /api/v1/models/user(with the key)Why
Nothing between the settings document and the picker asks OpenRouter which models this key may use:
resolveRouteModels: a route that declares nomodelsserves the installed catalog unchanged.discoverModels("Fetch available models"): for a provider pi-ai ships, it returns the installed catalog without any network call.OpenRouter already exposes the right list
GET /api/v1/models/userlists models "filtered by user provider preferences, privacy settings, and guardrails". It takes the ordinary API key, returns the sameModelschema as/models(context_length,top_provider,architecture.input_modalities,reasoning.supported_efforts,supported_parameters,endpoints), and on a regional hostname also applies in-region filtering.pi-ai 1.3.0 already exports a helper for it,
personalizeOpenRouterCatalog(catalog, { credential, baseUrl, signal })in@earendil-works/pi-ai/providers/openrouter, but nothing indsh-llm-pi-aicalls it. On its own it would not be enough: it only filters the static catalog, so with my key it would show 7 models, not 10.Proposal
discoverModels, when the provider isopenrouterand a credential resolves, answer from/models/userinstead of the installed catalog. Use the installed catalog entry where one exists, and build the rest from the response (top_provider.context_length/context_length,top_provider.max_completion_tokens,architecture.input_modalities,reasoning.supported_efforts). Fall back to the installed catalog on 401/403/404 or a network failure. "Fetch available models" then offers exactly the key's models for adoption, andcordis.patch.ymlstays the only thing that decides what the route serves, which matches the design note indiscovery.js.models, apply the same list when materializing the route catalog, falling back to the static list if the request fails.Workaround today
Declare a
modelslist on the OpenRouter route. A configured list replaces the installed catalog, and entries the catalog knows inherit its metadata by id. Entries it does not know must spell out their limits, and their reasoning levels too (see #4071):Related
#5691, #6307 and #4071 ask for a live catalog. This is the per-key side of the same gap: even a live
/modelslist would still show models the key's guardrails forbid./models/useraddresses both.All reactions