Profile launches could fail with HTTP 0 when a healthy local gateway
reset one loopback probe. Prefer the stable health identity, retry
transport failures, and retain the root endpoint fallback.
Runtime identity normalization previously ran before local OAuth provider
detection, so display-name matching could no longer select the managed
backend endpoint. Defer identity normalization until after detection and
cover the ordering with a regression test.
Provider compilation replaces display and capability aliases with stable
runtime identifiers, but plugin targets and fallback headers could retain
the precompiled names. Normalize both paths so routing and authentication
refer to the provider identity that the gateway actually registers.
Public OpenCode imports wrote strict providerPlugins auth with
authorization: Bearer public, which overrode a later real Providers
api_key edit and caused upstream 401s. Put the public token on the
provider api key instead and skip the plugin override.
Fixes#1562
Hidden route tags select the provider only after child startup, while
clients that support exact model strings need the gateway-visible ID up
front. Publish the client-to-target mapping and accept either identity
through existing discovery resolution.
Provider imports exposed live context limits, but CCR discarded them and
fell back to stale static entries. Preserve physical and effective limits
so generated catalogs and model discovery advertise the provider-specific
usable window.
Bug evidence from request_logs.sqlite:
request body: {"model":"NebulaCoder/nebulacoder-cot-v8.0", ...}
response body: {"error":{"message":"All target providers failed.",
"attempts":[
{"provider":"openai","provider_name":"provider-nebulacoder-ee070b8656",
"stage":"model_resolution",
"message":"Model "NebulaCoder/nebulacoder-cot-v8.0" is not configured
for target provider openai.
Allowed models: nebulacoder-v8.0, nebulacoder-cot-v8.0."}
]}}
Root cause: prepareUpstreamCredentialAttempt has a !target branch that uses:
bodyHasConfiguredProviderModelSelector(input.attempt.body, input.config)
? input.attempt.body ← original body with "NebulaCoder/" prefix
: normalizedBody?.body ... ← correctly stripped
When the body parses successfully, it prefers the original body with the
Provider/ prefix. This prefixed model name reaches ai-gateway which only
knows bare model names, causing a 400.
Fix: always use normalizedBody.body (which strips the Provider/ prefix)
when available. Remove the now-unused bodyHasConfiguredProviderModelSelector.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>