Repository navigation
fix(spend): book $0 for any paid call the gateway did not settle - #89
Merged
Merged
Conversation
The gateways now serve some paid requests without settling and say so: X-Settlement-Skipped: empty-result (sol pm empty search), unexecuted (sol rpc), free-fallback (chat), and X-Payment-Waived alone (blockrun.ai pm). settlementSkipped() only knew free-fallback and only the chat path asked it, so getSpending() and the cost log booked the full signed amount for calls that moved no money. settlementSkipped() is now true for any non-empty X-Settlement-Skipped or X-Payment-Waived, and every booking site goes through it: LLMClient raw GET/POST (bookPaidRaw), chat, both stream paths; SolanaLLMClient's recordSettlement now takes the response (exact, batch, stream, raw, 202 media, video delivery); BlockrunClient.recordSpending; Video, Voice, Speech, Music, Rpc, Portrait and Phone clients. settlement.skipped carries the gateway's reason. X-Payment-Settled: false alone is still booked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d-books-zero # Conflicts: # CHANGELOG.md
VickyXAI
pushed a commit
that referenced
this pull request
Oct 27, 2026
…Client costs per call Follow-ups from the review of #89: - settlementSkipReason(): a response that names a skipped settlement but also proves it settled (successful PAYMENT-RESPONSE with a transaction or a positive charge, or an X-Payment-Receipt hash) is not skipped; the conflict goes to stderr. blockrun.ai's free-fallback receipt has an empty transaction and no amount (chat/completions/route.ts), so today's skips still book $0. - Header values false / 0 / no / none are not a skip reason. - BlockrunClient: signFrom402() returns the signed cost with the payload and recordSpending() books that, replacing the per-client pendingCostUsd that a skipped call could zero under a concurrent call. Tests: four booked shapes and one skipped shape (free-fallback as sent) across every EVM site, a receipt-wins shape on every Solana site, predicate and stderr tests, and a concurrent BlockrunClient test. 87 fail on main.
VickyXAI
added a commit
that referenced
this pull request
Oct 27, 2026
…Client costs per call (#92) Follow-ups from the review of #89: - settlementSkipReason(): a response that names a skipped settlement but also proves it settled (successful PAYMENT-RESPONSE with a transaction or a positive charge, or an X-Payment-Receipt hash) is not skipped; the conflict goes to stderr. blockrun.ai's free-fallback receipt has an empty transaction and no amount (chat/completions/route.ts), so today's skips still book $0. - Header values false / 0 / no / none are not a skip reason. - BlockrunClient: signFrom402() returns the signed cost with the payload and recordSpending() books that, replacing the per-client pendingCostUsd that a skipped call could zero under a concurrent call. Tests: four booked shapes and one skipped shape (free-fallback as sent) across every EVM site, a receipt-wins shape on every Solana site, predicate and stderr tests, and a concurrent BlockrunClient test. 87 fail on main. Co-authored-by: 1bcMax <viewitter@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The gateways now serve some paid requests without settling them, and say so in headers:
/api/v1/pm/markets/search, empty resultX-Settlement-Skipped: empty-result,X-Payment-Waived: empty-result,X-Payment-Settled: false, no PAYMENT-RESPONSE/api/v1/rpc/*, JSON-RPC never executedX-Settlement-Skipped: unexecuted,X-Payment-Settled: falseX-Settlement-Skipped: free-fallback/v1/pm/*, empty resultX-Payment-Waived: empty-resultonlysettlementSkipped()(src/receipt.ts) only matchedfree-fallback, and only the chat paths asked it. Every other booking site (LLMClient raw GET/POST, both chat streams, all of SolanaLLMClient's raw, stream, batch and media paths, BlockrunClient, and the Video/Voice/Speech/Music/Rpc/Portrait/Phone clients) added the full signed amount on any 2xx. SogetSpending()andcost_log.jsonloverstated spend for calls that moved no money.Change
settlementSkipReason(response)(new) returns the trimmedX-Settlement-Skippedvalue, else theX-Payment-Waivedvalue, elseundefined.settlementSkipped()is true when either one names a reason. Empty or blank values don't count, so a call is booked unless the gateway names why it wasn't charged.X-Payment-Settled: falseon its own is not a skip: settlement just hadn't landed yet, and the payment is still owed.LLMClient: a newbookPaidRaw()handles all 4 raw GET/POST sites (first send and the 502/503 resend). The 2 stream sites (pre-auth hit and 402→sign) are guarded, andbookPaid(chat) is unchanged.SolanaLLMClient:recordSettlement(costUsd, response)now takes the response, so none of its 7 callers can skip the check (exact chat, batch, stream, raw 200, raw 202 image/music, GET raw, video delivery).BlockrunClient.recordSpending(response)covers get/post, stream and both poll exits. A skip drops the pending cost.VideoClient,VoiceClient,SpeechClient,MusicClient,RpcClient,PortraitClientandPhoneClienteach have their one booking site guarded.callsortotalUsdand writes no cost-log entry. This matches the existing free-fallback behaviour.Settlement.skippednow carries the gateway's reason ("free-fallback" | (string & {})). For free-fallback the output is identical to before.assertPaidchange was needed. Neither client throws or warns when PAYMENT-RESPONSE is missing on a 2xx.settlementSkipReasonreturnsundefinedfor a response-like object with noheaders, so a custom fetch can't turn a paid call into a throw after payment.ImageClientnever books spend at all (it was already like this), so there is nothing to gate there.Tests
test/unit/settlement-skipped-booking.test.tscovers 20 EVM booking sites, each driven through its public method against a fakedfetchwith the real EVM signer. Each site runs 5 skipped header shapes (sol pm, sol rpc, free-fallback,X-Payment-Waivedalone, and an unknown future reason), which must book $0, write no cost log and not throw. It also runs 3 booked shapes (settled,X-Payment-Settled: falsealone, empty/blank skip headers), which must book the full quote. There are predicate unit tests and chatsettlement.skippedreason tests too.test/unit/settlement-skipped-solana.test.tscovers the 7 SolanaLLMClient sites with 4 skipped and 2 booked shapes each.The tests catch the bug:
=== "free-fallback": 107 failed. Every site fails on every non-free-fallback skipped shape. No free-fallback or booked case fails.git diffshows only the intended files.Results
pnpm run test --run: 45 files passed, 1 skipped; 946 tests passed, 12 skipped (958), no type errors. The baseline on main was 733 passed and 12 skipped, so 213 tests are new.pnpm run typecheck: clean (exit 0).pnpm run lint: clean (exit 0). The new test files also pass eslint.pnpm run test:package: PASS.sync-brand-numbers --check: up to date.No version bump. The fix is added to CHANGELOG under
[Unreleased], following the repo's fix-PR convention.🤖 Generated with Claude Code