Two gaps in validate_max_tokens, both pre-existing and both reachable on paid requests. Neither was introduced by #27, but #27 made the guard's rationale explicit without extending it.
1. The Solana client never calls the guard
validate_max_tokens is called from blockrun_llm/client.py only — lines 610, 734, 2341, 2428.
blockrun_llm/solana_client.py imports validate_api_url, validate_image_quality and validate_video_input_type from .validation, but not validate_max_tokens. It puts the caller's value straight into paid request bodies:
solana_client.py:701 — body: Dict[str, Any] = {"model": model, "messages": messages, "max_tokens": max_tokens}
solana_client.py:805 — "max_tokens": max_tokens,
solana_client.py:2975 — same as 701 (async)
solana_client.py:3027 — same as 805 (async)
So max_tokens=2_000_000 raises ValueError on Base and is signed and sent on Solana. Whatever the bound means, it is not an SDK-wide invariant.
This matters more than it looks, because the gateway does not reject an over-ceiling max_tokens — it clamps to the model ceiling and quotes payment for the clamped value (measured against the live 402 leg 2026-07-21: claude-opus-4.8 sent 262144 and 1000000 both quote the 128000 price; gpt-5.2 sent 1e12 returns a quote, not a 400). There is no server-side rejection behind the missing client-side guard.
2. bool passes the integer check
validation.py:244 is if not isinstance(max_tokens, int):. bool is an int subclass, so:
>>> from blockrun_llm.validation import validate_max_tokens
>>> validate_max_tokens(True) # no error
The wire body then carries "max_tokens": true. A stray True — from a flag threaded into the wrong keyword, say — is not a token count and should fail locally like any other typo.
Suggested fix
- Import and call
validate_max_tokens(max_tokens) on the four Solana chat paths, so both chains share one bound. If Solana is meant to opt out, say so in a comment — right now the omission reads as an oversight.
- Reject
bool explicitly: check isinstance(max_tokens, bool) before the int check.
- Both want tests;
tests/unit/test_validation.py already has the boundary cases to model them on.
Found during /review of #27.
Two gaps in
validate_max_tokens, both pre-existing and both reachable on paid requests. Neither was introduced by #27, but #27 made the guard's rationale explicit without extending it.1. The Solana client never calls the guard
validate_max_tokensis called fromblockrun_llm/client.pyonly — lines 610, 734, 2341, 2428.blockrun_llm/solana_client.pyimportsvalidate_api_url,validate_image_qualityandvalidate_video_input_typefrom.validation, but notvalidate_max_tokens. It puts the caller's value straight into paid request bodies:solana_client.py:701—body: Dict[str, Any] = {"model": model, "messages": messages, "max_tokens": max_tokens}solana_client.py:805—"max_tokens": max_tokens,solana_client.py:2975— same as 701 (async)solana_client.py:3027— same as 805 (async)So
max_tokens=2_000_000raisesValueErroron Base and is signed and sent on Solana. Whatever the bound means, it is not an SDK-wide invariant.This matters more than it looks, because the gateway does not reject an over-ceiling
max_tokens— it clamps to the model ceiling and quotes payment for the clamped value (measured against the live 402 leg 2026-07-21:claude-opus-4.8sent 262144 and 1000000 both quote the 128000 price;gpt-5.2sent 1e12 returns a quote, not a 400). There is no server-side rejection behind the missing client-side guard.2.
boolpasses the integer checkvalidation.py:244isif not isinstance(max_tokens, int):.boolis anintsubclass, so:The wire body then carries
"max_tokens": true. A strayTrue— from a flag threaded into the wrong keyword, say — is not a token count and should fail locally like any other typo.Suggested fix
validate_max_tokens(max_tokens)on the four Solana chat paths, so both chains share one bound. If Solana is meant to opt out, say so in a comment — right now the omission reads as an oversight.boolexplicitly: checkisinstance(max_tokens, bool)before theintcheck.tests/unit/test_validation.pyalready has the boundary cases to model them on.Found during
/reviewof #27.