Skip to content

feat(video): expose Seedance reference media and output controls - #70

Closed
KillerQueen-Z wants to merge 254 commits into
BlockRunAI:mainfrom
KillerQueen-Z:codex/seedance-capability-parity
Closed

KillerQueen-Z wants to merge 254 commits into
BlockRunAI:mainfrom
KillerQueen-Z:codex/seedance-capability-parity

Conversation

@KillerQueen-Z

@KillerQueen-Z KillerQueen-Z commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Expose reference_videos/reference_audios and supported output controls on VideoClient and synchronous/asynchronous Solana video clients. Permit up to 30 reference images on Seedance 2.5, retain frame/reference exclusivity, and preserve last_frame_url plus backup status in parsed results.

Validation

66 video/Solana media unit tests passed with mocked transport and no funded wallet. Black 24.10.0 and Ruff 0.16.0 checks passed on changed files; Python compilation and git diff checks passed.

Rollout and limits

This PR does not deploy or change R2V_ENABLED. Existing production 503s caused by an explicit off switch require a separate controlled enablement. Publish client changes after the gateway changes are deployed.

No new paid upstream renders were performed. New request/response behavior is validated against the captured public upstream parameter schema and mocked contract/payment tests; perform a small paid render smoke test before production enablement. Automatic duration, editing/extension, 2.5 reference video/audio and 1080p, callbacks and draft/flex remain held pending cost/output or lifecycle validation.

See SEEDANCE_CAPABILITIES.md for input examples and capability limits. No authentication, signed-quote format or settlement formula changes.

Related PRs

1bcMax and others added 30 commits December 28, 2025 23:00
- Document that private keys NEVER leave the machine
- Key is only used for LOCAL EIP-712 signing
- Only signatures are transmitted, not keys
- Same security model as MetaMask transactions
- Update docstring to use correct provider/model format
  (e.g., openai/gpt-4o instead of gpt-4o)
- Add examples/arbitrage_analyzer.py showing integration
  with crypto arbitrage bots

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
x402 has $0.001 minimum payment requirement
More descriptive name for Base chain wallet
- Update all code and docs to use BASE_CHAIN_WALLET_KEY
- Clearer naming since we're using Base chain wallet

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change primary env var from BASE_CHAIN_WALLET_KEY to BLOCKRUN_WALLET_KEY
- Keep BASE_CHAIN_WALLET_KEY as fallback for backward compatibility
- Update README and docstrings to reflect new naming
Add DeepSeek, Qwen, xAI Grok, OpenAI OSS models
Add Nano Banana image generation models
Remove unavailable models from list
* add ci/cd

* add manual dispatch

* clean up ci workflow

* style: format code with black

* fix: remove unused imports and fix lint errors

* fix: include sanitized error response in list_models

* test: fix unit tests to match implementation

* chore: remove coverage reporting

---------

Co-authored-by: Arthur Chiu <achiurizo@users.noreply.github.com>
- Add wallet.py for wallet lifecycle management
- Auto-load wallet from ~/.blockrun/.session (privacy-friendly name)
- Generate MetaMask-compatible EIP-681 QR codes for USDC on Base
- Add Base logo to QR codes for brand recognition
- Auto-open QR in image viewer when wallet needs funding
- Update LLMClient, AsyncLLMClient, ImageClient to auto-load from .session
- Export wallet utilities: get_eip681_uri, save_wallet_qr, open_wallet_qr
- New functions don't require wallet initialization
- list_models() fetches from /api/pricing for full model details with pricing
- list_image_models() returns empty list if endpoint unavailable (404)
…_wallet

