Skip to main content
Custom functions extend your voice agent’s capabilities beyond navigation. This page shows real examples from the playground apps.

Function Discovery

NAVAI automatically discovers functions from the functions-modules/ directory:
src/ai/functions-modules/
├── session/
│   └── logout.fn.ts
├── support/
│   └── open-help.fn.ts
├── utils/
│   └── math.ts
├── test/
│   └── testing.ts
└── system/
    └── ai-service.ts
Run the generator to create module loaders:
# Web
npm run generate:module-loaders

# Mobile
npm run generate:ai-modules

Authentication Functions

Logout User

A function that clears authentication and navigates home:
// src/ai/functions-modules/session/logout.fn.ts

/**
 * Logs out the current user by clearing auth tokens and redirecting to home.
 * The agent can call this when user says "logout", "sign out", "cierra sesion", etc.
 */
export function logout_user(context: { navigate: (path: string) => void }) {
  try {
    // Clear authentication tokens from localStorage
    localStorage.removeItem("auth_token");
    localStorage.removeItem("refresh_token");
  } catch {
    // Ignore storage errors to avoid breaking other functions.
    // Some environments (incognito mode) may restrict localStorage.
  }

  // Navigate user to home page
  context.navigate("/");
  
  // Return success result
  return { ok: true, message: "Session closed." };
}
Voice commands:
  • “logout”
  • “sign out”
  • “cierra sesion”
  • “log me out”
Key features:
  • Receives context with navigate function
  • Performs side effects (clear storage)
  • Returns structured result
  • Error handling for edge cases

Open Help Center

Navigate to the help page:
// src/ai/functions-modules/support/open-help.fn.ts

/**
 * Opens the help center page.
 * Can be invoked by saying "open help", "need support", "ayuda", etc.
 */
export const open_help_center = (context: { navigate: (path: string) => void }) => {
  // Navigate to help page
  context.navigate("/help");
  
  // Return the path for confirmation
  return { ok: true, path: "/help" };
};
Voice commands:
  • “open help”
  • “need support”
  • “ayuda”
  • “help center”

Utility Functions

Math Operations

Export multiple utility functions from a single module:
// src/ai/functions-modules/utils/math.ts

/**
 * Adds two numbers together.
 */
export function sumar(a: number, b: number): number {
  return a + b;
}

/**
 * Checks if a person is an adult (18 or older).
 */
export const es_mayor_de_edad = (edad: number): boolean => {
  return edad >= 18;
};

/**
 * Module version - exported constants are ignored by generator.
 */
export const version = "1.0.0";
Voice commands:
  • “suma 5 y 3” (calls sumar(5, 3))
  • “es mayor de edad 25” (calls es_mayor_de_edad(25))
Exported constants and non-function exports are automatically ignored by the module generator.

Class-Based Functions

Service Initialization

Export classes that the agent can instantiate:
// src/ai/functions-modules/system/ai-service.ts

/**
 * AI service management class.
 * The agent can create instances and call methods.
 */
export default class ServicioIA {
  constructor(private readonly apiKey: string = "demo-key") {}

  /**
   * Initialize the AI service.
   */
  iniciar(): string {
    return `Servicio iniciado con key: ${this.apiKey}`;
  }
}
Voice interaction:
  • “initialize AI service”
  • “start AI with key abc123”
The agent can:
  1. Instantiate the class: new ServicioIA("abc123")
  2. Call methods: instance.iniciar()

Testing Functions

Secret Word Retrieval

Simple function for testing:
// src/ai/functions-modules/test/testing.ts

/**
 * Returns a secret word for testing purposes.
 */
export function secret_word(a: string): string {
  return "45_JOSE_MARIA_45";
}

/**
 * Module version.
 */
export const version = "1.0.0";
Voice command:
  • “what’s the secret word”
  • “tell me the secret”

Advanced Patterns

Async Operations

Functions can be async:
export async function fetch_user_data(context: { navigate: (path: string) => void }) {
  try {
    const response = await fetch("/api/user");
    const data = await response.json();
    return { ok: true, data };
  } catch (error) {
    return { ok: false, error: String(error) };
  }
}

Error Handling

Return structured errors:
export function risky_operation(value: number) {
  if (value < 0) {
    return { ok: false, error: "Value must be positive" };
  }
  
  try {
    const result = performOperation(value);
    return { ok: true, result };
  } catch (error) {
    return { ok: false, error: error instanceof Error ? error.message : "Unknown error" };
  }
}

Multiple Context Parameters

