Skip to main content

Architecture

This document covers the internal architecture of Agent-TS, including the component structure, message flow, and extension patterns.

Component Overview

File Responsibilities

Core Files

FileResponsibility
index.tsExpress server, webhook routes, agent lifecycle
smartpay-agent.tsOpenAI client, assistant management, conversation state
gateway-client.tsGateway API client with typed interfaces
config.tsEnvironment variable parsing and defaults
logger.tsPino logging adapter

Tools Directory

FileFunctions
wallet.tscheck_balance, get_transactions, initiate_transfer, get_account_limits
cards.tsget_user_cards, link_card, block_card, unblock_card
kyc.tsupload_kyc_image, upgrade_kyc
registration.tsregister_subscriber
agent.tsAgent-specific operations

Service Files

FileResponsibility
ocr-service.tsTesseract.js OCR extraction and scoring
id-card-validator.tsOpenAI Vision + heuristics for ID validation
image-compressor.tsImage optimization before upload
s3-uploader.tsPrivate S3 uploads with pre-signed URLs

Request Flow

Text Message Processing

Image Upload Processing

OpenAI Integration

Assistant Configuration

const assistant = await openai.beta.assistants.create({
name: "SmartPay Wallet Assistant",
model: "gpt-4o-mini",
instructions: `You are a helpful assistant for SmartPay mobile wallet users.
Help users check balances, send money, and manage their cards.
Always verify transaction details before executing.`,
tools: toolDefinitions
});

Tool Registration Pattern

// Define the tool schema
export const checkBalanceDefinition = {
type: "function" as const,
function: {
name: "check_balance",
description: "Get the user's current wallet balance",
parameters: {
type: "object",
properties: {
currency: {
type: "string",
enum: ["SLE", "USD"],
description: "Currency to check balance for"
}
},
required: []
}
}
};

// Implement the handler
export async function checkBalanceHandler(
args: { currency?: string },
context: ToolContext
): Promise<ToolResult> {
const balance = await gatewayClient.getBalance(
context.userId,
args.currency || "SLE"
);

return {
success: true,
data: balance,
message: `Balance: ${balance.balance} ${balance.currency}`
};
}

Security Layers

Input Sanitization

// chat-security.ts
export function sanitizeInput(input: string): string {
// Remove potential injection attacks
// Validate phone number format
// Strip control characters
return sanitized;
}

Rate Limiting

// rate-limiter.ts
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 30, // 30 requests per minute per phone
keyGenerator: (req) => req.body.phoneE164
});

Service Authentication

For internal service calls, the agent uses HMAC-based authentication:

// service-auth.ts
export function generateServiceAuthHeader(
secret: string,
timestamp: string,
body: string
): string {
const signature = crypto
.createHmac('sha256', secret)
.update(`${timestamp}${body}`)
.digest('hex');
return `HMAC ${timestamp}:${signature}`;
}

Error Handling

Graceful Degradation

try {
const result = await gatewayClient.transfer(params);
return { success: true, data: result };
} catch (error) {
if (error.code === 'INSUFFICIENT_BALANCE') {
return {
success: false,
error: 'Insufficient balance for this transfer',
userMessage: 'You do not have enough balance. Please top up.'
};
}
// Log and return generic error
logger.error('Transfer failed', { error, params });
return {
success: false,
error: 'Transfer failed',
userMessage: 'Unable to complete transfer. Please try again.'
};
}

Extension Guide

Adding a New Tool

  1. Create the tool definition and handler:
// src/tools/my-tool.ts
export const myToolDefinition = {
type: "function" as const,
function: {
name: "my_tool",
description: "Description of what this tool does",
parameters: {
type: "object",
properties: {
param1: { type: "string" }
},
required: ["param1"]
}
}
};

export async function myToolHandler(
args: { param1: string },
context: ToolContext
): Promise<ToolResult> {
// Implementation
return { success: true, data: result };
}
  1. Register in smartpay-agent.ts:
import { myToolDefinition, myToolHandler } from './tools/my-tool';

toolDefinitions.push(myToolDefinition);
toolHandlers['my_tool'] = myToolHandler;
  1. Restart the agent to apply changes.

Next Steps