#17348
fix(api): reconcile custom-provider catalog identities for client enrichment
Environment
- OmniRoute 3.8.52, image digest
sha256:90f903d7e9245973dc23f7f0a3afc0c63502c9bd25961097843361689df6474b - Observed through the OpenCode v2 client plugin
@omniroute/opencode-plugin-v2@0.1.0, but the defect is gateway-side (endpoint inconsistency), not plugin-side.
Summary
For custom openai-compatible-chat-* connections the enrichment overlay (/api/pricing/models)
cannot be joined to the served catalog (/v1/models), for two independent reasons:
- Key mismatch. Enrichment entries are keyed by the connection's internal provider string
(e.g.
openai-compatible-chat-8d3c46c4-20bc-40ad-a837-c475f578dc8a), while the served models carry routing prefixes (ih/...,owned_by: "ih"). Built-in providers work only because their key equalsowned_by(e.g.kilocode). - Id-shape mismatch / stale sync. Per connection the model-id shapes disagree between the two
endpoints:
- connection
...8d3c46c4...servesih/**on/v1/models(606 models,owned_by: ih) but its pricing entry listsag/**ids (384 models, 206 prefixed, 0ih/) - this looks like a stale sync from a previous prefix. - connection
...e7cc3ed0...serves prefixedsi/**on/v1/models(520 models) but its pricing entry lists 449 bare ids. - The pricing catalog contains no
siand noihkey at all.
- connection
Consequence
lookupEnrichment()misses for every model of these connections: noproviderDisplayName, noproviderAlias, nopricing, nofreeType.- Client catalogs therefore show raw ids (
si/kimi-k3, or barekimi-k3for unprefixed serves) with no provider attribution and cost 0/0 - even thoughowned_byis set correctly on all 4,497 served models. - The free-tier summary reports large unmatched counts; plugin refresh log:
catalog refresh: served=4659 default=59 presets=59 published=61 unmatched-free=471. /api/providersexposes neitherprefixnor a prefix->connection mapping, so clients cannot repair the join locally from public endpoints.
Evidence
/v1/modelsowned_byhistogram: kilocode 1446, tokenrouter 750, ih 606, si 520, zenmux 396, aihorde 219, .../api/pricing/modelskeys includeopenai-compatible-chat-8d3c46c4-...(first ids:ag/claude-opus-4-6-thinking,ag/claude-opus-5-5, ...) andopenai-compatible-chat-e7cc3ed0-...(first ids:openai-gpt-oss-120b,minimax-m2.1, ...); nosi, noihkey.- Working counter-example: key
kilocodewithkilocode/.../kc/...ids joins fine and yields labels + pricing in clients.
Expected
- Key (or alias) enrichment entries by routing prefix /
owned_byin addition to the connection id, or expose the prefix <-> connection mapping on/api/providers. - Keep id shapes consistent between
/v1/modelsand/api/pricing/modelsper connection; re-sync the pricing catalog when a connection prefix changes (theag/list of...8d3c46c4...appears to be a leftover of an earlier prefix).
Workaround in place (client-side, local only)
The OpenCode plugin was locally patched to fall back to a provider label derived from owned_by
when the enrichment lookup misses. Pricing and free markers remain unavailable for these
connections until the gateway side is fixed.
Thanks for the concrete examples. The two endpoints currently expose different identity contracts:
/v1/modelsuses the public routing prefix, while/api/pricing/modelsretains internal node IDs for the management UI. The current development branch also exposesdisplayPrefixand, where applicable,pricingKeyon those catalog entries, so we need to preserve that compatibility rather than simply rename the existing keys.The stale
ag/...entries under a node now routed asihneed a separate prefix-change reproduction. #17334 addresses repeated copies of the current prefix, and #17259 adds prices directly to/v1/models; neither alone establishes full coverage of this report./api/pricing/modelsis a management catalog and does not itself return a full price/free-tier overlay. The next step is to test the cross-endpoint join and prefix-change behavior with custom-provider fixtures, including upstream IDs that legitimately contain/. Unknown prices must remain unknown rather than being displayed as free. Keeping this open for that diagnosis; the exact migration failure is not yet reproduced.