Skip to main content

Agent-TS 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
olive-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: "OLIVE Wallet Assistant",
  model: "gpt-4o-mini",
  instructions: `You are a helpful assistant for OLIVE 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 olive-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