Direct OpenAI-compatible providers are reported as `openrouter` in logs and in `/usage` spend
Summary
The openrouter provider is a multiplexing slot: it serves the public OpenRouter aggregator and every direct OpenAI-compatible profile (deepseek, nvidia-nim, zai, ...). The slot's transport name is openrouter / OpenRouter, while the profile-aware name is display_name() (runtime_display_name()).
Several code paths use the transport name instead of the profile-aware name, so sessions that talk to DeepSeek through a direct profile are reported as openrouter:
- Log prefix – every line of a DeepSeek session is tagged
prv:openrouter|mod:deepseek. /usagespend ledger – spend is attributed to anopenrouterbucket on hosts where OpenRouter is not configured and was never used.- KV cache telemetry –
provider=OpenRouterwhilemodel=deepseek-flash.
Environment
- jcode
v0.84.0 (57d587899)— release build, Ubuntu 20.04, x86_64 - Provider: DeepSeek via API key (
~/.config/jcode/deepseek.env),openrouterreportsnot_configured - Also reproduced on
master(5f33d6239); the offending lines are unchanged there
Steps to reproduce
mkdir /tmp/jcode-repro
cat > /tmp/jcode-repro/config.toml <<'EOF'
[[providers.deepseek.models]]
id = "deepseek-flash"
context_window = 1048576
EOF
# API key: export DEEPSEEK_API_KEY=... or place it at
# $JCODE_HOME/config/jcode/deepseek.env
JCODE_HOME=/tmp/jcode-repro jcode run -p deepseek -m deepseek-flash "hi"
grep 'prv:' /tmp/jcode-repro/logs/*.logActual
A single run of the release binary in a clean JCODE_HOME:
[2026-09-17 10:18:33.787] [INFO] [ses:session_macaque_1789|prv:openrouter|mod:deepseek] TURN_CANCEL_REGISTERED ...
[2026-09-17 10:18:33.788] [INFO] [ses:session_macaque_1789|prv:openrouter|mod:deepseek] Locking tool list at 30 tools ...The same host's existing release-version logs contain prv:OpenRouter|mod:deepseek 503,047 times (2026-09-09) and 115,349 times (2026-09-10). The sessions' own metadata says the provider is DeepSeek ("provider_key": "deepseek" / "openai-compatible:deepseek").
KV cache telemetry on the same host, same release:
[2026-09-16 09:51:35] [INFO] KV_CACHE_USAGE: turn=65 call=1 provider=OpenRouter upstream=None model=deepseek-flash input=799739 cache_read=2432 ...
[2026-09-16 09:50:41] [AUTH] AUTH event=auth_status_check_fast provider=all ... openrouter=not_configured ...~/.jcode/provider_activity.json on that host (what /usage renders):
"openrouter": { "month_usd": 629.99, "all_time_usd": 774.80 },
"openai-compatible:deepseek": { "month_usd": 155.73, "all_time_usd": 192.34 }while jcode auth status reports openrouter not_configured, there is no auth.json and no OpenRouter API key anywhere, and no OpenRouter model was ever selected (the model picker usage contains only DeepSeek, Copilot, NVIDIA NIM and generic OpenAI-compatible entries). The openrouter bucket therefore cannot be genuine OpenRouter spend.
Expected
- A session using the
deepseekprofile is reported asdeepseek/DeepSeek, with the session's provider key (openai-compatible:deepseek) used wherever a stable identity is needed. /usageattributes spend to the provider that actually served the request.
Root cause
crates/jcode-base/src/provider/mod.rs:1776-1800–name()returns the transport slot label (ActiveProvider::OpenRouter => "OpenRouter"), whiledisplay_name()resolves the active profile throughruntime_display_name(). Callers that need "which provider is billing me" must use the latter (or the session provider key), but several usename().crates/jcode-app-core/src/agent/environment.rs:35–logging::set_provider_info(self.provider.name(), &self.provider.model())puts the slot name into every log line.crates/jcode-app-core/src/server/client_state.rs:500– theHistoryevent's provider name falls back toprovider.name()(the slot) when the session has noprovider_key; the TUI stores that verbatim and reuses it as the billing identity.crates/jcode-tui/src/tui/app/misc_ui.rs:327-341–record_api_key_spendderives the ledger key from the TUI's display name plusJCODE_RUNTIME_PROVIDER; a name ofOpenRouterwith the env var unset maps straight to theopenrouterbucket (source_key_for_provider_label,crates/jcode-base/src/provider_activity.rs:310).crates/jcode-base/src/session.rs:282–derive_session_provider_keyfalls back to env vars and finally to the raw name, so a direct profile can be recorded asopenrouter.
Suggested fix
Per the contract clarified in #691, Provider::name() should stay the stable machine-facing id. The fix belongs in the surfaces that read it while needing a profile identity:
Agent::set_log_context– logdisplay_name()(profile-aware) instead ofname().- the
Historyprovider-name fallback inclient_state.rs– same. record_api_key_spend//usageledger attribution – use an authoritative provider key rather than re-deriving it from a display label plus process env.derive_session_provider_key– fall back to the profile identity, not the raw transport name.
Related issues
- #691 – this is the concrete surface report that issue asked for.
name()stays the stable machine id; these are the surfaces still reading it where the profile identity is needed. - #380 – a specific case of the runtime-identity divergence described there (the History fallback is quoted in that issue).
- #1116 – same family: provider identity re-derived from a process-global env var, different trigger.
- #795 / PR #863 – fixed the auth/route half of "OpenRouter reported while not configured". The log prefix and
/usageledger half is still reproducible on v0.84.0 and onmaster.
Additional context
Related but not yet released: provider-keyed [pricing.providers."<profile>"] rate cards (in development after v0.84.0) are looked up with the same derived provider key. Because that key can degrade to openrouter, the user's card can be silently skipped and the call priced by the generic USD fallback instead. Details available on request.
I am happy to prepare a PR for the surfaces above if that helps.
Source: 1jehuang/jcode