Skip to main content

Basic Tool

Create a tool with the tool() factory function:
import { tool, serve } from '@reminix/runtime';

const getWeather = tool('get_weather', {
  description: 'Get the current weather for a city',
  input: {
    type: 'object',
    properties: {
      location: { type: 'string', description: 'City name' },
      units: { type: 'string', default: 'celsius' },
    },
    required: ['location'],
  },
  handler: async (input) => {
    const location = input.location as string;
    const units = input.units as string || 'celsius';
    return {
      location,
      temperature: 22,
      units,
      condition: 'sunny',
    };
  },
});

serve({ tools: [getWeather], port: 8080 });

Tool Options

The tool() function takes a name and options object:
const myTool = tool('tool_name', {
  // Required: Human-readable description
  description: 'What this tool does',

  // Required: JSON Schema for input
  input: {
    type: 'object',
    properties: {
      param1: { type: 'string', description: 'First parameter' },
      param2: { type: 'number', default: 10 },
    },
    required: ['param1'],
  },

  // Optional: JSON Schema for output (for documentation)
  output: {
    type: 'object',
    properties: {
      result: { type: 'string' },
    },
  },

  // Required: Handler function
  handler: async (input, context) => {
    return { result: 'done' };
  },
});

Parameter Schema

Define input using JSON Schema:
const processTool = tool('process', {
  description: 'Process data with various types',
  input: {
    type: 'object',
    properties: {
      // String parameter
      text: { type: 'string', description: 'Input text' },

      // Number parameter
      count: { type: 'integer', description: 'Item count' },

      // Boolean parameter
      enabled: { type: 'boolean', default: true },

      // Array parameter
      tags: {
        type: 'array',
        items: { type: 'string' },
        description: 'List of tags',
      },

      // Object parameter
      options: {
        type: 'object',
        properties: {
          format: { type: 'string' },
        },
      },

      // Enum parameter
      mode: {
        type: 'string',
        enum: ['fast', 'accurate', 'balanced'],
        default: 'balanced',
      },
    },
    required: ['text', 'count'],
  },
  handler: async (input) => {
    return { processed: true };
  },
});

Handler Function

The handler function receives input and optional context:
const myTool = tool('my_tool', {
  description: 'Example tool',
  input: {
    type: 'object',
    properties: {
      query: { type: 'string' },
    },
    required: ['query'],
  },
  handler: async (input, context) => {
    // input: Record<string, unknown> - the tool input
    const query = input.query as string;

    // context: Record<string, unknown> | undefined - optional metadata
    const userId = context?.userId as string | undefined;

    // Return any serializable value
    return { query, userId };
  },
});

Sync and Async Execution

Both sync and async handlers are supported:
// Async handler
const fetchTool = tool('fetch_data', {
  description: 'Fetch data from a URL',
  input: {
    type: 'object',
    properties: {
      url: { type: 'string' },
    },
    required: ['url'],
  },
  handler: async (input) => {
    const response = await fetch(input.url as string);
    return await response.json();
  },
});

// Sync handler
const calculateTool = tool('calculate', {
  description: 'Perform calculations',
  input: {
    type: 'object',
    properties: {
      a: { type: 'number' },
      b: { type: 'number' },
    },
    required: ['a', 'b'],
  },
  handler: (input) => {
    const a = input.a as number;
    const b = input.b as number;
    return { sum: a + b, product: a * b };
  },
});

Error Handling

Return errors as part of the output or throw exceptions:
const divideTool = tool('divide', {
  description: 'Divide two numbers',
  input: {
    type: 'object',
    properties: {
      a: { type: 'number' },
      b: { type: 'number' },
    },
    required: ['a', 'b'],
  },
  handler: (input) => {
    const a = input.a as number;
    const b = input.b as number;

    if (b === 0) {
      // Option 1: Return error in output
      return { error: 'Cannot divide by zero' };
    }

    return { result: a / b };
  },
});

const validateTool = tool('validate', {
  description: 'Validate data',
  input: {
    type: 'object',
    properties: {
      data: { type: 'object' },
    },
    required: ['data'],
  },
  handler: (input) => {
    const data = input.data as Record<string, unknown>;

    if (!data.requiredField) {
      // Option 2: Throw exception (caught and returned as error)
      throw new Error('Missing required field');
    }

    return { valid: true };
  },
});
When an exception is thrown, the response includes an error field:
{
  "output": null,
  "error": "Missing required field"
}

Output Schema

Define an output schema for documentation:
const getUserTool = tool('get_user', {
  description: 'Get user information',
  input: {
    type: 'object',
    properties: {
      userId: { type: 'string' },
    },
    required: ['userId'],
  },
  output: {
    type: 'object',
    properties: {
      id: { type: 'string' },
      name: { type: 'string' },
      email: { type: 'string' },
    },
  },
  handler: async (input) => {
    return {
      id: input.userId as string,
      name: 'John Doe',
      email: 'john@example.com',
    };
  },
});
The output schema is included in the tool’s metadata at /info.

