3. Registering functions
Functions are how you let the autopilot read or do things on the client. The autopilot decides when to call them; your code defines what they do.
Define a get_weather function
// src/autopilot.ts (extending the previous step)
import { AutopilotClient, setAutopilotClient } from '@llmor/autopilot-core';
export const client = new AutopilotClient();
setAutopilotClient(client);
client.registerFunction({
name: 'get_weather',
description: 'Returns the current weather for a city.',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: 'The city to look up.',
},
},
required: ['city'],
},
handler: async ({ city }: { city: string }) => {
const res = await fetch(
`https://wttr.in/${encodeURIComponent(city)}?format=j1`,
);
if (!res.ok) throw new Error(`weather lookup failed: ${res.status}`);
const data = await res.json();
const current = data.current_condition?.[0];
return {
city,
temperature_c: current?.temp_C,
description: current?.weatherDesc?.[0]?.value,
};
},
});
export async function bootAutopilot() {
await client.connect({
conversationUrl: import.meta.env.VITE_CONVERSATION_URL,
});
}A few things to notice:
parametersis JSON Schema, the same format the autopilot uses on the server side. Keep it strict —requiredis honored.- The handler can be sync or async; the return value is JSON-stringified and sent back to the autopilot.
- Thrown errors do not crash the connection. They are surfaced as a
functionErrorevent and reported to the autopilot as a failed call.
Call it
In the conversation, ask the autopilot something like "What's the weather in Berlin?" — it will invoke get_weather and stream a reply.
You can watch it happen by listening to events:
client.on('functionCall', ({ request }) => {
console.log('autopilot is calling', request.name, request.arguments);
});
client.on('functionSuccess', ({ request, result }) => {
console.log('returned', request.name, result);
});
client.on('functionError', ({ request, error }) => {
console.error('failed', request.name, error);
});Unregister at teardown (optional)
If your function is only relevant on a specific route:
import { onScopeDispose } from 'vue';
import { client } from '@/autopilot';
const dispose = client.registerFunction({ name: 'get_weather', /* … */ });
onScopeDispose(dispose);Clean up with the returned disposer rather than unregisterFunction('get_weather'). If the page is remounted, the new instance may register get_weather before the old one is disposed, and removing by name would delete the new registration. In Vue components, useAutopilotScope() handles this for you.
Registration changes are debounced and synced as JSON Patches at /autopilot/functions.