Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion docs/src/content/docs/providers/opencode-go.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: OpenCode Go
description: Configure an OpenCode Go API key, model routing, streaming, tools, and provider overrides.
description: Configure an OpenCode Go API key, account usage, model routing, streaming, tools, and provider overrides.
---

OpenCode Go uses the API at `https://opencode.ai/zen/go/v1`. Its catalog spans
Expand All @@ -22,6 +22,26 @@ claude-code-proxy serve
`opencode.apiKey` configuration key is also supported. The proxy does not
implement an OpenCode login flow.

To see the current percentage used and reset time for each account limit, run:

```sh
claude-code-proxy opencode usage
claude-code-proxy opencode usage --json
```

This fetches OpenCode Go's rolling five-hour, weekly, and monthly windows. The
upstream `/usage` endpoint is implemented by OpenCode but is not yet listed in
its public API table, so its response format may evolve. The JSON form preserves
additional upstream fields for scripting.

The proxy also exposes the same limits in the standard Claude Code Router
account format. In Claude Code Router, enable **Fetch usage** for the proxy
provider and select **Standard usage endpoint**. The dashboard will discover
`/.well-known/ccr/account`; `/v1/account/limits` is available as a compatible
alias. These routes use the proxy's configured OpenCode key and ignore the
incoming placeholder key. Successful upstream results are cached for 60 seconds;
an expired refresh failure is returned rather than silently serving stale data.

## Models

Run `claude-code-proxy models` for the statically registered catalog. Every
Expand Down
13 changes: 12 additions & 1 deletion docs/src/content/docs/reference/command-reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Command reference
description: Canonical claude-code-proxy command syntax for serving, monitoring, listing models, version output, and provider authentication.
description: Canonical claude-code-proxy command syntax for serving, monitoring, listing models, provider authentication, and OpenCode Go usage.
---

Running `claude-code-proxy` without a subcommand is equivalent to `claude-code-proxy serve`.
Expand Down Expand Up @@ -74,6 +74,17 @@ A missing credential makes `auth status` exit with status 1. Other provider comm

Logout removes the local proxy-owned credential. It does not call the provider to revoke a refresh token.

## OpenCode Go usage

```sh
claude-code-proxy opencode usage [--json]
```

Fetches the account's rolling five-hour, weekly, and monthly usage directly
from OpenCode Go. The default output is human-readable; `--json` prints the
upstream response for scripts. The command uses the same API key and base URL
as OpenCode model requests.

## Development commands

From a source checkout:
Expand Down
27 changes: 26 additions & 1 deletion docs/src/content/docs/reference/http-api.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: HTTP API
description: Local routes for health checks, Anthropic Messages, token counts, model discovery, OpenAI-compatible requests, and Codex images.
description: Local routes for health checks, account usage, Anthropic Messages, token counts, model discovery, OpenAI-compatible requests, and Codex images.
---

The server exposes the Anthropic and OpenAI routes supported by the proxy. Each route uses the configured provider credential for the selected model.
Expand All @@ -19,6 +19,31 @@ Liveness check:

It does not verify provider credentials or upstream availability.

## OpenCode Go account usage

```text
GET /.well-known/ccr/account
GET /v1/account/limits
```

Both routes return OpenCode Go's rolling five-hour, weekly, and monthly account
limits as the same normalized account snapshot. Available percentages become
quota meters with used and remaining values; reset times and upstream statuses
are included when supplied. The response follows Claude Code Router's standard
account endpoint contract, so a proxy provider configured with **Fetch usage**
and **Standard usage endpoint** can display the limits in its dashboard.

The server caches the latest successful upstream response for 60 seconds, so
frequent dashboard polling does not make an upstream request each time. After
the cache expires, an upstream failure is returned explicitly instead of
serving an unmarked stale snapshot.

The routes use the proxy-owned OpenCode credential. Incoming bearer or API-key
headers are ignored, as on generation routes. Responses include
`Cache-Control: no-store`. Because account usage is visible without client
authentication, keep the listener on loopback or protect it as described
above.

## `POST /v1/messages`

Accepts an Anthropic Messages request in streaming or non-streaming mode. `POST /v1/messages?beta=true` reaches the same route.
Expand Down
54 changes: 54 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ enum Commands {
#[command(subcommand)]
command: ProviderGroup,
},
/// Inspect OpenCode Go account state
#[command(name = "opencode")]
OpenCode {
#[command(subcommand)]
command: OpenCodeGroup,
},
}

#[derive(Debug, Subcommand)]
Expand All @@ -76,6 +82,16 @@ enum ProviderGroup {
},
}

#[derive(Debug, Subcommand)]
enum OpenCodeGroup {
/// Show rolling, weekly, and monthly usage limits
Usage {
/// Print the upstream response as JSON
#[arg(long)]
json: bool,
},
}

fn main() -> Result<()> {
let cli = Cli::parse();

Expand Down Expand Up @@ -165,6 +181,9 @@ fn main() -> Result<()> {
Commands::Kimi { command } => run_provider_cli("kimi", command),
Commands::Cursor { command } => run_provider_cli("cursor", command),
Commands::Grok { command } => run_provider_cli("grok", command),
Commands::OpenCode { command } => match command {
OpenCodeGroup::Usage { json } => run_opencode_usage(json),
},
}
}

Expand Down Expand Up @@ -222,6 +241,28 @@ fn run_provider_cli(name: &str, command: ProviderGroup) -> Result<()> {
}
}

fn run_opencode_usage(json: bool) -> Result<()> {
let client = claude_code_proxy::providers::opencode::client::OpenCodeClient::new(
config::opencode_base_url(),
config::opencode_api_key(),
)?;
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
let usage = runtime
.block_on(client.get_usage())
.map_err(|error| anyhow::anyhow!(error.message))?;
if json {
println!("{}", serde_json::to_string_pretty(&usage)?);
} else {
println!(
"{}",
claude_code_proxy::providers::opencode::usage::format_text(&usage)
);
}
Ok(())
}

fn print_models(registry: &Registry, full: bool) {
let grouped = registry.grouped_models();
for provider in ["codex", "kimi", "grok", "opencode", "cursor"] {
Expand Down Expand Up @@ -314,6 +355,19 @@ mod tests {
assert!(matches!(cli.command, Some(Commands::Demo)));
}

#[test]
fn opencode_usage_command_parses_json_flag() {
let cli =
Cli::try_parse_from(["claude-code-proxy", "opencode", "usage", "--json"]).unwrap();

assert!(matches!(
cli.command,
Some(Commands::OpenCode {
command: OpenCodeGroup::Usage { json: true }
})
));
}

#[test]
fn listen_url_brackets_ipv6_addresses() {
assert_eq!(listen_url("::1", 18765), "http://[::1]:18765");
Expand Down
Loading