Skip to main content

adk_payments/tools/
intervention.rs

1use std::sync::Arc;
2
3use adk_core::{AdkError, ErrorCategory, ErrorComponent, Result, Tool, ToolContext};
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::auth::INTERVENTION_CONTINUE_SCOPES;
9use crate::domain::{
10    CommerceActor, CommerceActorRole, CommerceMode, MerchantRef, ProtocolDescriptor,
11    ProtocolExtensions, SafeTransactionSummary, TransactionId,
12};
13use crate::guardrail::redact_tool_output;
14use crate::kernel::commands::{CommerceContext, ContinueInterventionCommand};
15use crate::kernel::service::InterventionService;
16
17/// JSON parameters accepted by `payments_intervention_continue`.
18#[derive(Debug, Deserialize)]
19#[serde(rename_all = "camelCase")]
20struct ContinueParams {
21    transaction_id: String,
22    intervention_id: String,
23    #[serde(default)]
24    continuation_token: Option<String>,
25    #[serde(default)]
26    result_summary: Option<String>,
27}
28
29/// Masked intervention response.
30#[derive(Debug, Serialize)]
31#[serde(rename_all = "camelCase")]
32struct InterventionResponse {
33    status: &'static str,
34    summary: SafeTransactionSummary,
35}
36
37struct ContinueInterventionTool {
38    intervention_service: Arc<dyn InterventionService>,
39}
40
41#[async_trait]
42impl Tool for ContinueInterventionTool {
43    fn name(&self) -> &str {
44        "payments_intervention_continue"
45    }
46
47    fn description(&self) -> &str {
48        "Resume or complete a payment intervention such as 3DS or buyer reconfirmation. The continuation token is required for safe resumption."
49    }
50
51    fn is_long_running(&self) -> bool {
52        true
53    }
54
55    fn required_scopes(&self) -> &[&str] {
56        INTERVENTION_CONTINUE_SCOPES
57    }
58
59    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
60        let params: ContinueParams = serde_json::from_value(args).map_err(|err| {
61            AdkError::new(
62                ErrorComponent::Tool,
63                ErrorCategory::InvalidInput,
64                "payments.tools.intervention_continue.invalid_args",
65                format!("invalid arguments for `intervention_continue`: {err}"),
66            )
67        })?;
68        let context = CommerceContext {
69            transaction_id: TransactionId::from(params.transaction_id),
70            session_identity: None,
71            actor: CommerceActor {
72                actor_id: "agent-tool".to_string(),
73                role: CommerceActorRole::AgentSurface,
74                display_name: Some("payment tool".to_string()),
75                tenant_id: None,
76                extensions: ProtocolExtensions::default(),
77            },
78            merchant_of_record: MerchantRef {
79                merchant_id: String::new(),
80                legal_name: "unknown".to_string(),
81                display_name: None,
82                statement_descriptor: None,
83                country_code: None,
84                website: None,
85                extensions: ProtocolExtensions::default(),
86            },
87            payment_processor: None,
88            mode: CommerceMode::HumanPresent,
89            protocol: ProtocolDescriptor::new("adk-tool", Some("1.0".to_string())),
90            extensions: ProtocolExtensions::default(),
91        };
92        let command = ContinueInterventionCommand {
93            context,
94            intervention_id: params.intervention_id,
95            continuation_token: params.continuation_token,
96            result_summary: params.result_summary,
97            extensions: ProtocolExtensions::default(),
98        };
99        let record = self.intervention_service.continue_intervention(command).await?;
100        let response = InterventionResponse { status: "ok", summary: record.safe_summary };
101        let value = serde_json::to_value(&response).map_err(|err| {
102            AdkError::new(
103                ErrorComponent::Tool,
104                ErrorCategory::Internal,
105                "payments.tools.serialize_failed",
106                format!("failed to serialize intervention response: {err}"),
107            )
108        })?;
109        Ok(redact_tool_output(&value))
110    }
111}
112
113/// Creates a `payments_intervention_continue` tool backed by the given intervention service.
114pub fn continue_intervention_tool(intervention_service: Arc<dyn InterventionService>) -> impl Tool {
115    ContinueInterventionTool { intervention_service }
116}