Skip to content

TUI cost widget shows zero dollar cost for OpenAI-compatible API-key providers (DeepSeek): remote billing gate uses a hard-coded provider allow-list #831

Description

@ainoob2025

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions