Skip to content

Releases: BlockRunAI/blockrun-llm

v1.9.0 — client-side spend limits

Choose a tag to compare

@VickyXAI VickyXAI released this 21 Jul 21:26
1f77d4a

Added

  • Client-side spend limits. max_cost_per_call and max_session_cost on
    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_CALL and BLOCKRUN_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 SpendLimitError carries quoted_usd,
    limit_usd and scope ("call" or "session"), and subclasses
    PaymentError so 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_usd and signed the quote in the next statement, with nothing compared
    against anything — while chat_completion documented a
    PaymentError: If budget is set and would be exceeded for a budget
    parameter that did not exist. That docstring is now true.

v1.8.2 — payment-safety fixes, and a version the package reports honestly

Choose a tag to compare

@VickyXAI VickyXAI released this 21 Jul 15:00
a94d964

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.

Security

  • A failed paid request no longer triggers a second payment, on either
    chain.
    Any error raised after the PAYMENT-SIGNATURE went 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_chat on 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 as APIError(503) — exactly a status the fallback logic treats as
    retriable. Callers catching httpx.TimeoutException are 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 raised TypeError and 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() advertised PaymentError: If budget is set and would be exceeded. There is no budget parameter anywhere in the SDK and no
    client-side spend cap — every 402 quote is signed automatically. The
    docstring now says that plainly and points at get_spending().

Changed

  • max_tokens above 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.8 sent 262144 and 1000000 both quote
    the 128000 price, and gpt-5.2 sent 1e12 returns a quote rather than a 400.
    The 402 disclosed the clamp in resource.description and 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 ValueError around MAX_TOKENS_SANITY_LIMIT claimed the
    gateway "enforces the real per-model ceiling and reports it". It does not.
    Both now describe clamping.

Fixed

  • max_tokens is validated on Solana too. validate_max_tokens was 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, so
    max_tokens=2_000_000 raised on Base and was signed and sent on Solana. With
    the gateway clamping rather than rejecting, nothing on either side caught it.
  • bool no longer passes as a number. bool is an int subclass, so
    isinstance(True, int) is True and max_tokens=True, temperature=True
    and top_p=False all sailed through and reached the wire as JSON true /
    false. All three numeric validators now reject it, naming bool rather
    than saying "must be a number" — which reads as wrong to anyone who knows
    bool is 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.

1.8.1 — max_tokens no longer capped below real model ceilings

Choose a tag to compare

@VickyXAI VickyXAI released this 21 Jul 05:17

validate_max_tokens rejected anything over 100,000 client-side. That number was not a model limit and not the gateway's — a hardcoded sanity check with no comment that quietly became the binding constraint on every caller. Asking zai/glm-5.2 for the 262,144 it advertises raised a ValueError instead of sending a request.

Probed the live gateway 2026-07-21 with the guard bypassed: 19 models advertise a ceiling above 100,000 and all 19 accepted itzai/glm-5.2 at 262,144, and the whole 128,000 class (claude-opus-4.8, claude-sonnet-5, claude-fable-5, gpt-5.6-sol/terra/luna, gpt-5.5, gpt-5.4, gpt-5.3-codex, glm-5/5.1/5-turbo). Zero rejections.

MAX_TOKENS_SANITY_LIMIT is now 1,000,000, documented as a typo guard. The bound stays so a stray 1e9 or a byte count fails locally rather than becoming a payment quote; it just must never be reachable by a real request.

The old message made this worse: max_tokens too large (maximum: 100000) reads like a provider response, and during an investigation it was taken for exactly that and recorded as an upstream model ceiling in a downstream token table. A test now pins that the message names the SDK's own number and disclaims being a model limit.

373 pass.

v1.7.0 — video input_type + Solana image quality

Choose a tag to compare

@VickyXAI VickyXAI released this 15 Jul 21:53
81a6f9f

Adds the two media params the gateway already accepts but the SDK had no way to send.

Added

input_type on video generationVideoClient.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 returns 400 before charging when the declared value disagrees.

Worth it when seed fields are built dynamically: if image_url arrives empty, the request otherwise degrades silently to text-to-video and you still pay for the clip. Declaring input_type="image" makes that a 400 instead. Accepted on both chains.

client.generate(
    "the portrait turns to face the camera",
    model="bytedance/seedance-2.0",
    image_url=maybe_empty_url,   # if this is falsy...
    input_type="image",          # ...you get a 400, not a text-to-video bill
)

quality on Solana image generation + editingSolanaLLMClient.image / image_edit, sync and async. low / medium / high / auto for openai/gpt-image-*; low meaningfully cuts generation time.

Solana only, by design. The Base gateway defines no quality field and strips unknown keys, so a value sent there would be silently dropped. ImageClient.generate/edit therefore keep rejecting it — now with a hint naming the Solana client.

Not included

Reference-to-video (reference_videos / reference_audios) is deliberately not exposed. Both gateways gate it behind R2V_ENABLED, currently off, so every call would return 503. It slots in once that flips.

Notes

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 in the SDK would only drift. Enums verified equal to the gateway zod enums on both chains.

336 unit tests; CI green on 3.9 / 3.11 / 3.12. Full notes in CHANGELOG.md, which also backfills 1.5.0 → 1.6.1.

v1.4.2 — Solana wallet auto-load (parity with Base)

Choose a tag to compare

@VickyXAI VickyXAI released this 15 Jun 00:09

Fixed

  • Solana clients auto-load the on-disk wallet (parity with Base). SolanaLLMClient / AsyncSolanaLLMClient now resolve the key as private_keySOLANA_WALLET_KEY → on-disk wallet (newest ~/.<provider>/solana-wallet.json, else ~/.blockrun/.solana-session), so SOLANA_WALLET_KEY is no longer required when a wallet session exists. A malformed key from any source raises a clean ValueError, and an unreadable session file is treated as "no wallet" rather than crashing. (#10)

Full Changelog: https://github.com/BlockRunAI/blockrun-llm/blob/main/CHANGELOG.md

v1.4.1 — streamed tool-call fix

Choose a tag to compare

@VickyXAI VickyXAI released this 14 Jun 21:16

Fixed

  • Streamed tool calls no longer crash the SDK ('dict' object has no attribute 'delta'). OpenAI streams tool calls incrementally (first frame id+function.name, later frames only function.arguments fragments), which the strict non-stream ToolCall schema rejected — forcing a model_construct fallback that left choices as raw dicts and crashed the stream-archiving loop. Added lenient ChatChunkToolCall/ChatChunkFunctionCall types for streaming delta.tool_calls, plus dict-tolerant accessors in the four sync/async archive loops. Affects LLMClient and SolanaLLMClient, sync and async. (#9)

Notes

  • Supersedes the never-published 1.4.0 stamp, so this release also carries the Coinbase Onramp (LLMClient.onramp()) addition from 1.4.0.

Full Changelog: https://github.com/BlockRunAI/blockrun-llm/blob/main/CHANGELOG.md