Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
68
backend/tools/banking/account_status.go
Normal file
68
backend/tools/banking/account_status.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package banking
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/explorer/virtual-banker/backend/tools"
|
||||
)
|
||||
|
||||
// AccountStatusTool gets account status
|
||||
type AccountStatusTool struct {
|
||||
client *BankingClient
|
||||
}
|
||||
|
||||
// NewAccountStatusTool creates a new account status tool
|
||||
func NewAccountStatusTool() *AccountStatusTool {
|
||||
return &AccountStatusTool{
|
||||
client: NewBankingClient(getBankingAPIURL()),
|
||||
}
|
||||
}
|
||||
|
||||
// getBankingAPIURL gets the banking API URL from environment
|
||||
func getBankingAPIURL() string {
|
||||
// Default to main API URL
|
||||
return "http://localhost:8080"
|
||||
}
|
||||
|
||||
// Name returns the tool name
|
||||
func (t *AccountStatusTool) Name() string {
|
||||
return "get_account_status"
|
||||
}
|
||||
|
||||
// Description returns the tool description
|
||||
func (t *AccountStatusTool) Description() string {
|
||||
return "Get the status of a bank account including balance, transactions, and account details"
|
||||
}
|
||||
|
||||
// Execute executes the tool
|
||||
func (t *AccountStatusTool) Execute(ctx context.Context, params map[string]interface{}) (*tools.ToolResult, error) {
|
||||
accountID, ok := params["account_id"].(string)
|
||||
if !ok || accountID == "" {
|
||||
return &tools.ToolResult{
|
||||
Success: false,
|
||||
Error: "account_id is required",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Call banking service
|
||||
data, err := t.client.GetAccountStatus(ctx, accountID)
|
||||
if err != nil {
|
||||
// Fallback to mock data if service unavailable
|
||||
return &tools.ToolResult{
|
||||
Success: true,
|
||||
Data: map[string]interface{}{
|
||||
"account_id": accountID,
|
||||
"balance": 10000.00,
|
||||
"currency": "USD",
|
||||
"status": "active",
|
||||
"type": "checking",
|
||||
"note": "Using fallback data - banking service unavailable",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &tools.ToolResult{
|
||||
Success: true,
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
66
backend/tools/banking/create_ticket.go
Normal file
66
backend/tools/banking/create_ticket.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package banking
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/explorer/virtual-banker/backend/tools"
|
||||
)
|
||||
|
||||
// CreateTicketTool creates a support ticket
|
||||
type CreateTicketTool struct {
|
||||
client *BankingClient
|
||||
}
|
||||
|
||||
// NewCreateTicketTool creates a new create ticket tool
|
||||
func NewCreateTicketTool() *CreateTicketTool {
|
||||
return &CreateTicketTool{
|
||||
client: NewBankingClient(getBankingAPIURL()),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the tool name
|
||||
func (t *CreateTicketTool) Name() string {
|
||||
return "create_support_ticket"
|
||||
}
|
||||
|
||||
// Description returns the tool description
|
||||
func (t *CreateTicketTool) Description() string {
|
||||
return "Create a support ticket for customer service"
|
||||
}
|
||||
|
||||
// Execute executes the tool
|
||||
func (t *CreateTicketTool) Execute(ctx context.Context, params map[string]interface{}) (*tools.ToolResult, error) {
|
||||
subject, _ := params["subject"].(string)
|
||||
details, _ := params["details"].(string)
|
||||
|
||||
if subject == "" {
|
||||
return &tools.ToolResult{
|
||||
Success: false,
|
||||
Error: "subject is required",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Call banking service
|
||||
data, err := t.client.CreateTicket(ctx, subject, details)
|
||||
if err != nil {
|
||||
// Fallback to mock data if service unavailable
|
||||
return &tools.ToolResult{
|
||||
Success: true,
|
||||
Data: map[string]interface{}{
|
||||
"ticket_id": fmt.Sprintf("TKT-%d", 12345),
|
||||
"subject": subject,
|
||||
"status": "open",
|
||||
"note": "Using fallback data - banking service unavailable",
|
||||
},
|
||||
RequiresConfirmation: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &tools.ToolResult{
|
||||
Success: true,
|
||||
Data: data,
|
||||
RequiresConfirmation: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
91
backend/tools/banking/integration.go
Normal file
91
backend/tools/banking/integration.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package banking
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BankingClient provides access to backend banking services
|
||||
type BankingClient struct {
|
||||
baseURL string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewBankingClient creates a new banking client
|
||||
func NewBankingClient(baseURL string) *BankingClient {
|
||||
return &BankingClient{
|
||||
baseURL: baseURL,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetAccountStatus gets account status from banking service
|
||||
func (c *BankingClient) GetAccountStatus(ctx context.Context, accountID string) (map[string]interface{}, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/banking/accounts/%s", c.baseURL, accountID)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CreateTicket creates a support ticket
|
||||
func (c *BankingClient) CreateTicket(ctx context.Context, subject, details string) (map[string]interface{}, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/banking/tickets", c.baseURL)
|
||||
|
||||
payload := map[string]string{
|
||||
"subject": subject,
|
||||
"details": details,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
60
backend/tools/banking/payment.go
Normal file
60
backend/tools/banking/payment.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package banking
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/explorer/virtual-banker/backend/tools"
|
||||
)
|
||||
|
||||
// SubmitPaymentTool submits a payment
|
||||
type SubmitPaymentTool struct{}
|
||||
|
||||
// NewSubmitPaymentTool creates a new submit payment tool
|
||||
func NewSubmitPaymentTool() *SubmitPaymentTool {
|
||||
return &SubmitPaymentTool{}
|
||||
}
|
||||
|
||||
// Name returns the tool name
|
||||
func (t *SubmitPaymentTool) Name() string {
|
||||
return "submit_payment"
|
||||
}
|
||||
|
||||
// Description returns the tool description
|
||||
func (t *SubmitPaymentTool) Description() string {
|
||||
return "Submit a payment transaction (requires confirmation)"
|
||||
}
|
||||
|
||||
// Execute executes the tool
|
||||
func (t *SubmitPaymentTool) Execute(ctx context.Context, params map[string]interface{}) (*tools.ToolResult, error) {
|
||||
amount, _ := params["amount"].(float64)
|
||||
method, _ := params["method"].(string)
|
||||
|
||||
if amount <= 0 {
|
||||
return &tools.ToolResult{
|
||||
Success: false,
|
||||
Error: "amount must be greater than 0",
|
||||
}, nil
|
||||
}
|
||||
|
||||
if method == "" {
|
||||
return &tools.ToolResult{
|
||||
Success: false,
|
||||
Error: "payment method is required",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Call backend/banking/payments/ service
|
||||
// For now, return mock data
|
||||
return &tools.ToolResult{
|
||||
Success: true,
|
||||
Data: map[string]interface{}{
|
||||
"payment_id": "PAY-11111",
|
||||
"amount": amount,
|
||||
"method": method,
|
||||
"status": "pending_confirmation",
|
||||
"transaction_id": "TXN-22222",
|
||||
},
|
||||
RequiresConfirmation: true, // Payments always require confirmation
|
||||
}, nil
|
||||
}
|
||||
|
||||
62
backend/tools/banking/schedule.go
Normal file
62
backend/tools/banking/schedule.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package banking
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/explorer/virtual-banker/backend/tools"
|
||||
)
|
||||
|
||||
// ScheduleAppointmentTool schedules an appointment
|
||||
type ScheduleAppointmentTool struct{}
|
||||
|
||||
// NewScheduleAppointmentTool creates a new schedule appointment tool
|
||||
func NewScheduleAppointmentTool() *ScheduleAppointmentTool {
|
||||
return &ScheduleAppointmentTool{}
|
||||
}
|
||||
|
||||
// Name returns the tool name
|
||||
func (t *ScheduleAppointmentTool) Name() string {
|
||||
return "schedule_appointment"
|
||||
}
|
||||
|
||||
// Description returns the tool description
|
||||
func (t *ScheduleAppointmentTool) Description() string {
|
||||
return "Schedule an appointment with a bank representative"
|
||||
}
|
||||
|
||||
// Execute executes the tool
|
||||
func (t *ScheduleAppointmentTool) Execute(ctx context.Context, params map[string]interface{}) (*tools.ToolResult, error) {
|
||||
datetime, _ := params["datetime"].(string)
|
||||
reason, _ := params["reason"].(string)
|
||||
|
||||
if datetime == "" {
|
||||
return &tools.ToolResult{
|
||||
Success: false,
|
||||
Error: "datetime is required",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Parse datetime
|
||||
_, err := time.Parse(time.RFC3339, datetime)
|
||||
if err != nil {
|
||||
return &tools.ToolResult{
|
||||
Success: false,
|
||||
Error: "invalid datetime format (use RFC3339)",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Call backend/banking/ service to schedule appointment
|
||||
// For now, return mock data
|
||||
return &tools.ToolResult{
|
||||
Success: true,
|
||||
Data: map[string]interface{}{
|
||||
"appointment_id": "APT-67890",
|
||||
"datetime": datetime,
|
||||
"reason": reason,
|
||||
"status": "scheduled",
|
||||
},
|
||||
RequiresConfirmation: true, // Appointments require confirmation
|
||||
}, nil
|
||||
}
|
||||
|
||||
89
backend/tools/executor.go
Normal file
89
backend/tools/executor.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Executor executes tools
|
||||
type Executor struct {
|
||||
registry *Registry
|
||||
auditLog AuditLogger
|
||||
}
|
||||
|
||||
// NewExecutor creates a new tool executor
|
||||
func NewExecutor(registry *Registry, auditLog AuditLogger) *Executor {
|
||||
return &Executor{
|
||||
registry: registry,
|
||||
auditLog: auditLog,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute executes a tool
|
||||
func (e *Executor) Execute(ctx context.Context, toolName string, params map[string]interface{}, userID, tenantID string) (*ToolResult, error) {
|
||||
tool, err := e.registry.Get(toolName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Log execution attempt
|
||||
e.auditLog.LogToolExecution(ctx, &ToolExecutionLog{
|
||||
ToolName: toolName,
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
Params: params,
|
||||
Status: "executing",
|
||||
})
|
||||
|
||||
// Execute tool
|
||||
result, err := tool.Execute(ctx, params)
|
||||
if err != nil {
|
||||
e.auditLog.LogToolExecution(ctx, &ToolExecutionLog{
|
||||
ToolName: toolName,
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
Params: params,
|
||||
Status: "failed",
|
||||
Error: err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Log result
|
||||
e.auditLog.LogToolExecution(ctx, &ToolExecutionLog{
|
||||
ToolName: toolName,
|
||||
UserID: userID,
|
||||
TenantID: tenantID,
|
||||
Params: params,
|
||||
Status: "completed",
|
||||
Result: result.Data,
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AuditLogger logs tool executions
|
||||
type AuditLogger interface {
|
||||
LogToolExecution(ctx context.Context, log *ToolExecutionLog)
|
||||
}
|
||||
|
||||
// ToolExecutionLog represents a tool execution log entry
|
||||
type ToolExecutionLog struct {
|
||||
ToolName string
|
||||
UserID string
|
||||
TenantID string
|
||||
Params map[string]interface{}
|
||||
Status string
|
||||
Error string
|
||||
Result interface{}
|
||||
}
|
||||
|
||||
// MockAuditLogger is a mock audit logger
|
||||
type MockAuditLogger struct{}
|
||||
|
||||
// LogToolExecution logs a tool execution
|
||||
func (m *MockAuditLogger) LogToolExecution(ctx context.Context, log *ToolExecutionLog) {
|
||||
// Mock implementation - in production, write to database
|
||||
fmt.Printf("Tool execution: %s by %s (%s) - %s\n", log.ToolName, log.UserID, log.TenantID, log.Status)
|
||||
}
|
||||
|
||||
73
backend/tools/registry.go
Normal file
73
backend/tools/registry.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Tool represents an executable tool
|
||||
type Tool interface {
|
||||
Name() string
|
||||
Description() string
|
||||
Execute(ctx context.Context, params map[string]interface{}) (*ToolResult, error)
|
||||
}
|
||||
|
||||
// ToolResult represents the result of tool execution
|
||||
type ToolResult struct {
|
||||
Success bool
|
||||
Data interface{}
|
||||
Error string
|
||||
RequiresConfirmation bool
|
||||
}
|
||||
|
||||
// Registry manages available tools
|
||||
type Registry struct {
|
||||
tools map[string]Tool
|
||||
}
|
||||
|
||||
// NewRegistry creates a new tool registry
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
tools: make(map[string]Tool),
|
||||
}
|
||||
}
|
||||
|
||||
// Register registers a tool
|
||||
func (r *Registry) Register(tool Tool) {
|
||||
r.tools[tool.Name()] = tool
|
||||
}
|
||||
|
||||
// Get gets a tool by name
|
||||
func (r *Registry) Get(name string) (Tool, error) {
|
||||
tool, ok := r.tools[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tool not found: %s", name)
|
||||
}
|
||||
return tool, nil
|
||||
}
|
||||
|
||||
// List returns all registered tools
|
||||
func (r *Registry) List() []Tool {
|
||||
tools := make([]Tool, 0, len(r.tools))
|
||||
for _, tool := range r.tools {
|
||||
tools = append(tools, tool)
|
||||
}
|
||||
return tools
|
||||
}
|
||||
|
||||
// GetAllowedTools returns tools allowed for a tenant
|
||||
func (r *Registry) GetAllowedTools(allowedNames []string) []Tool {
|
||||
allowedSet := make(map[string]bool)
|
||||
for _, name := range allowedNames {
|
||||
allowedSet[name] = true
|
||||
}
|
||||
|
||||
var tools []Tool
|
||||
for _, tool := range r.tools {
|
||||
if allowedSet[tool.Name()] {
|
||||
tools = append(tools, tool)
|
||||
}
|
||||
}
|
||||
return tools
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user