TypeScript/JavaScript Tool Calling Examples

Clean, production-ready examples for using tool calling with Captain API in TypeScript and JavaScript.

Basic Calculator Tool (Vercel AI SDK)

1import { createOpenAI } from '@ai-sdk/openai';
2import { generateText } from 'ai';
3import { z } from 'zod';
4
5const captain = createOpenAI({
6 apiKey: process.env.CAPTAIN_API_KEY!,
7 baseURL: 'https://api.runcaptain.com/v1',
8 headers: {
9 'X-Organization-ID': process.env.CAPTAIN_ORG_ID!
10 }
11});
12
13const result = await generateText({
14 model: captain.chat('captain-voyager-latest'),
15 messages: [
16 { role: 'user', content: 'What is 156 times 243?' }
17 ],
18 tools: {
19 calculate: {
20 description: 'Perform arithmetic calculations',
21 parameters: z.object({
22 operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
23 a: z.number(),
24 b: z.number()
25 }),
26 execute: async ({ operation, a, b }) => {
27 const operations = {
28 add: (x: number, y: number) => x + y,
29 subtract: (x: number, y: number) => x - y,
30 multiply: (x: number, y: number) => x * y,
31 divide: (x: number, y: number) => y !== 0 ? x / y : null
32 };
33 return { result: operations[operation](a, b) };
34 }
35 }
36 },
37 maxSteps: 5
38});
39
40console.log(result.text);

Weather API Tool

1import { createOpenAI } from '@ai-sdk/openai';
2import { generateText } from 'ai';
3import { z } from 'zod';
4
5const captain = createOpenAI({
6 apiKey: process.env.CAPTAIN_API_KEY!,
7 baseURL: 'https://api.runcaptain.com/v1',
8 headers: {
9 'X-Organization-ID': process.env.CAPTAIN_ORG_ID!
10 }
11});
12
13async function getWeather(location: string, unit: 'celsius' | 'fahrenheit' = 'celsius') {
14 const response = await fetch(
15 `https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${location}`
16 );
17 const data = await response.json();
18
19 return {
20 location,
21 temperature: unit === 'celsius' ? data.current.temp_c : data.current.temp_f,
22 unit,
23 condition: data.current.condition.text
24 };
25}
26
27const result = await generateText({
28 model: captain.chat('captain-voyager-latest'),
29 messages: [
30 { role: 'user', content: "What's the weather in Tokyo?" }
31 ],
32 tools: {
33 getWeather: {
34 description: 'Get current weather for a location',
35 parameters: z.object({
36 location: z.string().describe('City name'),
37 unit: z.enum(['celsius', 'fahrenheit']).optional()
38 }),
39 execute: getWeather
40 }
41 },
42 maxSteps: 5
43});
44
45console.log(result.text);

Database Query Tool

1import { createOpenAI } from '@ai-sdk/openai';
2import { generateText } from 'ai';
3import { z } from 'zod';
4import { Pool } from 'pg';
5
6const captain = createOpenAI({
7 apiKey: process.env.CAPTAIN_API_KEY!,
8 baseURL: 'https://api.runcaptain.com/v1',
9 headers: {
10 'X-Organization-ID': process.env.CAPTAIN_ORG_ID!
11 }
12});
13
14const pool = new Pool({
15 connectionString: process.env.DATABASE_URL
16});
17
18async function queryCustomer(customerId: string) {
19 const result = await pool.query(
20 'SELECT name, email, phone FROM customers WHERE id = $1',
21 [customerId]
22 );
23
24 if (result.rows.length === 0) {
25 return { error: 'Customer not found' };
26 }
27
28 return {
29 name: result.rows[0].name,
30 email: result.rows[0].email,
31 phone: result.rows[0].phone
32 };
33}
34
35const result = await generateText({
36 model: captain.chat('captain-voyager-latest'),
37 messages: [
38 { role: 'user', content: 'Get contact info for customer CUST-12345' }
39 ],
40 tools: {
41 queryCustomer: {
42 description: 'Look up customer information by ID',
43 parameters: z.object({
44 customerId: z.string().describe('Customer ID')
45 }),
46 execute: async ({ customerId }) => queryCustomer(customerId)
47 }
48 },
49 maxSteps: 5
50});
51
52console.log(result.text);

