feat: set calculator as custom tool on demand

This commit is contained in:
Abhishek Kumar 2026-04-02 14:07:03 +05:30
parent 89fce77438
commit f368fe5134
13 changed files with 265 additions and 157 deletions

View file

@ -0,0 +1,60 @@
"use client";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
export interface BuiltinToolConfigProps {
name: string;
onNameChange: (name: string) => void;
description: string;
onDescriptionChange: (description: string) => void;
title: string;
subtitle: string;
}
export function BuiltinToolConfig({
name,
onNameChange,
description,
onDescriptionChange,
title,
subtitle,
}: BuiltinToolConfigProps) {
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{subtitle}</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{/* Tool Name */}
<div className="space-y-2">
<Label htmlFor="tool-name">Tool Name</Label>
<Input
id="tool-name"
value={name}
onChange={(e) => onNameChange(e.target.value)}
placeholder="Tool name"
/>
</div>
{/* Tool Description */}
<div className="space-y-2">
<Label htmlFor="tool-description">Description</Label>
<p className="text-xs text-muted-foreground">
Provide a description which makes it easy for LLM to understand what this tool does
</p>
<Textarea
id="tool-description"
value={description}
onChange={(e) => onDescriptionChange(e.target.value)}
placeholder="Describe what this tool does..."
rows={3}
/>
</div>
</CardContent>
</Card>
);
}

View file

@ -1,3 +1,4 @@
export { BuiltinToolConfig, type BuiltinToolConfigProps } from "./BuiltinToolConfig";
export { EndCallToolConfig, type EndCallToolConfigProps } from "./EndCallToolConfig";
export { HttpApiToolConfig, type HttpApiToolConfigProps } from "./HttpApiToolConfig";
export { TransferCallToolConfig, type TransferCallToolConfigProps } from "./TransferCallToolConfig";

View file

