Summary
The TUI cost widget shows no dollar cost (stays at $0) for OpenAI-compatible API-key providers such as DeepSeek, even though the models.dev pricing catalog has correct prices and the local cost path resolves them.
Version
- jcode v0.68.0 (fcf5390), Linux x86_64, DeepSeek provider (OpenAI-compatible)
- Remote/TUI mode (the default interactive client talks to the daemon)
Root cause
resolve_remote_cost_pricing() in crates/jcode-tui/src/tui/app/misc_ui.rs decides whether a remote session is billed per token using a hard-coded provider-name allow-list:
let billed = if is_anthropic || is_openai {
api_key_billed
} else {
// Providers that are inherently cost-based when proxied remotely.
provider_name.contains("opencode")
|| provider_name.contains("openrouter")
|| provider_name.contains("bedrock")
|| provider_name.contains("cerebras")
|| provider_name.contains("compatible")
};
The daemon reports the active provider's display name via provider_name() -> runtime_display_name(). For DeepSeek (an OpenAI-compatible profile) that is "DeepSeek", which matches none of the allow-list strings. So billed == false, the function returns None, and accrue_remote_call_cost never adds anything — the cost stays $0.
The local cost path (update_cost_impl, same file) does this correctly:
crate::provider_catalog::openai_compatible_profile_by_id(provider_name.trim())
.is_some_and(|profile| profile.requires_api_key)
So the asymmetry is: local sessions bill correctly, remote (default TUI) sessions don't.
Verified
- The models.dev catalog resolves DeepSeek correctly (
deepseek-v4-flash = $0.14 input / $0.28 output / $0.0028 cache-read) via model_pricing::lookup.
- The problem is purely the
billed gate above returning false for the display-name "DeepSeek".
Proposed fix
Apply the same profile-aware requires_api_key check the local path uses to the remote path. Resolve by id OR display name, because the daemon sends the display name (no openai-compatible: prefix):
let billed = if is_anthropic || is_openai {
api_key_billed
} else {
// Providers that are inherently cost-based when proxied remotely.
provider_name.contains("opencode")
|| provider_name.contains("openrouter")
|| provider_name.contains("bedrock")
|| provider_name.contains("cerebras")
|| provider_name.contains("compatible")
// Any OpenAI-compatible profile that requires an API key meters
// per token when proxied remotely (DeepSeek, Z.AI, NVIDIA NIM,
// Moonshot, ...). The daemon reports the profile's display name,
// which does not carry the `openai-compatible:` prefix, so resolve
// by id OR display name and honor `requires_api_key` exactly like
// the local cost path does.
|| crate::provider_catalog::openai_compatible_profile_id_for_display_name(
&provider_name,
)
.is_some_and(|profile_id| {
crate::provider_catalog::openai_compatible_profile_by_id(profile_id)
.is_some_and(|profile| profile.requires_api_key)
})
};
Both helpers already exist and are pub in crates/jcode-base/src/provider_catalog.rs (openai_compatible_profile_id_for_display_name matches id OR display name; openai_compatible_profile_by_id(...).requires_api_key matches the local path's semantics).
Verification of the fix
Verified against the real profile catalog via an e2e probe:
| provider_name |
before |
after |
DeepSeek / deepseek |
false |
true |
Z.AI, Moonshot AI |
false |
true |
ollama (no API key) |
false |
false |
OpenRouter, OpenAI, Claude |
true |
true |
Environment
- jcode v0.68.0, Linux x86_64, DeepSeek provider.
- Full
cargo build on this machine is blocked by a missing libssl-dev (unrelated to this change); the change only adds pub helper calls already used elsewhere.
Summary
The TUI cost widget shows no dollar cost (stays at
$0) for OpenAI-compatible API-key providers such as DeepSeek, even though the models.dev pricing catalog has correct prices and the local cost path resolves them.Version
Root cause
resolve_remote_cost_pricing()incrates/jcode-tui/src/tui/app/misc_ui.rsdecides whether a remote session is billed per token using a hard-coded provider-name allow-list:The daemon reports the active provider's display name via
provider_name()->runtime_display_name(). For DeepSeek (an OpenAI-compatible profile) that is"DeepSeek", which matches none of the allow-list strings. Sobilled == false, the function returnsNone, andaccrue_remote_call_costnever adds anything — the cost stays$0.The local cost path (
update_cost_impl, same file) does this correctly:So the asymmetry is: local sessions bill correctly, remote (default TUI) sessions don't.
Verified
deepseek-v4-flash= $0.14 input / $0.28 output / $0.0028 cache-read) viamodel_pricing::lookup.billedgate above returning false for the display-name"DeepSeek".Proposed fix
Apply the same profile-aware
requires_api_keycheck the local path uses to the remote path. Resolve by id OR display name, because the daemon sends the display name (noopenai-compatible:prefix):Both helpers already exist and are
pubincrates/jcode-base/src/provider_catalog.rs(openai_compatible_profile_id_for_display_namematches id OR display name;openai_compatible_profile_by_id(...).requires_api_keymatches the local path's semantics).Verification of the fix
Verified against the real profile catalog via an e2e probe:
DeepSeek/deepseekZ.AI,Moonshot AIollama(no API key)OpenRouter,OpenAI,ClaudeEnvironment
cargo buildon this machine is blocked by a missinglibssl-dev(unrelated to this change); the change only addspubhelper calls already used elsewhere.