OpenCode (and other Anthropic-compatible clients) → Claude Code OAuth proxy with proper prompt-cache preservation.
OpenCode → omo_proxy (127.0.0.1:34156) → api.anthropic.com
↑
reuses ~/.claude/.credentials.json (Claude Max subscription)
The Anthropic Messages API has prompt caching that can give a ~9x cost reduction on long contexts (cache_read is 0.1x the input price). A naive proxy that rewrites or shuffles the request body (billing headers, session ids, cache_control markers) silently destroys cache prefixes and forces every request to be billed at full price. On a Claude Max plan that drains the rate limit in hours.
This proxy specifically preserves prompt caching while still authenticating against Claude Code's first-party OAuth token.
- Stable cache prefix. Billing/session strings persisted in
proxy-state.jsonsurvive process restarts, so previously written cache entries can still be reused. cache_controlcap = 4 (Anthropic max), not 1. When trimming is needed, keeps thetoolsandsystemanchors plus the latestmessagesmarkers.- Stable
metadata.user_id. Caller-supplied volatile fields are dropped. - OAuth token persistence. Refreshed tokens are written back to
~/.claude/.credentials.json, so a proxy restart never invalidates the refresh chain. - Tool name normalization. OpenCode lowercase tools are mapped to Claude Code's CapitalCase set; MCP tools (
mcp__*) pass through. - Usage logging. Every request prints a
[usage]line includingcache_read,cache_create,cache_create_5m,cache_create_1h,total_in, etc. - Hardened launcher.
ensure-claude-proxy.ps1checks the exactproxy.jsis the listener, not just "something on port 34156".
Log excerpt from a real session (224k token system+tools prefix):
#1: in=6 total_in=224281 out=1060 cache_read=0 cache_create=224275
#2: in=6 total_in=225387 out=467 cache_read=224275 cache_create=1106
#3: in=1 total_in=226023 out=517 cache_read=225381 cache_create=641
→ From request #2 onward Anthropic returns the entire prefix as cache_read, billing at 0.1x. Cost on the same conversation drops by ~9x compared to the broken-cache baseline.
omo_proxy/
├── proxy.js main Node.js proxy (804 lines)
├── start-proxy.cmd foreground launcher (Windows)
├── opencode-cc.cmd one-shot env wrapper (cmd)
├── opencode-cc.ps1 one-shot env wrapper (PowerShell)
├── shims/
│ ├── opencode.cmd transparent shim for cmd / PowerShell
│ ├── opencode transparent shim for Git Bash / WSL
│ └── ensure-claude-proxy.ps1 helper that ensures the right proxy.js is running
└── go/ legacy Go implementation (not maintained)
- Node.js 18+ (uses built-in
http,https,crypto,fs). - Claude Code installed and logged in (
~/.claude/.credentials.jsonmust exist with a validclaudeAiOauthblock). - A Claude subscription that includes the model you select in OpenCode (e.g.
Maxfor Opus).
git clone https://github.com/winglock/omo_proxy "$env:USERPROFILE\omo_proxy"
# (option A) just run the proxy in a foreground window
cd "$env:USERPROFILE\omo_proxy"
node proxy.js
# (option B) install the transparent opencode shim
Copy-Item "$env:USERPROFILE\omo_proxy\shims\opencode.cmd" "$env:USERPROFILE\bin\"
Copy-Item "$env:USERPROFILE\omo_proxy\shims\ensure-claude-proxy.ps1" "$env:USERPROFILE\bin\"
# make sure $env:USERPROFILE\bin is ahead of npm's path so 'opencode' resolves to the shimIf you keep the repo somewhere other than ~/omo_proxy, set:
setx OMO_PROXY_SCRIPT "C:\path\to\omo_proxy\proxy.js"The Node.js proxy works on any platform. macOS users get the bonus that Claude Code stores credentials in Keychain too — currently only the Go variant reads Keychain; on macOS you'll either symlink the credentials JSON into ~/.claude/.credentials.json or extend proxy.js's TokenManager to call security find-generic-password.
git clone https://github.com/winglock/omo_proxy ~/omo_proxy
cd ~/omo_proxy
node proxy.js &
ANTHROPIC_BASE_URL=http://127.0.0.1:34156/v1 \
ANTHROPIC_API_KEY=proxy-placeholder \
opencode{
"$schema": "https://opencode.ai/config.json",
"provider": {
"anthropic": {
"options": {
"baseURL": "http://127.0.0.1:34156/v1",
"apiKey": "proxy-placeholder"
}
}
}
}After a few requests:
Select-String -Path ".\proxy.log" -Pattern "\[usage\]" | Select-Object -Last 10Healthy pattern: cache_read is roughly equal to total_in from the second request onward, and cache_create is small.
If cache_read=0 on every request, something downstream of this proxy is mutating the prompt prefix per call. Check your client first; this proxy stabilizes the parts it controls.
Anthropic's classifier rejects unknown tool names (treats them as third-party). This proxy keeps only Claude Code's canonical set plus MCP passthrough:
kept: Bash, Read, Glob, Grep, Edit, Write, Task, TodoWrite, WebFetch, WebSearch, Skill, BashOutput, NotebookEdit, mcp__*
dropped: LSP tools (lsp_*), ast_grep_*, session_*, background_cancel, look_at, skill_mcp, websearch_web_search_exa, grep_app_searchGitHub, context7_*, etc. The dropped tools simply aren't available to the Claude model — non-Anthropic providers in the same OpenCode session keep all of them.
| Path | Behavior |
|---|---|
GET /health |
Local-only check. Returns 200 {"status":"ok"} if the loaded token is more than 5 minutes from expiry, 503 {"status":"token_refresh_required"} otherwise. Does not call upstream. |
POST /v1/messages* |
Transformed and forwarded. Streaming SSE supported. |
anything else under /v1/ |
Auth-rewritten and forwarded as-is. |
| Path | Direction | Contents |
|---|---|---|
~/.claude/.credentials.json |
read + write | Claude Code OAuth tokens. Updated on refresh. |
./proxy-state.json |
created on first run | Persisted billing lines + session id used to keep cache prefix byte-identical across restarts. Do not commit. |
./proxy.log |
append | Request/usage log; rotated at 10 MB. |
This is a personal-use local proxy for an individual subscriber's own first-party OAuth token. It is not affiliated with Anthropic or OpenCode. Don't use it to share credentials, redistribute access, or anything that violates the Claude Code TOS.
MIT