Access different context values:
type Context = {
  navigate: (path: string) => void;
  user?: { id: string; name: string };
  config?: Record<string, unknown>;
};

export function personalized_greeting(context: Context) {
  const userName = context.user?.name ?? "Guest";
  return { ok: true, message: `Welcome, ${userName}!` };
}

Function Naming

Conventions

  • Use snake_case for function names: logout_user, open_help_center
  • Use descriptive names: es_mayor_de_edad instead of check
  • Export functions directly: export function ... or export const ... = ...
  • Use .fn.ts suffix for single-function files (optional but recommended)

Discovery Rules

Only exported functions are discovered. Constants, types, and interfaces are ignored.
The generator finds:
  • Regular functions: export function name() {}
  • Arrow functions: export const name = () => {}
  • Default exports that are functions or classes
  • Methods of default exported classes
The generator ignores:
  • Unexported functions
  • Exported constants (unless they’re functions)
  • Type definitions
  • Interfaces

Context Object

Every function receives a context parameter:
type NavaiContext = {
  // Navigate to a route by path
  navigate: (path: string) => void;
  
  // Additional properties can be added by your app
  [key: string]: unknown;
};

Custom Context

Extend the context with app-specific data:
// In your VoiceNavigator setup
const agent = useWebVoiceAgent({
  navigate,
  moduleLoaders: NAVAI_WEB_MODULE_LOADERS,
  defaultRoutes: NAVAI_ROUTE_ITEMS,
  
  // Custom context passed to all functions
  customContext: {
    user: currentUser,
    theme: currentTheme,
    appVersion: "1.0.0"
  }
});
Functions can access custom context:
export function get_app_info(context: any) {
  return {
    ok: true,
    version: context.appVersion,
    user: context.user?.name
  };
}

File Organization

By Feature

Organize functions by domain:
functions-modules/
├── auth/
│   ├── login.fn.ts
│   ├── logout.fn.ts
│   └── reset-password.fn.ts
├── profile/
│   ├── update-bio.fn.ts
│   └── upload-avatar.fn.ts
└── settings/
    ├── change-theme.fn.ts
    └── update-preferences.fn.ts

By Type

Group by function type:
functions-modules/
├── navigation/
│   └── open-pages.ts
├── data/
│   └── fetch-resources.ts
└── actions/
    └── user-actions.ts

Testing Functions

Unit Tests

Test functions independently:
import { logout_user } from "./logout.fn";

test("logout_user clears tokens and navigates home", () => {
  const mockNavigate = jest.fn();
  const context = { navigate: mockNavigate };
  
  localStorage.setItem("auth_token", "test-token");
  
  const result = logout_user(context);
  
  expect(result.ok).toBe(true);
  expect(localStorage.getItem("auth_token")).toBeNull();
  expect(mockNavigate).toHaveBeenCalledWith("/");
});

Integration Tests

Test with the voice agent:
import { useWebVoiceAgent } from "@navai/voice-frontend";

test("voice command triggers logout", async () => {
  const { result } = renderHook(() => useWebVoiceAgent({ ... }));
  
  // Simulate voice command
  await result.current.processCommand("logout");
  
  expect(mockNavigate).toHaveBeenCalledWith("/");
});

Best Practices

1. Return Consistent Shapes

// Good: Consistent result shape
export function my_function() {
  if (error) {
    return { ok: false, error: "Something went wrong" };
  }
  return { ok: true, data: result };
}

// Avoid: Inconsistent returns
export function bad_function() {
  if (error) return null;
  return result;
}

2. Handle Errors Gracefully

export function safe_operation() {
  try {
    riskyCode();
    return { ok: true };
  } catch (error) {
    console.error("Operation failed:", error);
    return { ok: false, error: String(error) };
  }
}

3. Document with JSDoc

/**
 * Sends a password reset email to the user.
 * 
 * @param context - Navigation context
 * @param email - User's email address
 * @returns Result with confirmation message
 */
export async function send_password_reset(
  context: { navigate: (path: string) => void },
  email: string
) {
  // Implementation
}

4. Keep Functions Focused

// Good: Single responsibility
export function logout_user(context) {
  clearAuthTokens();
  context.navigate("/");
  return { ok: true };
}

// Avoid: Too many responsibilities
export function logout_and_show_message_and_track(context) {
  clearAuthTokens();
  showToast("Logged out");
  trackEvent("logout");
  updateAnalytics();
  context.navigate("/");
  return { ok: true };
}

Next Steps

Build docs developers (and LLMs) love