Repository navigation
Conversation
There was a problem hiding this comment.
The split between a signed payment and a usable one is right: _signed is set only after a payment field, unusable segCreds / state still reach the callback, and a signer refusal does not. The cancel path does not keep the same promise once the callback awaits.
_drain_runner_payment finishes the signer request across repeated cancellations, then the callback runs unprotected. A second cancel() while an async callback is awaiting ends the task immediately, so the manifest id is never recorded, and contextlib.suppress(Exception) does not catch that CancelledError. Callback exceptions on this path are discarded as well. The docstring says those exceptions propagate.
That matters for the gateway this is for. SdkRunnerTransport only copies manifest_ids onto RunnerCallError. CancelledError skips that handler, and the attempt stays in_flight with no sessions. An async callback that is cancelled mid-write never gets a second chance to persist the id.
Invalid segCreds now raises PaymentError from get_payment instead of the old LivepeerGatewayError ("missing segCreds") after a successful return. PaymentError is a subclass, but the builder adapter maps PaymentError to failure kind payment and a bare LivepeerGatewayError to http. Re-pinning changes that kind. payment_sent still follows the callback, so failover stays blocked.
Sent by Cursor Automation: build-track code reviews
Support sync and async on_payment_session callbacks in call_runner and expose LivePaymentSession.manifest_id for payment attribution. Invoke the callback for each new session. Validate segCreds when parsing signer responses and remove redundant caller checks. Cover callback ordering, failures, and cancellation.
f56d858 to
784249d
Compare
Use LivePaymentSessionCallback for every on_payment_session parameter instead of repeating the callable type, and export it from the package.
784249d to
30fa1ef
Compare
There was a problem hiding this comment.
A signer response that already contains a payment can still fail the call without ever reporting the session. That drops the manifest id SdkRunnerTransport stores, so the job is recorded as unpaid.
on_payment_session runs only after get_payment() returns. _payment_request now raises PaymentError for a missing, empty, or non-string segCreds, and it already raised for a missing state, before that return. The pinned contract (e6d243b, upstream livepeer#77) reports the session as soon as payment is a non-empty string, then rejects an unusable body. The builder treats that callback as proof the signer charged the allocation and copies manifest_id onto the attempt. With this order, a body such as {"payment": "...", "segCreds": ""} or a missing state becomes PaymentError with payment_sent=False and no sessions. Kind payment is not a failover outcome, so this attempt does not pay a second runner, but the charge is invisible to cost matching and a later retry can pay again.
Cancellation has the same hole once the signer has produced that payment. test_cancellation_remains_prompt cancels the callback on its first await and only checks that the task ends. The callback can return without recording manifest_id. The livepeer#77 path kept the payment task running, including the callback, and then raised CancelledError. Signer refusals and a missing payment field should still skip the callback.
Callback exceptions do propagate, and a runner failure after a successful sign still reports the session. Those parts match the tests.
Sent by Cursor Automation: build-track code reviews
There was a problem hiding this comment.
The cancellation and attribution gaps from the last pass are fixed: a payment string is reported before an unusable segCreds or state fails the call, and cancellation waits until that callback finishes.
One regression is still in the shared signer parse. Blank segCreds used to be rejected only after a dict state was stored. It is now rejected first, so a metered renewal retries with the previous cursor after the signer has already advanced.
The PR description still says invalid signer bodies skip the callback and that cancellation stays prompt. The docstring matches the new behavior; the description does not.
Sent by Cursor Automation: build-track code reviews
Update the livepeer-gateway pin to NE-Build-Track/livepeer-python-gateway#3 (upstream #81, 9c084b6) and adjust documentation to reflect changes in the SDK's payment session reporting contract.
A metered renewal was resubmitting the previous cursor because a blank segCreds failed before the new state was saved. call_runner again raises LivepeerGatewayError for that body, after the payment callback.
|
There was a problem hiding this comment.
The blank-segment cursor bug from the last pass is fixed. A dict state is stored before a missing or empty segCreds is rejected, and the next metered sign sends that cursor.
send_payment still raises PaymentError (Signer returned a payment with no segCreds) after that store, so run_payments retries the new cursor instead of the previous one. call_runner still reports the session, skips the paid request, and raises a bare LivepeerGatewayError (Live runner payment response missing segCreds), including when no callback is passed. A non-string segCreds or a missing state still raises _SignedPaymentError before self._state is replaced, then the callback runs. Signer refusals and a body with no payment string still skip the callback.
Cancellation still waits until that callback finishes, and a callback failure still propagates. The renewal test covers None and "".
Sent by Cursor Automation: build-track code reviews





Summary
This PR brings upstream livepeer/livepeer-python-gateway#81 onto
releases/finaland names/exportsLivePaymentSessionCallbackonce.call_runnertakes an optionalon_payment_sessioncallback, andLivePaymentSession.manifest_idexposes the manifest id from the 402 challenge so a gateway can match the session to a Clearinghouse usage record.#4 removed the in-session callback from #1. Callers that don't pass
on_payment_sessionsee no change in results, errors, or streams.Why
livepeer-builder-core collects
session.manifest_idfromon_payment_sessioninSdkRunnerTransportand treats a reported session as payment sent. Keeping our fork branch aligned with upstream livepeer#81 plus theLivePaymentSessionCallbacktype alias avoids carrying custom cancellation/shielding logic in the fork.What callers see
session.manifest_idis the challenge's manifest id. A blanksegCreds(Noneor"") is reported, thencall_runnerraisesLivepeerGatewayError(Live runner payment response missing segCreds). A non-stringsegCredsor a missingstateis reported, then raisesPaymentError. A blank segment still stores a dictstate, so the next metered renewal sends that cursor.Known limits
e6d243b. After this merges, that pin should move to the newreleases/finalhead. Theon_payment_sessionandmanifest_idcontract the adapter uses is unchanged.Test plan
uv run pytestat91cefc0: 246 passed. The new tests cover a blanksegCredsrenewal sending the new cursor, andcall_runnerraisingLivepeerGatewayErrorfor that body with and without a callback.uv run ruff checkon the touched files: the new lines are clean.remote_signer.pystill has four pre-existingUP045Optionalannotations that this change does not touch.pymthouse_livepeer-python-gateway) ofremote_signer.pyandtests/test_live_payment_session.py: 0 issues.remote_signer.pyandlive_runner.py: 0 issues. The tests directory still has pre-existing low-severity hardcoded secrets in older tests.