- LLMClient() now auto-creates wallet if none exists (no more ValueError)
- Add get_balance() method to check on-chain USDC balance
- Export generate_wallet function for explicit wallet creation
- Auto-normalize private keys (add 0x prefix if missing)
- Add qrcode[pil] as dependency
- Update tests to reflect new auto-create behavior
- Add status() to wallet.py - prints wallet address and balance
- Export status in __init__.py
- Bump version to 0.3.6
- Format wallet.py with black
- Add TYPE_CHECKING import for LLMClient type hint
- Update test to match current behavior (require explicit wallet setup)
- get_balance() now tries 3 RPCs before failing
- Order: publicnode.com, mainnet.base.org, meowrpc.com
- Fixes false $0 balance issue with unreliable RPCs
- Default timeout: 60s -> 120s
- Add search_timeout param (default 300s = 5 min)
- Auto-detect search requests and use longer timeout
- Update README with timeout documentation
- Add Claude Opus 4.5 (latest Anthropic flagship)
- Add Flux 1.1 Pro (Black Forest Labs image model)
- Remove Qwen models (not currently supported)
- Add testnet_client() convenience function
- Add TESTNET_API_URL class constant
- Add is_testnet() method to check if using testnet
- Update get_balance() to use correct USDC contract per network
- Update README with testnet usage examples and setup instructions
- Add BASE_SEPOLIA_CHAIN_ID and USDC_BASE_SEPOLIA constants
- Add get_chain_config() to dynamically select chain based on network
- Add asset parameter to create_payment_payload()
- Update EIP-712 domain to use dynamic chain_id and usdc_address
- Pass asset from server payment requirements in both sync/async clients
- Add get_usdc_domain_name() function
- Mainnet USDC uses 'USD Coin', testnet uses 'USDC'
- Use correct domain name based on network for signing
Use is_testnet() to determine correct network identifier (eip155:84532
for Base Sepolia) instead of always defaulting to mainnet (eip155:8453).
API no longer returns breakdown in 402 response.
1bcMax and others added 24 commits August 26, 2026 18:30
…er from probed ids (BlockRunAI#55)

Router Core was two commits behind upstream. V3.5 rebuilds every tier chain
around ids the public catalog actually lists, so the withheld kimi-k2.5/k2.6/
k2.7, both grok-4-fast pairs, grok-4-0709, claude-opus-4.6 and
gemini-3-pro-preview leave every rung including the fallbacks. Measured against
the live catalog, each profile carried 3-4 off-catalog rungs per decision
before this; now every profile carries none. The newer generation the catalog
already sells enters as fallback rungs (GPT-5.6 Luna/Terra, Gemini 3.6 Flash,
GLM-5.3, Grok 4.3/4.5, Kimi K3, Qwen 3.7 Plus, MiniMax M3); primaries moved
only where portfolio.py already holds calibration evidence for the successor.

Priors come with it: 66 model profiles (was 30) and a 71-model capability
snapshot. Two stale capability values had been reaching the hard filter, Haiku
4.5 at 8K max output (64K) and Sonnet 4.6 at 200K context (1M).

config.py was not hand-transcribed. A TS->Python converter was validated by
running it over config.ts at the OLD pin and checking it reproduced the current
config.py tier chains line for line (223/223) before it was pointed at HEAD.

Separately, routing_profile="free" had collapsed to one model with no
fallbacks. Four of the five ids in the SDK-only FREE_TIERS table were retired
by NVIDIA and they were every primary plus all but one fallback. Nothing looked
broken because the gateway server-redirects a retired free id: the dead rung
returns 200 and answers normally while serving a different model, which is the
shape that defeats unavailable_models. The table is rebuilt from ids verified
by a two-pass probe that reads back the response's own model field, since a 200
proves nothing. nemotron-3-ultra-550b and nemotron-3-nano-omni-30b-a3b-reasoning
are excluded despite listing at $0 -- both answer as nemotron-3-nano-30b. Every
tier is back to four candidates and the free tier is no longer NVIDIA-only.

The new depth test asserts fallback depth per tier rather than membership: the
table stayed internally consistent all through the rot, so a membership check
could never have caught it.

669 unit tests pass, the 88 cross-language parity decisions included. mypy
reports the same 198 baseline errors before and after.

Co-authored-by: 1bcMax <viewitter@gmail.com>
Vendored copy update. blockrun/ci.yml's brand-script-sync fails on any drift
from brand/sync-brand-numbers.mjs, so this must land before BlockRunAI/blockrun#469.

Fixes in this build: markers inside fenced blocks can opt in with @LiVe and any
that silently drift are now reported; fence offsets are recomputed per marker
(a badge expands 2 chars to ~150 and shifted every later fence test); an
unterminated fence now runs to EOF instead of un-fencing the tail; and the walk
is filtered through git ls-files, which the header always claimed but never did.

Co-authored-by: 1bcMax <viewitter@gmail.com>
…lockRunAI#57)

The gateway repriced six Grok SKUs to xAI's list price on 2026-09-04, removing
a resale spread of up to +140% on output. This repo quoted the old numbers.

  xai/grok-4.3         $1.50/$4.00 -> $1.25/$2.50
  xai/grok-4.5         $2.50/$9.00 -> $2.00/$6.00
  xai/grok-build-0.1   $1.50/$3.00 -> $1.00/$2.00

No price guard reaches this repo — the gateway's drift sweep covers its own
sheets only, and brand/consumers.json lists 15 repos it cannot see. Found by
grep.

CHANGELOG entries left alone on purpose: a dated entry records what the price
was that day.


Claude-Session: https://claude.ai/code/session_01ChUparsmmkz1GJrvzqmuvL

Co-authored-by: 1bcMax <viewitter@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lockRunAI#59)

Every paid path assumed x402 — a 402 challenge, a local signature, a retry —
which made holding a wallet the price of admission. A key from user.blockrun.ai
now works in the same place, across all fourteen client classes, with no new
client type and no signature change: the private_key parameter accepts a brk_
key, and BLOCKRUN_API_KEY is read when it is empty.

Four things beyond the header, each a silent failure rather than an import
error:

- api.blockrun.ai serves /v1 at the root and answers /api/v1 with wrong_host,
  so the account rail needs its own base URL.
- poll_url is minted relative to the x402 gateway's host, so it arrives as
  /api/v1/... — resolved unchanged, every async job polls a 404 to timeout.
- The async submit answers 202 on the FIRST post here. ImageClient raised
  "API error: 202" and VideoClient raised "Expected 402 on first POST", so both
  were broken for keys before they began.
- setup_agent_wallet() minted a keyfile unconditionally. With a key configured
  there is nothing to sign with, so it writes nothing.

SolanaLLMClient and AsyncSolanaLLMClient take the key too, and no longer need
the optional x402 SDK when one is present: on the account rail there is no
transfer to sign, so the chain stops being a question.

blockrun_llm.apikey holds the rail in one module — precedence, auth_headers,
poll-URL resolution, the two refusals — rather than fourteen copies that drift.
Each client's httpx.Client carries the key as a default header, so the sixteen
request sites did not have to be edited one by one.

Wallet users are untouched: precedence is explicit argument, then
BLOCKRUN_API_KEY, then the wallet variables. BLOCKRUN_API_KEY_URL is
deliberately separate from BLOCKRUN_API_URL — the latter names an x402 gateway,
and following it would send the key to a host configured for another rail.

Wallet-only helpers refuse rather than answer wrongly. get_balance() returning
0 is indistinguishable from an empty wallet, and an agent gating on it would
stop calling a funded account.

tests/conftest.py clears BLOCKRUN_API_KEY for every test: without it, the
"no credential" tests fail on the machine of anyone who has a key exported.


Claude-Session: https://claude.ai/code/session_01T5RKUETYjxwNRqkURLnatJ

Co-authored-by: 1bcMax <viewitter@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…BlockRunAI#60)

* fix: complete account clients and preserve payment selection

* fix: constrain Anthropic to the supported HTTP transport major

* fix: skip Solana signer initialization for API accounts

* fix(apikey): blank env is unset, and close the account rail's last gaps

Review follow-ups on this branch, in descending order of blast radius.

`resolve_api_key` checked `ENV_API_KEY not in os.environ`, which is presence
rather than value. `BLOCKRUN_API_KEY=` in a .env file, a bare
`docker -e BLOCKRUN_API_KEY`, and an unpopulated `${{ secrets.X }}` all arrive
as the empty string, so every client constructor in the package started raising
for wallet users who never opted into the account rail — `setup_agent_wallet()`
included, which the comment there says an agent may call unconditionally on
either rail. Blank is now unset again; a non-blank value that is not a `brk_`
key still refuses, because silently spending USDC instead of credit is the
wrong way to report a typo.

`max_retries=0` was set only on the account rail. The wallet rail kept the
Anthropic SDK's default of 2, and `_BlockRunX402Transport` signs a fresh
payment for every 402 it sees — so a 5xx retried after the gateway settled
signs and settles again. Measured at three on-chain transfers for one
`messages.create()`. The setdefault now applies to both rails, and an explicit
`max_retries=` still wins.

The account rail's remaining wallet assumptions:

- `list_portraits` was the only sibling of `list_realfaces` without the 402
  guard, so an out-of-credit account got `HTTP 402` instead of the credit
  refusal with the top-up link.
- Both listings are keyed by wallet address and did `wallet_address or
  self.account.address`, which is `AttributeError` on None. They now raise
  `wallet_only()`, naming the method like every other wallet-only helper.
- The Solana media probe had no 402 guard, so an out-of-credit account fell
  into the x402 branch — which has no signer to reach for on this rail and,
  without the optional SDK installed, no decoder either.
- `SolanaLLMClient._absolute_url` never went through `resolve_poll_url`, so
  account-rail slow-path polls went to `api.blockrun.ai/api/v1/...`, which the
  gateway answers with `wrong_host`. Every slow image and video job on account
  credit polled a dead URL until its budget ran out. Routing it through the
  shared helper also pins the Authorization header to the gateway's origin,
  which is what the Base clients already do.

Dead line: the fourth `raise_for_api_key_402` in `_post_with_payment` sits
where the status can no longer be 402, since the branch above always raises or
returns.

`payment_mode` used string literals where apikey.py exports the constants for
exactly this reason. CLAUDE.md still said "No API keys — wallet signature is
authentication"; AGENTS.md was updated in this branch and it was not.

21 new cases. Each new guard was mutation-tested: reverting any one of the
seven fixes individually turns its test red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMLNFbR6Jg2HBFpRUGBjyh

---------

Co-authored-by: 1bcMax <vicky.fuyu@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: type reasoning token usage

* fix(types): read a flat reasoning_tokens extra, and pin the guard with tests

Two gaps in the new property, both verified against the live gateway.

ChatUsage sets `extra = "allow"` so an upstream shape change still reaches
callers. A property named `reasoning_tokens` takes precedence over an extra of
the same name, so a payload carrying it at the top level of `usage` answered
None while `model_dump()` still held the number. The nested OpenAI shape stays
authoritative; the flat value is the fallback.

The `isinstance(value, int) and value >= 0` filter had no test — gutting it to
`return value` left both existing tests green. Five mutants now die: gutting the
guard, dropping the bool check, dropping the negative check, removing the flat
fallback, and turning the None test into a falsy test (which would collapse a
real `reasoning_tokens: 0` into None).

`bool` is excluded explicitly. It is an int subclass, so `True` would otherwise
arrive as a token count of 1.

Probed openai/gpt-5.4-nano for $0.002: the gateway forwards the nested OpenAI
shape (`completion_tokens_details.reasoning_tokens`, and `prompt_tokens_details`
alongside it) and sends no flat extra today, so the fallback is insurance rather
than a live fix. `reasoning_tokens: 0` on a non-reasoning turn is real and now
pinned as a measurement rather than an absence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMLNFbR6Jg2HBFpRUGBjyh

---------

Co-authored-by: x <x@y>
Co-authored-by: 1bcMax <vicky.fuyu@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`--check` was green because it only validates the keys a marker actually
renders, and those five were current. The rest of the snapshot had drifted:
withFallback 48 → 34, withFallbackAllEntries 89 → 73, aliases 229 → 259, plus
three mcp context keys the artifact now carries. No rendered number changes, so
this is not a docs fix — it stops the next marker added for one of those keys
from rendering a stale value on day one.

Produced by `scripts/sync-brand-numbers.mjs --refresh`, no hand-edited digits.

Supersedes BlockRunAI#51, which proposed the same refresh against a 26 Aug main. Its five
rendered numbers have since landed independently, so the branch had nothing left
to deliver and conflicted with the API-key documentation merged in BlockRunAI#59/BlockRunAI#60.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMLNFbR6Jg2HBFpRUGBjyh
… client

The rule: an API key goes to api.blockrun.ai, a Solana key to sol.blockrun.ai,
a Base key to blockrun.ai. Sending one to the wrong front door is not a 404 —
a key handed to an x402 host is a key disclosed to a host that never needed it.

Every client already followed it except one. The Solana constructors default
api_url to the SVM gateway rather than None, so the account-rail branch passed
a hard-coded None to avoid sending the key to sol.blockrun.ai — which also
threw away an api_url the caller had typed. Every other client honours it.
The branch now tells "the caller typed this" apart from "nobody passed
anything", and validates the resolved host like the rest do.

87 cases across all 17 exported clients: the three credential-to-host pairings,
that BLOCKRUN_API_URL (an x402 host) never captures an API-key client, that
BLOCKRUN_API_KEY_URL does, that an explicit api_url wins everywhere, and that
passing the Solana default explicitly still resolves to the account rail.
Both mutants die: reverting to None, and letting the SVM default through.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMLNFbR6Jg2HBFpRUGBjyh
Closes BlockRunAI#61.

api.blockrun.ai answers a rate limit with Retry-After and the gateway sets it
deliberately: it is what turns a refused request into a caller that waits
instead of one that spins against the limit the header exists to prevent. It
never survived the SDK boundary — APIError carried message, status_code and
response, and `grep -rn retry_after blockrun_llm/` returned nothing. Every
consumer on the account rail had to guess or spin.

APIError gains `retry_after`, kept as the raw header string. The HTTP spec
allows both a delay in seconds and an HTTP-date, and inventing a number for the
date form would be worse than handing back what arrived; `retry_after_seconds`
parses the delay form and answers None for everything else, including a
negative value.

`retry_after_of()` is the single reader. It tolerates a response with no
headers attribute, because it runs inside an error path and raising there would
replace the real failure with an AttributeError about the failure.

Migrated all 75 raise sites that hold a response, across 14 files. The
remaining 15 have no response to read — poll-budget timeouts, a missing
poll_url, stream probes that exhausted retries — so they carry None because
there is nothing to carry, not because they were skipped. The issue warns that
a partial migration is worse than none; this one is complete.

portrait.py, realface.py and video.py each carried a byte-identical private
`_raise_api_error`. They now delegate to one `raise_api_error` in validation.py,
so the next change to how failures are reported lands in three places at once
rather than two out of three.

45 new cases, including the 30 client x method table from BlockRunAI#58 that this issue
asked to salvage. Mutants that die: the reader always returning None, the
shared helper dropping the header, a negative delay passing through, the
seconds parser inventing a number for an unparseable value, and stripping every
retry_after kwarg from any one client file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMLNFbR6Jg2HBFpRUGBjyh
Raise sites build their message from the status code and stash the sanitized
body on `.response`, so the one line worth reading never reached the string a
caller actually prints or logs.

Read against the live gateway with a real account key, a free-tier stream 429
surfaced as:

    API error: 429

while the body said:

    Free tier rate limit reached (30 requests/minute per IP).
    Retry after 10s, or use a paid model

That gap is not cosmetic. Working from the first string I misread the limit as
throttling on the customer's key and started looking for a routing bug; the
second says plainly that it is the free tier, metered per IP, and what to do
instead. The same 429 now reads:

    API error: 429: Free model capacity exhausted — retry shortly, or use a
    paid model (from $0.002/request).

Folded into APIError.__init__ rather than the 90 raise sites, so it holds for
every one of them and for any added later. Sanitizer placeholders
("API request failed" and friends) are not appended — they restate the status
code and push the real prefix off the end of the line. A message that already
contains the upstream text is left alone, `.response` is untouched, and a
bodyless error is unchanged.

11 cases. Confirmed against the live gateway: the free-tier 429 now arrives
with both the explanation and Retry-After: 30.

For the record, since the number prompted this: an account key is not rate
limited. 25 sequential non-streaming calls returned 200 with no rate-limit
headers at all, and a paid model streamed 5 of 5 on the same key. Only the free
NVIDIA tier is capped, per IP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMLNFbR6Jg2HBFpRUGBjyh
…RunAI#63)

Music is never fast: MiniMax takes one to three minutes and the gateway
answers 202 + poll_url — since 2026-09-08, at once. MusicClient treated
every non-200 as an error, so a music request could not succeed on either
rail; the enterprise ledger showed 11 of 11 creates in 30 days answered
202, and this client raised "API error: 202" for each.

The image client already polls. Its loop moves to jobs.py and both clients
use it: the wallet rail replays the create's PAYMENT-SIGNATURE on each poll
(the job is bound to that wallet, and settles on the completed poll); the
account rail sees its 202 on the first post and polls with the key.
A poll budget that runs out has cost nothing.

Co-authored-by: 1bcMax <viewitter@gmail.com>
Co-authored-by: 1bcMax <viewitter@gmail.com>
…le catalog (BlockRunAI#62)

Opened by brand/fanout-brand-numbers.mjs. brand-numbers.json here was behind the
published artifact, so every br: marker in this repo rendered a number that is no
longer true.

The markers did their job — they rendered the input they were given. --check is
offline on purpose so PR CI stays deterministic, which means it validates against
this repo's own snapshot, and only --refresh updates that snapshot. This job is
what runs it.

Produced by --refresh, not by hand.

Co-authored-by: 1bcMax <viewitter@gmail.com>
…le catalog (BlockRunAI#66)

Opened by brand/fanout-brand-numbers.mjs. brand-numbers.json here was behind the
published artifact, so every br: marker in this repo rendered a number that is no
longer true.

The markers did their job — they rendered the input they were given. --check is
offline on purpose so PR CI stays deterministic, which means it validates against
this repo's own snapshot, and only --refresh updates that snapshot. This job is
what runs it.

Produced by --refresh, not by hand.

Co-authored-by: 1bcMax <viewitter@gmail.com>
…rable/escAttr) (BlockRunAI#68)

Verbatim from blockrun-mcp f9480ad2. A brand value fetched from the mirror
is now refused, and attribute-escaped, before the unattended brand-sync bot
writes it into this repo's markdown. --check output unchanged here.

Co-authored-by: 1bcMax <viewitter@gmail.com>
…Base Sepolia (BlockRunAI#69)

The chain table knew Base and Base Sepolia and fell back to Base for any
other network, while `asset` and `extra` were taken from the 402 as given.
Against arc.blockrun.ai (eip155:5042, USDC at 0x3600…, domain name "USDC")
that signed chainId 8453 against Arc's contract — an invalid signature, a
401 from the facilitator, after the SDK had reported a payment.

EVM_NETWORKS maps a 402's `network` to the SDK's OWN chain id, USDC address
and EIP-712 domain; create_payment_payload signs those. The 402 SELECTS the
network and supplies nothing else: its `extra` no longer reaches the domain
(a hostile 402 cannot steer a signature onto another contract), an unknown
network raises naming what is supported, and an `asset` that is not that
network's USDC raises before signing. The twelve EVM clients that did not
pass the 402's asset now do, so the check protects every route.
get_chain_config / get_usdc_domain_name read the same table; the
`base-sepolia` alias still resolves.

Verified against arc.blockrun.ai with an unfunded throwaway key: Circle's
/verify answers insufficient_funds and recovers the throwaway's own address
as payer — the signature verifies on Arc's domain, only the balance is
missing. Six new tests recover the signer against each domain (Arc passes,
Base fails for an Arc payment), pin the refusals, and that a 402's `extra`
is ignored. 983 unit tests, ruff and black clean. 1.17.0, mirroring
@blockrun/llm 3.16.0.

Co-authored-by: 1bcMax <viewitter@gmail.com>
@VickyXAI

VickyXAI commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Landing via #80, with your commit and authorship kept.

@VickyXAI VickyXAI closed this Oct 2, 2026
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.

4 participants