- Início
- Documentation
- Extensões
- Extensões
Extensões
Guia principal para criação de extensões de runtime em packages/coding-agent.
Este documento abrange o runtime de extensões atual em:
src/extensibility/extensions/types.tssrc/extensibility/extensions/runner.tssrc/extensibility/extensions/wrapper.tssrc/extensibility/extensions/index.tssrc/modes/controllers/extension-ui-controller.ts
Para caminhos de descoberta e regras de carregamento do sistema de arquivos, consulte docs/extension-loading.md.
O que é uma extensão
Seção intitulada “O que é uma extensão”Uma extensão é um módulo TS/JS que exporta uma factory padrão:
import type { ExtensionAPI } from "@f5-sales-demo/xcsh";
export default function myExtension(pi: ExtensionAPI) { // registrar handlers/tools/commands/renderers}As extensões podem combinar todos os seguintes em um único módulo:
- manipuladores de eventos (
pi.on(...)) - ferramentas invocáveis pelo LLM (
pi.registerTool(...)) - comandos de barra (
pi.registerCommand(...)) - atalhos de teclado e flags
- renderização de mensagens personalizada
- APIs de injeção de sessão/mensagem (
sendMessage,sendUserMessage,appendEntry)
Modelo de runtime
Seção intitulada “Modelo de runtime”- As extensões são importadas e suas funções factory são executadas.
- Durante essa fase de carregamento, os métodos de registro são válidos; os métodos de ação de runtime ainda não estão inicializados.
ExtensionRunner.initialize(...)conecta ações/contextos ativos para o modo ativo.- Eventos de ciclo de vida de sessão/agente/ferramenta são emitidos para os manipuladores.
- Toda execução de ferramenta é encapsulada com interceptação de extensão (
tool_call/tool_result).
Ciclo de vida da extensão (simplificado)
caminhos de carregamento │ ▼importar módulo + executar factory (somente registro) │ ▼ExtensionRunner.initialize(modo/sessão/registro de ferramentas) │ ├─ emitir eventos de sessão/agente para manipuladores ├─ encapsular execução de ferramenta (tool_call/tool_result) └─ expor ações de runtime (sendMessage, setActiveTools, ...)Restrição importante de loader.ts:
- chamar métodos de ação como
pi.sendMessage()durante o carregamento da extensão lançaExtensionRuntimeNotInitializedError - registre primeiro; execute comportamentos de runtime a partir de eventos/comandos/ferramentas
Início rápido
Seção intitulada “Início rápido”import type { ExtensionAPI } from "@f5-sales-demo/xcsh";import { Type } from "@sinclair/typebox";
export default function (pi: ExtensionAPI) { pi.setLabel("Safety + Utilities");
pi.on("session_start", async (_event, ctx) => { ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, "info"); });
pi.on("tool_call", async (event) => { if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) { return { block: true, reason: "Blocked by extension policy" }; } });
pi.registerTool({ name: "hello_extension", label: "Hello Extension", description: "Return a greeting", parameters: Type.Object({ name: Type.String() }), async execute(_toolCallId, params, _signal, _onUpdate, _ctx) { return { content: [{ type: "text", text: `Hello, ${params.name}` }], details: { greeted: params.name }, }; }, });
pi.registerCommand("hello-ext", { description: "Show queue state", handler: async (_args, ctx) => { ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, "info"); }, });}Superfícies da API de extensão
Seção intitulada “Superfícies da API de extensão”1) Registro e ações (ExtensionAPI)
Seção intitulada “1) Registro e ações (ExtensionAPI)”Métodos principais:
on(event, handler)registerTool,registerCommand,registerShortcut,registerFlagregisterMessageRenderersendMessage,sendUserMessage,appendEntrygetActiveTools,getAllTools,setActiveToolsgetSessionName,setSessionNamesetModel,getThinkingLevel,setThinkingLevelregisterProviderevents(barramento de eventos compartilhado)
No modo interativo, os manipuladores de input são executados antes da verificação automática de título da primeira mensagem integrada. Extensões que chamam await pi.setSessionName(...) a partir de input podem definir o nome de sessão persistido e impedir que o título gerado automaticamente padrão seja executado para aquela sessão.
Também expostos:
pi.loggerpi.typeboxpi.pi(exportações do pacote)
Semântica de entrega de mensagens
Seção intitulada “Semântica de entrega de mensagens”pi.sendMessage(message, options) suporta:
deliverAs: "steer"(padrão) — interrompe a execução atualdeliverAs: "followUp"— enfileirado para executar após a execução atualdeliverAs: "nextTurn"— armazenado e injetado no próximo prompt do usuáriotriggerTurn: true— inicia um turno quando ocioso (nextTurnignora isso)
pi.sendUserMessage(content, { deliverAs }) sempre passa pelo fluxo de prompt; durante o streaming, enfileira como steer/follow-up.
2) Contexto do manipulador (ExtensionContext)
Seção intitulada “2) Contexto do manipulador (ExtensionContext)”Manipuladores e o execute de ferramentas recebem ctx com:
uihasUIcwdsessionManager(somente leitura)modelRegistry,modelgetContextUsage()compact(...)isIdle(),hasPendingMessages(),abort()shutdown()getSystemPrompt()
3) Contexto de comando (ExtensionCommandContext)
Seção intitulada “3) Contexto de comando (ExtensionCommandContext)”Manipuladores de comandos também recebem:
waitForIdle()newSession(...)switchSession(...)branch(entryId)navigateTree(targetId, { summarize })reload()
Use o contexto de comando para fluxos de controle de sessão; esses métodos são intencionalmente separados dos manipuladores de eventos gerais.
Superfície de eventos (nomes e comportamentos atuais)
Seção intitulada “Superfície de eventos (nomes e comportamentos atuais)”As uniões de eventos canônicos e os tipos de payload estão em types.ts.
Ciclo de vida da sessão
Seção intitulada “Ciclo de vida da sessão”session_startsession_before_switch/session_switchsession_before_branch/session_branchsession_before_compact/session.compacting/session_compactsession_before_tree/session_treesession_shutdown
Pré-eventos canceláveis:
session_before_switch→{ cancel?: boolean }session_before_branch→{ cancel?: boolean; skipConversationRestore?: boolean }session_before_compact→{ cancel?: boolean; compaction?: CompactionResult }session_before_tree→{ cancel?: boolean; summary?: { summary: string; details?: unknown } }
Ciclo de vida do prompt e do turno
Seção intitulada “Ciclo de vida do prompt e do turno”inputbefore_agent_startcontextagent_start/agent_endturn_start/turn_endmessage_start/message_update/message_end
Ciclo de vida da ferramenta
Seção intitulada “Ciclo de vida da ferramenta”tool_call(pré-execução, pode bloquear)tool_result(pós-execução, pode corrigir content/details/isError)tool_execution_start/tool_execution_update/tool_execution_end(observabilidade)
tool_result é estilo middleware: os manipuladores são executados na ordem das extensões e cada um vê as modificações anteriores.
Sinais de confiabilidade/runtime
Seção intitulada “Sinais de confiabilidade/runtime”auto_compaction_start/auto_compaction_endauto_retry_start/auto_retry_endttsr_triggeredtodo_reminder
Interceptação de comandos do usuário
Seção intitulada “Interceptação de comandos do usuário”user_bash(substituir com{ result })user_python(substituir com{ result })
resources_discover
Seção intitulada “resources_discover”resources_discover existe nos tipos de extensão e em ExtensionRunner.
Nota de runtime atual: ExtensionRunner.emitResourcesDiscover(...) está implementado, mas não há callsites de AgentSession invocando-o na base de código atual.
Detalhes de criação de ferramentas
Seção intitulada “Detalhes de criação de ferramentas”registerTool usa ToolDefinition de types.ts.
Assinatura atual de execute:
execute( toolCallId, params, signal, onUpdate, ctx,): Promise<AgentToolResult>Template:
pi.registerTool({ name: "my_tool", label: "My Tool", description: "...", parameters: Type.Object({}), async execute(_id, _params, signal, onUpdate, ctx) { if (signal?.aborted) { return { content: [{ type: "text", text: "Cancelled" }] }; } onUpdate?.({ content: [{ type: "text", text: "Working..." }] }); return { content: [{ type: "text", text: "Done" }], details: {} }; }, onSession(event, ctx) { // reason: start|switch|branch|tree|shutdown }, renderCall(args, theme) { // renderização TUI opcional }, renderResult(result, options, theme, args) { // renderização TUI opcional },});tool_call/tool_result interceptam todas as ferramentas assim que o registro é encapsulado em sdk.ts, incluindo ferramentas integradas e ferramentas de extensão/personalizadas.
Pontos de integração de UI
Seção intitulada “Pontos de integração de UI”ctx.ui implementa a interface ExtensionUIContext. O suporte difere por modo.
Modo interativo (extension-ui-controller.ts)
Seção intitulada “Modo interativo (extension-ui-controller.ts)”Suportado:
- diálogos:
select,confirm,input,editor - notificações/status/texto do editor/entrada do terminal/overlays personalizados
- listagem/carregamento de temas por nome (
setThemesuporta nomes de string) - alternância de expansão de ferramentas
Métodos no-op atuais neste controlador:
setFootersetHeadersetEditorComponent
Observação: setWidget atualmente é roteado para texto da linha de status via setHookWidget(...).
Modo RPC (rpc-mode.ts)
Seção intitulada “Modo RPC (rpc-mode.ts)”ctx.ui é suportado por eventos RPC extension_ui_request:
- métodos de diálogo (
select,confirm,input,editor) fazem round-trip para respostas do cliente - métodos fire-and-forget emitem requisições (
notify,setStatus,setWidgetpara arrays de string,setTitle,setEditorText)
Não suportado/no-op na implementação RPC:
onTerminalInputcustomsetFooter,setHeader,setEditorComponentsetWorkingMessage- troca/carregamento de temas (
setThemeretorna falha) - controles de expansão de ferramentas são inertes
Caminhos print/headless/subagente
Seção intitulada “Caminhos print/headless/subagente”Quando nenhum contexto de UI é fornecido à inicialização do runner, ctx.hasUI é false e os métodos são no-op/retornam valores padrão.
Modo interativo em segundo plano
Seção intitulada “Modo interativo em segundo plano”O modo em segundo plano instala um objeto de contexto de UI não interativo. Na implementação atual, ctx.hasUI ainda pode ser true enquanto diálogos interativos retornam comportamento padrão/no-op.
Padrões de sessão e estado
Seção intitulada “Padrões de sessão e estado”Para estado de extensão durável:
- Persistir com
pi.appendEntry(customType, data). - Reconstruir estado a partir de
ctx.sessionManager.getBranch()emsession_start,session_branch,session_tree. - Manter o
detailsdo resultado da ferramenta estruturado quando o estado deve ser visível/reconstruível a partir do histórico de resultados de ferramentas.
Exemplo de padrão de reconstrução:
pi.on("session_start", async (_event, ctx) => { let latest; for (const entry of ctx.sessionManager.getBranch()) { if (entry.type === "custom" && entry.customType === "my-state") { latest = entry.data; } } // restaurar a partir do latest});Pontos de extensão de renderização
Seção intitulada “Pontos de extensão de renderização”Renderizador de mensagens personalizado
Seção intitulada “Renderizador de mensagens personalizado”pi.registerMessageRenderer("my-type", (message, { expanded }, theme) => { // retornar Componente pi-tui});Usado pela renderização interativa quando mensagens personalizadas são exibidas.
Renderizador de chamada/resultado de ferramenta
Seção intitulada “Renderizador de chamada/resultado de ferramenta”Forneça renderCall / renderResult nas definições de registerTool para visualização personalizada de ferramentas no TUI.
Restrições e armadilhas
Seção intitulada “Restrições e armadilhas”- Ações de runtime não estão disponíveis durante o carregamento da extensão.
- Erros em
tool_callbloqueiam a execução (fail-closed). - Conflitos de nome de comando com built-ins são ignorados com diagnósticos.
- Atalhos reservados são ignorados (
ctrl+c,ctrl+d,ctrl+z,ctrl+k,ctrl+p,ctrl+l,ctrl+o,ctrl+t,ctrl+g,shift+tab,shift+ctrl+p,alt+enter,escape,enter). - Trate
ctx.reload()como terminal para o frame atual do manipulador de comando.
Extensões vs hooks vs ferramentas personalizadas
Seção intitulada “Extensões vs hooks vs ferramentas personalizadas”Use a superfície correta:
- Extensões (
src/extensibility/extensions/*): sistema unificado (eventos + ferramentas + comandos + renderizadores + registro de provider). - Hooks (
src/extensibility/hooks/*): API de eventos legada separada. - Custom-tools (
src/extensibility/custom-tools/*): módulos focados em ferramentas; quando carregados junto com extensões, são adaptados e ainda passam pelos wrappers de interceptação de extensão.
Se você precisa de um único pacote que gerencie política, ferramentas, UX de comando e renderização juntos, use extensões.