Tool Calling with Large Context

1import { createOpenAI } from '@ai-sdk/openai';
2import { generateText } from 'ai';
3import { z } from 'zod';
4import { readFile } from 'fs/promises';
5
6const captain = createOpenAI({
7 apiKey: process.env.CAPTAIN_API_KEY!,
8 baseURL: 'https://api.runcaptain.com/v1',
9 headers: {
10 'X-Organization-ID': process.env.CAPTAIN_ORG_ID!
11 }
12});
13
14// Read large financial report
15const reportContent = await readFile('financial_report.txt', 'utf-8');
16
17const result = await generateText({
18 model: captain.chat('captain-voyager-latest'),
19 messages: [
20 {
21 role: 'user',
22 content: 'What is the total revenue across all quarters?'
23 }
24 ],
25 tools: {
26 calculateTotal: {
27 description: 'Calculate the sum of a list of numbers',
28 parameters: z.object({
29 numbers: z.array(z.number()).describe('List of numbers to sum')
30 }),
31 execute: async ({ numbers }) => ({
32 total: numbers.reduce((a, b) => a + b, 0)
33 })
34 },
35 calculateAverage: {
36 description: 'Calculate the average of a list of numbers',
37 parameters: z.object({
38 numbers: z.array(z.number()).describe('List of numbers')
39 }),
40 execute: async ({ numbers }) => ({
41 average: numbers.reduce((a, b) => a + b, 0) / numbers.length
42 })
43 }
44 },
45 extra_body: {
46 captain: {
47 context: reportContent
48 }
49 },
50 maxSteps: 5
51});
52
53console.log(result.text);

OpenAI SDK (Node.js)

1import OpenAI from 'openai';
2
3const client = new OpenAI({
4 apiKey: process.env.CAPTAIN_API_KEY,
5 baseURL: 'https://api.runcaptain.com/v1',
6 defaultHeaders: {
7 'X-Organization-ID': process.env.CAPTAIN_ORG_ID
8 }
9});
10
11const tools = [
12 {
13 type: 'function' as const,
14 function: {
15 name: 'calculate',
16 description: 'Perform arithmetic calculations',
17 parameters: {
18 type: 'object',
19 properties: {
20 operation: {
21 type: 'string',
22 enum: ['add', 'subtract', 'multiply', 'divide']
23 },
24 a: { type: 'number' },
25 b: { type: 'number' }
26 },
27 required: ['operation', 'a', 'b']
28 },
29 strict: true
30 }
31 }
32];
33
34const response = await client.chat.completions.create({
35 model: 'captain-voyager-latest',
36 messages: [
37 { role: 'user', content: 'What is 50 + 75?' }
38 ],
39 tools
40});
41
42if (response.choices[0].finish_reason === 'tool_calls') {
43 const toolCall = response.choices[0].message.tool_calls![0];
44 const args = JSON.parse(toolCall.function.arguments);
45
46 // Execute tool client-side
47 const operations = {
48 add: (a: number, b: number) => a + b,
49 subtract: (a: number, b: number) => a - b,
50 multiply: (a: number, b: number) => a * b,
51 divide: (a: number, b: number) => a / b
52 };
53
54 const result = operations[args.operation as keyof typeof operations](args.a, args.b);
55 console.log(`Result: ${result}`);
56}

Multiple Tools