@ -31,7 +31,7 @@ import {
renderToolIcon,
type ToolCategory,
} from "../config";
import { EndCallToolConfig, HttpApiToolConfig, TransferCallToolConfig } from "./components";
import { BuiltinToolConfig, EndCallToolConfig, HttpApiToolConfig, TransferCallToolConfig } from "./components";
// Extended HttpApiConfig with parameters (until client types are regenerated)
interface HttpApiConfigWithParams {
@ -201,7 +201,9 @@ export default function ToolDetailPage() {
if (!tool) return;
// Validation based on tool type
if (tool.category === "transfer_call") {
if (tool.category === "calculator") {
// No validation needed for built-in tools
} else if (tool.category === "transfer_call") {
// Validate destination for Transfer Call tools (supports both E.164 and SIP endpoints)
const e164Pattern = /^\+[1-9]\d{1,14}$/;
const sipPattern = /^(PJSIP|SIP)\/[\w\-\.@]+$/i;
@ -236,7 +238,17 @@ export default function ToolDetailPage() {
let requestBody;
if (tool.category === "end_call") {
if (tool.category === "calculator") {
// Built-in tool - only name/description, no config
requestBody = {
name,
description: description || undefined,
definition: {
schema_version: 1,
type: "calculator",
},
};
} else if (tool.category === "end_call") {
// Build end call request body
requestBody = {
name,
@ -400,6 +412,7 @@ const data = await response.json();`;
const isEndCallTool = tool.category === "end_call";
const isTransferCallTool = tool.category === "transfer_call";
const isBuiltinTool = tool.category === "calculator";
const categoryConfig = getCategoryConfig(tool.category as ToolCategory);
return (
@ -435,7 +448,7 @@ const data = await response.json();`;
</div>
</div>
<div className="flex items-center gap-2">
{!isEndCallTool && !isTransferCallTool && (
{!isEndCallTool && !isTransferCallTool && !isBuiltinTool && (
<Button
variant="outline"
onClick={() => setShowCodeDialog(true)}
@ -458,7 +471,16 @@ const data = await response.json();`;
</div>
</div>
{isEndCallTool ? (
{isBuiltinTool ? (
<BuiltinToolConfig
name={name}
onNameChange={setName}
description={description}
onDescriptionChange={setDescription}
title="Calculator Configuration"
subtitle="Built-in calculator for arithmetic operations. No additional configuration needed."
/>
) : isEndCallTool ? (
<EndCallToolConfig
name={name}
onNameChange={setName}

View file

@ -1,11 +1,18 @@
"use client";
import { Cog, Globe, type LucideIcon, PhoneForwarded, PhoneOff, Puzzle } from "lucide-react";
import { Calculator, Cog, Globe, type LucideIcon, PhoneForwarded, PhoneOff, Puzzle } from "lucide-react";
import { type ReactNode } from "react";
import type { EndCallConfig } from "@/client/types.gen";
import type {
CalculatorToolDefinition,
EndCallConfig,
EndCallToolDefinition,
HttpApiToolDefinition,
TransferCallConfig,
TransferCallToolDefinition,
} from "@/client/types.gen";
export type ToolCategory = "http_api" | "end_call" | "transfer_call" | "native" | "integration";
export type ToolCategory = "http_api" | "end_call" | "transfer_call" | "calculator" | "native" | "integration";
export type EndCallMessageType = "none" | "custom";
@ -56,6 +63,18 @@ export const TOOL_CATEGORIES: ToolCategoryConfig[] = [
description: "Transfer the caller to another phone number when requested",
},
},
{
value: "calculator",
label: "Calculator",
description: "Built-in calculator for arithmetic operations",
icon: Calculator,
iconName: "calculator",
iconColor: "#F59E0B",
autoFill: {
name: "Calculator",
description: "Perform arithmetic calculations (supports +, -, *, /, **, %, and parentheses)",
},
},
{
value: "native",
label: "Native (Coming Soon)",
@ -103,6 +122,8 @@ export function getToolTypeLabel(category: string): string {
return "Transfer Call Tool";
case "http_api":
return "HTTP API Tool";
case "calculator":
return "Calculator Tool";
case "native":
return "Native Tool";
case "integration":
@ -121,14 +142,6 @@ export const DEFAULT_END_CALL_CONFIG: EndCallConfig = {
endCallReason: false,
};
// Transfer Call tool specific configuration
export interface TransferCallConfig {
destination: string;
messageType: EndCallMessageType; // Reuse the same type
customMessage?: string;
timeout: number;
}
export const DEFAULT_TRANSFER_CALL_CONFIG: TransferCallConfig = {
destination: "",
messageType: "none",
@ -136,38 +149,7 @@ export const DEFAULT_TRANSFER_CALL_CONFIG: TransferCallConfig = {
timeout: 30,
};
// Tool definition types for different categories
export interface HttpApiToolDefinition {
schema_version: number;
type: "http_api";
config: {
method: string;
url: string;
headers?: Record<string, string>;
credential_uuid?: string;
parameters?: Array<{
name: string;
type: string;
description: string;
required: boolean;
}>;
timeout_ms?: number;
};
}
export interface EndCallToolDefinition {
schema_version: number;
type: "end_call";
config: EndCallConfig;
}
export interface TransferCallToolDefinition {
schema_version: number;
type: "transfer_call";
config: TransferCallConfig;
}
export type ToolDefinition = HttpApiToolDefinition | EndCallToolDefinition | TransferCallToolDefinition;
export type ToolDefinition = HttpApiToolDefinition | EndCallToolDefinition | TransferCallToolDefinition | CalculatorToolDefinition;
export function createEndCallDefinition(config: EndCallConfig): EndCallToolDefinition {
return {
@ -196,12 +178,21 @@ export function createHttpApiDefinition(): HttpApiToolDefinition {
};
}
export function createCalculatorDefinition(): CalculatorToolDefinition {
return {
schema_version: 1,
type: "calculator",
};
}
export function createToolDefinition(category: ToolCategory): ToolDefinition {
switch (category) {
case "end_call":
return createEndCallDefinition(DEFAULT_END_CALL_CONFIG);
case "transfer_call":
return createTransferCallDefinition(DEFAULT_TRANSFER_CALL_CONFIG);
case "calculator":
return createCalculatorDefinition();
case "http_api":
default:
return createHttpApiDefinition();

View file

@ -227,6 +227,8 @@ export default function ToolsPage() {
return <Badge variant="default">HTTP API</Badge>;
case "end_call":
return <Badge variant="destructive">End Call</Badge>;
case "calculator":
return <Badge variant="secondary">Calculator</Badge>;
case "native":
return <Badge variant="secondary">Native</Badge>;
case "integration":

View file

@ -85,6 +85,20 @@ export type BodyTranscribeAudioApiV1WorkflowRecordingsTranscribePost = {
language?: string;
};
/**
* Tool definition for Calculator tools (no configuration needed).
*/
export type CalculatorToolDefinition = {
/**
* Schema version
*/
schema_version?: number;
/**
* Tool type
*/
type: 'calculator';
};
export type CallDispositionCodes = {
disposition_codes?: Array<string>;
};
@ -328,7 +342,9 @@ export type CreateToolRequest = {
type?: 'end_call';
} & EndCallToolDefinition) | ({
type?: 'transfer_call';
} & TransferCallToolDefinition);
} & TransferCallToolDefinition) | ({
type?: 'calculator';
} & CalculatorToolDefinition);
};
export type CreateWorkflowRequest = {
@ -1290,7 +1306,9 @@ export type UpdateToolRequest = {
type?: 'end_call';
} & EndCallToolDefinition) | ({
type?: 'transfer_call';
} & TransferCallToolDefinition)) | null;
} & TransferCallToolDefinition) | ({
type?: 'calculator';
} & CalculatorToolDefinition)) | null;
status?: string | null;
};