Support account API keys in LiteLLM provider and sidecar - #32
Support account API keys in LiteLLM provider and sidecar#32KillerQueen-Z wants to merge 1 commit into
Conversation
…ecorded #32 shipped the same feature independently and caught two things this branch had wrong. Credit to @KillerQueen-Z. The real bug: the BlockRun CLI records the chain a user picked interactively in ~/.blockrun/payment-chain (legacy: ~/.blockrun/.chain), and this branch ignored it. Someone who ran the setup flow and chose Base had already answered the question the new Solana default asks — silently moving them is exactly the breakage the compatibility branch exists to prevent. Both files are read now, current name first. That order is not hypothetical: both exist on the machine this was developed on and DISAGREE, payment-chain saying solana and the older .chain saying base. An explicitly passed wallet key now picks its own chain from its format too. A hex key is not base58, so the key on the call is a better answer than anything on disk — routing it to the other chain's signer cannot work. Three more, same source: - Retry-After survives the hop. A 429 carries the wait time every stock OpenAI and Anthropic client schedules its backoff from; dropping it turned a client that would have waited into one that spins. - A malformed key raises instead of falling through to the wallet rail. The caller asked for the account rail, so a silent fallback either 402s for a wallet they do not have or spends from one they did not mean to touch. Passing both credentials at once is refused for the same reason: no reading is obviously right and the two spend different money. - Wallet keys are hashed out of the client-cache keys, which are dict keys and so surfaced in reprs, KeyErrors and anything dumping locals on a crash. The test suite's chain probe now stubs the chain files as well as the session files. It had to: this machine's .chain flipped a test the moment the feature landed, which is the same laptop-vs-CI divergence the session stub already existed to stop. 399 passed. Live re-verified on both rails after the rework. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Je8JRrGK7mifyGbf8VjRM
|
Heads-up: #33 landed the same feature independently — my fault for not checking open PRs before starting. Rather than pick one wholesale I've read both and folded this PR's wins into #33 (commit 7b40e4d), with credit to you in the CHANGELOG and commit message. Two real bugs this PR caught that #33 had:
Three more taken: Why #33 as the base rather than this one — one blocking reason and one of degree:
Also from that ledger check, in case it is useful to you: bare Anthropic model ids were being billed $0 on the account rail — fixed in BlockRunAI/enterprise#23 — and Happy to be overruled if you'd rather rebase this one on top instead — your call, and the |
|
Correction to my comment above — one of my two reasons was wrong, and I want it on the record rather than left standing. I said the Python SDK support was unreleased. It is released. blockrun-llm#59 merged at 16:19 UTC today and What that changes, and what it does not:
So the accurate statement is narrower than what I wrote: not "blocked on an unreleased SDK" but "written against an SDK design that did not land". The import fails against 1.15.0 today. The second reason stands unchanged — endpoint coverage, and that this PR's own body notes And one point in your favour that is now stronger than when I first wrote: with 1.15.0 out, delegating to the SDK is the better long-term shape, and #33's hand-rolled httpx transport is the part of it I'd expect to be replaced. #33 ships today and is verified end to end; a follow-up that moves it onto |
|
Closing as the older of two open PRs doing the same thing — #33 is the one carried forward. I opened #33 without checking for an existing PR first; that was my mistake and the duplicated effort is on me. Rather than pick one wholesale I read both and folded this branch's wins into #33 (commit 7b40e4d), with credit to @KillerQueen-Z in the CHANGELOG and the commit message. Two real bugs in #33 came from this PR: the CLI-recorded chain ( The deciding factor, corrected since my first comment: the Python SDK support is released (blockrun-llm#59 → PyPI 1.15.0), so "blocked on an unreleased dependency" was wrong. But this branch imports The design idea here is still the better one long-term. #33 hand-rolls an httpx transport; delegating to |
#33) * feat: an API key is a way in, and Solana is the default chain (0.10.0) The x402 wallet is the right primitive for an agent — it can hold one, and it needs no account. It is the wrong first step for a person evaluating this package for an afternoon, who now has to fund a Solana wallet before the first "hello". That was the wall, and it is the reason most people who found this repo never made a call from it. So: set BLOCKRUN_API_KEY=brk_live_... (issued at user.blockrun.ai, topped up by card) and every surface routes to api.blockrun.ai with a Bearer header. No wallet, no private key, no chain, no [solana] extra, no USDC, no gas. The wallet rail is untouched and stays the default when no key is present. One seam, not two implementations. The rail is chosen once, in _adapter, and everything above it — the LiteLLM provider, the sidecar's routes, the media job manager, the audit log — is unchanged, because the account API returns the gateway's body verbatim and the shapes already match. api_key on a litellm.completion() call now carries either credential: the brk_ prefix picks the account rail, and no wallet key format begins with one, so existing callers cannot be misrouted. Covered on the new rail: chat (incl. streaming), /v1/messages, /v1/responses, images (generations + edits), video submit/poll/download, speech, music, sound effects, /v1/models. The one gap is the native Gemini protocol, which api.blockrun.ai does not publish — the sidecar answers 501 naming the wallet rail and the /v1/chat/completions route to the same models, rather than letting a bare 404 come back from a host that never served it. cost_usd is null on this rail, deliberately. There is no per-call on-chain charge and there never will be; the new cost_source "blockrun_account" says the authoritative figure lives in the account ledger, where tagging it litellm_estimate would have claimed a real number existed and was missed. Solana also becomes the default chain for the wallet rail. It settles in about a second for a fraction of the fee, so it is where a caller who never made a choice should land. A host holding only a Base credential keeps using Base with a one-line warning: a hex key cannot be parsed by the SVM signer, so flipping it silently would have turned an upgrade into an outage. Anything explicit still wins, BLOCKRUN_CHAIN=solana on a Base-only host included — once a choice is made, quietly serving the other chain would be a lie about which chain moved money. Verified live against api.blockrun.ai and both gateways, and reconciled against the account ledger: chat, streaming, Anthropic messages, responses, images and speech all bill at exact list price; failed calls bill nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Je8JRrGK7mifyGbf8VjRM * fix: fold in the review wins from #32, and honour the chain the CLI recorded #32 shipped the same feature independently and caught two things this branch had wrong. Credit to @KillerQueen-Z. The real bug: the BlockRun CLI records the chain a user picked interactively in ~/.blockrun/payment-chain (legacy: ~/.blockrun/.chain), and this branch ignored it. Someone who ran the setup flow and chose Base had already answered the question the new Solana default asks — silently moving them is exactly the breakage the compatibility branch exists to prevent. Both files are read now, current name first. That order is not hypothetical: both exist on the machine this was developed on and DISAGREE, payment-chain saying solana and the older .chain saying base. An explicitly passed wallet key now picks its own chain from its format too. A hex key is not base58, so the key on the call is a better answer than anything on disk — routing it to the other chain's signer cannot work. Three more, same source: - Retry-After survives the hop. A 429 carries the wait time every stock OpenAI and Anthropic client schedules its backoff from; dropping it turned a client that would have waited into one that spins. - A malformed key raises instead of falling through to the wallet rail. The caller asked for the account rail, so a silent fallback either 402s for a wallet they do not have or spends from one they did not mean to touch. Passing both credentials at once is refused for the same reason: no reading is obviously right and the two spend different money. - Wallet keys are hashed out of the client-cache keys, which are dict keys and so surfaced in reprs, KeyErrors and anything dumping locals on a crash. The test suite's chain probe now stubs the chain files as well as the session files. It had to: this machine's .chain flipped a test the moment the feature landed, which is the same laptop-vs-CI divergence the session stub already existed to stop. 399 passed. Live re-verified on both rails after the rework. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Je8JRrGK7mifyGbf8VjRM --------- Co-authored-by: 1bcMax <viewitter@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Account API users previously entered wallet initialization in both the LiteLLM custom provider and local sidecar. Add explicit account authentication across sync/async chat, native streaming, Responses/Messages passthrough, media and authenticated polling. Preserve existing wallet selections and prefer Solana for new wallet users.
Account requests retain 401/402/429 and Retry-After, never replay x402 payments, isolate cached clients by credentials, and distinguish account billing from wallet settlement. README adds registration, keys/credits, preview installation, endpoint coverage and release dependencies.
Validation: 321 tests pass, 3 skipped; Python correctness lint (F/E9), wheel build and diff checks pass. Live temporary-key tests pass for LiteLLM JSON and async SSE, actual sidecar startup/catalog, chat JSON/SSE, Anthropic Messages JSON and Responses JSON. Media polling and quota errors use deterministic HTTP tests; not all live media services were exercised. Responses SSE/video remain blocked on Enterprise #10 deployment/configuration. Native Gemini account availability is unverified.
Release dependency: Python SDK #58 must be published and the package dependency floor updated before releasing this integration. requirements-api-preview.txt pins its exact fork commit for review installation; current PyPI versions do not imply this feature is released. Full stylistic Ruff baseline is not clean; targeted correctness lint passes.