feat(ext): add ra2a-ext-x402 payment extension crate (#46) - #47
feat(ext): add ra2a-ext-x402 payment extension crate (#46)#47GentechLabs wants to merge 1 commit into
Conversation
Implements the google-agentic-commerce/a2a-x402 v0.1 message-level handshake (payment-required -> payment-submitted -> payment-completed) for the A2A Rust SDK, per issue qntx#46 and qntx#10. - x402 payment state rides in Message.metadata (x402.payment.* keys) - payment-required maps onto TaskState::InputRequired; task resumes to Working when payment is submitted — composes with the task lifecycle - server: PaymentGate CallInterceptor parks unpaid priced message/send requests with the x402 PaymentRequired offer; PriceLookup/MapPricing abstract the pricing source; payTo/resource stamped into accepts - client: PaymentClient CallInterceptor + PaymentSigner trait sign a pending offer via any wallet/x402 stack and rewrite the outgoing message into the payment-submitted resubmission (same task id) - X402Extension declares the canonical v0.1 extension URI on the agent card; gate is opt-in (X-A2A-Extensions request header or call-scope activation per spec section 7) - settlement helper submitted_task_id correlates the resubmission - 8 integration-style tests; clippy clean at -D warnings
|
Can't merge this.
|
|
Also doesn't use r402 — this is a hand-rolled JSON handshake, no verify/settle types from the SDK. |
|
Both points confirmed — the PR is a hand-rolled handshake where it should be typed SDK calls, and the flow leaks money on every count. Walking the state machine:
We've offered a fix in r402 #91 (pre-settlement |
|
Thank you for the review — both points are fair, and we've re-checked the code against them: 1. 2. The offer comes back as a JSON-RPC error instead of an 3. Doesn't use r402 — hand-rolled JSON handshake, no verify/settle types from the SDK. Fix plan (in order):
We'll push the rewire as commits onto |
Implements the x402 payments extension requested in #46 (see also #10) — a new workspace crate
ra2a-ext-x402that plugs the google-agentic-commerce/a2a-x402 v0.1 message-level handshake into the SDK:payment-required→payment-submitted→payment-completed.Design
Message.metadataunder the spec'sx402.payment.*keys (status,required,payload). No new wire types — the offer/payload JSON is the shape an x402 V2 client already produces, so no scheme logic is duplicated and the crate stays chain-agnostic.payment-requiredis represented by parking the task inTaskState::InputRequiredwith the offer attached to the status message; when the client submits payment the task simply resumes toWorking. Nothing about the existing state machine changes.X402Extensiondeclares the canonical v0.1 URI on the agent card; the server gate only charges requests where the client activated the extension (via theX-A2A-Extensionsrequest header or call-scope activation).Server side
PaymentGateis aCallInterceptorformessage/send: for a priced skill without payment it short-circuits with the parkedInputRequiredtask and the x402PaymentRequiredoffer (payTo/resource stamped into eachacceptsentry). Pricing is pluggable throughPriceLookup(MapPricingcovers the agent-card-metadata shape; implement the trait for a DB or billing service).Client side
PaymentClientis aCallInterceptor+PaymentSignertrait: given a pending offer mirrored onto the outgoing message, the operator's wallet/x402 stack signs it and the interceptor rewrites the message into thepayment-submittedresubmission (same task id, per §4.5 correlation).submitted_messagebuilds that message for manual orchestration;submitted_task_idis the server-side settlement helper.Tests
8 integration-style tests cover the full handshake — offer validity and
payTo/resourcestamping,InputRequiredparking, pass-through for unactivated clients / free skills / already-paid requests, card-declaration idempotency and therequiredflag, resubmission correlation, and the status vocabulary.cargo clippy --workspace --all-targets --all-features -- -D warningsis clean; fmt applies.Refs #46. Closes out the server+client halves described in the issue's design sketch (executor wrapper ↔ interceptor, r402 types ↔ spec-shaped JSON).