All notable changes to blockrun-llm will be documented in this file.
-
Router Core lands in the Python SDK.
blockrun_llm/router_core/is a faithful port of@blockrun/router-core(upstream commit18bf4ab) — the product-neutral routing engine the TypeScript SDK bundles and the gateway runs. The same request now routes identically across all three.blockrun_llm/router_adapter.pyis the host glue (catalog id resolution, x402 payment floors, capacity filtering), ported from the TypeScript SDK'ssrc/router-adapter.ts.What the Python SDK did not have before:
- Portfolio (V3) ranking, not just tier lookup: candidates are scored on task affinity, cost, speed and reliability, so the cheapest capable model wins instead of a hardcoded tier primary.
- Hard capability filtering. A model that cannot hold the conversation,
emit the requested
max_tokens, call tools, or read images is dropped before scoring — previouslysmart_chatcould route to a model the request would fail on with a non-transient 400. - Task classification (
chat,code_edit,code_agent,tool_agent,tool_agent_parallel,reasoning_math,reasoning_mcq,long_context,extraction,vision,debug) with per-task calibrated model evidence. - Explainable decisions:
routing.candidates,routing.candidate_scores(quality / cost / speed / reliability per model),routing.task_type,routing.profileandrouting.router_versionare now on the response. - Live tier configuration, shared with the other products, replacing this SDK's separately hand-maintained tables.
-
client.route(prompt, ...)returns the routing decision without making or paying for a model call (TypeScript SDK parity). The first call may fetch the public catalog for prices; routing itself is local and free.
- The
freeprofile pointed at models NVIDIA has retired. Its tier table led withnvidia/deepseek-v4-flash(EOL 2026-08-12, HTTP 410) and fell back tonvidia/llama-4-maverickandnvidia/qwen3-coder-480b(also EOL), so free routing depended entirely on the gateway's redirect safety net. It now routes over the live free lineup (Step 3.7 Flash, Mistral Nemotron, Nemotron Nano Omni / 9B / 12B VL), and the adapter drops any candidate the catalog does not price at $0 — a paid model can no longer leak into a free-profile call. - Models the catalog marks unavailable no longer win routing.
/v1/modelsrows withavailable: falseare skipped when building the pricing map; every smart call to one would have failed with a non-transient error.
routing.methodis now"portfolio"for the default strategy ("rules"for the free profile and the config-only V2 rollback). Code that assertedmethod == "rules"needs updating.blockrun_llm/router.pyis now a thin compatibility shim over the core:route()andclassify_by_rules()keep working, andRoutingDecisionkeeps its previous keys plus the new metadata. Its hand-maintainedAUTO_TIERS/ECO_TIERS/PREMIUM_TIERStables are gone — tier configuration lives inrouter_core.DEFAULT_ROUTING_CONFIG, andFREE_TIERSmoved torouter_adapter.- Routing cost estimates now include the server margin and the x402 minimum
payment, so
routing.cost_estimatematches what the gateway actually charges. Free models are never floored up to the paid minimum.
tests/unit/test_router_core.pyports all four upstream vitest suites (88 cases) as the parity guard — the Python port must keep choosing the same models as the TypeScript SDK.tests/unit/test_router_adapter.pycovers the host layer:free/*→nvidia/*id resolution, the payment floor, capacity filtering, and the free-profile guarantees.
solana_key_to_bytesaccepts the key formats users actually have on disk. Alongside the existing bs58 encodings (64-byte keypair and 32-byte seed), it now decodes the Solana CLI JSON byte-array format (~/.config/solana/id.json) and 64-byte hex keys with or without0x. Previously these failed withInvalid Solana private key: Non-base58 characterand no further guidance. TypeScript SDK parity (@blockrun/llm 3.9.0).
- An invalid Solana key now says where it was loaded from and what it appears
to be.
get_or_create_solana_walletfailures name the source (theSOLANA_WALLET_KEYenvironment variable or the~/.blockrun/.solana-sessionpath). A 32-byte hex key is identified as the EVM (Base) wallet format rather than rejected as a character-set error, and unrecognized input lists the accepted formats.
-
Client-side spend limits.
max_cost_per_callandmax_session_coston every client (Base and Solana, sync and async) refuse a quote that costs more than you allowed:client = LLMClient(max_cost_per_call=0.25, max_session_cost=10.00)
Also settable per-deployment without code changes, via
BLOCKRUN_MAX_COST_PER_CALLandBLOCKRUN_MAX_SESSION_COST. An explicit argument wins over the env var; a malformed env value is ignored rather than raising, so a bad deploy variable cannot brick every client.The refusal happens before the paid request is sent, so nothing settles and nothing is charged — signing alone moves no money, the gateway submitting the signed authorization does. The new
SpendLimitErrorcarriesquoted_usd,limit_usdandscope("call"or"session"), and subclassesPaymentErrorso existing handlers keep working and the model fallback chain refuses it rather than shopping for a cheaper model.Both limits are opt-in and unset by default, so nothing changes for existing callers. Until now the SDK had no ceiling anywhere: it computed
cost_usdand signed the quote in the next statement, with nothing compared against anything — whilechat_completiondocumented aPaymentError: If budget is set and would be exceededfor abudgetparameter that did not exist. That docstring is now true.
Supersedes 1.8.1, which was published from a tree where VERSION and
__init__.py still read 1.8.0. That wheel reports __version__ == "1.8.0" and
cannot be corrected in place, since PyPI does not allow overwriting a published
file. Install 1.8.2 to get a package whose self-reported version is truthful.
- A failed paid request no longer triggers a second payment, on either
chain. Any error raised after the
PAYMENT-SIGNATUREwent out is now refused for model fallback, for both Base (_should_fallback) and Solana (_should_fallback_solana). Previously a post-settlement failure was indistinguishable from a transient one, so the chain advanced to the next model and signed again —smart_chaton the premium complex tier could settle six payments and return no tokens, the "CHARGED BUT REQUEST FAILED" outcome this file already documents under 1.7.1. This covers every error escaping the paid leg, not only timeouts: the dominant post-settlement failure is a paid 5xx, which arrives asAPIError(503)— exactly a status the fallback logic treats as retriable. Callers catchinghttpx.TimeoutExceptionare unaffected; the marker is an attribute, not a new exception type. - The clamp warning cannot break the request it warns about. Its parse ran
on
resource.description, a server-controlled string, immediately before signing. A non-string value raisedTypeErrorand aborted the call, and the pattern backtracked super-linearly on a long digit run (measured on CPython 3.13: 0.49s at 8k digits, 1.95s at 16k, and it keeps squaring). The number is now matched by a bounded pattern against a length-capped slice, an ambiguous description (a per-unit rate alongside the ceiling) stays silent instead of naming the wrong number, and the whole helper swallows its own failures. - Removed a documented payment guard that does not exist.
chat_completion()advertisedPaymentError: If budget is set and would be exceeded. There is nobudgetparameter anywhere in the SDK and no client-side spend cap — every 402 quote is signed automatically. The docstring now says that plainly and points atget_spending().
max_tokensabove a model's ceiling is no longer silently absorbed. The gateway does not reject an over-ceiling value; it clamps to the model's own ceiling and quotes payment for the clamped value. Verified against the live 402 leg on 2026-07-21:claude-opus-4.8sent 262144 and 1000000 both quote the 128000 price, andgpt-5.2sent 1e12 returns a quote rather than a 400. The 402 disclosed the clamp inresource.descriptionand the SDK discarded it while signing. Callers now get a warning naming what they asked for and what they are being charged for, before the signature goes out.- The comment and
ValueErroraroundMAX_TOKENS_SANITY_LIMITclaimed the gateway "enforces the real per-model ceiling and reports it". It does not. Both now describe clamping.
max_tokensis validated on Solana too.validate_max_tokenswas called from the Base client and nowhere else; the Solana client put the caller's value straight into paid request bodies at all four chat entry points, somax_tokens=2_000_000raised on Base and was signed and sent on Solana. With the gateway clamping rather than rejecting, nothing on either side caught it.boolno longer passes as a number.boolis anintsubclass, soisinstance(True, int)isTrueandmax_tokens=True,temperature=Trueandtop_p=Falseall sailed through and reached the wire as JSONtrue/false. All three numeric validators now reject it, namingboolrather than saying "must be a number" — which reads as wrong to anyone who knowsboolis one.- Line endings are normalized repo-wide via
.gitattributes(* text=auto). 17 tracked files were CRLF against an otherwise-LF tree, which turned a 51-line change into a 1045-line diff in #27.
max_tokensno longer capped below what models actually serve. The SDK rejected anything over 100000 client-side. That was not a model limit and not the gateway's — it was an undocumented sanity check that quietly became the binding constraint on every caller. Askingzai/glm-5.2for the 262144 it advertises raised aValueErrorthat never reached the network and named a limit no provider had set. The bound is nowMAX_TOKENS_SANITY_LIMIT(1000000), a typo guard for obviously-wrong values (a byte count, a timestamp, a stray1e9) rather than a ceiling any real request can hit.- The rejection message no longer reads like a provider response.
"max_tokens too large (maximum: 100000)"was taken for an upstream model ceiling during an investigation and recorded as one, on the strength of 19 identical "rejections" that never left the process. The message now says the number is the SDK's own.
- Adopt a wallet you already own, deliberately.
list_discovered_wallets()shows wallets belonging to other applications on your system, andimport_wallet(address)makes one of them the active BlockRun wallet. Automatic selection still never adopts a discovered wallet — this is the opt-in path for users whose funds live in a wallet another tool created. Solana counterparts:list_discovered_solana_wallets()/import_solana_wallet(address). - Adopting backs up the wallet it replaces. The outgoing key is written to
~/.blockrun/.session.backup-<timestamp>(mode 0600) before being overwritten, so switching wallets can never strand funds in the old one. - Matching is on the derived address, never the file's claim.
list_discovered_wallets()returns no private keys, andimport_wallet()resolves each candidate's address from its key. A wallet file naming an address it cannot sign for can neither be displayed as that address nor adopted by it.
- Keep the canonical BlockRun wallet authoritative. Automatic wallet
resolution no longer adopts a newer
wallet.jsonorsolana-wallet.jsonfound in another application's dot-directory. The SDK now uses the user's own~/.blockrun/.sessionor~/.blockrun/.solana-session; provider-wallet discovery remains available only for an explicit, user-confirmed migration. - Migration notice on first run after the lockdown. When a new wallet is created and other providers' wallets exist on the system, the SDK now names those addresses and explains how to import one deliberately, instead of silently leaving the user on an empty wallet. Addresses are derived from the discovered key, so a wallet file claiming an address it cannot sign for cannot trick you into funding it.
get_or_create_wallet()again honours the legacy~/.blockrun/wallet.key. It resolved only.session, so a user holding the legacy file was issued a brand new wallet and lost sight of their funds. It now delegates toload_wallet(), matching the TypeScript SDK.
-
The settlement header was read under a name no gateway sends. Both gateways emit
PAYMENT-RESPONSE(the x402 v2 spec name) —blockrunat 36 call sites,blockrun-solat 25 — and neither emitsX-PAYMENT-RESPONSEeven once. The SDK read only the legacy name, in four hand-rolled places, so_last_settlementdecoded nothing against production: no tx hash, no settlement on any paid call. The sidecar hit this exact bug and fixed it in blockrun-litellm 0.6.0, live-verified against a real paid call; the SDK half was never done. Both names now go through one helper (tx_log.read_settlement_header) so they can't drift apart again. The legacy name stays accepted for other facilitators. -
The paid-request error no longer claims your money is gone.
"API error after payment"reads as funds are lost, which is usually false — a real image-edit 500 was reported as lost USDC by two readers before anyone checked the gateway. It now reports only what the settlement header proves: a tx hash means SETTLED and is named; absence means unknown.Absence is not reported as "payment likely not taken", which the first cut of this change did. That trades a false alarm for a false all-clear, and the all-clear lands on precisely the wrong requests: Solana's paid chat path settles in parallel with the upstream call and re-raises immediately (
logChargedButFailed(...); throw primaryError), so a request the gateway logs asCHARGED BUT REQUEST FAILED — refund manuallyanswers before settlement lands, and therefore carries no header at all. Absence and "you were charged" co-occur systematically on the one path where it costs money. Base settles after the upstream call and does match the optimistic reading, but a set of headers doesn't tell the SDK which gateway produced it. So the wording names the usual case without asserting it, and points at wallet history.Gated on
tx_hash, never the header'ssuccessfield: the gateways hard-codesuccess: trueeven when settle didn't land, so older clients don't surface a spurious error. A tx hash is the only field that means money moved — the same field the gateways gate their own revenue accounting on.
-
input_typeon video generation (VideoClient.generate,SolanaLLMClient.video,AsyncSolanaLLMClient.video). Declares the intended seed mode —text/image/first_last_frame/reference. The gateway infers the mode from the seed fields and rejects with 400 before charging when the declared value disagrees, turning an expensive silent failure into a loud one: a droppedimage_urlotherwise yields a text-to-video clip you still pay for. Accepted on both chains. -
qualityon Solana image generation + editing (SolanaLLMClient.image/image_edit, sync and async).low/medium/high/autoforopenai/gpt-image-*;lowmeaningfully cuts generation time.Solana only, by design. The Base gateway defines no
qualityfield and strips unknown keys, so a value sent there would be silently dropped —ImageClient.generate/edittherefore keep rejecting it, now with a hint pointing at the Solana client.
- Reference-to-video (
reference_videos/reference_audios) is not exposed. Both gateways gate it behindR2V_ENABLED, which is currently off, so every call would return 503. It slots in once that flips. - Validation covers spelling only. Whether a declared mode matches the seed
fields, and which models accept
quality, stay the gateway's call — it answers both before billing, so a second copy here would only drift.
- Fail fast when the payer has no USDC token account (#23). Below this an unfunded wallet burned all 5 payment retries, each costing the gateway 4 verify retries — 20 facilitator calls per doomed request.
- Attach the BlockRun builder-code service code to Base-chain x402 payments (#21).
- Keep Solana video settlement blockhash fresh via proactive re-sign (#22).
Seedance 2.0 jobs could run long enough to exhaust the older two-retry
settlement loop and surface
transaction_simulation_failed.
- Solana media surface: video / music / speech / portrait / realface / price /
rpc (#16), plus the
rpc_batchcache fix (#17), asolana<0.40pin (#18), and media hardening (#19).
ChatCompletionChunk.cost_usdon streamed calls. The streaming paths now attach the real per-call x402 charge to every chunk (_iter_and_archive/_aiter_and_archive, Base + Solana), the streaming analogue ofChatResponse.cost_usd. It rides on the per-call chunk object, so it's race-free under shared-client concurrency (unlikeclient._last_call_cost, which goes stale). Downstream consumers (e.g.blockrun-litellm) can report the actual wallet deduction on streamed calls instead of a token×list-price estimate. Free / 200-first streams skip the signer and carry nocost_usd.
ChatResponse.cost_usdandChatResponse.settlement(#11, #12). Every chat completion now carries the real per-call x402 charge (and the decoded on-chain settlement receipt when present), so downstream consumers (e.g.blockrun-litellm) can report the actual wallet deduction instead of a token×list-price estimate. The cost is attached race-free (set on the response object itself, not read back off the shared client); the free / 200-first path reports exactly0.0(never a stale prior charge).
zai/glm-5.2— Z.AI's newest flagship. 1M-token context, top open-source on long-horizon coding, billed per-token at $1.40/$4.40 (same as glm-5.1). Added to the README ZAI table (as the new flagship) and to the chat-model sweep (including the reasoning set). Available now via direct call; SmartChat sees it live in/v1/models.
- SmartChat/Eco SIMPLE tier now routes to
moonshot/kimi-k2.7. Moonshot's current flagship (256K context, image+video input,reasoning_content) and the only k2 still visible in/v1/models— k2.6 and k2.5 are nowhidden:true, so pinning the primary to either would silently degrade the tier. k2.6 retained as the documented previous-gen fallback.
- Clear error when a Solana key is passed to the Base (EVM) client. Feeding
a base58 Solana secret key into
LLMClient/setup_agent_wallet()(or any EVM-chain client) used to fail with the crypticPrivate key must be 66 characters (0x + 64 hexadecimal characters). The SDK now detects the base58 Solana key shape and raises an actionable error pointing toSolanaLLMClient/setup_agent_solana_wallet()and the[solana]extra. Valid 64-hex EVM keys (including malformed ones) are unaffected and still get the hex error.
- Solana clients auto-load the on-disk wallet (parity with Base).
SolanaLLMClient/AsyncSolanaLLMClientnow resolve the key asprivate_key→SOLANA_WALLET_KEY→ on-disk wallet (newest~/.<provider>/solana-wallet.json, else~/.blockrun/.solana-session), soSOLANA_WALLET_KEYis no longer required when a wallet session exists — matching the BaseLLMClient.load_wallet()fallback. A malformed key from any source now raises a cleanValueError(instead of a raw base58/solders exception), and an unreadable session file is treated as "no wallet" rather than crashing.
- Streamed tool calls no longer crash the SDK (
'dict' object has no attribute 'delta'). OpenAI streams tool calls incrementally — the first frame carriesid+function.name, later frames onlyfunction.argumentsfragments — which the strict non-streamToolCallschema rejected, forcing amodel_constructfallback that leftchoicesas raw dicts and crashed the stream-archiving loop. Added lenientChatChunkToolCall/ChatChunkFunctionCalltypes (all fields optional) for the streamingdelta.tool_calls, and hardened the four sync/async archive loops (client.py,solana_client.py) with dict-tolerant accessors so any futuremodel_constructfallback can't crash the stream. AffectsLLMClientandSolanaLLMClient, sync and async.
LLMClient.onramp(address)— Coinbase Onramp (FREE). Mints a one-timepay.coinbase.comlink to fund a wallet with fiat (card/bank, 60+ currencies → Base USDC). POSTs{address, network: "base", asset: "USDC"}to/v1/onramp/token. The x402 signature only authenticates the wallet, so the funding address must equal the signing wallet — passclient.get_wallet_address(). The returned URL is single-use and expires in ~5 min, so mint it at click time and never cache it. Base / USDC only; the address is validated against^0x[0-9a-fA-F]{40}$and a non-Coinbase URL raisesAPIError("gateway returned no onramp url"). Not added to the Solana client (Base-only). Addsvalidation.validate_eth_address.
- README payment section rewritten into an explicit two-phase money flow:
Phase 1 fund your wallet once (buy via
onramp(), transfer Base USDC, or skip with free NVIDIA models —get_balance()to check); Phase 2 every request pays itself via automatic x402. Plus per-call pay-as-you-go costs, spend tracking (get_spending()/blockrun_llm.billing), BaseScan settlement verification, and the non-custodial key-never-leaves-your-machine guarantee.
- Video poll budget default raised 5min → 15min
(
DEFAULT_GENERATE_BUDGET_SECONDS = 900). Generation itself is 1-3min, but the upstream pipeline can lag the status read-path several minutes behind actual completion (observed 2026-06-11: video done in 100s, status flipped ~7.5min later). Jobs stay claimable ~48h, so a patient default beats a premature give-up. Override per call withbudget_seconds.
- Automatic mid-poll re-signing. The x402 authorization window is 600s; on
budgets longer than that a poll eventually 402s. The client now fetches a
fresh challenge from the same poll_url and re-signs with the same wallet
(the gateway enforces wallet binding, not signature equality), capped at 2
re-signs — a fresh signature that 402s again raises
PaymentError. - Recoverable timeouts. The budget-exhausted
APIErrornow carriespoll_urlin its details and explains that the job stays claimable for ~48h — re-GET the poll_url with a fresh same-wallet signature to fetch (and settle) the finished video. A client timeout is no longer a dead end.
AsyncSolanaLLMClientnow hasimage,image_edit, andget_balance. This completes async-Solana public-method parity with the syncSolanaLLMClientand the async EVM client.image/image_editare backed by a new async_request_image_with_paymentthat handles the gateway's async202 + pollslow path (gpt-image-2, dall-e-3, nano-banana-pro 4K) — signing once and polling until completion, settling only on the completed poll.get_balanceruns the synchronous Solana RPC read in a worker thread (asyncio.to_thread) so it doesn't block the event loop.
- Video poll: terminal success is keyed on
status == "completed", not a literal HTTP 200 (parity with the Go 0.16.2 / TS 3.2.3 fixes). A completed-but-non-200 poll no longer spins to the budget deadline and raises "did not complete / no payment taken" for a job the caller was already charged for.
AsyncSolanaLLMClient.search(...)— async standalone search (Grok Live Search) parity with the syncSolanaLLMClientand the async EVM client. Thin wrapper over the async raw payment helper; same signature (query,sources,max_results,from_date,to_date,timeout).
AsyncSolanaLLMClientpassthrough parity. The async Solana client now mirrors the syncSolanaLLMClient(andAsyncLLMClient) for the data passthroughs it previously lacked: prediction markets (pm+ allpm_*), Exa web search (exa,exa_search,exa_find_similar,exa_contents,exa_answer), DefiLlama (defi+defi_*), 0x DEX (dex+dex_*), and Modal sandboxes (modal+modal_sandbox_*). Added the async raw request helpers (_request_with_payment_raw/_get_with_payment_raw) these build on, with Solana x402 signing, caching, and settlement capture.VideoClient.generate_from_content(content, …)— submits a standard Seedancecontent[]body to the gateway'sPOST /v1/videosendpoint (validates unsupported inputs before charging, then delegates to the same x402 submit+poll pipeline asgenerate). For migrating existingcontent[]-shaped payloads unchanged; most callers should still prefergenerate(...)with structured kwargs.
- DefiLlama passthrough (
/v1/defillama/*, live since 2026-05-02 — coverage backfill).defi(path, **params)plus typed conveniencesdefi_protocols/defi_protocol(slug)/defi_chains/defi_yields/defi_prices(coins)onLLMClient,AsyncLLMClientandSolanaLLMClient. $0.005/call ($0.001 for prices). - 0x DEX passthrough (
/v1/zerox/*, live since 2026-05-02 — coverage backfill). Free (no x402; BlockRun monetizes via on-chain affiliate fee):dex(path, ...)+dex_price/dex_quote/dex_gasless_price/dex_gasless_quote/dex_gasless_submit/dex_gasless_status/dex_chains/dex_gasless_chainson all three clients. - Modal sandbox compute (
/v1/modal/*, live since 2026-04-09 — coverage backfill).modal(path, body)+modal_sandbox_create($0.01 CPU / $0.05 GPU) /modal_sandbox_exec/modal_sandbox_status/modal_sandbox_terminate($0.001 each) on all three clients.
XClientand the entire X/Twitter (AttentionVC) surface. The backend removed the AttentionVC integration on 2026-04-30; every/v1/x/*endpoint has returned HTTP 404 since. Deleted:x_client.py(XClient), the 15x_*methods onLLMClient/AsyncLLMClient/SolanaLLMClient, and the 18X*response types (XUser,XTweet,XSearchResponse, ...).XSearchSource(Grok Live Searchsources:["x"]) is unrelated and stays. If you need X/Twitter data, use Grok Live Search (SearchClient/client.search(...)with thexsource) instead.
RpcClient— Multi-chain JSON-RPC (40+ chains). Mirrors the new backendPOST /v1/rpc/{network}(Tatum gateway passthrough, launched 2026-06-07). Flat $0.002 per call; a JSON-RPC batch charges per element.call(network, method, params)— single JSON-RPC 2.0 call. EVM chains speaketh_*; non-EVM (Solana / Bitcoin-family / NEAR / Sui / XRP Ledger / Polkadot) speak their native JSON-RPC.batch(network, requests)— JSON-RPC batch, priced per element.SUPPORTED_NETWORKS(40 curated chains) +NETWORK_ALIASES(eth, arb, op, matic, bnb, avax, sol, btc, xrp, dot, ...). Unknown well-formed slugs fall through server-side to{slug}-mainnet, so new Tatum chains work without an SDK update.- New types:
RpcResponse(JSON-RPC envelope +network/cache_hit/tx_hashgateway metadata),RpcError.
VideoClient.generate()new Seedance parameters (backend 2026-06-02):last_frame_url— first-and-last-frame interpolation: the model tweens fromimage_url(first frame) tolast_frame_url(final frame). Requiresimage_url+ a Seedance model. Priced as image-to-video.reference_image_urls— omni / multi-reference: up to 9 reference images for character/style consistency (Seedance 2.0 only); cite them as "image 1", "image 2" in the prompt. Mutually exclusive withimage_url/last_frame_url/real_face_asset_id.- token360 passthroughs that were already live upstream:
aspect_ratio,seed,watermark,return_last_frame. - Client-side validation mirrors the backend mutual-exclusion rules.
- Free-tier router table rebuilt from a 2026-06-07 live sweep (every
visible free model probed):
nvidia/qwen3-next-80b-a3b-thinkinghit NVIDIA end-of-life 2026-05-21 (HTTP 410) — dropped as COMPLEX/REASONING primary. COMPLEX →nvidia/qwen3-coder-480b(871ms probe); REASONING →nvidia/nemotron-3-nano-omni-30b-a3b-reasoning(681ms, explicit reasoning + vision).nvidia/mistral-small-4-119bis timing out upstream (3/3 probes >60s) — dropped as SIMPLE primary and from all fallback chains.nvidia/deepseek-v4-flashRECOVERED from the 05-09 NIM regression (896ms probe) — reinstated as SIMPLE primary.
- README free-model tables updated to match (qwen3-next retired, mistral-small flagged as timing out); sweep example pruned.
- GLM flat-rate pricing fully retired. Z.AI's remaining launch promos ended
2026-06-06:
zai/glm-5now bills per-token at $0.60/$1.92 andzai/glm-5-turboat $1.20/$4.00 (no more flat $0.001/call anywhere in the family; glm-5.1 stays $1.40/$4.40). README ZAI section rewritten. zai/glm-5removed from the ECO COMPLEX router fallback chain — its slot existed only for the flat-rate pricing; at $0.60/$1.92 the existing per-token chain (deepseek-v4-pro $0.435/$0.87 first) is both cheaper and stronger.
SpeechClient— BlockRun Voice (ElevenLabs TTS + sound effects).generate()(aliasspeak()) →POST /v1/audio/speech— OpenAI-compatible text-to-speech. Models:elevenlabs/flash-v2.5(default, $0.05/1k chars),elevenlabs/turbo-v2.5($0.05/1k),elevenlabs/multilingual-v2($0.10/1k),elevenlabs/v3($0.10/1k). Voice aliases (sarah, george, laura, charlie, river, roger, callum, harry) or raw ElevenLabs voice_ids;response_formatmp3/opus/pcm/wav; optionalspeed0.7–1.2. Price scales with character count, minimum $0.001/request.sound_effect()→POST /v1/audio/sound-effects— cinematic sound effects up to 22s, flat $0.05/generation (elevenlabs/sound-effects).list_voices()→GET /v1/audio/voices— free voice discovery (rate-limited 60 req/min/IP).- New types:
SpeechResponse,SpeechAudio.
- xAI catalog additions (resold via OpenRouter credit pool, 2026-06-04):
xai/grok-4.3($1.50/$4.00, 1M context, reasoning + vision) andxai/grok-build-0.1($1.50/$3.00, 256K, fast agentic coding). Added to the chat sweep script and README. Older Grok chat SKUs (grok-3/4/4.1-fast families) are now hidden from/v1/models; direct calls still work.
zai/glm-5.1launch promo ended (2026-06-05) — now bills per-token at $1.40/$4.40 instead of flat $0.001/call. Removed from the ECO COMPLEX router fallback chain (it became the most expensive option there);zai/glm-5(still flat $0.001/call) takes the cheap long-context fallback slot.deepseek/deepseek-v4-propricing corrected to $0.435/$0.87 — DeepSeek made the 75% launch promo the permanent list price after 2026-05-31 (README and router comments previously said the promo would expire back to list).
- Concurrent Solana payments now reach ~100% success. Sharing one
SolanaLLMClient/AsyncSolanaLLMClientacross concurrent paid requests from a single wallet previously hitinvalid_exact_svm_payload_amount_mismatchandauthorization already used(replay) rejections under load (~3-10% failures), because the underlying x402 client is not concurrency-safe and a rejected payment couldn't recover. Two fixes:- A per-client signing lock (
threading.Lockfor sync, lazyasyncio.Lockfor async) serialises the fast nonce/signature critical section. - A whole-request payment retry: a non-permanent payment rejection re-runs
the entire request with a fresh 402 probe + fresh signature (new nonce,
correct amount, current blockhash), for sync/async and streaming/non-stream
(streaming only before the first chunk, so output is never replayed). New
_is_unrecoverable_payment_errornarrows the no-retry set to genuinely terminal cases (no funds / bad key / denylisted). - Verified at concurrency 10 on a shared client: opus-4.7, gemini-3.1-pro and gpt-5.5 all went from ~69-99% to 100/100.
- A per-client signing lock (
response_format(JSON mode) andstopsequences on chat. The gateway now honors both OpenAI params on/v1/chat/completions— natively for OpenAI/Azure, and emulated for Anthropic/Bedrock (a raw-JSON system instruction with code-fence stripping for{"type": "json_object"};stopmapped tostop_sequences). Threaded throughchat,chat_completion, andchat_completion_streamon bothLLMClientandSolanaLLMClient(sync and async). Example:client.chat("openai/gpt-4o", "...", response_format={"type": "json_object"}).- Genuine
openai/gpt-4oandopenai/gpt-4o-minidocumented in the README pricing table (gpt-4o $2.50/$10.00 · 128K; gpt-4o-mini $0.15/$0.60 · 128K). The gateway no longer substitutes gpt-5.x for these IDs.
SolanaLLMClientno longer truncates long chats and slow images at 60s. The historical flatDEFAULT_TIMEOUT = 60.0applied to every method on the mega-class — chat, image, music, search, X, exa, pyth — while the Base SDK splits the same surface across per-use-case clients (LLMClient=120s,ImageClient=200s,MusicClient=210s,VideoClient=360s). Long chats with highmax_tokens, slow image generations, and deep search queries were silently dying inside the SDK at 60s. Raises the flatDEFAULT_TIMEOUTto120.0(matches Base chat) and introduces per-use-case constants (DEFAULT_CHAT_TIMEOUT,DEFAULT_IMAGE_TIMEOUT,DEFAULT_SEARCH_TIMEOUT,DEFAULT_FAST_TIMEOUT). Each request now carries the timeout for its workload rather than the single client default:image()/image_edit()useDEFAULT_IMAGE_TIMEOUT(200s),search()and theexa_*methods useDEFAULT_SEARCH_TIMEOUT(300s), and chat uses the 120s baseline — sync and async. Closes #7.solana_key_to_bytes()now wraps every failure in the documentedValueError("Invalid Solana private key: …"). A bareexcept ValueError: raiseused to let modernbase58's raw "Invalid character" error escape past the wrapper, so callers (and thetest_invalid_key_raisestest) matching on the documented message broke. All decode failures are now wrapped consistently.transaction_simulation_failedno longer wastes 5+ minutes on pointless retries. Adds a_PERMANENT_PAYMENT_PATTERNStable mirroring the gateway-sideblockrun-sol/src/lib/x402-solana.tsPERMANENT_ERRORSclassification._should_fallback_solananow short-circuits when the exception's reason matches a permanent pattern — even when the exception type itself is "transient" (httpx.Timeout,httpx.NetworkError). Worst-case wall-clock for a deterministic Solana settlement failure drops from ~5min (3 generation attempts) to one attempt's worth. Closes #6.
- New module-level helpers:
_is_permanent_payment_error(reason: str) -> bool— case-insensitive substring match against the permanent classification, used by both the streaming fallback decision and any future retry classifier so one policy applies everywhere.DEFAULT_CHAT_TIMEOUT,DEFAULT_IMAGE_TIMEOUT,DEFAULT_SEARCH_TIMEOUT,DEFAULT_FAST_TIMEOUTconstants (importable fromblockrun_llm.solana_client) so callers can use the same numbers as the SDK does.
-
Per-call
timeout=override on every long-running public method (level 2 of #7) —chat,chat_completion,chat_completion_stream,image,image_edit,search, sync and async. The kwarg wins over the per-use-case default and the constructor value, so a single oversized request can raise (or tighten) its own budget without reconfiguring the client:client.chat_completion(model, messages, max_tokens=8192, timeout=240) client.image("...", model="openai/gpt-image-2", timeout=300)
-
image_timeout/search_timeoutconstructor parameters on bothSolanaLLMClientandAsyncSolanaLLMClient(defaulting toDEFAULT_IMAGE_TIMEOUT/DEFAULT_SEARCH_TIMEOUT) — mirrors the per-client tuning the Base SDK gets from separateImageClient/ search-awareLLMClientclasses.
SolanaLLMClient(..., timeout=<float>)still works, but the default value of the constructor parameter is nowDEFAULT_CHAT_TIMEOUT(120s) instead of the old 60s, and it governs the chat baseline specifically; image and search read from their own constructor parameters / constants. Callers passing an explicit value are unaffected.
- 18 Base SDK clients still emit the generic
PaymentError("Payment was rejected. Check your wallet balance.")— see the v0.32.0 follow-up note. Tracked separately.
anthropic/claude-opus-4.8($5/$25 per M, 1M context, 128K output, agentic coding + adaptive thinking) — Anthropic's most capable Claude. Promoted toPREMIUM_TIERS["COMPLEX"]primary; opus-4.7 and opus-4.5 retained as fallbacks. Also replaces opus-4.7 in thePREMIUM_TIERS["REASONING"]fallback chain. Added to the README pricing table andexamples/sweep_all_chat_models.py.
- Image generation 202 + poll slow path now handled transparently in both
ImageClient.generate()/.edit()(Base) andSolanaLLMClient.image()/.image_edit()(Solana). Slow models (openai/gpt-image-2,openai/dall-e-3,google/nano-banana-proat 4K, etc.) routinely exceed the gateway's 30s inline window and come back as202+poll_urlinstead of the finished image. The Solana path used to pass the job stub straight toImageResponse(**data)and crash with a Pydantic ValidationError ("missing fielddata"); the Base path raised a confusingAPIError 202. Both now poll the samepoll_urlwith the same PAYMENT-SIGNATURE onIMAGE_POLL_INTERVAL_SECONDS(5s default) untilstatus: completed, then return the parsedImageResponse. Settlement only happens on the completed poll, so timing out the budget (IMAGE_POLL_BUDGET_SECONDS, 300s default) raisesAPIError 504and no payment is taken. - PaymentError now preserves the gateway's real failure reason. On a 402
retry response, the SDK used to raise a generic
"Payment rejected. Check your Solana USDC balance."— losing the facilitator's actual reason (transaction_simulation_failed,insufficient_funds,payment_expired, etc.). The newPaymentError(message, *, status_code=..., response=...)keyword args carry the gateway body so callers and upstream proxies can surface the real reason. All fourSolanaLLMClientretry paths (sync raw, sync get, sync stream, async post, async stream) and the BaseImageClientretry use the sharedvalidation.build_payment_rejected_errorhelper.
PaymentErrorconstructor is now keyword-extended. ExistingPaymentError("...")calls are unchanged. The two new optional kwargs arestatus_code: Optional[int]andresponse: Optional[dict].
blockrun-litellm >= 0.3.9surfacesPaymentError.response.detailson the 402 HTTP body. If you wrapPaymentErroryourself, pullexc.response.get("details")for the structured facilitator reason.- Follow-up: 18 other Base SDK clients (
client.py,phone.py,realface.py,surf.py,voice.py, etc.) still inline the legacyraise PaymentError("Payment was rejected. Check your wallet balance.")pattern. They should migrate tobuild_payment_rejected_errorin a follow-up PR — not blocking, but customers debugging settlement failures on those endpoints still lose context until then.
google/gemini-3.5-flash— Google's newest-generation Flash with built-in thinking mode: frontier-class quality at Flash speed and pricing ($0.50/M in, $3.00/M out, 1M context). Now live in production. Added to the README model pricing table and wired into the smart router's COMPLEX tier as the leading fallback (ahead ofgoogle/gemini-3-flash-preview, which remains available).
- Default image-edit model is now
openai/gpt-image-2(wasopenai/gpt-image-1) acrossImageClient.edit(),LLMClient.image_edit()(sync + async), andSolanaLLMClient.image_edit(). Matches the production/v1/images/image2imageschema default and aligns Python, TypeScript, and Go SDKs. Passmodel=explicitly to keep using the cheapergpt-image-1.
- Multi-image fusion across all edit entry points. The
imageparameter now acceptsUnion[str, List[str]]onImageClient.edit(),LLMClient.image_edit()(sync + async), andSolanaLLMClient.image_edit()— pass a single base64data:image/...data URI to edit one image, or a list of 2–4 URIs to fuse them (e.g. a subject photo + a brand logo). Matches the now-live/v1/images/image2imagecontract, which previously rejected arrays with400 "expected string, received array". Single-string calls are unchanged and fully backward compatible. Fusion caps mirror the server:openai/*up to 4 source images,google/*(Nano Banana) up to 3; amaskcannot be combined with multiple source images.
- Documented the full set of edit-capable models (
openai/gpt-image-1,openai/gpt-image-2,google/nano-banana,google/nano-banana-pro) and corrected theedit()/image_edit()docs, which incorrectly claimed a plain URL was accepted — the route requires a base64data:image/...data URI.
-
RealFaceClient— real-person face enrollment via x402. RealFace registers a real person's likeness (vs.PortraitClient, which is for AI-generated characters). The asset works exactly like a Virtual Portrait on Seedance 2.0 / 2.0-fast — both return ata_xxxxxxxxid you pass asreal_face_asset_idonVideoClient.generate()— but enrollment proves the rights-holder is the person in the photo via a brief on-phone liveness check. No KYC. Three-step flow:init(name)— free, rate-limited. Returns agroup_id+ anh5_linkthe real person scans on their phone.status(group_id)/wait_for_active(group_id)— free. Poll until the person finishes the liveness check.enroll(name, image_url, group_id)— $0.01 USDC, one-time. Settles only after the face matches the live capture, so425(group not active),422(face mismatch), and502(upstream failure) return errors with no charge.
Plus
list_realfaces()over the freeGET /v1/wallet/<address>/realfacesendpoint.from blockrun_llm import RealFaceClient faces = RealFaceClient() init = faces.init(name="Jane — spokesperson") # show init.h5_link as a QR faces.wait_for_active(init.group_id) # they do the phone check rf = faces.enroll(name="Jane — spokesperson", image_url="https://example.com/jane.jpg", group_id=init.group_id) print(rf.asset_id) # ta_… → pass as real_face_asset_id on Seedance 2.0
-
RealFaceInit,RealFaceStatus,RealFaceEnrollment,RealFaceList,RealFaceListItemexported from the package root.
- Reversed the v0.28.1 "real-person video is unsupported" stance.
Real-person likeness is now supported through the no-KYC RealFace liveness
flow above (KYC is no longer required). The
VideoClientclass/parameter docstrings, thereal_face_asset_idvalidator message, and the README now describereal_face_asset_idas accepting either a Virtual Portrait (PortraitClient, $0.01) or a RealFace (RealFaceClient, $0.01). No wire-format change — both still pass the sameta_id.seedance-1.5-prodoes not support either asset type.
-
PortraitClient— Virtual Portrait enrollment via x402. WrapsPOST /v1/portrait/enroll($0.01 USDC, one-time, no KYC) and the freeGET /v1/wallet/<address>/portraitslisting endpoint. Enroll an AI character image, get back ata_xxxxxxxxasset id, then reuse it asreal_face_asset_idonVideoClient.generate()for Seedance 2.0 / 2.0-fast to keep the same character across multiple videos. Settlement is held until upstream registration succeeds, so failed enrollments (content filter, image too large) return 502 with no charge.from blockrun_llm import PortraitClient p = PortraitClient().enroll( name="My Spokesperson", image_url="https://example.com/character.jpg", ) print(p.asset_id) # ta_abcdef1234567890 print(p.settlement.tx_hash) # 0x9f3a…
-
PortraitEnrollment,PortraitUsage,PortraitSettlement,PortraitList,PortraitListItemexported from the package root.
VideoClientSeedance docs realigned with the (then-)dropped RealFace path. (Reversed in 0.29.0 — real-person video is now supported via the no-KYC RealFace liveness flow.) At the time, theVideoClientclass docstring, thereal_face_asset_idparameter docstring, the validator error message, and the README example were changed to describereal_face_asset_idexclusively as a Virtual Portrait (POST /v1/portrait/enroll, $0.01, no KYC). No behavior change — the wire format (theta_id) is unchanged.
VideoClient.generate()— face-reference, resolution, and audio controls to align with the documented/v1/videos/generationsschema:real_face_asset_id="ta_xxxxxx"— condition Seedance 2.0 fast/pro on a Virtual Portrait or Token360 RealFace asset. Validates theta_prefix and is mutually exclusive withimage_url.resolution="360p" | "480p" | "720p" | "1080p" | "4K"— drop to 480p for ~half the per-clip Seedance cost; bump to 1080p / 4K for higher fidelity. Grok ignores this field.generate_audio=True/False— override Seedance's default (audio on for text-to-video, off for image- or face-conditioned). Grok ignores.
- Refreshed Seedance pricing in the
VideoClientdocstring and README to match the live per-M-token billing (token360 charges by tokens at ~20,256 tok/sec at 720p), replacing the old per-second figures:bytedance/seedance-1.5-pro— $4.32/M (flat) ≈ $0.46 / 5s 720pbytedance/seedance-2.0-fast— $11.20/M text · $6.60/M imagebytedance/seedance-2.0— $14.00/M text · $8.60/M imagexai/grok-imagine-videounchanged at $0.050/sec.
-
Opt-in per-transaction log to a project-local folder. Pass
transaction_log=TruetoLLMClient,AsyncLLMClient,SolanaLLMClient, orAsyncSolanaLLMClient(or setBLOCKRUN_TX_LOG=1) and every paid call appends one plain-text row to./log/transactions.log:2026-05-21 15:44:46 chat anthropic/claude-sonnet-4.6 in= 3 out=4 $0.034137 0x6513d128…Columns: timestamp, endpoint tag, model (left-padded 30), prompt/completion tokens, USD cost (6 decimals), and the first 10 chars of the on-chain settlement hash (Base tx hash or Solana signature). The hash is decoded from the
X-PAYMENT-RESPONSEheader the facilitator returns after settlement, so each row is verifiable against BaseScan / Solscan with one click — the row matches what hit the ledger.Pass a string/Path instead of
Trueto choose a different directory. Disabled by default; no impact on the existing~/.blockrun/cache,~/.blockrun/data/, or~/.blockrun/cost_log.jsonllayers — this lives in its own folder next to your code. -
TransactionLogger,decode_settlement_header,format_roware exported from the package root for callers who want to build their own reconciliation tooling on top of the same primitives.
-
PhoneClient— Twilio-backed phone lookup + number provisioning via x402. New moduleblockrun_llm/phone.pywraps the backend's/v1/phone/*partner endpoints. Methods:lookup(phone_number)— carrier + line-type ($0.01)lookup_fraud(phone_number)— adds SIM-swap / call-forwarding signals ($0.05)buy_number(country="US", area_code=None)— provision a US/CA number with a 30-day lease bound to your wallet ($5.00). Settlement is held until Twilio confirms the purchase, so failed buys never charge your wallet.renew_number(phone_number)— extend by 30 days ($5.00)list_numbers()— list your active numbers ($0.001)release_number(phone_number)— return a number to the pool (free, still flows through x402 for wallet-identity verification) Use the provisioned number as thefrom_caller ID inVoiceClient.call().
-
SurfClient— asksurf.ai crypto-data gateway via x402. New moduleblockrun_llm/surf.pywraps/v1/surf/*and exposes ~83 endpoints covering exchange data, on-chain SQL, prediction markets (Polymarket + Kalshi), wallet/social analytics, and project intelligence. Tiered pricing matches the backend: tier 1 / 2 / 3 → $0.001 / $0.005 / $0.020. API:SurfClient.endpoints()— full discovery catalogSurfClient.endpoint_info(path)/SurfClient.price(path)— single-endpoint metadataclient.get(path, params)/client.post(path, body)— direct callersclient.call(path, params=…, body=…)— auto-routes GET vs POST from the catalog Required-param validation runs client-side before the network round trip.
VoiceClient.call()docs reflect newfromresolution on the backend: iffrom_is omitted and your wallet owns exactly one active number, the backend auto-picks it; 0 owned → 403no_active_number; 2+ owned → 400ambiguous_fromwith the candidate list in the error body. No code change was needed — the SDK already forwardedfrom_correctly — but the docstring was stale.
VoiceClient— AI-powered outbound phone calls via x402. New moduleblockrun_llm/voice.pywraps the backend'sPOST /v1/voice/call(paid, $0.54/call) andGET /v1/voice/call/{call_id}(free polling). The AI agent dials a US/Canada E.164 number and conducts a real-time conversation following yourtaskinstructions; STT + LLM + TTS are handled upstream by Bland.ai. Full pass-through forfrom,voice(7 presets + custom Bland IDs),max_duration(1–30 min),language,first_sentence,wait_for_greeting,interruption_threshold, andmodeltier (base / enhanced / turbo). Status polling returns the full Bland call record (status, transcript, recording URL, ended_reason). Exported asVoiceClientfromblockrun_llm. See README "Voice Calls" section for usage.
-
Default Solana RPC is now BlockRun's proxy —
SolanaLLMClient/AsyncSolanaLLMClientresolve their RPC endpoint tohttps://sol.blockrun.ai/api/v1/solana/rpcwhen noSOLANA_RPC_URLenv var or explicitrpc_urlarg is set. This is BlockRun's own multi-region, Tatum-backed Solana JSON-RPC proxy. It is free for anyone using the SDK — the cost is bundled into LLM inference fees you already pay. Method-aware caching on the server (getLatestBlockhashat 30s TTL) collapses bursty signing traffic to a handful of upstream RPC calls, so partners no longer need to register Helius / Tatum / QuickNode for typical loads.The previous default
https://api.mainnet-beta.solana.comis still reachable viaSOLANA_RPC_URL=...but is no longer the default — its public rate limit (~10-40 RPS) is too aggressive for any real concurrency.No code change required to opt in: upgrade and you're using it. To stay on a private Helius / Tatum / QuickNode RPC, set
SOLANA_RPC_URL(the 0.23.0 env-var mechanism is unchanged).
XClient(BlockRun/v1/x/*AttentionVC integration) — the backend/v1/x/*endpoints were removed on 2026-04-30. AllXClientmethod calls now return HTTP 404 until a replacement X/Twitter data upstream is reintroduced. The class is kept in the SDK so existing imports do not break; instantiation now emits aDeprecationWarningso callers can migrate cleanly when a replacement ships.
-
Custom Solana RPC support via env vars — Solana clients (
SolanaLLMClient+AsyncSolanaLLMClient) now resolve their RPC endpoint from explicit args, then these env vars, then the public default:SOLANA_RPC_URL— the JSON-RPC endpoint URL. Use this when your provider embeds auth in the URL (Helius style:https://mainnet.helius-rpc.com/?api-key=...).SOLANA_RPC_API_KEY— convenience shortcut for the commonx-api-key: <value>header style (Tatum, some Triton tiers). Internally becomesSOLANA_RPC_HEADERS='{"x-api-key":"..."}'.SOLANA_RPC_HEADERS— JSON dict for arbitrary header auth ('{"x-api-key":"...","x-rate-tier":"pro"}').
This unblocks production traffic — the public
api.mainnet-beta.solana.comrate-limits aggressively (~10-40 RPS) and a partner deploying behind a free-tier Helius key was seeing failures at 30-100 concurrent requests.Previously the only way to switch RPCs was to edit
_adapter.pysource; that change is lost on every upgrade. Env vars make this idempotent across releases. -
Header-auth Solana gateways (Tatum, header-only Triton) now work — the upstream x402 SDK's
register_exact_svm_clientonly takesrpc_url, not custom headers, so the underlyingsolana.rpc.api.Clientwas always built withoutextra_headers. We now pre-populate the SVM scheme's client cache with a properly-configuredSolanaClientbefore any payment payload is constructed.
For Tatum (header-auth):
export SOLANA_RPC_URL=https://solana-mainnet.gateway.tatum.io
export SOLANA_RPC_API_KEY=t-...For Helius (URL-embedded auth):
export SOLANA_RPC_URL='https://mainnet.helius-rpc.com/?api-key=...'For arbitrary header schemes:
export SOLANA_RPC_URL=https://your.gateway/...
export SOLANA_RPC_HEADERS='{"x-api-key":"...","x-rate-tier":"pro"}'- Live test against
solana-mainnet.gateway.tatum.iowithx-api-keyheader — the signing pipeline (blockhash fetch + TransferChecked tx construction + signature) completed end-to-end through Tatum and submitted the payment to BlockRun's gateway. (Final on-chain settlement failed for an unrelated reason: the test wallet was empty.)
- Helius free tier is 10 RPS — adequate for low QPS, not for bursty 50-100 concurrent. Move to Helius Developer ($99/mo, 25 RPS) or Tatum (200 RPS).
- A separate
0.24.0will add client-side blockhash caching so ~10 RPS of paid traffic resolves to <1 RPS of upstream RPC calls — at that point Helius free becomes viable for most production loads. Tracked separately because the change touches the x402 scheme cache more invasively.
-
Tool calling on Solana.
SolanaLLMClient.chat_completion,SolanaLLMClient.chat_completion_stream,AsyncSolanaLLMClient.chat_completion, andAsyncSolanaLLMClient.chat_completion_streamnow accepttools/tool_choicekwargs and forward them to the upstream model. Previously the parameters were missing from the Solana SDK methods so partners couldn't use function calling on the Solana chain — but the BlockRun backend always supported the field uniformly; the SDK was the bottleneck.Live-verified:
client.chat_completion("nvidia/deepseek-v4-flash", [...], tools=[get_weather], tool_choice="auto")returnedtool_call: get_weather('{"city": "Tokyo"}')againstsol.blockrun.ai.
AsyncSolanaLLMClient— async counterpart ofSolanaLLMClient. Mirrors the sync API for chat completions (both non-streaming and streaming) soasynciocallers don't need to thread-pool around blocking I/O. Built on the asyncx402Client(instead ofx402ClientSync) +httpx.AsyncClient. Public surface for the first release:chat(),chat_completion(),chat_completion_stream(),list_models(),close()plus__aenter__/__aexit__. Image / Exa / Predexon / Music endpoints are still sync-only on Solana (they'll follow if there's demand). Same retry policy andfallback_modelssemantics as every other streaming client.- Paid streaming now writes to
~/.blockrun/cost_log.jsonland~/.blockrun/data/— closing the audit-trail gap that 0.20.x introduced.LLMClient(sync + async) andSolanaLLMClient(sync + new async) all accumulate streamed content during the SSE iteration, then callsave_to_cacheoncedata: [DONE]arrives, building a syntheticchat.completionresponse so the local archive matches the non-stream paid path one-for-one. Free models skip the archive (cost_usd == 0). Failures during the stream do not produce a partial archive row.
- Async Solana streaming via
AsyncSolanaLLMClient.chat_completion_streamagainstsol.blockrun.aiwith the freenvidia/deepseek-v4-flashmodel: 2 content chunks,"Hello! How can I", on the second attempt (first hit a transient NVIDIA NIM upstream timeout that resolved itself). - 12/12 Base streaming unit tests + 6/6 Solana streaming unit tests still pass — the archive-on-completion change is additive and doesn't touch the existing assertions.
- Streaming on Solana.
SolanaLLMClient.chat_completion_stream(...)is now a thing, mirroring the BaseLLMClientAPI one-for-one: yieldsChatCompletionChunkper SSEdata:line, does the 402 → sign-locally-with-SVM-x402 → retry-with-PAYMENT-SIGNATURE dance before the first chunk, supports the same retry policy (5xx ×3 with 1s/2s/4s backoff) andfallback_modelschain walking. - Constraint: like Base, fallback can only fire before the first chunk is yielded — once any chunk has reached the caller, switching models would concatenate two distinct responses.
- Async is not yet implemented for the Solana client (consistent with
the rest of
SolanaLLMClientwhich is sync-only today).
- 6 new mock-based unit tests in
tests/unit/test_streaming_solana.py: free-model direct streaming, paid-model sign-and-retry, recovery after 2× 503, raising after exhausted retries, fallback-chain walking, and payment-rejected →PaymentError.
- Live call against
sol.blockrun.aiwith the freenvidia/deepseek-v4-flashmodel: 2 content chunks, content "Silence.", 0.8s.
- Streaming 5xx retry policy.
_stream_with_paymentnow retries transient upstream errors (500 / 502 / 503 / 504) up to three times per phase with exponential backoff (1s / 2s / 4s), instead of the single retry shipped in 0.20.0. Both the unauthenticated probe and the paid retry honor the same policy. Tuned for NVIDIA NIM upstream flakiness on free models — most transient hiccups now self-heal before bubbling up to the caller. Exposed asLLMClient._STREAM_5XX_STATUSES/_STREAM_5XX_BACKOFFSso callers can monkey-patch the policy in tests or override at runtime. fallback_modelsparameter onchat_completion_stream(sync + async). Walks the chain when the primary upstream produces a retriable error (timeouts, network errors, 5xx after exhausting in-band retries). Constraint: fallback only triggers before the first chunk is yielded — once any byte has reached the caller, switching upstreams would concatenate two distinct responses. After-first-chunk failures propagate to the caller as before.
- Six new unit tests in
tests/unit/test_streaming.pycovering: recovery after two 503s, raising after exhausting retries, retry on the paid (post-402) retry leg, fallback to a healthy model after a primary 503-storm, no fallback after a chunk has been yielded, and no fallback on a non-retriable 4xx.
- Server-Sent Events streaming for chat completions. New methods
LLMClient.chat_completion_stream(...)andAsyncLLMClient.chat_completion_stream(...)return an iterator of :class:ChatCompletionChunkobjects, yielding one chunk per SSE event until the upstream emitsdata: [DONE]. The 402 → sign-locally → retry flow is identical to the non-streaming path; free models (e.g.nvidia/deepseek-v4-flash) stream directly without a payment dance. New types exported:ChatCompletionChunk,ChatChunkChoice,ChatChunkDelta. Validated end-to-end against the productionblockrun.aigateway (sync + async, free model). Caveats:search_parametersand the Responses-API models (codex,gpt-5.4-pro) reject streaming server-side with 400 — same constraint as the gateway. Six new unit tests cover the free path, paid 402-sign- retry path, payment rejection, and tolerance for malformed chunks. - Local billing / cost-tracking surface. Every paid call now writes a
{ts, endpoint, cost_usd, model, wallet, network, client_kind}row to~/.blockrun/cost_log.jsonl. New helpers on top:get_cost_log_summary(*, from_date, to_date, wallet, network, group_by)— aggregate byendpoint/model/wallet/network/client_kind/day/month.export_cost_log_csv(...)andexport_cost_log_json(...)— render filtered per-call records, optionally to a file.python -m blockrun_llm.billing summary | export {csv|json}CLI with--from / --to / --wallet / --network / --group-by / --outputflags. Older 3-field cost-log rows remain readable;by_endpointis still emitted as a backwards-compat alias when grouping by endpoint.
- Predexon v2 typed helpers — full coverage across sync, async, Solana.
All three clients now expose the same 17
pm_*methods:- Canonical cross-venue (Tier 1):
pm_markets,pm_listings,pm_outcome - Polymarket (Tier 1):
pm_polymarket_markets,pm_polymarket_events,pm_polymarket_markets_keyset,pm_polymarket_events_keyset,pm_polymarket_positions,pm_polymarket_trades,pm_polymarket_leaderboard - Kalshi / Limitless (Tier 1):
pm_kalshi_markets,pm_limitless_markets - Sports (Tier 1):
pm_sports_categories,pm_sports_markets - Wallet identity (Tier 2):
pm_wallet_identity,pm_wallet_identities,pm_wallet_cluster
- Canonical cross-venue (Tier 1):
exa_*methods onLLMClient(Base USDC).exa(),exa_search(),exa_find_similar(),exa_contents(),exa_answer()— same surface and pricing as the existingSolanaLLMClientversions ($0.01/request for search / find-similar / answer, $0.002/URL for contents).fallback_models=[...]onchat()andchat_completion()(sync + async). On timeout, network error, or 5xx, the SDK transparently walks the list before raising. 4xx andPaymentErrorpropagate immediately. Each fallback hop logs one line to stderr so the caller can see which model actually served the response.smart_chat()uses the tier's fallback chain automatically.RoutingDecisiongained afallbacks: List[str]field populated from the chosen tier;smart_chat()plumbs it through tochat().examples/sweep_all_chat_models.py— runnable end-to-end sweep over every chat model the SDK exposes, with a forward-compat diff against/v1/models, async smoke, budget guard, and optional JSON output.examples/sweep_all_media_models.py— sister script for image and music models. Video is excluded by design (long polling, expensive).- New chat models in router / pricing tables:
anthropic/claude-opus-4.7($5/$25 per M, 1M context, 128K output, agentic coding + adaptive thinking) — promoted toPREMIUM_TIERS["COMPLEX"]primary; opus-4.5 retained as fallback.zai/glm-5.1(flat $0.001/call, 200K context) — added toECO_TIERS["COMPLEX"]fallback chain for long-context work.
/v1/images/modelsis deprecated; image models live in/v1/modelswithcategories: ["image"].list_image_models()(module-level, sync, async) andlist_all_models()now read the unified catalog with the same return shape, so existing callers keep working without an extra request.- Pricing reads aligned with the current
/v1/modelsschema._get_model_pricing()now reads nestedpricing.input/pricing.outputfor paid models andpricing.flatfor flat-billed models, falling back to the legacy top-level keys. Router cost estimates and savings % reflect the right numbers again, and flat-billed models compete in routing decisions on the right basis. FREE_TIERS["MEDIUM"]primary moved fromnvidia/deepseek-v4-flashtonvidia/llama-4-maverick; v4-flash references inAUTO_TIERS/ECO_TIERS/FREE_TIERSfallback chains likewise redirected so the safety net hits a working model when the primary is unavailable.- ZAI GLM-5 family pricing corrected from per-token to flat $0.001/call across the README pricing tables to match the catalog.
- OpenAI dated-version responses (e.g.
gpt-5.5-2026-04-20for a request toopenai/gpt-5.5) are no longer flagged as redirects — only base-id mismatches count.
black-forest/flux-1.1-pro— dropped from the README image table and from the media-sweep target list. Not in the live catalog.
- Predexon v2 endpoints exposed via typed helpers. All v2 endpoints went live in production on 2026-05-07 (
blockrun-web-00451-cnw). The genericpm()/pm_query()passthrough already handled them, but agents can now discover the new shape from method names + docstrings. Ten new convenience methods onLLMClient— each is a thin wrapper, no breaking changes to the existingpm()API:- Canonical cross-venue (Tier 1):
pm_markets(**filters),pm_listings(**filters),pm_outcome(predexon_id). Predexon's unified data layer with cross-venue IDs across Polymarket, Kalshi, Limitless, Opinion, Predict.Fun. - Polymarket keyset pagination (Tier 1):
pm_polymarket_markets_keyset(**filters),pm_polymarket_events_keyset(**filters)— cursor-based for stable traversal of large result sets. - Sports markets (Tier 1):
pm_sports_categories(),pm_sports_markets(**filters). - Wallet identity & clustering (Tier 2):
pm_wallet_identity(wallet)(GET),pm_wallet_identities(addresses)(POST, up to 200),pm_wallet_cluster(address)(GET on-chain relationship graph).
- Canonical cross-venue (Tier 1):
pm()/pm_query()docstrings updated to advertise v2 examples and surface the Tier 1 / Tier 2 split inline.
- DeepSeek V4 family in paid catalog. Backend added
deepseek/deepseek-v4-pro(1.6T MoE / 49B active, 1M context — strongest open-weight reasoner; MMLU-Pro 87.5, GPQA 90.1, SWE-bench 80.6, LiveCodeBench 93.5; $0.50 in / $1.00 out per 1M under the 75% promo through 2026-05-31, list $2.00/$4.00). The legacydeepseek/deepseek-chatanddeepseek/deepseek-reasonerIDs are now V4 Flash non-thinking / thinking modes — repriced to $0.20 in / $0.40 out per 1M, 1M context (was $0.28/$0.42, 128K). Same upstream asnvidia/deepseek-v4-flashbut on the paid endpoint with higher reliability and 5MB request bodies. - Smart router: free tier primaries repointed to visible models.
FREE_TIERS["SIMPLE"]was pinned tonvidia/gpt-oss-120b(nowhidden: truein catalog — privacy-delisted from/v1/modelsthoughavailable: truefor direct callers) andFREE_TIERS["MEDIUM"]tonvidia/deepseek-v3.2(hidden — NVIDIA NIM hung, backend redirects to v4-flash). Both are absent from/v1/models, so Python's pricing dict (built from that endpoint) could not resolve them and SmartChat silently fell through. Repointed primaries to visible IDs:SIMPLE→nvidia/mistral-small-4-119b,MEDIUM→nvidia/deepseek-v4-flash. Direct calls by full ID (client.chat("nvidia/gpt-oss-120b", ...)) still work — only auto-routing changed. - Smart router: V4 Pro promoted into reasoning fallbacks.
AUTO_TIERS["REASONING"]andECO_TIERS["REASONING"]now listdeepseek/deepseek-v4-proas the first fallback afterdeepseek-reasoner(V4 Flash thinking stays primary because it's cheaper).ECO_TIERS["COMPLEX"]adds V4 Pro to fallbacks for harder reasoning tasks. - README refresh: DeepSeek pricing table shows V4 Pro / V4 Flash chat / V4 Flash reasoner with correct prices and 1M context. NVIDIA free table notes that
gpt-oss-120b/20bare hidden from/v1/modelsbut still callable by direct ID (re-enabled 2026-04-30 after a brief privacy delisting). XClientdeprecated. BlockRun's/v1/x/*(AttentionVC-partnered) integration was removed from the backend on 2026-04-30 (commit 80dcf52). The class is kept in the SDK so existing imports do not break, but instantiation now emits aDeprecationWarning— all calls return HTTP 404 until a replacement upstream is wired up.- DeepSeek V4 thinking + tool-call multi-turn now works. Backend commit
f8a2d44(2026-05-03) preservesreasoning_contenton assistant messages withtool_callsfor DeepSeek V4 thinking-mode (deepseek-reasoner/deepseek-v4-pro) — previously the streaming/v1/messagespath stripped it, causing upstream 400 "reasoning_content in the thinking mode must be passed back" on tool-using multi-turn sessions, which the route then mis-classified as transient 503 → 5 retries with backoff on a deterministic failure. SDKChatMessagealready carriedreasoning_contentandthinkingfields, so the fix is purely server-side; this entry exists so users seeing past failures know they're resolved.
- Smart router: AUTO/ECO
SIMPLEprimaries promoted frommoonshot/kimi-k2.5→moonshot/kimi-k2.6(Moonshot's flagship — 256K context, vision +reasoning_content, $0.95 in / $4.00 out per 1M). The catalog now hideskimi-k2.5as superseded, so it no longer appears in/v1/modelsand the SDK could not resolve its pricing — routing was silently falling through to the next fallback.kimi-k2.5retained as the first fallback for clients explicitly pinned to its pricing. - Doc refresh: README Smart Routing example output and SIMPLE tier table now reference
moonshot/kimi-k2.6.
- New flagship model:
openai/gpt-5.5(released 2026-04-23, first fully retrained base since GPT-4.5). 1M context, 128K output, native agent + computer use. Pricing $5.00 / $30.00 per 1M tokens. - Smart router:
PREMIUM_TIERS["MEDIUM"]now points atopenai/gpt-5.5;gpt-5.4demoted to first fallback. The cost-savings baseline inestimate_costwas rebased from GPT-5.4 ($2.50/$15) to GPT-5.5 ($5.00/$30) so reported savings stay meaningful against the current flagship. - Doc-example refresh:
AnthropicClientcross-provider example andexamples/arbitrage_analyzer.pyfrontiertier now referenceopenai/gpt-5.5. - Reconciles
__version__andVERSION(previously drifted at 0.16.1 vs 0.15.0); both now 0.17.0.
ImageClientdefault timeout 120s → 200s. The gateway's per-call OpenAI timeout forgpt-image-2was bumped to 180s server-side (it routinely takes ~120-180s at 1536x1024 and larger), so the SDK's old 120s default was cutting the request before the server had a chance to return. New default leaves ~20s of buffer above the server cap. Existing users passing an explicittimeout=are unaffected.
- VideoClient switches to async submit+poll. Upstream
/v1/videos/generationsmoved from sync to async on 2026-04-23 (submit returns a job id; client polls until completion). Public signature ofVideoClient.generate(...)is unchanged — still blocks until the video is ready and returnsVideoResponsewith the MP4 URL and tx hash. Internally the client now signs once, submits, and replays the same signature on GET polls every 5s until upstream completes. Settlement only fires on the first completed poll, so upstream failure or budget exhaustion = zero charge. - Added
budget_secondsparameter togenerate()(default 300s) to cap the polling window. - Bumped advertised
max_timeout_secondson video requests from 300s to 600s so the signed auth stays valid across the full polling window.
- New image model:
openai/gpt-image-2(ChatGPT Images 2.0). Reasoning-driven generation with multilingual text rendering + character consistency. Pricing: $0.06 for 1024² / $0.12 for 1536×1024 or 1024×1536. Supports bothclient.generate()andclient.edit()via the/v1/images/image2imageendpoint. - New video models: 3 ByteDance Seedance variants on
VideoClient:bytedance/seedance-1.5-pro— $0.03/sec, 720p, 5s default (up to 10s).bytedance/seedance-2.0-fast— $0.15/sec, ~60-80s generation, sweet-spot price/quality.bytedance/seedance-2.0— $0.30/sec, 720p Pro quality. All support text-to-video and image-to-video. Pass the model ID toVideoClient.generate(..., model=...).
- README Image/Video sections list new models; image editing section notes
gpt-image-1andgpt-image-2as supported. - Also:
pyproject.tomlversion was stuck at 0.13.0 despite__version__saying 0.14.1 (prevented PyPI publishes from shipping the NVIDIA refresh). Both now aligned at 0.15.0.
- NVIDIA free-tier refresh (backend 2026-04-21). Router updated to point at the current survivors + the two new models:
nvidia/qwen3-next-80b-a3b-thinking(reasoning flagship, 116 tok/s) andnvidia/mistral-small-4-119b(fastest free chat, 114 tok/s). - Retired IDs no longer referenced by
router.py:nvidia/nemotron-super-49b,nvidia/nemotron-ultra-253b,nvidia/mistral-large-3-675b. The backend still redirects them, but offline routing now points at the canonical successors (nvidia/qwen3-next-80b-a3b-thinking,nvidia/mistral-small-4-119b,nvidia/llama-4-maverick,nvidia/glm-4.7). - AUTO / ECO
SIMPLEprimaries switched fromnvidia/kimi-k2.5(retired) tomoonshot/kimi-k2.5— backend redirect still works, but the router now references the canonical target. - README NVIDIA table refreshed (8 visible models +
moonshot/kimi-k2.5).
- New
SearchClient— wrapsPOST /v1/search(standalone Grok Live Search). $0.025 per source + margin, 1–50 sources per call. - New
XClient— 13 methods mapping the/v1/x/*endpoints (user lookup/info/followers/following/verified-followers/tweets/mentions, tweet lookup/replies/thread, search, trending, articles/rising). Replaces orphanedX*types that had no caller. - New
PriceClient— Pyth-backed market data with.price(),.history(),.list_symbols(). Crypto, FX and commodity are fully free (price + history + list); stocks across 12 markets (us/hk/jp/kr/gb/de/fr/nl/ie/lu/cn/ca) and theusstocklegacy alias charge for price + history, list stays free. The client handles both paths transparently. ChatMessagegains optionalreasoning_contentandthinkingfields for reasoning-capable models (DeepSeek Reasoner, Grok 4 / 4.20 reasoning).ChatUsagegains optionalcache_read_input_tokens/cache_creation_input_tokensfor Anthropic prompt caching telemetry.Modelgains optionalbilling_mode(paid/flat/free),flat_price,categories,hiddensolist_models()can surface full backend metadata.- New market-data types:
PricePoint,PriceBar,PriceHistoryResponse,SymbolListResponse. VERSIONfile synced to match__init__.py.
- New
VideoClient— generate AI videos viaxai/grok-imagine-video($0.05/sec, 8s default). VideoResponse,VideoClip,VideoModeltypes added.- Text-to-video and image-to-video supported; client blocks until polling completes (~30-120s).
ImageDatanow exposessource_urlandbacked_upfor gateway-mirrored assets.- Grok Imagine image models (
xai/grok-imagine-image,-pro) routable viaImageClient. - Grok 4.20 chat models (
xai/grok-4.20-reasoning,-non-reasoning,-multi-agent) routable via the chat API.
- 43+ models supported
- Base and Solana chain payments
- x402 v2 protocol
- Image generation support
- Anthropic-compatible client
- Smart model routing
- Response caching