1use chio_core::receipt::{
2 economics::FinancialBudgetAuthorityReceiptMetadata, economics::SettlementStatus,
3};
4use serde::{Deserialize, Serialize};
5
6use crate::receipt_query::ReceiptReadContext;
7
8pub const MAX_COST_ATTRIBUTION_LIMIT: usize = 200;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct CostAttributionQuery {
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub capability_id: Option<String>,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub agent_subject: Option<String>,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub tool_server: Option<String>,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub tool_name: Option<String>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub since: Option<u64>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub until: Option<u64>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub limit: Option<usize>,
29 #[serde(skip)]
31 pub read_context: Option<ReceiptReadContext>,
32}
33
34impl Default for CostAttributionQuery {
35 fn default() -> Self {
36 Self {
37 capability_id: None,
38 agent_subject: None,
39 tool_server: None,
40 tool_name: None,
41 since: None,
42 until: None,
43 limit: Some(100),
44 read_context: None,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
50#[serde(rename_all = "camelCase")]
51pub struct CostAttributionSummary {
52 pub matching_receipts: u64,
53 pub returned_receipts: u64,
54 pub total_cost_charged: u64,
55 pub total_attempted_cost: u64,
56 pub max_delegation_depth: u64,
57 pub distinct_root_subjects: u64,
58 pub distinct_leaf_subjects: u64,
59 pub lineage_gap_count: u64,
60 pub truncated: bool,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
64#[serde(rename_all = "camelCase")]
65pub struct CostAttributionChainHop {
66 pub capability_id: String,
67 pub subject_key: String,
68 pub issuer_key: String,
69 pub delegation_depth: u64,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub parent_capability_id: Option<String>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75#[serde(rename_all = "camelCase")]
76pub struct RootCostAttributionRow {
77 pub root_subject_key: String,
78 pub receipt_count: u64,
79 pub total_cost_charged: u64,
80 pub total_attempted_cost: u64,
81 pub distinct_leaf_subjects: u64,
82 pub max_delegation_depth: u64,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
86#[serde(rename_all = "camelCase")]
87pub struct LeafCostAttributionRow {
88 pub root_subject_key: String,
89 pub leaf_subject_key: String,
90 pub receipt_count: u64,
91 pub total_cost_charged: u64,
92 pub total_attempted_cost: u64,
93 pub max_delegation_depth: u64,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
97#[serde(rename_all = "camelCase")]
98pub struct CostAttributionReceiptRow {
99 pub seq: u64,
100 pub receipt_id: String,
101 pub timestamp: u64,
102 pub capability_id: String,
103 pub tool_server: String,
104 pub tool_name: String,
105 pub decision_kind: String,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub root_subject_key: Option<String>,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub leaf_subject_key: Option<String>,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub grant_index: Option<u32>,
112 pub delegation_depth: u64,
113 pub cost_charged: u64,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub attempted_cost: Option<u64>,
116 pub currency: String,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub budget_total: Option<u64>,
119 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub budget_remaining: Option<u64>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub settlement_status: Option<SettlementStatus>,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub payment_reference: Option<String>,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub budget_authority: Option<FinancialBudgetAuthorityReceiptMetadata>,
127 pub lineage_complete: bool,
128 pub chain: Vec<CostAttributionChainHop>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
132#[serde(rename_all = "camelCase")]
133pub struct CostAttributionReport {
134 pub summary: CostAttributionSummary,
135 pub by_root: Vec<RootCostAttributionRow>,
136 pub by_leaf: Vec<LeafCostAttributionRow>,
137 pub receipts: Vec<CostAttributionReceiptRow>,
138}