Serving Tools

Serve one or more tools:
import { tool, serve } from '@reminix/runtime';

const toolA = tool('tool_a', {
  description: 'Tool A',
  input: { type: 'object', properties: { x: { type: 'string' } }, required: ['x'] },
  handler: (input) => ({ a: input.x }),
});

const toolB = tool('tool_b', {
  description: 'Tool B',
  input: { type: 'object', properties: { y: { type: 'number' } }, required: ['y'] },
  handler: (input) => ({ b: input.y }),
});

serve({ tools: [toolA, toolB], port: 8080 });

API Endpoints

When tools are served, these endpoints are available:
EndpointMethodDescription
/healthGETHealth check
/infoGETDiscovery (lists all tools with schemas)
/tools/{name}/callPOSTCall a tool

Call a Tool

curl -X POST http://localhost:8080/tools/get_weather/call \
  -H "Content-Type: application/json" \
  -d '{"input": {"location": "San Francisco", "units": "fahrenheit"}}'
Response:
{
  "output": {
    "location": "San Francisco",
    "temperature": 65,
    "units": "fahrenheit",
    "condition": "foggy"
  }
}

Discovery

curl http://localhost:8080/info
Response:
{
  "tools": [
    {
      "name": "get_weather",
      "type": "tool",
      "description": "Get the current weather for a city",
      "input": {
        "type": "object",
        "properties": {
          "location": { "type": "string", "description": "City name" },
          "units": { "type": "string", "default": "celsius" }
        },
        "required": ["location"]
      },
      "output": {
        "type": "object",
        "properties": {
          "temp": { "type": "number" },
          "condition": { "type": "string" }
        }
      }
    }
  ]
}

Complete Example

/**
 * Runtime Tools Example
 *
 * Usage:
 *   npx tsx main.ts
 *
 * Test endpoints:
 *   curl http://localhost:8080/health
 *   curl http://localhost:8080/info
 *   curl -X POST http://localhost:8080/tools/get_weather/call \
 *     -H "Content-Type: application/json" \
 *     -d '{"input": {"location": "San Francisco"}}'
 */

import { tool, serve } from '@reminix/runtime';

const WEATHER_DATA: Record<string, { temp: number; condition: string }> = {
  'san francisco': { temp: 65, condition: 'foggy' },
  'new york': { temp: 45, condition: 'cloudy' },
  'los angeles': { temp: 75, condition: 'sunny' },
};

const getWeather = tool('get_weather', {
  description: 'Get the current weather for a city',
  input: {
    type: 'object',
    properties: {
      location: { type: 'string', description: 'City name' },
      units: {
        type: 'string',
        enum: ['celsius', 'fahrenheit'],
        default: 'fahrenheit',
      },
    },
    required: ['location'],
  },
  output: {
    type: 'object',
    properties: {
      location: { type: 'string' },
      temperature: { type: 'number' },
      units: { type: 'string' },
      condition: { type: 'string' },
    },
  },
  handler: async (input) => {
    const location = (input.location as string).toLowerCase();
    const units = (input.units as string) || 'fahrenheit';

    const weather = WEATHER_DATA[location];
    if (!weather) {
      return {
        error: `Weather data not available for "${input.location}"`,
        availableCities: Object.keys(WEATHER_DATA),
      };
    }

    let temp = weather.temp;
    if (units === 'celsius') {
      temp = Math.round(((temp - 32) * 5) / 9);
    }

    return {
      location: input.location as string,
      temperature: temp,
      units,
      condition: weather.condition,
    };
  },
});

const calculate = tool('calculate', {
  description: 'Perform basic math operations',
  input: {
    type: 'object',
    properties: {
      a: { type: 'number', description: 'First operand' },
      b: { type: 'number', description: 'Second operand' },
      operation: {
        type: 'string',
        enum: ['add', 'subtract', 'multiply', 'divide'],
        description: 'Math operation',
      },
    },
    required: ['a', 'b', 'operation'],
  },
  handler: (input) => {
    const a = input.a as number;
    const b = input.b as number;
    const op = input.operation as string;

    let result: number;
    switch (op) {
      case 'add':
        result = a + b;
        break;
      case 'subtract':
        result = a - b;
        break;
      case 'multiply':
        result = a * b;
        break;
      case 'divide':
        if (b === 0) return { error: 'Cannot divide by zero' };
        result = a / b;
        break;
      default:
        return { error: `Unknown operation: ${op}` };
    }

    return { a, b, operation: op, result };
  },
});

serve({ tools: [getWeather, calculate], port: 8080 });

Next Steps