mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-25 12:01:04 +02:00
feat: enable dynamic transfer destination resolution
This commit is contained in:
parent
f3bcf24370
commit
6ce1bbcbf8
10 changed files with 1476 additions and 52 deletions
|
|
@ -1,14 +1,23 @@
|
|||
"use client";
|
||||
|
||||
import type { RecordingResponseSchema } from "@/client/types.gen";
|
||||
import { CredentialSelector, ParameterEditor, type ToolParameter } from "@/components/http";
|
||||
import { RecordingSelect, StaticTextWarning } from "@/components/flow/TextOrAudioInput";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
|
||||
import { type EndCallMessageType } from "../../config";
|
||||
import {
|
||||
type EndCallMessageType,
|
||||
type TransferApprovedRouteRow,
|
||||
type TransferResolverPolicy,
|
||||
} from "../../config";
|
||||
|
||||
export interface TransferCallToolConfigProps {
|
||||
name: string;
|
||||
|
|
@ -26,6 +35,24 @@ export interface TransferCallToolConfigProps {
|
|||
recordings?: RecordingResponseSchema[];
|
||||
timeout?: number; // Make optional to match API type
|
||||
onTimeoutChange: (timeout: number) => void;
|
||||
resolverEnabled: boolean;
|
||||
onResolverEnabledChange: (enabled: boolean) => void;
|
||||
resolverUrl: string;
|
||||
onResolverUrlChange: (url: string) => void;
|
||||
resolverCredentialUuid: string;
|
||||
onResolverCredentialUuidChange: (uuid: string) => void;
|
||||
resolverTimeoutMs: number;
|
||||
onResolverTimeoutMsChange: (timeoutMs: number) => void;
|
||||
resolverWaitMessage: string;
|
||||
onResolverWaitMessageChange: (message: string) => void;
|
||||
resolverPolicy: TransferResolverPolicy;
|
||||
onResolverPolicyChange: (policy: TransferResolverPolicy) => void;
|
||||
approvedRoutes: TransferApprovedRouteRow[];
|
||||
onApprovedRoutesChange: (routes: TransferApprovedRouteRow[]) => void;
|
||||
fallbackRoute: string;
|
||||
onFallbackRouteChange: (route: string) => void;
|
||||
parameters: ToolParameter[];
|
||||
onParametersChange: (parameters: ToolParameter[]) => void;
|
||||
}
|
||||
|
||||
export function TransferCallToolConfig({
|
||||
|
|
@ -44,7 +71,52 @@ export function TransferCallToolConfig({
|
|||
recordings = [],
|
||||
timeout,
|
||||
onTimeoutChange,
|
||||
resolverEnabled,
|
||||
onResolverEnabledChange,
|
||||
resolverUrl,
|
||||
onResolverUrlChange,
|
||||
resolverCredentialUuid,
|
||||
onResolverCredentialUuidChange,
|
||||
resolverTimeoutMs,
|
||||
onResolverTimeoutMsChange,
|
||||
resolverWaitMessage,
|
||||
onResolverWaitMessageChange,
|
||||
resolverPolicy,
|
||||
onResolverPolicyChange,
|
||||
approvedRoutes,
|
||||
onApprovedRoutesChange,
|
||||
fallbackRoute,
|
||||
onFallbackRouteChange,
|
||||
parameters,
|
||||
onParametersChange,
|
||||
}: TransferCallToolConfigProps) {
|
||||
const updateRoute = (
|
||||
index: number,
|
||||
patch: Partial<TransferApprovedRouteRow>,
|
||||
) => {
|
||||
onApprovedRoutesChange(
|
||||
approvedRoutes.map((route, i) =>
|
||||
i === index ? { ...route, ...patch } : route,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const removeRoute = (index: number) => {
|
||||
onApprovedRoutesChange(approvedRoutes.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const addRoute = () => {
|
||||
onApprovedRoutesChange([
|
||||
...approvedRoutes,
|
||||
{
|
||||
key: "",
|
||||
destination: "",
|
||||
message: "",
|
||||
timeout_seconds: 30,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
|
@ -186,6 +258,203 @@ export function TransferCallToolConfig({
|
|||
Default: 30 seconds
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 pt-4 border-t">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<Label>Dynamic Transfer Resolver</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Resolve the destination from an HTTP endpoint when the transfer tool is called.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={resolverEnabled}
|
||||
onCheckedChange={onResolverEnabledChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{resolverEnabled && (
|
||||
<div className="space-y-5">
|
||||
<div className="grid gap-2">
|
||||
<Label>Resolver URL</Label>
|
||||
<Input
|
||||
value={resolverUrl}
|
||||
onChange={(e) => onResolverUrlChange(e.target.value)}
|
||||
placeholder="https://crm.example.com/resolve-transfer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CredentialSelector
|
||||
value={resolverCredentialUuid}
|
||||
onChange={onResolverCredentialUuidChange}
|
||||
label="Resolver Credential (Optional)"
|
||||
description="Select a credential for the resolver endpoint, or leave empty for no auth."
|
||||
/>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Resolver Timeout</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={resolverTimeoutMs}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value) || 3000;
|
||||
onResolverTimeoutMsChange(Math.min(Math.max(value, 500), 5000));
|
||||
}}
|
||||
min="500"
|
||||
max="5000"
|
||||
className="w-36"
|
||||
/>
|
||||
<Label className="text-xs text-muted-foreground">
|
||||
Default: 3000 ms. Maximum: 5000 ms.
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Resolver Wait Message</Label>
|
||||
<Textarea
|
||||
value={resolverWaitMessage}
|
||||
onChange={(e) => onResolverWaitMessageChange(e.target.value)}
|
||||
placeholder="One moment while I find the right team."
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Resolver Policy</Label>
|
||||
<Select
|
||||
value={resolverPolicy}
|
||||
onValueChange={(value) =>
|
||||
onResolverPolicyChange(value as TransferResolverPolicy)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select policy" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="approved_routes_only">
|
||||
Approved routes only
|
||||
</SelectItem>
|
||||
<SelectItem value="approved_routes_or_static_fallback">
|
||||
Approved routes or static fallback
|
||||
</SelectItem>
|
||||
<SelectItem value="allow_raw_destination">
|
||||
Allow raw destination
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3">
|
||||
<div>
|
||||
<Label>Resolver Arguments</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Values the agent should pass when calling this transfer tool, such as state, department, or reason.
|
||||
</p>
|
||||
</div>
|
||||
<ParameterEditor
|
||||
parameters={parameters}
|
||||
onChange={onParametersChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Fallback Route</Label>
|
||||
<Input
|
||||
value={fallbackRoute}
|
||||
onChange={(e) => onFallbackRouteChange(e.target.value)}
|
||||
placeholder="referral_default"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<Label>Approved Routes</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Route keys the resolver can return, mapped to known transfer destinations.
|
||||
</p>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addRoute}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Route
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{approvedRoutes.map((route, index) => (
|
||||
<div key={index} className="grid gap-3 rounded-md border p-3">
|
||||
<div className="grid gap-3 md:grid-cols-[1fr_1fr_auto]">
|
||||
<div className="grid gap-2">
|
||||
<Label>Route Key</Label>
|
||||
<Input
|
||||
value={route.key}
|
||||
onChange={(e) =>
|
||||
updateRoute(index, { key: e.target.value })
|
||||
}
|
||||
placeholder="referral_tx"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>Destination</Label>
|
||||
<Input
|
||||
value={route.destination}
|
||||
onChange={(e) =>
|
||||
updateRoute(index, { destination: e.target.value })
|
||||
}
|
||||
placeholder="+1234567890"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removeRoute(index)}
|
||||
aria-label="Remove route"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-[1fr_160px]">
|
||||
<div className="grid gap-2">
|
||||
<Label>Message</Label>
|
||||
<Input
|
||||
value={route.message || ""}
|
||||
onChange={(e) =>
|
||||
updateRoute(index, { message: e.target.value })
|
||||
}
|
||||
placeholder="I’ll transfer you now."
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>Timeout</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={route.timeout_seconds ?? 30}
|
||||
min="5"
|
||||
max="120"
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value) || 30;
|
||||
updateRoute(index, {
|
||||
timeout_seconds: Math.min(Math.max(value, 5), 120),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{approvedRoutes.length === 0 && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
No approved routes configured.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import type {
|
|||
HttpApiToolDefinition,
|
||||
RecordingResponseSchema,
|
||||
ToolResponse,
|
||||
TransferCallConfig as APITransferCallConfig,
|
||||
UpdateToolRequest,
|
||||
} from "@/client/types.gen";
|
||||
import {
|
||||
|
|
@ -46,11 +45,14 @@ import { useAuth } from "@/lib/auth";
|
|||
import {
|
||||
createMcpDefinition,
|
||||
DEFAULT_END_CALL_REASON_DESCRIPTION,
|
||||
type ExtendedTransferCallConfig,
|
||||
type EndCallMessageType,
|
||||
getCategoryConfig,
|
||||
getToolTypeLabel,
|
||||
MCP_URL_PATTERN,
|
||||
renderToolIcon,
|
||||
type TransferApprovedRouteRow,
|
||||
type TransferResolverPolicy,
|
||||
type ToolCategory,
|
||||
} from "../config";
|
||||
import { BuiltinToolConfig, EndCallToolConfig, HttpApiToolConfig, TransferCallToolConfig } from "./components";
|
||||
|
|
@ -67,6 +69,18 @@ function normalizeParameterType(value: string | null | undefined): ParameterType
|
|||
}
|
||||
}
|
||||
|
||||
function approvedRoutesToRows(
|
||||
routes: ExtendedTransferCallConfig["approved_routes"] | undefined | null,
|
||||
): TransferApprovedRouteRow[] {
|
||||
if (!routes) return [];
|
||||
return Object.entries(routes).map(([key, route]) => ({
|
||||
key,
|
||||
destination: route.destination || "",
|
||||
message: route.message || "",
|
||||
timeout_seconds: route.timeout_seconds ?? 30,
|
||||
}));
|
||||
}
|
||||
|
||||
export default function ToolDetailPage() {
|
||||
const { toolUuid } = useParams<{ toolUuid: string }>();
|
||||
const { user, getAccessToken, redirectToLogin, loading } = useAuth();
|
||||
|
|
@ -113,6 +127,16 @@ export default function ToolDetailPage() {
|
|||
const [transferMessageType, setTransferMessageType] = useState<EndCallMessageType>("none");
|
||||
const [transferTimeout, setTransferTimeout] = useState(30);
|
||||
const [transferAudioRecordingId, setTransferAudioRecordingId] = useState("");
|
||||
const [transferResolverEnabled, setTransferResolverEnabled] = useState(false);
|
||||
const [transferResolverUrl, setTransferResolverUrl] = useState("");
|
||||
const [transferResolverCredentialUuid, setTransferResolverCredentialUuid] = useState("");
|
||||
const [transferResolverTimeoutMs, setTransferResolverTimeoutMs] = useState(3000);
|
||||
const [transferResolverWaitMessage, setTransferResolverWaitMessage] = useState("");
|
||||
const [transferResolverPolicy, setTransferResolverPolicy] =
|
||||
useState<TransferResolverPolicy>("approved_routes_only");
|
||||
const [transferApprovedRoutes, setTransferApprovedRoutes] = useState<TransferApprovedRouteRow[]>([]);
|
||||
const [transferFallbackRoute, setTransferFallbackRoute] = useState("");
|
||||
const [transferParameters, setTransferParameters] = useState<ToolParameter[]>([]);
|
||||
|
||||
// HTTP API form state - custom message type
|
||||
const [customMessageType, setCustomMessageType] = useState<'text' | 'audio'>('text');
|
||||
|
|
@ -182,19 +206,44 @@ export default function ToolDetailPage() {
|
|||
}
|
||||
} else if (tool.category === "transfer_call") {
|
||||
// Populate transfer call specific fields
|
||||
const config = tool.definition?.config as APITransferCallConfig | undefined;
|
||||
const config = tool.definition?.config as ExtendedTransferCallConfig | undefined;
|
||||
if (config) {
|
||||
setTransferDestination(config.destination || "");
|
||||
setTransferMessageType(config.messageType || "none");
|
||||
setCustomMessage(config.customMessage || "");
|
||||
setTransferAudioRecordingId(config.audioRecordingId || "");
|
||||
setTransferTimeout(config.timeout ?? 30);
|
||||
setTransferResolverEnabled(Boolean(config.resolver));
|
||||
setTransferResolverUrl(config.resolver?.url || "");
|
||||
setTransferResolverCredentialUuid(config.resolver?.credential_uuid || "");
|
||||
setTransferResolverTimeoutMs(config.resolver?.timeout_ms ?? 3000);
|
||||
setTransferResolverWaitMessage(config.resolver?.wait_message || "");
|
||||
setTransferResolverPolicy(config.resolver?.policy || "approved_routes_only");
|
||||
setTransferApprovedRoutes(approvedRoutesToRows(config.approved_routes));
|
||||
setTransferFallbackRoute(config.fallback_route || "");
|
||||
setTransferParameters(
|
||||
(config.parameters || []).map((p) => ({
|
||||
name: p.name || "",
|
||||
type: normalizeParameterType(p.type),
|
||||
description: p.description || "",
|
||||
required: p.required ?? true,
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
setTransferDestination("");
|
||||
setTransferMessageType("none");
|
||||
setCustomMessage("");
|
||||
setTransferAudioRecordingId("");
|
||||
setTransferTimeout(30);
|
||||
setTransferResolverEnabled(false);
|
||||
setTransferResolverUrl("");
|
||||
setTransferResolverCredentialUuid("");
|
||||
setTransferResolverTimeoutMs(3000);
|
||||
setTransferResolverWaitMessage("");
|
||||
setTransferResolverPolicy("approved_routes_only");
|
||||
setTransferApprovedRoutes([]);
|
||||
setTransferFallbackRoute("");
|
||||
setTransferParameters([]);
|
||||
}
|
||||
} else if (tool.category === "mcp") {
|
||||
// Populate MCP specific fields
|
||||
|
|
@ -296,10 +345,60 @@ export default function ToolDetailPage() {
|
|||
if (tool.category === "calculator") {
|
||||
// No validation needed for built-in tools
|
||||
} else if (tool.category === "transfer_call") {
|
||||
if (!normalizedTransferDestination) {
|
||||
if (!transferResolverEnabled && !normalizedTransferDestination) {
|
||||
setError("Please enter a transfer destination");
|
||||
return;
|
||||
}
|
||||
if (transferResolverEnabled) {
|
||||
const resolverUrlValidation = validateUrl(transferResolverUrl);
|
||||
if (!resolverUrlValidation.valid) {
|
||||
setError(resolverUrlValidation.error || "Invalid resolver URL");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
transferResolverPolicy === "approved_routes_or_static_fallback" &&
|
||||
!normalizedTransferDestination
|
||||
) {
|
||||
setError("Please enter a transfer destination for static fallback");
|
||||
return;
|
||||
}
|
||||
|
||||
const routeKeys = transferApprovedRoutes
|
||||
.map((route) => route.key.trim())
|
||||
.filter(Boolean);
|
||||
const invalidRoutes = transferApprovedRoutes.filter(
|
||||
(route) => !route.key.trim() || !route.destination.trim()
|
||||
);
|
||||
if (invalidRoutes.length > 0) {
|
||||
setError("All approved routes must have a route key and destination");
|
||||
return;
|
||||
}
|
||||
if (new Set(routeKeys).size !== routeKeys.length) {
|
||||
setError("Approved route keys must be unique");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
transferFallbackRoute.trim() &&
|
||||
!routeKeys.includes(transferFallbackRoute.trim())
|
||||
) {
|
||||
setError("Fallback route must match an approved route key");
|
||||
return;
|
||||
}
|
||||
const invalidTransferParams = transferParameters.filter(
|
||||
(p) => !p.name.trim() || !p.description.trim()
|
||||
);
|
||||
if (invalidTransferParams.length > 0) {
|
||||
setError("All resolver arguments must have a name and description");
|
||||
return;
|
||||
}
|
||||
const transferParamNames = transferParameters
|
||||
.map((p) => p.name.trim())
|
||||
.filter(Boolean);
|
||||
if (new Set(transferParamNames).size !== transferParamNames.length) {
|
||||
setError("Resolver argument names must be unique");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (tool.category === "mcp") {
|
||||
// Validate MCP server URL (must be http(s))
|
||||
if (!mcpUrl.trim()) {
|
||||
|
|
@ -370,6 +469,57 @@ export default function ToolDetailPage() {
|
|||
},
|
||||
};
|
||||
} else if (tool.category === "transfer_call") {
|
||||
const approvedRoutesObject = transferApprovedRoutes.reduce(
|
||||
(acc, route) => {
|
||||
const key = route.key.trim();
|
||||
if (!key) return acc;
|
||||
acc[key] = {
|
||||
destination: route.destination.trim(),
|
||||
message: route.message?.trim() || undefined,
|
||||
timeout_seconds: route.timeout_seconds || undefined,
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, {
|
||||
destination: string;
|
||||
message?: string;
|
||||
timeout_seconds?: number;
|
||||
}>,
|
||||
);
|
||||
const transferConfig: ExtendedTransferCallConfig = {
|
||||
destination: normalizedTransferDestination,
|
||||
messageType: transferMessageType,
|
||||
customMessage: transferMessageType === "custom" ? customMessage : undefined,
|
||||
audioRecordingId: transferMessageType === "audio" ? transferAudioRecordingId || undefined : undefined,
|
||||
timeout: transferTimeout,
|
||||
resolver: transferResolverEnabled
|
||||
? {
|
||||
type: "http",
|
||||
url: transferResolverUrl.trim(),
|
||||
credential_uuid: transferResolverCredentialUuid || undefined,
|
||||
timeout_ms: transferResolverTimeoutMs,
|
||||
wait_message: transferResolverWaitMessage.trim() || undefined,
|
||||
policy: transferResolverPolicy,
|
||||
}
|
||||
: undefined,
|
||||
approved_routes:
|
||||
transferResolverEnabled && Object.keys(approvedRoutesObject).length > 0
|
||||
? approvedRoutesObject
|
||||
: undefined,
|
||||
fallback_route:
|
||||
transferResolverEnabled && transferFallbackRoute.trim()
|
||||
? transferFallbackRoute.trim()
|
||||
: undefined,
|
||||
parameters:
|
||||
transferResolverEnabled && transferParameters.length > 0
|
||||
? transferParameters.map((p) => ({
|
||||
name: p.name.trim(),
|
||||
type: p.type,
|
||||
description: p.description.trim(),
|
||||
required: p.required,
|
||||
}))
|
||||
: undefined,
|
||||
};
|
||||
// Build transfer call request body
|
||||
requestBody = {
|
||||
name,
|
||||
|
|
@ -377,14 +527,8 @@ export default function ToolDetailPage() {
|
|||
definition: {
|
||||
schema_version: 1,
|
||||
type: "transfer_call",
|
||||
config: {
|
||||
destination: normalizedTransferDestination,
|
||||
messageType: transferMessageType,
|
||||
customMessage: transferMessageType === "custom" ? customMessage : undefined,
|
||||
audioRecordingId: transferMessageType === "audio" ? transferAudioRecordingId || undefined : undefined,
|
||||
timeout: transferTimeout,
|
||||
},
|
||||
},
|
||||
config: transferConfig,
|
||||
} as UpdateToolRequest["definition"],
|
||||
};
|
||||
} else if (tool.category === "mcp") {
|
||||
requestBody = {
|
||||
|
|
@ -658,6 +802,24 @@ const data = await response.json();`;
|
|||
recordings={recordings}
|
||||
timeout={transferTimeout}
|
||||
onTimeoutChange={setTransferTimeout}
|
||||
resolverEnabled={transferResolverEnabled}
|
||||
onResolverEnabledChange={setTransferResolverEnabled}
|
||||
resolverUrl={transferResolverUrl}
|
||||
onResolverUrlChange={setTransferResolverUrl}
|
||||
resolverCredentialUuid={transferResolverCredentialUuid}
|
||||
onResolverCredentialUuidChange={setTransferResolverCredentialUuid}
|
||||
resolverTimeoutMs={transferResolverTimeoutMs}
|
||||
onResolverTimeoutMsChange={setTransferResolverTimeoutMs}
|
||||
resolverWaitMessage={transferResolverWaitMessage}
|
||||
onResolverWaitMessageChange={setTransferResolverWaitMessage}
|
||||
resolverPolicy={transferResolverPolicy}
|
||||
onResolverPolicyChange={setTransferResolverPolicy}
|
||||
approvedRoutes={transferApprovedRoutes}
|
||||
onApprovedRoutesChange={setTransferApprovedRoutes}
|
||||
fallbackRoute={transferFallbackRoute}
|
||||
onFallbackRouteChange={setTransferFallbackRoute}
|
||||
parameters={transferParameters}
|
||||
onParametersChange={setTransferParameters}
|
||||
/>
|
||||
) : isMcpTool ? (
|
||||
<Card>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,35 @@ import type {
|
|||
export type ToolCategory = "http_api" | "end_call" | "transfer_call" | "calculator" | "native" | "integration" | "mcp";
|
||||
|
||||
export type EndCallMessageType = "none" | "custom" | "audio";
|
||||
export type TransferResolverPolicy =
|
||||
| "approved_routes_only"
|
||||
| "approved_routes_or_static_fallback"
|
||||
| "allow_raw_destination";
|
||||
|
||||
export interface TransferResolverConfig {
|
||||
type: "http";
|
||||
url: string;
|
||||
credential_uuid?: string | null;
|
||||
timeout_ms: number;
|
||||
wait_message?: string | null;
|
||||
policy: TransferResolverPolicy;
|
||||
}
|
||||
|
||||
export interface TransferApprovedRoute {
|
||||
destination: string;
|
||||
message?: string | null;
|
||||
timeout_seconds?: number | null;
|
||||
}
|
||||
|
||||
export interface TransferApprovedRouteRow extends TransferApprovedRoute {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ExtendedTransferCallConfig extends TransferCallConfig {
|
||||
resolver?: TransferResolverConfig | null;
|
||||
approved_routes?: Record<string, TransferApprovedRoute> | null;
|
||||
fallback_route?: string | null;
|
||||
}
|
||||
|
||||
export interface ToolCategoryConfig {
|
||||
value: ToolCategory;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -2988,6 +2988,50 @@ export type HttpApiToolDefinition = {
|
|||
config: HttpApiConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* HttpTransferResolverConfig
|
||||
*
|
||||
* HTTP endpoint used to resolve transfer destination at call time.
|
||||
*/
|
||||
export type HttpTransferResolverConfig = {
|
||||
/**
|
||||
* Type
|
||||
*
|
||||
* Resolver type.
|
||||
*/
|
||||
type?: 'http';
|
||||
/**
|
||||
* Url
|
||||
*
|
||||
* HTTP or HTTPS endpoint for transfer resolution.
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Credential Uuid
|
||||
*
|
||||
* Reference to an external credential for resolver authentication.
|
||||
*/
|
||||
credential_uuid?: string | null;
|
||||
/**
|
||||
* Timeout Ms
|
||||
*
|
||||
* Resolver request timeout in milliseconds.
|
||||
*/
|
||||
timeout_ms?: number;
|
||||
/**
|
||||
* Wait Message
|
||||
*
|
||||
* Optional short message played while Dograh resolves routing.
|
||||
*/
|
||||
wait_message?: string | null;
|
||||
/**
|
||||
* Policy
|
||||
*
|
||||
* Controls what resolver responses are allowed to select.
|
||||
*/
|
||||
policy?: 'approved_routes_only' | 'approved_routes_or_static_fallback' | 'allow_raw_destination';
|
||||
};
|
||||
|
||||
/**
|
||||
* Hugging Face
|
||||
*
|
||||
|
|
@ -5791,6 +5835,40 @@ export type ToolResponse = {
|
|||
created_by?: CreatedByResponse | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* TransferApprovedRoute
|
||||
*
|
||||
* A pre-approved destination the transfer resolver may select.
|
||||
*/
|
||||
export type TransferApprovedRoute = {
|
||||
/**
|
||||
* Destination
|
||||
*
|
||||
* Phone number, SIP endpoint, or template for this route.
|
||||
*/
|
||||
destination: string;
|
||||
/**
|
||||
* Message
|
||||
*
|
||||
* Optional message to play before transferring to this route.
|
||||
*/
|
||||
message?: string | null;
|
||||
/**
|
||||
* Timeout Seconds
|
||||
*
|
||||
* Optional route-specific transfer answer timeout.
|
||||
*/
|
||||
timeout_seconds?: number | null;
|
||||
/**
|
||||
* Metadata
|
||||
*
|
||||
* Optional non-secret route metadata for logs and resolver context.
|
||||
*/
|
||||
metadata?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* TransferCallConfig
|
||||
*
|
||||
|
|
@ -5827,6 +5905,30 @@ export type TransferCallConfig = {
|
|||
* Maximum seconds to wait for the destination to answer.
|
||||
*/
|
||||
timeout?: number;
|
||||
/**
|
||||
* Parameters
|
||||
*
|
||||
* Parameters the model may provide when calling this transfer tool, for example state, department, or transfer reason.
|
||||
*/
|
||||
parameters?: Array<ToolParameter> | null;
|
||||
/**
|
||||
* Optional resolver that determines transfer routing at call time.
|
||||
*/
|
||||
resolver?: HttpTransferResolverConfig | null;
|
||||
/**
|
||||
* Approved Routes
|
||||
*
|
||||
* Approved route keys that a resolver may select.
|
||||
*/
|
||||
approved_routes?: {
|
||||
[key: string]: TransferApprovedRoute;
|
||||
} | null;
|
||||
/**
|
||||
* Fallback Route
|
||||
*
|
||||
* Approved route key to use when resolver resolution fails.
|
||||
*/
|
||||
fallback_route?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue