use super::{Capability, CapabilityStatus};
use crate::tool_types::ToolHints;
use crate::tools::{Tool, ToolExecutionResult};
use crate::traits::ToolContext;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
pub struct FakeFinancialCapability;
impl Capability for FakeFinancialCapability {
fn id(&self) -> &str {
"fake_financial"
}
fn name(&self) -> &str {
"Fake Financial Tools"
}
fn description(&self) -> &str {
"Demo capability: financial management tools (transactions, budgets, reports, forecasting). State stored in session filesystem."
}
fn status(&self) -> CapabilityStatus {
CapabilityStatus::Available
}
fn icon(&self) -> Option<&str> {
Some("dollar-sign")
}
fn category(&self) -> Option<&str> {
Some("Demo Tools")
}
fn system_prompt_addition(&self) -> Option<&str> {
Some(
"Financial data is stored in /finance/ (transactions.json, budgets.json, accounts.json).",
)
}
fn tools(&self) -> Vec<Box<dyn Tool>> {
vec![
Box::new(FinanceListTransactionsTool),
Box::new(FinanceCreateTransactionTool),
Box::new(FinanceGetBalanceTool),
Box::new(FinanceListBudgetsTool),
Box::new(FinanceCreateBudgetTool),
Box::new(FinanceGetExpenseReportTool),
Box::new(FinanceGetRevenueReportTool),
Box::new(FinanceForecastCashFlowTool),
]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Transaction {
id: String,
date: String,
transaction_type: String, category: String,
amount: f64,
description: String,
account: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Budget {
category: String,
monthly_limit: f64,
current_spent: f64,
period_start: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Account {
name: String,
balance: f64,
currency: String,
}
pub struct FinanceListTransactionsTool;
#[async_trait]
impl Tool for FinanceListTransactionsTool {
fn name(&self) -> &str {
"finance_list_transactions"
}
fn display_name(&self) -> Option<&str> {
Some("List Transactions")
}
fn description(&self) -> &str {
"List financial transactions. Filter by type (income/expense) or category."
}
fn parameters_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"transaction_type": {
"type": "string",
"enum": ["income", "expense"],
"description": "Optional: Filter by transaction type"
},
"category": {
"type": "string",
"description": "Optional: Filter by category"
}
},
"additionalProperties": false
})
}
fn hints(&self) -> ToolHints {
ToolHints::default()
.with_readonly(true)
.with_idempotent(true)
}
async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
ToolExecutionResult::tool_error("finance_list_transactions requires context")
}
async fn execute_with_context(
&self,
arguments: Value,
context: &ToolContext,
) -> ToolExecutionResult {
let file_store = match &context.file_store {
Some(store) => store,
None => return ToolExecutionResult::tool_error("File system not available"),
};
let type_filter = arguments.get("transaction_type").and_then(|v| v.as_str());
let category_filter = arguments.get("category").and_then(|v| v.as_str());
let transactions: Vec<Transaction> = match file_store
.read_file(context.session_id, "/finance/transactions.json")
.await
{
Ok(Some(file)) => serde_json::from_str(file.content.as_deref().unwrap_or(""))
.unwrap_or_else(|_| {
vec![
Transaction {
id: "TXN-001".to_string(),
date: "2025-01-05".to_string(),
transaction_type: "income".to_string(),
category: "sales".to_string(),
amount: 15000.0,
description: "Q1 Product Sales".to_string(),
account: "operating".to_string(),
},
Transaction {
id: "TXN-002".to_string(),
date: "2025-01-06".to_string(),
transaction_type: "expense".to_string(),
category: "payroll".to_string(),
amount: 8500.0,
description: "January Payroll".to_string(),
account: "operating".to_string(),
},
]
}),
_ => {
let initial = vec![Transaction {
id: "TXN-001".to_string(),
date: chrono::Utc::now().format("%Y-%m-%d").to_string(),
transaction_type: "income".to_string(),
category: "sales".to_string(),
amount: 15000.0,
description: "Initial Sale".to_string(),
account: "operating".to_string(),
}];
let content = serde_json::to_string_pretty(&initial).unwrap();
let _ = file_store
.write_file(
context.session_id,
"/finance/transactions.json",
&content,
"text",
)
.await;
initial
}
};
let mut filtered = transactions;
if let Some(tx_type) = type_filter {
filtered.retain(|t| t.transaction_type == tx_type);
}
if let Some(category) = category_filter {
filtered.retain(|t| t.category == category);
}
let total: f64 = filtered.iter().map(|t| t.amount).sum();
ToolExecutionResult::success(json!({
"transactions": filtered,
"total_count": filtered.len(),
"total_amount": total
}))
}
fn requires_context(&self) -> bool {
true
}
}
pub struct FinanceCreateTransactionTool;
#[async_trait]
impl Tool for FinanceCreateTransactionTool {
fn name(&self) -> &str {
"finance_create_transaction"
}
fn display_name(&self) -> Option<&str> {
Some("Create Transaction")
}
fn description(&self) -> &str {
"Record a new financial transaction (income or expense)."
}
fn parameters_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"transaction_type": {
"type": "string",
"enum": ["income", "expense"]
},
"category": {"type": "string"},
"amount": {"type": "number", "minimum": 0},
"description": {"type": "string"},
"account": {"type": "string", "default": "operating"}
},
"required": ["transaction_type", "category", "amount", "description"],
"additionalProperties": false
})
}
async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
ToolExecutionResult::tool_error("finance_create_transaction requires context")
}
async fn execute_with_context(
&self,
arguments: Value,
context: &ToolContext,
) -> ToolExecutionResult {
let file_store = match &context.file_store {
Some(store) => store,
None => return ToolExecutionResult::tool_error("File system not available"),
};
let transaction_type = match arguments.get("transaction_type").and_then(|v| v.as_str()) {
Some(t) => t,
None => {
return ToolExecutionResult::tool_error(
"Missing required parameter: transaction_type",
);
}
};
let category = match arguments.get("category").and_then(|v| v.as_str()) {
Some(c) => c,
None => return ToolExecutionResult::tool_error("Missing required parameter: category"),
};
let amount = match arguments.get("amount").and_then(|v| v.as_f64()) {
Some(a) => a,
None => return ToolExecutionResult::tool_error("Missing required parameter: amount"),
};
let description = match arguments.get("description").and_then(|v| v.as_str()) {
Some(d) => d,
None => {
return ToolExecutionResult::tool_error("Missing required parameter: description");
}
};
let account = arguments
.get("account")
.and_then(|v| v.as_str())
.unwrap_or("operating");
let mut transactions: Vec<Transaction> = match file_store
.read_file(context.session_id, "/finance/transactions.json")
.await
{
Ok(Some(file)) => {
serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
}
_ => vec![],
};
let transaction_id = format!("TXN-{:03}", transactions.len() + 1);
let date = chrono::Utc::now().format("%Y-%m-%d").to_string();
let transaction = Transaction {
id: transaction_id.clone(),
date: date.clone(),
transaction_type: transaction_type.to_string(),
category: category.to_string(),
amount,
description: description.to_string(),
account: account.to_string(),
};
transactions.push(transaction.clone());
let content = serde_json::to_string_pretty(&transactions).unwrap();
match file_store
.write_file(
context.session_id,
"/finance/transactions.json",
&content,
"text",
)
.await
{
Ok(_) => ToolExecutionResult::success(json!({
"transaction_id": transaction_id,
"amount": amount,
"type": transaction_type,
"date": date
})),
Err(e) => ToolExecutionResult::internal_error(e),
}
}
fn requires_context(&self) -> bool {
true
}
}
pub struct FinanceGetBalanceTool;
#[async_trait]
impl Tool for FinanceGetBalanceTool {
fn name(&self) -> &str {
"finance_get_balance"
}
fn display_name(&self) -> Option<&str> {
Some("Get Balance")
}
fn description(&self) -> &str {
"Get current account balances."
}
fn parameters_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"account": {
"type": "string",
"description": "Optional: Get balance for specific account"
}
},
"additionalProperties": false
})
}
fn hints(&self) -> ToolHints {
ToolHints::default()
.with_readonly(true)
.with_idempotent(true)
}
async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
ToolExecutionResult::tool_error("finance_get_balance requires context")
}
async fn execute_with_context(
&self,
arguments: Value,
_context: &ToolContext,
) -> ToolExecutionResult {
let account_filter = arguments.get("account").and_then(|v| v.as_str());
let accounts = vec![
Account {
name: "operating".to_string(),
balance: 125000.0,
currency: "USD".to_string(),
},
Account {
name: "savings".to_string(),
balance: 50000.0,
currency: "USD".to_string(),
},
Account {
name: "payroll".to_string(),
balance: 75000.0,
currency: "USD".to_string(),
},
];
let filtered: Vec<_> = if let Some(account) = account_filter {
accounts.into_iter().filter(|a| a.name == account).collect()
} else {
accounts
};
let total_balance: f64 = filtered.iter().map(|a| a.balance).sum();
ToolExecutionResult::success(json!({
"accounts": filtered,
"total_balance": total_balance,
"currency": "USD"
}))
}
fn requires_context(&self) -> bool {
true
}
}
pub struct FinanceListBudgetsTool;
#[async_trait]
impl Tool for FinanceListBudgetsTool {
fn name(&self) -> &str {
"finance_list_budgets"
}
fn display_name(&self) -> Option<&str> {
Some("List Budgets")
}
fn description(&self) -> &str {
"List all budgets by category."
}
fn parameters_schema(&self) -> Value {
json!({"type": "object", "properties": {}, "additionalProperties": false})
}
fn hints(&self) -> ToolHints {
ToolHints::default()
.with_readonly(true)
.with_idempotent(true)
}
async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
ToolExecutionResult::tool_error("Requires context")
}
async fn execute_with_context(
&self,
_arguments: Value,
_context: &ToolContext,
) -> ToolExecutionResult {
let budgets = vec![
Budget {
category: "payroll".to_string(),
monthly_limit: 50000.0,
current_spent: 38500.0,
period_start: "2025-01-01".to_string(),
},
Budget {
category: "marketing".to_string(),
monthly_limit: 15000.0,
current_spent: 8200.0,
period_start: "2025-01-01".to_string(),
},
];
ToolExecutionResult::success(json!({"budgets": budgets, "total_count": budgets.len()}))
}
fn requires_context(&self) -> bool {
true
}
}
pub struct FinanceCreateBudgetTool;
#[async_trait]
impl Tool for FinanceCreateBudgetTool {
fn name(&self) -> &str {
"finance_create_budget"
}
fn display_name(&self) -> Option<&str> {
Some("Create Budget")
}
fn description(&self) -> &str {
"Create or update a budget for a category."
}
fn parameters_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"category": {"type": "string"},
"monthly_limit": {"type": "number", "minimum": 0}
},
"required": ["category", "monthly_limit"],
"additionalProperties": false
})
}
async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
ToolExecutionResult::tool_error("Requires context")
}
async fn execute_with_context(
&self,
arguments: Value,
_context: &ToolContext,
) -> ToolExecutionResult {
let category = arguments
.get("category")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let monthly_limit = arguments
.get("monthly_limit")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
ToolExecutionResult::success(json!({
"category": category,
"monthly_limit": monthly_limit,
"status": "created"
}))
}
fn requires_context(&self) -> bool {
true
}
}
pub struct FinanceGetExpenseReportTool;
#[async_trait]
impl Tool for FinanceGetExpenseReportTool {
fn name(&self) -> &str {
"finance_get_expense_report"
}
fn display_name(&self) -> Option<&str> {
Some("Get Expense Report")
}
fn description(&self) -> &str {
"Generate expense report for a time period, broken down by category."
}
fn parameters_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"period": {
"type": "string",
"enum": ["current_month", "last_month", "quarter", "year"],
"default": "current_month"
}
},
"additionalProperties": false
})
}
fn hints(&self) -> ToolHints {
ToolHints::default()
.with_readonly(true)
.with_idempotent(true)
}
async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
ToolExecutionResult::tool_error("Requires context")
}
async fn execute_with_context(
&self,
arguments: Value,
_context: &ToolContext,
) -> ToolExecutionResult {
let period = arguments
.get("period")
.and_then(|v| v.as_str())
.unwrap_or("current_month");
let expense_breakdown = json!([
{"category": "payroll", "amount": 85000.0, "percentage": 60.0},
{"category": "marketing", "amount": 25000.0, "percentage": 18.0},
{"category": "operations", "amount": 20000.0, "percentage": 14.0},
{"category": "other", "amount": 11500.0, "percentage": 8.0}
]);
ToolExecutionResult::success(json!({
"period": period,
"total_expenses": 141500.0,
"breakdown": expense_breakdown,
"top_category": "payroll"
}))
}
fn requires_context(&self) -> bool {
true
}
}
pub struct FinanceGetRevenueReportTool;
#[async_trait]
impl Tool for FinanceGetRevenueReportTool {
fn name(&self) -> &str {
"finance_get_revenue_report"
}
fn display_name(&self) -> Option<&str> {
Some("Get Revenue Report")
}
fn description(&self) -> &str {
"Generate revenue report for a time period."
}
fn parameters_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"period": {
"type": "string",
"enum": ["current_month", "last_month", "quarter", "year"],
"default": "current_month"
}
},
"additionalProperties": false
})
}
fn hints(&self) -> ToolHints {
ToolHints::default()
.with_readonly(true)
.with_idempotent(true)
}
async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
ToolExecutionResult::tool_error("Requires context")
}
async fn execute_with_context(
&self,
arguments: Value,
_context: &ToolContext,
) -> ToolExecutionResult {
let period = arguments
.get("period")
.and_then(|v| v.as_str())
.unwrap_or("current_month");
let revenue_breakdown = json!([
{"source": "product_sales", "amount": 150000.0, "percentage": 75.0},
{"source": "subscriptions", "amount": 35000.0, "percentage": 17.5},
{"source": "services", "amount": 15000.0, "percentage": 7.5}
]);
ToolExecutionResult::success(json!({
"period": period,
"total_revenue": 200000.0,
"breakdown": revenue_breakdown,
"growth_vs_last_period": 12.5
}))
}
fn requires_context(&self) -> bool {
true
}
}
pub struct FinanceForecastCashFlowTool;
#[async_trait]
impl Tool for FinanceForecastCashFlowTool {
fn name(&self) -> &str {
"finance_forecast_cash_flow"
}
fn display_name(&self) -> Option<&str> {
Some("Forecast Cash Flow")
}
fn description(&self) -> &str {
"Forecast cash flow for the next 3-6 months based on historical data."
}
fn parameters_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"months": {
"type": "integer",
"minimum": 1,
"maximum": 12,
"default": 3,
"description": "Number of months to forecast"
}
},
"additionalProperties": false
})
}
fn hints(&self) -> ToolHints {
ToolHints::default()
.with_readonly(true)
.with_idempotent(true)
}
async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
ToolExecutionResult::tool_error("Requires context")
}
async fn execute_with_context(
&self,
arguments: Value,
_context: &ToolContext,
) -> ToolExecutionResult {
let months = arguments
.get("months")
.and_then(|v| v.as_i64())
.unwrap_or(3) as i32;
let forecast: Vec<Value> = (1..=months)
.map(|m| {
json!({
"month": m,
"projected_revenue": 200000.0 + (m as f64 * 5000.0),
"projected_expenses": 140000.0 + (m as f64 * 2000.0),
"net_cash_flow": 60000.0 + (m as f64 * 3000.0),
"ending_balance": 125000.0 + (m as f64 * 60000.0)
})
})
.collect();
ToolExecutionResult::success(json!({
"forecast_months": months,
"forecast": forecast,
"assumptions": "Based on 10% monthly growth"
}))
}
fn requires_context(&self) -> bool {
true
}
}