Frontend Tools

Build client-side tools that run in the browser

Create tools that run in the browser with access to DOM, state, and UI.


Why Frontend Tools?

  • Access browser APIs - DOM, localStorage, navigation
  • Use React state - Read/write app state directly
  • Show rich UI - Render components as results
  • Real-time updates - Stream results as they happen

Basic Tool

import { useTools } from '@yourgpt/copilot-sdk/react';
import { z } from 'zod';

function MyTools() {
  useTools({
    search_products: {
      description: 'Search the product catalog',
      parameters: z.object({
        query: z.string().describe('Search query'),
        limit: z.number().optional().default(10),
      }),
      handler: async ({ query, limit }) => {
        const results = await fetch(`/api/search?q=${query}&limit=${limit}`);
        return results.json();
      },
    },
  });

  return null;
}

Tool Structure

FieldRequiredDescription
descriptionYesWhat the tool does (AI reads this)
parametersYesZod schema for inputs
handlerYesAsync function that runs
requiresApprovalNoAsk user before running
hiddenNoHide tool from chat UI (still executes)

Multiple Tools

useTools({
  get_weather: {
    description: 'Get weather for a city',
    parameters: z.object({ city: z.string() }),
    handler: async ({ city }) => fetchWeather(city),
  },

  add_to_cart: {
    description: 'Add product to cart',
    parameters: z.object({
      productId: z.string(),
      quantity: z.number().default(1),
    }),
    handler: async ({ productId, quantity }) => {
      await cart.add(productId, quantity);
      return { success: true };
    },
  },

  navigate: {
    description: 'Go to a page in the app',
    parameters: z.object({ path: z.string() }),
    handler: async ({ path }) => {
      router.push(path);
      return { navigated: path };
    },
  },
});

With Approval

Require user confirmation before running:

useTools({
  delete_account: {
    description: 'Permanently delete user account',
    parameters: z.object({}),
    requiresApproval: true,
    handler: async () => {
      await deleteAccount();
      return { deleted: true };
    },
  },
});

Use requiresApproval: true for destructive or sensitive actions.


Hidden Tools

Hide tool execution from the chat UI while still executing normally:

useTools({
  navigate: {
    description: 'Navigate to a page in the app',
    parameters: z.object({ path: z.string() }),
    hidden: true, // Won't show in chat UI
    handler: async ({ path }) => {
      router.push(path);
      return { navigated: path };
    },
  },

  track_event: {
    description: 'Track analytics event',
    parameters: z.object({
      event: z.string(),
      properties: z.record(z.any()).optional(),
    }),
    hidden: true, // Silent background operation
    handler: async ({ event, properties }) => {
      analytics.track(event, properties);
      return { tracked: true };
    },
  },
});

Hidden tools are useful for navigation, analytics, or background operations that shouldn't clutter the chat UI.


AI Response Control

Control how the AI responds after tool execution:

handler: async ({ query }) => {
  const results = await search(query);

  return {
    data: results,
    _aiResponseMode: 'brief',      // 'verbose' | 'brief' | 'silent'
    _aiContext: `Found ${results.length} results`,
    _aiContent: 'Here are the results:',
  };
}
ModeBehavior
verboseAI explains results in detail
briefAI gives short summary
silentNo AI response, just show tool result

Error Handling

handler: async ({ query }) => {
  try {
    const results = await search(query);
    return { success: true, data: results };
  } catch (error) {
    return {
      success: false,
      error: error.message,
    };
  }
}

The success flag

The success field is read by the AI to decide how to continue reasoning after a tool call. Returning success: false tells the AI the tool did not accomplish its goal — it can then retry with different parameters, try an alternative tool, or explain the failure to the user.

// ✅ Good — AI understands what went wrong and can adapt
return {
  success: false,
  error: 'No results found for that date range. Try a wider range.',
};

// ❌ Avoid — throwing an exception stops the agentic loop entirely
throw new Error('No results found');

Always include a descriptive error string alongside success: false. The AI uses this message to reason about its next step, so vague errors like "Unknown error" reduce its ability to recover gracefully.


Best Practices

  1. Clear descriptions - AI uses this to decide when to call the tool
  2. Validate with Zod - Type safety and clear parameter docs
  3. Return structured data - AI understands JSON better than strings
  4. Handle errors - Return error info instead of throwing
  5. Use approval for destructive actions - Delete, purchase, send

Next Steps

On this page