Ir al contenido

Acciones y tool calling

Una acción es una capacidad que le entregas al modelo. Se convierte en una tool dentro de la petición y, cuando el modelo la llama, wu valida los parámetros, comprueba permisos, opcionalmente pregunta al usuario y ejecuta tu handler con una API en sandbox, nunca con wu en sí.

wu.ai.action('addToCart', {
description: 'Add an item to the shopping cart',
parameters: {
productId: { type: 'string', required: true, description: 'SKU of the product' },
quantity: { type: 'number', description: 'Defaults to 1' },
},
handler: async (params, api) => {
api.emit('cart:add', params);
return { added: params.productId };
},
confirm: true,
});
function action(name: string, config: {
description?: string;
parameters?: Record<string, ParamDef> | JSONSchema;
handler: (params: object, api: SandboxedApi) => any | Promise<any>;
confirm?: boolean;
permissions?: string[];
dangerous?: boolean;
}): Wu;
Opción Por defecto Significado
description Execute: <name> Se envía al modelo. Es lo único que le indica cuándo llamar a tu acción: escríbela bien
parameters {} Forma abreviada o JSON Schema en crudo; se normaliza antes de llegar al modelo
handler obligatorio async (params, api) => result. Lanzar está bien: el fallo se le informa de vuelta al modelo
confirm false Suspende y espera a una persona antes de ejecutar
permissions [] Flags que deben estar activos para que esta acción se ejecute siquiera
dangerous false Registra un aviso con los parámetros en cada ejecución. Nada más

Registrar el mismo nombre dos veces reemplaza la definición anterior.

Esta es toda la superficie que recibe tu handler:

Miembro Presente cuando Hace
api.getState(path) el flag global readStore es true (por defecto) Lee del store de wu
api.setState(path, value) el flag global writeStore es true y la acción declara 'writeStore' en permissions Escribe en el store de wu
api.emit(event, data) el flag global emitEvents es true (por defecto) Emite en el bus de eventos de wu como wu-ai, llevando el traceId/depth causal

Fíjate en la asimetría: las lecturas solo necesitan el flag global, pero las escrituras necesitan el flag global y una adhesión explícita en la acción. Esa doble puerta es deliberada: activar writeStore globalmente no permite retroactivamente escribir a todas las acciones existentes.

wu.ai.init({ permissions: { writeStore: true } });
wu.ai.action('setTheme', {
description: 'Change the UI theme',
parameters: { theme: { type: 'string', required: true } },
permissions: ['writeStore'], // ← sin esto, api.setState es undefined
handler: (p, api) => { api.setState('ui.theme', p.theme); return { theme: p.theme }; },
});

Los metadatos causales que viajan en api.emit son lo que permite a la protección de bucles cortar un ciclo IA → evento → trigger → IA. Emitir con tu propio wu.emit en su lugar rompe esa cadena.

La forma abreviada es por propiedad, y se normaliza a JSON Schema:

parameters: {
productId: { type: 'string', required: true, description: 'SKU' },
quantity: { type: 'number' },
gift: { type: 'boolean' },
}

Pasar un objeto JSON Schema completo también funciona: se reenvía tal cual. La validación corre antes del handler; si algo no cuadra, el modelo recibe { success: false, reason: 'Invalid params: …' } y normalmente reintenta con los argumentos corregidos.

Tanto el bucle de tools como las llamadas directas obtienen la misma forma:

{ success: true, result: any } | { success: false, reason: string }

reason cubre todas las vías de rechazo: acción no registrada, permiso denegado, parámetros inválidos, denegación del usuario o el mensaje de error del handler. El modelo lo ve como el resultado de la tool y puede reaccionar.

Con confirm: true, la ejecución se suspende y wu emite ai:tool:confirm:

wu.on('ai:tool:confirm', ({ data }) => {
const ok = window.confirm(`${data.message}\n${JSON.stringify(data.params)}`);
ok ? wu.ai.confirmTool(data.callId) : wu.ai.rejectTool(data.callId);
});

El evento lleva { callId, action, params, message }.

function execute(name: string, params: object): Promise<{ success: boolean; result?: any; reason?: string }>;
function tools(): Array<{ name: string; description: string; parameters: JSONSchema }>;

execute() recorre el mismo pipeline que una llamada a tool del modelo, con un trace ID nuevo a profundidad 0. Es lo que usan los agentes externos por CDP o WebMCP:

// Desde la consola de devtools, un script de Playwright, un cliente MCP…
await window.wu.ai.execute('addToCart', { productId: 'SKU-42' });

Cada ejecución exitosa se añade a un log en memoria (últimas 100 entradas) con el nombre de la acción, los parámetros, el resultado y el traceId, y emite ai:action:executed. Las denegaciones emiten ai:action:denied; los fallos del handler emiten ai:action:error. wu.ai.getStats().actions muestra los nombres registrados y el tamaño del log.

El log es solo de memoria y está limitado. Si necesitas un registro de auditoría de verdad, escucha los eventos y envíalos a algún sitio.

Además de las tuyas, hay otras dos fuentes que registran en el mismo registro:

  • Browser actions: 10 de ellas, registradas automáticamente cuando existe window. Consulta Control del navegador.
  • Capacidades: wu.ai.capability(app, name, config) registra app:name y se limpia con wu.ai.removeApp(app). Consulta Agentes.

Todas aparecen en tools() y todas las puede llamar el modelo, así que vigila el total: cada acción registrada son tokens en cada petición.