Skip to main content

stateset_core/models/
a2a_skill.rs

1//! A2A (Agent-to-Agent) Commerce Skill Schemas
2//!
3//! Defines the input/output schemas for A2A commerce skills that enable
4//! AI agents to discover, quote, purchase, and confirm delivery from each other.
5
6use chrono::{DateTime, Utc};
7use rust_decimal::Decimal;
8use serde::{Deserialize, Serialize};
9use stateset_primitives::CurrencyCode;
10use strum::{Display, EnumString};
11use uuid::Uuid;
12
13use super::agent_card::TrustLevel;
14use super::cart::CartAddress;
15use super::x402::{X402Asset, X402Network};
16
17// =============================================================================
18// commerce.discover_sellers
19// =============================================================================
20
21/// Input for discovering seller agents
22#[derive(Debug, Clone, Default, Serialize, Deserialize)]
23pub struct DiscoverSellersInput {
24    /// Product categories to search for
25    pub categories: Option<Vec<String>>,
26    /// Free-text search query
27    pub query: Option<String>,
28    /// Required payment methods (networks)
29    pub payment_networks: Option<Vec<X402Network>>,
30    /// Required payment assets
31    pub payment_assets: Option<Vec<X402Asset>>,
32    /// Minimum trust level required
33    pub min_trust_level: Option<TrustLevel>,
34    /// Geographic region (for shipping)
35    pub region: Option<String>,
36    /// Maximum results to return
37    pub limit: Option<u32>,
38}
39
40/// Output from `discover_sellers` skill
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct DiscoverSellersOutput {
43    /// List of matching seller agents
44    pub sellers: Vec<SellerInfo>,
45    /// Total count of matching sellers
46    pub total_count: u32,
47    /// Whether more results are available
48    pub has_more: bool,
49}
50
51/// Information about a seller agent
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct SellerInfo {
54    /// Agent card ID
55    pub agent_id: Uuid,
56    /// Agent name
57    pub name: String,
58    /// Agent description
59    pub description: Option<String>,
60    /// Supported payment networks
61    pub payment_networks: Vec<X402Network>,
62    /// Supported payment assets
63    pub payment_assets: Vec<X402Asset>,
64    /// Trust level
65    pub trust_level: TrustLevel,
66    /// Business category
67    pub category: Option<String>,
68    /// Endpoint URL for A2A communication
69    pub endpoint_url: Option<String>,
70    /// Average rating (1-5)
71    pub rating: Option<f32>,
72    /// Number of completed transactions
73    pub transaction_count: Option<u32>,
74}
75
76// =============================================================================
77// commerce.request_quote
78// =============================================================================
79
80/// Input for requesting a quote from a seller
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct RequestQuoteInput {
83    /// Seller agent ID
84    pub seller_agent_id: Uuid,
85    /// Items to quote
86    pub items: Vec<QuoteItem>,
87    /// Shipping address (if physical goods)
88    pub shipping_address: Option<CartAddress>,
89    /// Preferred payment network
90    pub payment_network: Option<X402Network>,
91    /// Preferred payment asset
92    pub payment_asset: Option<X402Asset>,
93    /// Special instructions or requirements
94    pub notes: Option<String>,
95}
96
97/// Item to include in a quote request
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct QuoteItem {
100    /// Product SKU or identifier
101    pub sku: Option<String>,
102    /// Product name or description
103    pub name: String,
104    /// Quantity requested
105    pub quantity: i32,
106    /// Maximum unit price willing to pay (optional)
107    pub max_unit_price: Option<Decimal>,
108    /// Product specifications or requirements
109    pub specifications: Option<String>,
110}
111
112/// Output from `request_quote` skill
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct RequestQuoteOutput {
115    /// Quote ID for reference
116    pub quote_id: Uuid,
117    /// Quote number (human-readable)
118    pub quote_number: String,
119    /// Status of the quote
120    pub status: QuoteStatus,
121    /// Seller agent ID
122    pub seller_agent_id: Uuid,
123    /// Quoted items with pricing
124    pub items: Vec<QuotedItem>,
125    /// Subtotal before tax/shipping
126    pub subtotal: Decimal,
127    /// Tax amount
128    pub tax_amount: Decimal,
129    /// Shipping amount
130    pub shipping_amount: Decimal,
131    /// Any discounts applied
132    pub discount_amount: Decimal,
133    /// Total amount
134    pub total: Decimal,
135    /// Currency code
136    pub currency: CurrencyCode,
137    /// Payment network for this quote
138    pub payment_network: X402Network,
139    /// Payment asset for this quote
140    pub payment_asset: X402Asset,
141    /// When the quote expires
142    pub valid_until: DateTime<Utc>,
143    /// Estimated delivery date (if applicable)
144    pub estimated_delivery: Option<DateTime<Utc>>,
145    /// Seller notes
146    pub seller_notes: Option<String>,
147}
148
149/// A quoted item with pricing
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct QuotedItem {
152    /// Item line number
153    pub line_number: u32,
154    /// SKU
155    pub sku: Option<String>,
156    /// Item name
157    pub name: String,
158    /// Quantity available
159    pub quantity: i32,
160    /// Unit price
161    pub unit_price: Decimal,
162    /// Line total
163    pub total: Decimal,
164    /// Availability status
165    pub availability: ItemAvailability,
166    /// Lead time in days (if not immediately available)
167    pub lead_time_days: Option<i32>,
168}
169
170/// Quote status
171#[derive(
172    Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, Serialize, Deserialize, Default,
173)]
174#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
175#[serde(rename_all = "snake_case")]
176#[non_exhaustive]
177pub enum QuoteStatus {
178    /// Quote is pending seller response
179    #[default]
180    Pending,
181    /// Quote has been provided
182    Quoted,
183    /// Quote was accepted by buyer
184    Accepted,
185    /// Quote was rejected by buyer
186    Rejected,
187    /// Quote has expired
188    Expired,
189    /// Quote was converted to purchase
190    Purchased,
191}
192
193/// Item availability status
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
195#[serde(rename_all = "snake_case")]
196#[non_exhaustive]
197pub enum ItemAvailability {
198    /// Item is in stock and ready to ship
199    #[default]
200    InStock,
201    /// Item is available but limited quantity
202    LimitedStock,
203    /// Item is on backorder
204    Backorder,
205    /// Item is out of stock
206    OutOfStock,
207    /// Item is discontinued
208    Discontinued,
209    /// Item is available for pre-order
210    PreOrder,
211}
212
213// =============================================================================
214// commerce.initiate_purchase
215// =============================================================================
216
217/// Input for initiating a purchase from a quote
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct InitiatePurchaseInput {
220    /// Quote ID to purchase from
221    pub quote_id: Uuid,
222    /// Buyer's wallet address for payment
223    pub payer_address: String,
224    /// Payment network to use
225    pub network: X402Network,
226    /// Payment asset to use
227    pub asset: X402Asset,
228    /// Shipping address (can override quote)
229    pub shipping_address: Option<CartAddress>,
230    /// Billing address
231    pub billing_address: Option<CartAddress>,
232    /// Purchase notes
233    pub notes: Option<String>,
234}
235
236/// Output from `initiate_purchase` skill
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct InitiatePurchaseOutput {
239    /// Purchase ID
240    pub purchase_id: Uuid,
241    /// Purchase number (human-readable)
242    pub purchase_number: String,
243    /// Purchase status
244    pub status: PurchaseStatus,
245    /// x402 payment intent for signing
246    pub payment_intent_id: Uuid,
247    /// Signing hash for the payment
248    pub signing_hash: String,
249    /// Amount to pay (in smallest unit)
250    pub amount: u64,
251    /// Amount to pay (human-readable)
252    pub amount_display: String,
253    /// Payment asset
254    pub asset: X402Asset,
255    /// Payment network
256    pub network: X402Network,
257    /// Payee address (seller)
258    pub payee_address: String,
259    /// Validity window for the payment
260    pub valid_until: DateTime<Utc>,
261    /// Cart ID (if a cart was created)
262    pub cart_id: Option<Uuid>,
263}
264
265/// Purchase status
266#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, Serialize, Deserialize, Default)]
267#[strum(serialize_all = "snake_case")]
268#[serde(rename_all = "snake_case")]
269#[non_exhaustive]
270pub enum PurchaseStatus {
271    /// Purchase initiated, awaiting payment
272    #[default]
273    Initiated,
274    /// Payment is pending (intent signed)
275    PaymentPending,
276    /// Payment confirmed
277    Paid,
278    /// Order is being fulfilled
279    Fulfilling,
280    /// Order has shipped
281    Shipped,
282    /// Order delivered
283    Delivered,
284    /// Purchase completed
285    Completed,
286    /// Purchase was cancelled
287    Cancelled,
288    /// Purchase is disputed
289    Disputed,
290}
291
292// =============================================================================
293// commerce.confirm_delivery
294// =============================================================================
295
296/// Input for confirming delivery of a purchase
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct ConfirmDeliveryInput {
299    /// Purchase ID to confirm
300    pub purchase_id: Uuid,
301    /// Confirmation signature from buyer
302    pub confirmation_signature: String,
303    /// Rating for the seller (1-5)
304    pub rating: Option<u8>,
305    /// Feedback comments
306    pub feedback: Option<String>,
307}
308
309/// Output from `confirm_delivery` skill
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct ConfirmDeliveryOutput {
312    /// Purchase ID
313    pub purchase_id: Uuid,
314    /// Updated status (should be Completed)
315    pub status: PurchaseStatus,
316    /// Order ID created from this purchase
317    pub order_id: Option<Uuid>,
318    /// Confirmation timestamp
319    pub confirmed_at: DateTime<Utc>,
320    /// Any escrow release transaction hash
321    pub release_tx_hash: Option<String>,
322}
323
324// =============================================================================
325// A2A Quote (Persisted)
326// =============================================================================
327
328/// A persisted A2A quote between agents
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct A2AQuote {
331    pub id: Uuid,
332    pub quote_number: String,
333    pub status: QuoteStatus,
334    pub buyer_agent_id: Uuid,
335    pub seller_agent_id: Uuid,
336    pub items: Vec<QuotedItem>,
337    pub subtotal: Decimal,
338    pub tax_amount: Decimal,
339    pub shipping_amount: Decimal,
340    pub discount_amount: Decimal,
341    pub total: Decimal,
342    pub currency: CurrencyCode,
343    pub payment_network: Option<X402Network>,
344    pub payment_asset: Option<X402Asset>,
345    pub shipping_address: Option<CartAddress>,
346    pub valid_until: DateTime<Utc>,
347    pub purchase_id: Option<Uuid>,
348    pub payment_intent_id: Option<Uuid>,
349    pub notes: Option<String>,
350    pub metadata: Option<String>,
351    pub created_at: DateTime<Utc>,
352    pub updated_at: DateTime<Utc>,
353}
354
355// =============================================================================
356// A2A Purchase (Persisted)
357// =============================================================================
358
359/// A persisted A2A purchase between agents
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct A2APurchase {
362    pub id: Uuid,
363    pub purchase_number: String,
364    pub status: PurchaseStatus,
365    pub buyer_agent_id: Uuid,
366    pub seller_agent_id: Uuid,
367    pub quote_id: Option<Uuid>,
368    pub cart_id: Option<Uuid>,
369    pub order_id: Option<Uuid>,
370    pub payment_intent_id: Option<Uuid>,
371    pub items: Vec<QuotedItem>,
372    pub total: Decimal,
373    pub currency: CurrencyCode,
374    pub fulfillment_type: Option<String>,
375    pub tracking_info: Option<String>,
376    pub delivered_at: Option<DateTime<Utc>>,
377    pub delivery_confirmed_at: Option<DateTime<Utc>>,
378    pub delivery_confirmation_signature: Option<String>,
379    pub buyer_rating: Option<u8>,
380    pub buyer_feedback: Option<String>,
381    pub seller_rating: Option<u8>,
382    pub seller_feedback: Option<String>,
383    pub notes: Option<String>,
384    pub metadata: Option<String>,
385    pub created_at: DateTime<Utc>,
386    pub updated_at: DateTime<Utc>,
387}
388
389// =============================================================================
390// Input Types for Repository
391// =============================================================================
392
393/// Input for creating an A2A quote
394#[derive(Debug, Clone, Default, Serialize, Deserialize)]
395pub struct CreateA2AQuote {
396    pub buyer_agent_id: Uuid,
397    pub seller_agent_id: Uuid,
398    pub items: Vec<QuotedItem>,
399    pub subtotal: Decimal,
400    pub tax_amount: Option<Decimal>,
401    pub shipping_amount: Option<Decimal>,
402    pub discount_amount: Option<Decimal>,
403    pub total: Decimal,
404    pub currency: Option<CurrencyCode>,
405    pub payment_network: Option<X402Network>,
406    pub payment_asset: Option<X402Asset>,
407    pub shipping_address: Option<CartAddress>,
408    pub valid_until: DateTime<Utc>,
409    pub notes: Option<String>,
410    pub metadata: Option<String>,
411}
412
413/// Input for creating an A2A purchase
414#[derive(Debug, Clone, Default, Serialize, Deserialize)]
415pub struct CreateA2APurchase {
416    pub buyer_agent_id: Uuid,
417    pub seller_agent_id: Uuid,
418    pub quote_id: Option<Uuid>,
419    pub payment_intent_id: Option<Uuid>,
420    pub items: Vec<QuotedItem>,
421    pub total: Decimal,
422    pub currency: Option<CurrencyCode>,
423    pub fulfillment_type: Option<String>,
424    pub notes: Option<String>,
425    pub metadata: Option<String>,
426}
427
428/// Filter for listing A2A quotes
429#[derive(Debug, Clone, Default, Serialize, Deserialize)]
430pub struct A2AQuoteFilter {
431    pub buyer_agent_id: Option<Uuid>,
432    pub seller_agent_id: Option<Uuid>,
433    pub status: Option<QuoteStatus>,
434    pub from_date: Option<DateTime<Utc>>,
435    pub to_date: Option<DateTime<Utc>>,
436    pub limit: Option<u32>,
437    pub offset: Option<u32>,
438}
439
440/// Filter for listing A2A purchases
441#[derive(Debug, Clone, Default, Serialize, Deserialize)]
442pub struct A2APurchaseFilter {
443    pub buyer_agent_id: Option<Uuid>,
444    pub seller_agent_id: Option<Uuid>,
445    pub status: Option<PurchaseStatus>,
446    pub order_id: Option<Uuid>,
447    pub from_date: Option<DateTime<Utc>>,
448    pub to_date: Option<DateTime<Utc>>,
449    pub limit: Option<u32>,
450    pub offset: Option<u32>,
451}
452
453#[cfg(test)]
454mod tests {
455    use super::*;
456    use rust_decimal_macros::dec;
457
458    #[test]
459    fn test_quote_item_creation() {
460        let item = QuoteItem {
461            sku: Some("WIDGET-001".to_string()),
462            name: "Premium Widget".to_string(),
463            quantity: 5,
464            max_unit_price: Some(dec!(29.99)),
465            specifications: None,
466        };
467
468        assert_eq!(item.quantity, 5);
469        assert_eq!(item.max_unit_price, Some(dec!(29.99)));
470    }
471
472    #[test]
473    fn test_quote_status_display() {
474        assert_eq!(QuoteStatus::Pending.to_string(), "pending");
475        assert_eq!(QuoteStatus::Purchased.to_string(), "purchased");
476    }
477
478    #[test]
479    fn test_purchase_status_display() {
480        assert_eq!(PurchaseStatus::Initiated.to_string(), "initiated");
481        assert_eq!(PurchaseStatus::Completed.to_string(), "completed");
482    }
483}