Toolkits are extensible collections of AI tools that provide specific capabilities to the chat interface. Each toolkit contains multiple related tools (e.g., a "Web Search" toolkit might include general search, company research, and Wikipedia search tools).
Once you've created your toolkit, integrating it into the OpenChat system is simple. Just add your toolkit to the registry objects, and it's immediately available in the UI.
After building your toolkit, you need to register it in three places:
- Add your toolkit id to the enum in
shared.ts - Import and add it to the
clientToolkitsobject inclient.ts - Import and add it to the
serverToolkitsobject inserver.ts
That's it! Once added to these objects, your toolkit will immediately appear in the UI with full functionality.
The toolkit system uses a client/server separation architecture where each toolkit has both client-side and server-side implementations:
toolkit/
├── base.ts # Shared configuration and schemas
├── client.tsx # UI components and client logic
├── server.ts # Server-side tool execution
└── tools/
├── tools.ts # Tool name definitions
└── tool-name/
├── base.ts # Tool schema definition
├── client.tsx # Tool UI components
└── server.ts # Tool execution logic
The separation serves several critical purposes:
- Server-side code runs in a secure environment with access to API keys
- Client-side code never imports server-side code that depends on API keys
- Shared schemas ensure type consistency between client and server
- Zod schemas provide runtime validation
- TypeScript ensures compile-time safety
Defines the fundamental types for the entire toolkit system:
BaseTool- Shared tool definition with schemasClientTool- Client-side tool with UI componentsServerTool- Server-side tool with execution logicClientToolkit&ServerToolkit- Complete toolkit definitions
Provides factory functions to create toolkits:
createClientToolkit()- Combines base config with client-specific configcreateServerToolkit()- Combines base config with server-specific config
Provides factory functions to create individual tools:
createBaseTool()- Creates shared tool definitioncreateClientTool()- Adds client UI componentscreateServerTool()- Adds server execution logic
Define:
- Purpose: What domain does your toolkit cover?
- Tools: What specific actions will it provide?
- Parameters: What configuration does it need?
- Dependencies: What external APIs or services?
mkdir -p src/toolkits/toolkits/my-toolkit/tools/my-toolCreate tools/tools.ts:
export enum MyToolkitTools {
MyTool = "my-tool",
AnotherTool = "another-tool",
}Create base.ts:
import { z } from "zod";
import type { ToolkitConfig } from "@/toolkits/types";
import { baseMyTool } from "./tools/my-tool/base";
import { MyToolkitTools } from "./tools/tools";
export const myToolkitParameters = z.object({
// any input needed from the user on the client
});
export const baseMyToolkitConfig: ToolkitConfig<
MyToolkitTools,
typeof myToolkitParameters.shape
> = {
tools: {
[MyToolkitTools.MyTool]: baseMyTool,
// Add other tools
},
parameters: myToolkitParameters,
};Create tools/my-tool/base.ts:
import { z } from "zod";
import { createBaseTool } from "@/toolkits/create-tool";
export const baseMyTool = createBaseTool({
description: "Describe what this tool does",
inputSchema: z.object({
query: z.string().min(1).max(100),
// Add input parameters
}),
outputSchema: z.object({
result: z.string(),
// Add output fields
}),
});Create tools/my-tool/server.ts:
import type { ServerToolConfig } from "@/toolkits/types";
import type { baseMyTool } from "./base";
export const myToolConfigServer: ServerToolConfig<
typeof baseMyTool.inputSchema.shape,
typeof baseMyTool.outputSchema.shape
> = {
callback: async (args) => {
// Implement your tool logic here
const result = await callExternalAPI(args.query);
return {
result: result.data,
};
},
message: (result) => `Tool completed: ${result.result}`,
};Create tools/my-tool/client.tsx:
import type { ClientToolConfig } from "@/toolkits/types";
import type { baseMyTool } from "./base";
export const myToolConfigClient: ClientToolConfig<
typeof baseMyTool.inputSchema.shape,
typeof baseMyTool.outputSchema.shape
> = {
CallComponent: ({ args, isPartial }) => (
<div className="flex items-center gap-2">
<span>🔍</span>
<span>Searching for: {args.query}</span>
{isPartial && <span className="animate-pulse">...</span>}
</div>
),
ResultComponent: ({ args, result }) => (
<div className="border rounded p-4">
<h3>Search Results for: {args.query}</h3>
<p>{result.result}</p>
</div>
),
};Create client.tsx:
import { MyIcon } from "lucide-react";
import { createClientToolkit } from "@/toolkits/create-toolkit";
import { ToolkitGroups } from "@/toolkits/types";
import { baseMyToolkitConfig } from "./base";
import { MyToolkitTools } from "./tools/tools";
import { myToolConfigClient } from "./tools/my-tool/client";
export const myClientToolkit = createClientToolkit(
baseMyToolkitConfig,
{
name: "My Toolkit",
description: "Brief description of what this toolkit does",
icon: MyIcon,
form: null, // if the toolkit requires parameters from the user, add a form here
type: ToolkitGroups.DataSource, // Choose appropriate group
addToolkitWrapper: ({ children }) => <div>{children}</div> // if you need escalated permission s from the user, you can wrap the add button to ensure you have the necessary scopes
},
{
[MyToolkitTools.MyTool]: myToolConfigClient,
// Add other tool clients
},
);Create server.ts:
import { createServerToolkit } from "@/toolkits/create-toolkit";
import { baseMyToolkitConfig } from "./base";
import { MyToolkitTools } from "./tools/tools";
import { myToolConfigServer } from "./tools/my-tool/server";
export const myToolkitServer = createServerToolkit(
baseMyToolkitConfig,
`System prompt for the AI explaining how to use this toolkit effectively.
Provide guidance on:
- When to use each tool
- How to sequence tool calls
- Best practices for this domain`,
async (params) => {
return {
[MyToolkitTools.MyTool]: myToolConfigServer,
// Add other tool servers
};
},
);- Add your toolkit to
shared.ts:
export enum Toolkits {
// ... existing toolkits
MyToolkit = "my-toolkit",
}
export type ServerToolkitNames = {
// ... existing mappings
[Toolkits.MyToolkit]: MyToolkitTools;
};
export type ServerToolkitParameters = {
// ... existing mappings
[Toolkits.MyToolkit]: typeof myToolkitParameters.shape;
};- Add to
client.ts:
import { myClientToolkit } from "./my-toolkit/client";
export const clientToolkits: ClientToolkits = {
// ... existing toolkits
[Toolkits.MyToolkit]: myClientToolkit,
};- Add to
server.ts:
import { myToolkitServer } from "./my-toolkit/server";
export const serverToolkits: ServerToolkits = {
// ... existing toolkits
[Toolkits.MyToolkit]: myToolkitServer,
};- Keep tools focused on single responsibilities
- Use clear, descriptive names and descriptions
- Design intuitive input/output schemas
- Provide helpful error messages
- Use Zod schemas for all inputs and outputs
- Validate parameters on both client and server
- Provide sensible defaults where possible
- Create responsive, accessible components
- Show loading states for long-running operations
- Provide clear visual feedback
- Handle error states gracefully
- Write clear tool descriptions
- Provide helpful system prompts
- Include usage examples
- Document parameter requirements
Look at existing toolkits for inspiration:
exa/- Web search with multiple specialized toolsgithub/- API integration with authenticationimage/- File handling and processingnotion/- Complex API with rich data structures
Organize your toolkit into the appropriate group:
ToolkitGroups.Native- Built-in system toolsToolkitGroups.DataSource- External data and searchToolkitGroups.KnowledgeBase- Document and knowledge management
You can also create a new group if your toolkit does not fall into one of these categories
Happy toolkit building!