- Inicio
- Documentation
- Herramientas de ejecución
- Herramientas Personalizadas
Herramientas Personalizadas
Las herramientas personalizadas son funciones invocables por el modelo que se integran en el mismo pipeline de ejecución de herramientas que las herramientas incorporadas.
Una herramienta personalizada es un módulo TypeScript/JavaScript que exporta una factoría. La factoría recibe una API del host (CustomToolAPI) y retorna una herramienta o un arreglo de herramientas.
Qué es esto (y qué no es)
Sección titulada «Qué es esto (y qué no es)»- Herramienta personalizada: invocable por el modelo durante un turno (
execute+ esquema TypeBox). - Extensión: framework de ciclo de vida/eventos que puede registrar herramientas e interceptar/modificar eventos.
- Hook: scripts externos de pre/post comando.
- Skill: paquete estático de guía/contexto, no código de herramienta ejecutable.
Si necesita que el modelo invoque código directamente, use una herramienta personalizada.
Rutas de integración en el código actual
Sección titulada «Rutas de integración en el código actual»Existen dos estilos de integración activos:
-
Herramientas personalizadas proporcionadas por el SDK (
options.customTools)- Envueltas en herramientas del agente mediante
CustomToolAdaptero wrappers de extensión. - Siempre incluidas en el conjunto inicial de herramientas activas durante el bootstrap del SDK.
- Envueltas en herramientas del agente mediante
-
Módulos descubiertos en el sistema de archivos mediante la API del cargador (
discoverAndLoadCustomTools/loadCustomTools)- Expuestas como APIs de biblioteca en
src/extensibility/custom-tools/loader.ts. - El código del host puede invocarlas para descubrir y cargar módulos de herramientas desde rutas de configuración/proveedor/plugin.
- Expuestas como APIs de biblioteca en
Flujo de invocación de herramientas del modelo
Invocación de herramienta por el LLM │ ▼Registro de herramientas (incorporadas + adaptadores de herramientas personalizadas) │ ▼CustomTool.execute(toolCallId, params, onUpdate, ctx, signal) │ ├─ onUpdate(...) -> resultado parcial transmitido └─ return result -> contenido/detalles finales de la herramientaUbicaciones de descubrimiento (API del cargador)
Sección titulada «Ubicaciones de descubrimiento (API del cargador)»discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames) fusiona:
- Proveedores de capacidad (
toolCapability), incluyendo:- Configuración nativa OMP (
~/.xcsh/agent/tools,.xcsh/tools) - Configuración de Claude (
~/.claude/tools,.claude/tools) - Configuración de Codex (
~/.codex/tools,.codex/tools) - Proveedor de caché de plugins del marketplace de Claude
- Configuración nativa OMP (
- Manifiestos de plugins instalados (
~/.xcsh/plugins/node_modules/*mediante el cargador de plugins) - Rutas configuradas explícitas pasadas al cargador
Comportamiento importante
Sección titulada «Comportamiento importante»- Las rutas resueltas duplicadas se deduplicar.
- Los conflictos de nombres de herramientas se rechazan contra las incorporadas y las herramientas personalizadas ya cargadas.
- Los archivos
.mdy.jsonson descubiertos como metadatos de herramientas por algunos proveedores, pero el cargador de módulos ejecutables los rechaza como herramientas ejecutables. - Las rutas configuradas relativas se resuelven desde
cwd;~se expande.
Contrato del módulo
Sección titulada «Contrato del módulo»Un módulo de herramienta personalizada debe exportar una función (se prefiere la exportación por defecto):
import type { CustomToolFactory } from "@f5-sales-demo/xcsh";
const factory: CustomToolFactory = (pi) => ({ name: "repo_stats", label: "Repo Stats", description: "Counts tracked TypeScript files", parameters: pi.typebox.Type.Object({ glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: "**/*.ts" })), }),
async execute(toolCallId, params, onUpdate, ctx, signal) { onUpdate?.({ content: [{ type: "text", text: "Scanning files..." }], details: { phase: "scan" }, });
const result = await pi.exec("git", ["ls-files", params.glob ?? "**/*.ts"], { signal, cwd: pi.cwd }); if (result.killed) { throw new Error("Scan was cancelled"); } if (result.code !== 0) { throw new Error(result.stderr || "git ls-files failed"); }
const files = result.stdout.split("\n").filter(Boolean); return { content: [{ type: "text", text: `Found ${files.length} files` }], details: { count: files.length, sample: files.slice(0, 10) }, }; },
onSession(event) { if (event.reason === "shutdown") { // cleanup resources if needed } },});
export default factory;Tipo de retorno de la factoría:
CustomToolCustomTool[]Promise<CustomTool | CustomTool[]>
Superficie de API pasada a las factorías (CustomToolAPI)
Sección titulada «Superficie de API pasada a las factorías (CustomToolAPI)»Desde types.ts y loader.ts:
cwd: directorio de trabajo del hostexec(command, args, options?): helper de ejecución de procesosui: contexto de UI (puede ser no-op en modos headless)hasUI:falseen flujos no interactivoslogger: logger compartido de archivostypebox:@sinclair/typeboxinyectadopi: exportaciones de@f5-sales-demo/xcshinyectadaspushPendingAction(action): registrar una acción de vista previa para la herramienta ocultaresolve(docs/resolve-tool-runtime.md)
El cargador comienza con un contexto de UI no-op y requiere que el código del host invoque setUIContext(...) cuando la UI real esté lista.
Contrato de ejecución y tipado
Sección titulada «Contrato de ejecución y tipado»Firma de CustomTool.execute:
execute(toolCallId, params, onUpdate, ctx, signal)paramsestá tipado estáticamente desde su esquema TypeBox medianteStatic<TParams>.- La validación de argumentos en tiempo de ejecución ocurre antes de la ejecución en el bucle del agente.
onUpdateemite resultados parciales para la transmisión en la UI.ctxincluye el estado de sesión/modelo y un helperabort().signaltransporta la cancelación.
CustomToolAdapter conecta esto con la interfaz de herramientas del agente y reenvía las invocaciones en el orden correcto de argumentos.
Cómo se exponen las herramientas al modelo
Sección titulada «Cómo se exponen las herramientas al modelo»- Las herramientas se envuelven en instancias de
AgentTool(CustomToolAdaptero wrappers de extensión). - Se insertan en el registro de herramientas de la sesión por nombre.
- En el bootstrap del SDK, las herramientas personalizadas y las registradas por extensiones se incluyen forzosamente en el conjunto activo inicial.
--toolsen CLI actualmente valida solo nombres de herramientas incorporadas; la inclusión de herramientas personalizadas se maneja a través de las rutas de descubrimiento/registro y las opciones del SDK.
Hooks de renderizado
Sección titulada «Hooks de renderizado»Hooks de renderizado opcionales:
renderCall(args, theme)renderResult(result, options, theme, args?)
Comportamiento en tiempo de ejecución en TUI:
- Si los hooks existen, la salida de la herramienta se renderiza dentro de un contenedor
Box. renderResultrecibe{ expanded, isPartial, spinnerFrame? }.- Los errores del renderizador se capturan y registran; la UI recurre al renderizado de texto por defecto.
Manejo de sesión/estado
Sección titulada «Manejo de sesión/estado»El método opcional onSession(event, ctx) recibe eventos del ciclo de vida de la sesión, incluyendo:
start,switch,branch,tree,shutdownauto_compaction_start,auto_compaction_endauto_retry_start,auto_retry_endttsr_triggered,todo_reminder
Use ctx.sessionManager para reconstruir el estado desde el historial cuando cambie el contexto de rama/sesión.
Semánticas de fallos y cancelación
Sección titulada «Semánticas de fallos y cancelación»Fallos síncronos/asíncronos
Sección titulada «Fallos síncronos/asíncronos»- Lanzar excepciones (o promesas rechazadas) en
executese trata como fallo de la herramienta. - El runtime del agente convierte los fallos en mensajes de resultado de herramienta con
isError: truey contenido de texto del error. - Con wrappers de extensión, los manejadores de
tool_resultpueden reescribir adicionalmente contenido/detalles e incluso anular el estado de error.
Cancelación
Sección titulada «Cancelación»- La cancelación del agente se propaga a través de
AbortSignalhaciaexecute. - Reenvíe
signalal trabajo de subprocesos (pi.exec(..., { signal })) para cancelación cooperativa. ctx.abort()permite que una herramienta solicite la cancelación de la operación actual del agente.
Errores de onSession
Sección titulada «Errores de onSession»- Los errores de
onSessionse capturan y registran como advertencias; no provocan el fallo de la sesión.
Restricciones reales a considerar en el diseño
Sección titulada «Restricciones reales a considerar en el diseño»- Los nombres de herramientas deben ser globalmente únicos en el registro activo.
- Prefiera salidas deterministas con forma de esquema en
detailspara la reconstrucción del renderizador/estado. - Proteja el uso de la UI con
pi.hasUI. - Trate los archivos
.md/.jsonen directorios de herramientas como metadatos, no como módulos ejecutables.