feat(live-runner): self-funding metered single-shot calls - #50
Conversation
- hoist _post_empty into http.py as post_empty (TCPConnector(ssl=False), typed status errors via the shared error mapper) so payment callers can branch on 404/409/403/482 - route LivePaymentSession.send_payment through it; the new payment_url kwarg targets a specific payment endpoint such as the session-scoped one - parse payment_interval_ms from payment challenges and surface it as server_payment_interval on call results and streams - carry the challenge session_id and the fixed-price payment_session drop through call_runner(stream=True) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The orchestrator debits the prepaid balance on -livePaymentInterval while a metered (hour/720p) single-shot request is in flight and cancels it on the first under-funded tick; the signer's preroll only covers ~10s. Spawn a payment loop inside call_runner for the lifetime of the awaited request: first payment after one interval, cadence at 60% of the challenge's payment_interval_ms with a 3s fallback, stop on 404 (released) / 409 (fixed-price) / 403 (mismatch), skip on 482, retry transient errors. No opt-out flag: offchain mode is the no-payments path and fixed pricing never starts the loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A streamed single-shot response outlives the call_runner call, so the LiveRunnerCallStream owns its funding: the loop starts with the stream, aclose() cancels it before tearing down the transport, and a 404 from the payment endpoint sets a read-only released flag. No public start/stop controls - a single-shot stream is its request, so stopping funding without closing is not a meaningful state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed endpoint
Derive .../apps/{runner_id}/session/{session_id}/payment from the
single-shot runner URL and the challenge manifest_id so the loop gets
liveness semantics (404 once the session is released, 409 for fixed
pricing) instead of blind-crediting the generic /payment endpoint,
which stays as the fallback when the URL shape does not match.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Streams the story via call_runner(stream=True); with --signer-url the call pays its 402 challenge and keeps funding itself while the stream runs, demonstrating that metered single-shot calls need no payment code on the caller side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ce44571 to
b0f46d4
Compare
|
Superseded by #53, which covers both single-shot and reserved sessions with one shared loop on |
Metered (
hour/720p) single-shot calls currently pay only the initial 402 challenge. On go-livepeer master (since livepeer/go-livepeer#4000),ProxyLiveRunnerSingleShotreserves a paid session per request and debits it every-livePaymentInterval(5s default) — balance is checked before each debit, and the first under-funded tick releases the session and cancels the in-flight request. The signer'slivetype prerolls only ~10s, so any metered call longer than that dies unless the caller hand-writes a payment loop.This makes
call_runnerown that loop, the single-shot sibling of #31: whatever holds the session open funds it, and callers never hand-manage a background task.What this adds
live/lv2v), a payment task runs for exactly the lifetime of the awaited request and is cancelled the moment the response lands. Zero new public API — noauto_payflag: an opt-out is "broken mode" (the caller is blocked awaiting the response and can't fund manually), offchain (nosigner_url) is the natural no-payments path, and the loop can't overspend (the signer bills only elapsed seconds).stream=True): the stream outlives the call, soLiveRunnerCallStreamowns the task — auto-started, cancelled byaclose()before transport teardown, plus a read-onlyreleasedflag set when the orchestrator reports the session gone. No publicstop_payments(): a single-shot stream is its request, so "keep stream, stop funding" isn't a real state.payment_interval_mswhen advertised (go-livepeerrs/singleshot-payment-interval), 3s fallback on master.releasedon streams); 409 → fixed-price, stop; 403 → mismatch, stop + log; 482 → skip the cycle; transient errors retry next tick. Loop failures never mask the request result — the orchestrator cancels the call itself if funding actually stops.…/apps/{runner_id}/session/{manifest_id}/payment(derived from the runner URL + challengemanifest_id) for real liveness semantics, falling back to the generic/paymentcredit endpoint when the URL shape doesn't match.post_emptyhoisted intohttp.pywith typed status errors,send_payment(payment_url=),payment_interval_ms→server_payment_intervalon results/streams, challengesession_idcarried onto streams.Commits
323c964payment plumbing (post_empty, payment_url kwarg, interval parse)3936c77fund metered single-shot calls while they rune1c6f8fstream owns the payment loopf7ddca0session-scoped payment endpoint with generic fallbackb0f46d4examples/text/client.py: self-funding SSE clientTesting
Relation to #31
Lands first and defines the loop policy; #31 (persistent sessions) can rebase onto it and reuse the same helper.
reserve_session(auto_pay=True)andstop_payments()stay in #31 — reserved sessions outlive their calls, so external funding control is a real use case there.🤖 Generated with Claude Code