Overview
The buildNavaiAgent function creates a fully configured OpenAI Realtime agent with navigation tools, function execution capabilities, and automatic tool registration.
Import
import { buildNavaiAgent } from '@navai/voice-frontend';
Type Signature
async function buildNavaiAgent(
options: BuildNavaiAgentOptions
): Promise<BuildNavaiAgentResult>
Parameters
options
BuildNavaiAgentOptions
required
Agent configuration optionsnavigate
(path: string) => void
required
Navigation function for route changes
Array of available routes for navigationroutes: [
{
name: 'home',
path: '/',
description: 'Home page',
synonyms: ['main', 'dashboard']
}
]
functionModuleLoaders
NavaiFunctionModuleLoaders
Map of module paths to import functions
backendFunctions
NavaiBackendFunctionDefinition[]
Array of backend function definitionsbackendFunctions: [
{
name: 'send_email',
description: 'Send an email to a user',
source: 'backend'
}
]
executeBackendFunction
ExecuteNavaiBackendFunction
Function to execute backend functionsexecuteBackendFunction: async ({ functionName, payload }) => {
// Execute backend function
return result;
}
Agent display name (defaults to "Navai Voice Agent")
Base system instructions (defaults to "You are a voice assistant embedded in a web app.")
Return Value
Configured OpenAI Realtime agent instance ready for connection
Array of warning messages from:
- Function loading errors
- Backend function conflicts
- Invalid tool names
- Duplicate function names
- Reserved name conflicts
The agent automatically includes these tools:
1. navigate_to
Navigate to an allowed route in the app.
Parameters:
target (string): Route name or path (e.g., "profile", "/settings")
Returns:
{ "ok": true, "path": "/profile" }
Error:
{ "ok": false, "error": "Unknown or disallowed route." }
2. execute_app_function
Execute an allowed frontend or backend function.
Parameters:
function_name (string): Name of the function to execute
payload (object | null): Function arguments
payload.args: Array of function arguments
payload.constructorArgs: Array for class constructor
payload.methodArgs: Array for class methods
Returns:
{
"ok": true,
"function_name": "search",
"source": "frontend",
"result": { /* function result */ }
}
Error:
{
"ok": false,
"function_name": "unknown_fn",
"error": "Unknown or disallowed function.",
"available_functions": ["search", "get_user"]
}
Functions with valid names (matching /^[a-zA-Z0-9_-]{1,64}$/) get direct tool aliases:
// Instead of:
execute_app_function("search", { payload: { args: ["query"] } })
// Can use:
search({ payload: { args: ["query"] } })
Examples
Basic Agent
import { buildNavaiAgent } from '@navai/voice-frontend';
const { agent, warnings } = await buildNavaiAgent({
navigate: (path) => router.push(path),
routes: [
{ name: 'home', path: '/', description: 'Home page' },
{ name: 'profile', path: '/profile', description: 'User profile' }
]
});
// Log any warnings
warnings.forEach(warning => console.warn(warning));
// Use agent with RealtimeSession
import { RealtimeSession } from '@openai/agents/realtime';
const session = new RealtimeSession(agent);
await session.connect({ apiKey: 'your-api-key' });
With Frontend Functions
const { agent } = await buildNavaiAgent({
navigate,
routes,
functionModuleLoaders: {
'./functions/search.ts': () => import('./functions/search'),
'./functions/profile.ts': () => import('./functions/profile')
}
});
With Backend Functions
const { agent } = await buildNavaiAgent({
navigate,
routes,
backendFunctions: [
{
name: 'send_email',
description: 'Send an email',
source: 'backend'
},
{
name: 'create_order',
description: 'Create a new order',
source: 'backend'
}
],
executeBackendFunction: async ({ functionName, payload }) => {
const response = await fetch('/api/functions/execute', {
method: 'POST',
body: JSON.stringify({ functionName, payload })
});
return response.json();
}
});
With Custom Instructions
const { agent } = await buildNavaiAgent({
navigate,
routes,
agentName: 'My App Assistant',
baseInstructions: `You are a helpful assistant for MyApp.
Be friendly and concise.
Always confirm before executing sensitive actions.`
});
Complete Example
const { agent, warnings } = await buildNavaiAgent({
navigate: (path) => router.push(path),
routes: [
{
name: 'home',
path: '/',
description: 'Home page',
synonyms: ['main', 'dashboard']
},
{
name: 'search',
path: '/search',
description: 'Search page'
}
],
functionModuleLoaders: {
'./functions/search.ts': () => import('./functions/search')
},
backendFunctions: [
{
name: 'send_notification',
description: 'Send a push notification',
source: 'backend'
}
],
executeBackendFunction: async ({ functionName, payload }) => {
const response = await backendClient.executeFunction({
functionName,
payload
});
return response;
},
agentName: 'MyApp Voice',
baseInstructions: 'You are a voice assistant for MyApp.'
});
// Handle warnings
if (warnings.length > 0) {
console.warn('Agent warnings:', warnings);
}
// Connect agent
const session = new RealtimeSession(agent);
await session.connect({ apiKey });
Generated Instructions
The agent receives system instructions in this format:
You are a voice assistant embedded in a web app.
Allowed routes:
- home (/) aliases: main, dashboard
- profile (/profile)
Allowed app functions:
- search: Search for content
- send_notification: Send a push notification
Rules:
- If user asks to go/open a section, always call navigate_to.
- If user asks to run an internal action, call execute_app_function or the matching direct function tool.
- Always include payload in execute_app_function. Use null when no arguments are needed.
- For execute_app_function, pass arguments using payload.args (array).
- For class methods, pass payload.constructorArgs and payload.methodArgs.
- Never invent routes or function names that are not listed.
- If destination/action is unclear, ask a brief clarifying question.
Function Naming Rules
Reserved Names
These names cannot be used as direct tools:
navigate_to
execute_app_function
Functions with these names are still available via execute_app_function.
Direct tools are created for functions matching:
- Pattern:
/^[a-zA-Z0-9_-]{1,64}$/
- Length: 1-64 characters
- Characters: letters, numbers, underscore, hyphen
Conflict Resolution
- Frontend vs Backend: Frontend functions take precedence
- Duplicates: First occurrence wins, later ones are ignored
- Invalid Names: Available via
execute_app_function only
Warnings
The function emits warnings for:
// Backend function conflicts with frontend
"[navai] Ignored backend function \"search\": name conflicts with a frontend function."
// Duplicate backend functions
"[navai] Ignored duplicated backend function \"send_email\"."
// Reserved name conflict
"[navai] Function \"navigate_to\" is available only via execute_app_function because its name conflicts with a built-in tool."
// Invalid tool name
"[navai] Function \"my function\" is available only via execute_app_function because its name is not a valid tool id."
// Duplicate after normalization
"[navai] Renamed duplicated function \"search\" to \"search_2\"."
Error Handling
Function execution errors are caught and returned:
{
"ok": false,
"function_name": "search",
"error": "Function execution failed.",
"details": "Database connection timeout"
}
Types
// packages/voice-frontend/src/agent.ts:11-24
export type NavaiBackendFunctionDefinition = {
name: string;
description?: string;
source?: string;
};
export type ExecuteNavaiBackendFunctionInput = {
functionName: string;
payload: Record<string, unknown> | null;
};
export type ExecuteNavaiBackendFunction = (
input: ExecuteNavaiBackendFunctionInput
) => Promise<unknown> | unknown;
export type BuildNavaiAgentOptions = NavaiFunctionContext & {
routes: NavaiRoute[];
functionModuleLoaders?: NavaiFunctionModuleLoaders;
backendFunctions?: NavaiBackendFunctionDefinition[];
executeBackendFunction?: ExecuteNavaiBackendFunction;
agentName?: string;
baseInstructions?: string;
};
export type BuildNavaiAgentResult = {
agent: RealtimeAgent;
warnings: string[];
};