1import { createOpenAI } from '@ai-sdk/openai';
2import { generateText } from 'ai';
3import { z } from 'zod';
4
5const captain = createOpenAI({
6 apiKey: process.env.CAPTAIN_API_KEY!,
7 baseURL: 'https://api.runcaptain.com/v1',
8 headers: {
9 'X-Organization-ID': process.env.CAPTAIN_ORG_ID!
10 }
11});
12
13const result = await generateText({
14 model: captain.chat('captain-voyager-latest'),
15 messages: [
16 {
17 role: 'user',
18 content: "What's the current price of AAPL and its ROI if I bought at $150?"
19 }
20 ],
21 tools: {
22 getStockPrice: {
23 description: 'Get current stock price',
24 parameters: z.object({
25 symbol: z.string().describe('Stock ticker symbol')
26 }),
27 execute: async ({ symbol }) => {
28 const response = await fetch(
29 `https://api.example.com/stocks/${symbol}`
30 );
31 return await response.json();
32 }
33 },
34 calculateROI: {
35 description: 'Calculate return on investment',
36 parameters: z.object({
37 initial: z.number().describe('Initial investment'),
38 current: z.number().describe('Current value')
39 }),
40 execute: async ({ initial, current }) => ({
41 roi: ((current - initial) / initial) * 100
42 })
43 },
44 getCompanyInfo: {
45 description: 'Get company information',
46 parameters: z.object({
47 symbol: z.string().describe('Stock ticker symbol')
48 }),
49 execute: async ({ symbol }) => {
50 const response = await fetch(
51 `https://api.example.com/companies/${symbol}`
52 );
53 return await response.json();
54 }
55 }
56 },
57 maxSteps: 10
58});
59
60console.log(result.text);

Error Handling

1import { createOpenAI } from '@ai-sdk/openai';
2import { generateText } from 'ai';
3import { z } from 'zod';
4
5const captain = createOpenAI({
6 apiKey: process.env.CAPTAIN_API_KEY!,
7 baseURL: 'https://api.runcaptain.com/v1',
8 headers: {
9 'X-Organization-ID': process.env.CAPTAIN_ORG_ID!
10 }
11});
12
13async function safeExecute<T>(
14 fn: () => Promise<T>,
15 errorMessage: string
16): Promise<T | { error: string }> {
17 try {
18 return await fn();
19 } catch (error) {
20 return {
21 error: `${errorMessage}: ${error instanceof Error ? error.message : 'Unknown error'}`
22 };
23 }
24}
25
26const result = await generateText({
27 model: captain.chat('captain-voyager-latest'),
28 messages: [
29 { role: 'user', content: 'Your query' }
30 ],
31 tools: {
32 riskyOperation: {
33 description: 'Perform an operation that might fail',
34 parameters: z.object({
35 input: z.string()
36 }),
37 execute: async ({ input }) => {
38 return safeExecute(
39 async () => {
40 // Your risky operation
41 const result = await someRiskyFunction(input);
42 return { result };
43 },
44 'Operation failed'
45 );
46 }
47 }
48 },
49 maxSteps: 5
50});
51
52console.log(result.text);

Streaming with Tools

1import { createOpenAI } from '@ai-sdk/openai';
2import { streamText } from 'ai';
3import { z } from 'zod';
4
5const captain = createOpenAI({
6 apiKey: process.env.CAPTAIN_API_KEY!,
7 baseURL: 'https://api.runcaptain.com/v1',
8 headers: {
9 'X-Organization-ID': process.env.CAPTAIN_ORG_ID!
10 }
11});
12
13const result = await streamText({
14 model: captain.chat('captain-voyager-latest'),
15 messages: [
16 { role: 'user', content: 'What is 50 + 25?' }
17 ],
18 tools: {
19 calculate: {
20 description: 'Perform calculations',
21 parameters: z.object({
22 operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
23 a: z.number(),
24 b: z.number()
25 }),
26 execute: async ({ operation, a, b }) => {
27 const ops = {
28 add: (x: number, y: number) => x + y,
29 subtract: (x: number, y: number) => x - y,
30 multiply: (x: number, y: number) => x * y,
31 divide: (x: number, y: number) => x / y
32 };
33 return { result: ops[operation](a, b) };
34 }
35 }
36 },
37 maxSteps: 5
38});
39
40for await (const chunk of result.textStream) {
41 process.stdout.write(chunk);
42}

