Skip to main content

In-Product Tools

In-product tools (also called external tools) allow your Distri agents to interact with your application's functionality. These tools execute in your frontend or backend environment, giving you full control over the implementation.

Overview

In-product tools are functions that your agent can call during execution. Unlike server-side tools, they run in your application code, allowing you to:

  • Integrate with your existing UI components
  • Access browser APIs and DOM
  • Call your backend services
  • Perform client-side operations

Defining a Tool

Tools are defined using the DistriFnTool type from @distri/core:

import type { DistriFnTool } from '@distri/core';

const myTool: DistriFnTool = {
name: 'tool_name',
description: 'What this tool does',
type: 'function',
parameters: {
type: 'object',
properties: {
param1: { type: 'string', description: 'Description of param1' },
param2: { type: 'number', description: 'Description of param2' },
},
required: ['param1'],
},
handler: async (input) => {
const result = await doSomething(input.param1, input.param2);
return `Tool executed: ${result}`;
},
};

Tool Handler Return Types

handler: async (input) => {
return 'Simple text response';
}

Example: Google Maps Tools

Here's a complete example of in-product tools for Google Maps:

import type { DistriFnTool } from '@distri/core';

interface GoogleMapsManagerRef {
setMapCenter: (opts: { latitude: number; longitude: number; zoom?: number }) => Promise<void>;
addMarker: (opts: { latitude: number; longitude: number; title: string; description?: string }) => Promise<void>;
}

export const createMapTools = (map: GoogleMapsManagerRef): DistriFnTool[] => [
{
name: 'set_map_center',
description: 'Center the map at a specific location',
type: 'function',
parameters: {
type: 'object',
properties: {
latitude: { type: 'number', description: 'Latitude coordinate' },
longitude: { type: 'number', description: 'Longitude coordinate' },
zoom: {
type: 'number',
minimum: 1,
maximum: 20,
default: 13,
description: 'Zoom level (1-20)',
},
},
required: ['latitude', 'longitude'],
},
handler: async ({ latitude, longitude, zoom }) => {
await map.setMapCenter({ latitude, longitude, zoom });
return `Map centered at ${latitude}, ${longitude}`;
},
},
{
name: 'add_marker',
description: 'Add a marker to the map',
type: 'function',
parameters: {
type: 'object',
properties: {
latitude: { type: 'number' },
longitude: { type: 'number' },
title: { type: 'string' },
description: { type: 'string' },
},
required: ['latitude', 'longitude', 'title'],
},
handler: async ({ latitude, longitude, title, description }) => {
await map.addMarker({ latitude, longitude, title, description });
return `Marker added: ${title}`;
},
},
];

Using Tools in React

Pass in-product tools to the Chat component via externalTools:

import { useRef, useEffect, useState } from 'react';
import { DistriProvider, Chat, useAgent } from '@distri/react';
import type { DistriAnyTool } from '@distri/react';
import { createMapTools } from './tools';

function App() {
return (
<DistriProvider config={{ clientId: 'YOUR_CLIENT_ID' }}>
<MapsContent />
</DistriProvider>
);
}

function MapsContent() {
const { agent } = useAgent({ agentIdOrDef: 'maps_agent' });
const [threadId] = useState(() => crypto.randomUUID());
const mapRef = useRef<GoogleMapsManagerRef>(null);
const [tools, setTools] = useState<DistriAnyTool[]>([]);

useEffect(() => {
if (mapRef.current) {
setTools(createMapTools(mapRef.current));
}
}, []);

if (!agent) return null;

return (
<>
<GoogleMapsComponent ref={mapRef} />
<Chat
agent={agent}
threadId={threadId}
externalTools={tools}
/>
</>
);
}

Agent Configuration

In your agent definition, specify that the agent uses external tools:

---
name = "maps_agent"
description = "Agent that uses Google Maps tools"

[tools]
external = ["*"] # Allow all external tools
# Or specify specific tools:
# external = ["set_map_center", "add_marker"]
---

Context-Aware Tools

Tools often need access to application state or configuration:

export function createBrowserTool(options?: {
threadId?: string;
apiBase?: string;
onComplete?: (response: any) => void;
}): DistriFnTool {
return {
name: 'browser_step',
type: 'function',
description: 'Execute browser commands',
parameters: {
type: 'object',
properties: {
command: {
type: 'string',
enum: ['navigate', 'click', 'type', 'scroll'],
description: 'The browser command to execute',
},
data: {
type: 'object',
description: 'Command-specific data',
},
},
required: ['command'],
},
handler: async (input) => {
try {
const response = await fetch(`${options?.apiBase}/browser_step`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
command: input.command,
data: input.data,
thread_id: options?.threadId,
}),
});

const result = await response.json();

if (options?.onComplete) {
options.onComplete(result);
}

return [{
part_type: 'data',
data: {
success: result.success,
summary: result.summary,
},
}];
} catch (error) {
return [{
part_type: 'data',
data: {
success: false,
error: error instanceof Error ? error.message : String(error),
},
}];
}
},
};
}

Tool Factory Pattern

Create tool factories that generate tools with shared configuration:

import type { DistriAnyTool } from '@distri/react';

export const createAppTools = (options?: {
threadId?: string;
apiBase?: string;
onUpdate?: (data: any) => void;
}): DistriAnyTool[] => {
return [
createMapTool(options),
createSearchTool(options),
createDataTool(options),
];
};

Best Practices

Error Handling

Always handle errors gracefully:

handler: async (input) => {
try {
const result = await performOperation(input);
return { success: true, result };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}

Minimal Responses

Return concise responses to avoid context bloat:

// Good: Minimal response
handler: async (input) => {
const data = await fetchDetailedData(input);
await storeInSession(threadId, data); // Store detailed data separately
return { success: true, summary: `Processed ${data.length} items` };
}

// Avoid: Large response
handler: async (input) => {
const data = await fetchDetailedData(input);
return data; // Too large, fills up context
}

Type Safety

Use TypeScript interfaces for tool inputs:

interface SearchInput {
query: string;
limit?: number;
}

const tool: DistriFnTool = {
name: 'search',
// ...
handler: async (input: SearchInput) => {
// Type-safe access
const results = await search(input.query, input.limit ?? 10);
return results;
},
};

Clear Descriptions

Provide detailed descriptions so the agent understands when to use each tool:

{
name: 'set_map_center',
description: 'Center the map view at specific latitude/longitude coordinates. Use this when the user asks to show a location, navigate to a place, or focus the map on specific coordinates.',
// ...
}