Pular para o conteúdo

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.ts
  • src/extensibility/extensions/runner.ts
  • src/extensibility/extensions/wrapper.ts
  • src/extensibility/extensions/index.ts
  • src/modes/controllers/extension-ui-controller.ts

Para caminhos de descoberta e regras de carregamento do sistema de arquivos, consulte docs/extension-loading.md.

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)
  1. As extensões são importadas e suas funções factory são executadas.
  2. 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.
  3. ExtensionRunner.initialize(...) conecta ações/contextos ativos para o modo ativo.
  4. Eventos de ciclo de vida de sessão/agente/ferramenta são emitidos para os manipuladores.
  5. 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ça ExtensionRuntimeNotInitializedError
  • registre primeiro; execute comportamentos de runtime a partir de eventos/comandos/ferramentas
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");
},
});
}

Métodos principais:

  • on(event, handler)
  • registerTool, registerCommand, registerShortcut, registerFlag
  • registerMessageRenderer
  • sendMessage, sendUserMessage, appendEntry
  • getActiveTools, getAllTools, setActiveTools
  • getSessionName, setSessionName
  • setModel, getThinkingLevel, setThinkingLevel
  • registerProvider
  • events (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.logger
  • pi.typebox
  • pi.pi (exportações do pacote)

pi.sendMessage(message, options) suporta:

  • deliverAs: "steer" (padrão) — interrompe a execução atual
  • deliverAs: "followUp" — enfileirado para executar após a execução atual
  • deliverAs: "nextTurn" — armazenado e injetado no próximo prompt do usuário
  • triggerTurn: true — inicia um turno quando ocioso (nextTurn ignora isso)

pi.sendUserMessage(content, { deliverAs }) sempre passa pelo fluxo de prompt; durante o streaming, enfileira como steer/follow-up.

Manipuladores e o execute de ferramentas recebem ctx com:

  • ui
  • hasUI
  • cwd
  • sessionManager (somente leitura)
  • modelRegistry, model
  • getContextUsage()
  • compact(...)
  • isIdle(), hasPendingMessages(), abort()
  • shutdown()
  • getSystemPrompt()

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.

  • session_start
  • session_before_switch / session_switch
  • session_before_branch / session_branch
  • session_before_compact / session.compacting / session_compact
  • session_before_tree / session_tree
  • session_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 } }
  • input
  • before_agent_start
  • context
  • agent_start / agent_end
  • turn_start / turn_end
  • message_start / message_update / message_end
  • 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.

  • auto_compaction_start / auto_compaction_end
  • auto_retry_start / auto_retry_end
  • ttsr_triggered
  • todo_reminder
  • user_bash (substituir com { result })
  • user_python (substituir com { result })

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.

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.

ctx.ui implementa a interface ExtensionUIContext. O suporte difere por modo.

Suportado:

  • diálogos: select, confirm, input, editor
  • notificações/status/texto do editor/entrada do terminal/overlays personalizados
  • listagem/carregamento de temas por nome (setTheme suporta nomes de string)
  • alternância de expansão de ferramentas

Métodos no-op atuais neste controlador:

  • setFooter
  • setHeader
  • setEditorComponent

Observação: setWidget atualmente é roteado para texto da linha de status via setHookWidget(...).

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, setWidget para arrays de string, setTitle, setEditorText)

Não suportado/no-op na implementação RPC:

  • onTerminalInput
  • custom
  • setFooter, setHeader, setEditorComponent
  • setWorkingMessage
  • troca/carregamento de temas (setTheme retorna falha)
  • controles de expansão de ferramentas são inertes

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.

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.

Para estado de extensão durável:

  1. Persistir com pi.appendEntry(customType, data).
  2. Reconstruir estado a partir de ctx.sessionManager.getBranch() em session_start, session_branch, session_tree.
  3. Manter o details do 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
});
pi.registerMessageRenderer("my-type", (message, { expanded }, theme) => {
// retornar Componente pi-tui
});

Usado pela renderização interativa quando mensagens personalizadas são exibidas.

Forneça renderCall / renderResult nas definições de registerTool para visualização personalizada de ferramentas no TUI.

  • Ações de runtime não estão disponíveis durante o carregamento da extensão.
  • Erros em tool_call bloqueiam 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.

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.