Real-World Example: E-commerce Assistant

1import { createOpenAI } from '@ai-sdk/openai';
2import { generateText } from 'ai';
3import { z } from 'zod';
4
5const captain = createOpenAI({
6 apiKey: process.env.CAPTAIN_API_KEY!,
7 baseURL: 'https://api.runcaptain.com/v1',
8 headers: {
9 'X-Organization-ID': process.env.CAPTAIN_ORG_ID!
10 }
11});
12
13const result = await generateText({
14 model: captain.chat('captain-voyager-latest'),
15 messages: [
16 {
17 role: 'user',
18 content: 'Find available products under $50 and check inventory for the cheapest one'
19 }
20 ],
21 tools: {
22 searchProducts: {
23 description: 'Search for products with filters',
24 parameters: z.object({
25 maxPrice: z.number().optional(),
26 category: z.string().optional(),
27 inStock: z.boolean().optional()
28 }),
29 execute: async (filters) => {
30 const response = await fetch('https://api.yourstore.com/products', {
31 method: 'POST',
32 headers: { 'Content-Type': 'application/json' },
33 body: JSON.stringify(filters)
34 });
35 return await response.json();
36 }
37 },
38 checkInventory: {
39 description: 'Check inventory for a product',
40 parameters: z.object({
41 productId: z.string()
42 }),
43 execute: async ({ productId }) => {
44 const response = await fetch(
45 `https://api.yourstore.com/inventory/${productId}`
46 );
47 return await response.json();
48 }
49 },
50 getProductDetails: {
51 description: 'Get detailed information about a product',
52 parameters: z.object({
53 productId: z.string()
54 }),
55 execute: async ({ productId }) => {
56 const response = await fetch(
57 `https://api.yourstore.com/products/${productId}`
58 );
59 return await response.json();
60 }
61 }
62 },
63 maxSteps: 10
64});
65
66console.log(result.text);
67console.log(`Tools used: ${result.toolCalls?.length || 0}`);

TypeScript Types

1import { z } from 'zod';
2
3// Define tool parameter schema
4const CalculateParams = z.object({
5 operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
6 a: z.number(),
7 b: z.number()
8});
9
10// Infer TypeScript type from schema
11type CalculateParams = z.infer<typeof CalculateParams>;
12
13// Type-safe tool execution
14async function calculate(params: CalculateParams): Promise<{ result: number }> {
15 const operations = {
16 add: (x: number, y: number) => x + y,
17 subtract: (x: number, y: number) => x - y,
18 multiply: (x: number, y: number) => x * y,
19 divide: (x: number, y: number) => x / y
20 };
21
22 return {
23 result: operations[params.operation](params.a, params.b)
24 };
25}
26
27// Use in tool definition
28const tools = {
29 calculate: {
30 description: 'Perform arithmetic calculations',
31 parameters: CalculateParams,
32 execute: calculate
33 }
34};

Environment Configuration

1// .env file
2CAPTAIN_API_KEY=cap_dev_...
3CAPTAIN_ORG_ID=019a0e27-...
4
5// Load environment variables
6import { config } from 'dotenv';
7config();
8
9// Validate required variables
10const requiredEnvVars = ['CAPTAIN_API_KEY', 'CAPTAIN_ORG_ID'];
11
12for (const envVar of requiredEnvVars) {
13 if (!process.env[envVar]) {
14 throw new Error(`Missing required environment variable: ${envVar}`);
15 }
16}
17
18// Create client with validated config
19const captain = createOpenAI({
20 apiKey: process.env.CAPTAIN_API_KEY!,
21 baseURL: 'https://api.runcaptain.com/v1',
22 headers: {
23 'X-Organization-ID': process.env.CAPTAIN_ORG_ID!
24 }
25});