diff --git a/docs/openapi/wallet.yaml b/docs/openapi/wallet.yaml index 38f1c64ff..1198828b8 100644 --- a/docs/openapi/wallet.yaml +++ b/docs/openapi/wallet.yaml @@ -259,6 +259,12 @@ components: data_for_tool: {} invoice_id: type: string + inbox: + type: string + description: Inbox the invoice belongs to + auto_pay: + type: boolean + description: Automatically pay future invoices from this provider and tool RestoreCoinbaseMPCWalletRequest: type: object required: diff --git a/shinkai-bin/shinkai-node/src/network/handle_commands_list.rs b/shinkai-bin/shinkai-node/src/network/handle_commands_list.rs index 261901472..85e75b170 100644 --- a/shinkai-bin/shinkai-node/src/network/handle_commands_list.rs +++ b/shinkai-bin/shinkai-node/src/network/handle_commands_list.rs @@ -17,11 +17,7 @@ impl Node { let listen_address_clone = self.listen_address; let libp2p_manager_clone = self.libp2p_manager.clone(); tokio::spawn(async move { - let _ = Self::ping_all( - listen_address_clone, - libp2p_manager_clone, - ) - .await; + let _ = Self::ping_all(listen_address_clone, libp2p_manager_clone).await; }); } NodeCommand::GetPublicKeys(sender) => { @@ -1245,11 +1241,16 @@ impl Node { let _ = Node::v2_api_get_shinkai_tool_metadata(db_clone, bearer, tool_router_key, res).await; }); } - NodeCommand::V2ApiGetToolWithOffering { bearer, tool_key_name, res } => { + NodeCommand::V2ApiGetToolWithOffering { + bearer, + tool_key_name, + res, + } => { let db_clone = Arc::clone(&self.db); let node_name_clone = self.node_name.clone(); tokio::spawn(async move { - let _ = Node::v2_api_get_tool_with_offering(db_clone, node_name_clone, bearer, tool_key_name, res).await; + let _ = Node::v2_api_get_tool_with_offering(db_clone, node_name_clone, bearer, tool_key_name, res) + .await; }); } NodeCommand::V2ApiGetToolsWithOfferings { bearer, res } => { @@ -1508,6 +1509,8 @@ impl Node { bearer, invoice_id, data_for_tool, + inbox, + auto_pay, res, } => { let db_clone = Arc::clone(&self.db); @@ -1520,6 +1523,8 @@ impl Node { bearer, invoice_id, data_for_tool, + inbox, + auto_pay, node_name, res, ) @@ -1669,7 +1674,11 @@ impl Node { let _ = Node::v2_api_get_job_scope(db_clone, bearer, job_id, res).await; }); } - NodeCommand::V2ApiGetMessageTraces { bearer, message_id, res } => { + NodeCommand::V2ApiGetMessageTraces { + bearer, + message_id, + res, + } => { let db_clone = Arc::clone(&self.db); tokio::spawn(async move { let _ = Node::v2_api_get_message_traces(db_clone, bearer, message_id, res).await; diff --git a/shinkai-bin/shinkai-node/src/network/network_manager/network_handlers.rs b/shinkai-bin/shinkai-node/src/network/network_manager/network_handlers.rs index 93e387648..9fc572acb 100644 --- a/shinkai-bin/shinkai-node/src/network/network_manager/network_handlers.rs +++ b/shinkai-bin/shinkai-node/src/network/network_manager/network_handlers.rs @@ -1,32 +1,44 @@ use crate::{ - managers::IdentityManager, network::{ + managers::IdentityManager, + network::{ agent_payments_manager::{ - external_agent_offerings_manager::ExtAgentOfferingsManager, my_agent_offerings_manager::MyAgentOfferingsManager - }, libp2p_manager::NetworkEvent, node::ProxyConnectionInfo, Node - } + external_agent_offerings_manager::ExtAgentOfferingsManager, + my_agent_offerings_manager::MyAgentOfferingsManager, + }, + libp2p_manager::NetworkEvent, + node::ProxyConnectionInfo, + Node, + }, }; use ed25519_dalek::{SigningKey, VerifyingKey}; use libp2p::{request_response::ResponseChannel, PeerId}; +use serde_json::json; use shinkai_message_primitives::schemas::ws_types::WSUpdateHandler; use shinkai_message_primitives::{ schemas::{ - invoices::{Invoice, InvoiceRequest, InvoiceRequestNetworkError}, shinkai_name::ShinkaiName - }, shinkai_message::{ - shinkai_message::{MessageBody, MessageData, ShinkaiMessage}, shinkai_message_error::ShinkaiMessageError, shinkai_message_extension::EncryptionStatus, shinkai_message_schemas::MessageSchemaType - }, shinkai_utils::{ - encryption::clone_static_secret_key, shinkai_logging::{shinkai_log, ShinkaiLogLevel, ShinkaiLogOption}, shinkai_message_builder::{ShinkaiMessageBuilder, ShinkaiNameString}, signatures::{clone_signature_secret_key, signature_public_key_to_string} - } + invoices::{Invoice, InvoiceRequest, InvoiceRequestNetworkError}, + shinkai_name::ShinkaiName, + }, + shinkai_message::{ + shinkai_message::{MessageBody, MessageData, ShinkaiMessage}, + shinkai_message_error::ShinkaiMessageError, + shinkai_message_extension::EncryptionStatus, + shinkai_message_schemas::MessageSchemaType, + }, + shinkai_utils::{ + encryption::clone_static_secret_key, + shinkai_logging::{shinkai_log, ShinkaiLogLevel, ShinkaiLogOption}, + shinkai_message_builder::{ShinkaiMessageBuilder, ShinkaiNameString}, + signatures::{clone_signature_secret_key, signature_public_key_to_string}, + }, }; -use serde_json::json; use shinkai_sqlite::SqliteManager; use std::sync::{Arc, Weak}; use std::{io, net::SocketAddr}; use tokio::sync::Mutex; use x25519_dalek::{PublicKey as EncryptionPublicKey, StaticSecret as EncryptionStaticKey}; - - pub enum PingPong { Ping, Pong, @@ -518,7 +530,10 @@ pub async fn handle_network_message_cases( ), ); - eprintln!("🔑 InvoiceRequestNetworkError Received from: {:?} to {:?}", requester, receiver); + eprintln!( + "🔑 InvoiceRequestNetworkError Received from: {:?} to {:?}", + requester, receiver + ); let content = message.get_message_content().unwrap_or("".to_string()); match serde_json::from_str::(&content) { @@ -591,6 +606,25 @@ pub async fn handle_network_message_cases( &format!("Failed to store invoice: {:?}", e), ); } + let inbox_name = match message.get_message_inbox() { + Ok(name) => name, + Err(_) => String::new(), + }; + let pref_key = format!( + "autopay:{}:{}:{}:{}", + invoice.requester_name.full_name, + invoice.provider_name.full_name, + invoice.shinkai_offering.tool_key, + inbox_name + ); + if let Ok(Some(true)) = maybe_db.get_preference::(&pref_key) { + if let Err(e) = my_agent_offering_manager + .auto_pay_invoice(invoice.clone(), my_agent_offering_manager.node_name.clone()) + .await + { + eprintln!("failed to auto pay invoice: {:?}", e); + } + } if let Err(e) = maybe_db.add_tracing( &invoice.invoice_id, None, @@ -649,8 +683,13 @@ pub async fn handle_network_message_cases( Ok(invoice) => { let mut ext_agent_offering_manager = ext_agent_offering_manager.lock().await; if let Err(e) = ext_agent_offering_manager - .network_confirm_invoice_payment_and_process(requester, invoice, Some(message.external_metadata)) - .await { + .network_confirm_invoice_payment_and_process( + requester, + invoice, + Some(message.external_metadata), + ) + .await + { shinkai_log( ShinkaiLogOption::Network, ShinkaiLogLevel::Error, @@ -809,9 +848,12 @@ pub async fn send_ack( ); } } else { - return Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "No channel defined."))); + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + "No channel defined.", + ))); } - + Ok(()) } diff --git a/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_my_agent_offers.rs b/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_my_agent_offers.rs index b5e5aab6d..a5430dbd9 100644 --- a/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_my_agent_offers.rs +++ b/shinkai-bin/shinkai-node/src/network/v2_api/api_v2_commands_my_agent_offers.rs @@ -69,10 +69,7 @@ impl Node { let manager = my_agent_payments_manager.lock().await; // Request the invoice - match manager - .network_request_invoice(network_tool, usage, None) - .await - { + match manager.network_request_invoice(network_tool, usage, None).await { Ok(invoice_request) => { let invoice_value = match serde_json::to_value(invoice_request) { Ok(value) => value, @@ -107,6 +104,8 @@ impl Node { bearer: String, invoice_id: String, data_for_tool: Value, + inbox: Option, + auto_pay: Option, node_name: ShinkaiName, res: Sender>, ) -> Result<(), NodeError> { @@ -216,12 +215,7 @@ impl Node { let payment = match my_agent_offerings_manager .lock() .await - .pay_invoice_and_send_receipt( - invoice_id, - data_for_tool, - node_name.clone(), - None, - ) + .pay_invoice_and_send_receipt(invoice_id, data_for_tool, node_name.clone(), None) .await { Ok(payment) => payment, @@ -250,6 +244,21 @@ impl Node { } }; + if let Some(true) = auto_pay { + if let Some(inbox_name) = inbox.clone() { + let pref_key = format!( + "autopay:{}:{}:{}:{}", + invoice.requester_name.full_name, + invoice.provider_name.full_name, + invoice.shinkai_offering.tool_key, + inbox_name + ); + if let Err(e) = db.set_preference(&pref_key, &true, Some("auto pay preference")) { + eprintln!("Failed to store auto-pay preference: {:?}", e); + } + } + } + // Send success response with payment details let payment_value = match serde_json::to_value(payment) { Ok(value) => value, diff --git a/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_wallets.rs b/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_wallets.rs index 59d867b6e..78216f67e 100644 --- a/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_wallets.rs +++ b/shinkai-libs/shinkai-http-api/src/api_v2/api_v2_handlers_wallets.rs @@ -2,7 +2,10 @@ use async_channel::Sender; use serde::Deserialize; use serde_json::Value; use shinkai_message_primitives::schemas::{ - coinbase_mpc_config::CoinbaseMPCWalletConfig, wallet_complementary::{WalletRole, WalletSource}, wallet_mixed::{Address, Asset, NetworkProtocolFamilyEnum}, x402_types::Network + coinbase_mpc_config::CoinbaseMPCWalletConfig, + wallet_complementary::{WalletRole, WalletSource}, + wallet_mixed::{Address, Asset, NetworkProtocolFamilyEnum}, + x402_types::Network, }; use utoipa::{OpenApi, ToSchema}; use warp::Filter; @@ -148,6 +151,10 @@ pub async fn create_local_wallet_handler( pub struct PayInvoiceRequest { pub invoice_id: String, pub data_for_tool: Value, + #[serde(default)] + pub inbox: Option, + #[serde(default)] + pub auto_pay: Option, } #[utoipa::path( @@ -171,6 +178,8 @@ pub async fn pay_invoice_handler( bearer, invoice_id: payload.invoice_id, data_for_tool: payload.data_for_tool, + inbox: payload.inbox, + auto_pay: payload.auto_pay, res: res_sender, }) .await @@ -273,7 +282,10 @@ pub async fn get_wallet_balance_handler( let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string(); let (res_sender, res_receiver) = async_channel::bounded(1); sender - .send(NodeCommand::V2ApiGetWalletBalance { bearer, res: res_sender }) + .send(NodeCommand::V2ApiGetWalletBalance { + bearer, + res: res_sender, + }) .await .map_err(|_| warp::reject::reject())?; diff --git a/shinkai-libs/shinkai-http-api/src/node_commands.rs b/shinkai-libs/shinkai-http-api/src/node_commands.rs index c1b8291c2..4d6c92518 100644 --- a/shinkai-libs/shinkai-http-api/src/node_commands.rs +++ b/shinkai-libs/shinkai-http-api/src/node_commands.rs @@ -583,6 +583,8 @@ pub enum NodeCommand { bearer: String, invoice_id: String, data_for_tool: Value, + inbox: Option, + auto_pay: Option, res: Sender>, }, V2ApiListInvoices {