Skip to content

Support account API keys in LiteLLM provider and sidecar - #32

Closed
KillerQueen-Z wants to merge 1 commit into
BlockRunAI:mainfrom
KillerQueen-Z:feat/account-api-key
Closed

Support account API keys in LiteLLM provider and sidecar#32
KillerQueen-Z wants to merge 1 commit into
BlockRunAI:mainfrom
KillerQueen-Z:feat/account-api-key

Conversation

@KillerQueen-Z

Copy link
Copy Markdown
Collaborator

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.

VickyXAI pushed a commit that referenced this pull request Sep 5, 2026
…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
@VickyXAI

VickyXAI commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

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:

  1. The chain the CLI recorded was ignored. ~/.blockrun/payment-chain / .chain — someone who ran the interactive setup and chose Base had already answered the question the new Solana default asks, and feat: an API key is a way in, and Solana is the default chain (0.10.0) #33 silently overrode them. That is exactly the breakage the compatibility branch exists to prevent. Now read, current name first. Not hypothetical: both files exist on my dev machine and disagree (payment-chain solana, older .chain base), so the order is pinned by a test.
  2. An explicit wallet key should pick its own chain from its format. A hex key is not base58; routing it to the other signer cannot work.

Three more taken: Retry-After passthrough on 429; raising on a malformed key instead of falling through to the wallet rail (and refusing both credentials at once); hashing the wallet key out of the client-cache keys.

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 /api/pricing omits 22 mainnet paid models, filed as BlockRunAI/blockrun#516.

Happy to be overruled if you'd rather rebase this one on top instead — your call, and the auth= design is the better long-term shape. Otherwise I'd suggest closing this in favour of #33.

@VickyXAI

VickyXAI commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

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 blockrun-llm 1.15.0 went to PyPI at 16:22. I had checked the published wheel for a module named api_key and concluded it was absent; the module #59 shipped is named apikey, and it is there. My check searched for the wrong filename.

What that changes, and what it does not:

  • The blocker is gone as a category. There is now a released, supported way to do account keys from the Python SDK.
  • This PR still does not work against it. It imports blockrun_llm.api_key.resolve_api_auth. The released module is blockrun_llm.apikey, and it exports resolve_api_key / auth_headers / configure_credential / payment_mode and eight more — no resolve_api_auth, no api_key module. This branch was written against #58's design, and #58 lost to #59; I've commented there that it should be closed rather than rebased.

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 /v1beta, Responses SSE and video completion as unverified.

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 blockrun_llm.apikey would be a real improvement, and this PR is the closest thing to a design for it.

@VickyXAI

VickyXAI commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

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 (~/.blockrun/payment-chain) being ignored, and an explicitly passed wallet key not picking its own chain from its format. Three more were taken: Retry-After passthrough, raising on a malformed key instead of falling back to the wallet rail, and hashing the wallet key out of the client-cache keys.

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 blockrun_llm.api_key.resolve_api_auth, and the module that shipped is blockrun_llm.apikey with a different surface — it was written against #58's design, and #58 lost to #59 and is now closed. The import fails against 1.15.0 today.

The design idea here is still the better one long-term. #33 hand-rolls an httpx transport; delegating to blockrun_llm.apikey the way this branch intended is cleaner now that it exists, and that is the follow-up I would want against current main rather than a rebase of this branch.

@VickyXAI VickyXAI closed this Sep 5, 2026
VickyXAI added a commit that referenced this pull request Sep 5, 2026
#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants