- Accueil
- Documentation
- Extensions
- Extensions
Extensions
Guide principal pour la création d’extensions runtime dans packages/coding-agent.
Ce document couvre le runtime d’extension actuel dans :
src/extensibility/extensions/types.tssrc/extensibility/extensions/runner.tssrc/extensibility/extensions/wrapper.tssrc/extensibility/extensions/index.tssrc/modes/controllers/extension-ui-controller.ts
Pour les chemins de découverte et les règles de chargement du système de fichiers, consultez docs/extension-loading.md.
Qu’est-ce qu’une extension
Section intitulée « Qu’est-ce qu’une extension »Une extension est un module TS/JS exportant une factory par défaut :
import type { ExtensionAPI } from "@f5-sales-demo/xcsh";
export default function myExtension(pi: ExtensionAPI) { // register handlers/tools/commands/renderers}Les extensions peuvent combiner tous les éléments suivants dans un seul module :
- gestionnaires d’événements (
pi.on(...)) - outils appelables par LLM (
pi.registerTool(...)) - commandes slash (
pi.registerCommand(...)) - raccourcis clavier et indicateurs
- rendu de messages personnalisé
- API d’injection de session/message (
sendMessage,sendUserMessage,appendEntry)
Modèle de runtime
Section intitulée « Modèle de runtime »- Les extensions sont importées et leurs fonctions factory sont exécutées.
- Durant cette phase de chargement, les méthodes d’enregistrement sont valides ; les méthodes d’action runtime ne sont pas encore initialisées.
ExtensionRunner.initialize(...)connecte les actions/contextes actifs pour le mode en cours.- Les événements du cycle de vie de session/agent/outil sont émis vers les gestionnaires.
- Chaque exécution d’outil est enveloppée avec l’interception d’extension (
tool_call/tool_result).
Extension lifecycle (simplified)
load paths │ ▼import module + run factory (registration only) │ ▼ExtensionRunner.initialize(mode/session/tool registry) │ ├─ emit session/agent events to handlers ├─ wrap tool execution (tool_call/tool_result) └─ expose runtime actions (sendMessage, setActiveTools, ...)Contrainte importante de loader.ts :
- appeler des méthodes d’action comme
pi.sendMessage()lors du chargement de l’extension lèveExtensionRuntimeNotInitializedError - enregistrez d’abord ; effectuez le comportement runtime depuis les événements/commandes/outils
Démarrage rapide
Section intitulée « Démarrage rapide »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"); }, });}Surfaces de l’API d’extension
Section intitulée « Surfaces de l’API d’extension »1) Enregistrement et actions (ExtensionAPI)
Section intitulée « 1) Enregistrement et actions (ExtensionAPI) »Méthodes principales :
on(event, handler)registerTool,registerCommand,registerShortcut,registerFlagregisterMessageRenderersendMessage,sendUserMessage,appendEntrygetActiveTools,getAllTools,setActiveToolsgetSessionName,setSessionNamesetModel,getThinkingLevel,setThinkingLevelregisterProviderevents(bus d’événements partagé)
En mode interactif, les gestionnaires input s’exécutent avant la vérification automatique du titre du premier message intégrée. Les extensions qui appellent await pi.setSessionName(...) depuis input peuvent définir le nom de session persisté et empêcher le titre auto-généré par défaut de s’exécuter pour cette session.
Également exposés :
pi.loggerpi.typeboxpi.pi(exports du paquet)
Sémantique de livraison des messages
Section intitulée « Sémantique de livraison des messages »pi.sendMessage(message, options) prend en charge :
deliverAs: "steer"(par défaut) — interrompt l’exécution en coursdeliverAs: "followUp"— mis en file d’attente pour s’exécuter après l’exécution en coursdeliverAs: "nextTurn"— stocké et injecté lors de la prochaine invite utilisateurtriggerTurn: true— démarre un tour lorsqu’inactif (nextTurnignore cela)
pi.sendUserMessage(content, { deliverAs }) passe toujours par le flux d’invite ; lors du streaming, il est mis en file d’attente en tant que steer/follow-up.
2) Contexte du gestionnaire (ExtensionContext)
Section intitulée « 2) Contexte du gestionnaire (ExtensionContext) »Les gestionnaires et les execute d’outils reçoivent ctx avec :
uihasUIcwdsessionManager(lecture seule)modelRegistry,modelgetContextUsage()compact(...)isIdle(),hasPendingMessages(),abort()shutdown()getSystemPrompt()
3) Contexte de commande (ExtensionCommandContext)
Section intitulée « 3) Contexte de commande (ExtensionCommandContext) »Les gestionnaires de commandes reçoivent en plus :
waitForIdle()newSession(...)switchSession(...)branch(entryId)navigateTree(targetId, { summarize })reload()
Utilisez le contexte de commande pour les flux de contrôle de session ; ces méthodes sont intentionnellement séparées des gestionnaires d’événements généraux.
Surface des événements (noms et comportements actuels)
Section intitulée « Surface des événements (noms et comportements actuels) »Les unions d’événements canoniques et les types de charge utile se trouvent dans types.ts.
Cycle de vie de la session
Section intitulée « Cycle de vie de la session »session_startsession_before_switch/session_switchsession_before_branch/session_branchsession_before_compact/session.compacting/session_compactsession_before_tree/session_treesession_shutdown
Pré-événements annulables :
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 } }
Cycle de vie de l’invite et du tour
Section intitulée « Cycle de vie de l’invite et du tour »inputbefore_agent_startcontextagent_start/agent_endturn_start/turn_endmessage_start/message_update/message_end
Cycle de vie de l’outil
Section intitulée « Cycle de vie de l’outil »tool_call(pré-exécution, peut bloquer)tool_result(post-exécution, peut modifier le contenu/les détails/isError)tool_execution_start/tool_execution_update/tool_execution_end(observabilité)
tool_result est de style middleware : les gestionnaires s’exécutent dans l’ordre des extensions et chacun voit les modifications précédentes.
Signaux de fiabilité/runtime
Section intitulée « Signaux de fiabilité/runtime »auto_compaction_start/auto_compaction_endauto_retry_start/auto_retry_endttsr_triggeredtodo_reminder
Interception des commandes utilisateur
Section intitulée « Interception des commandes utilisateur »user_bash(remplacement avec{ result })user_python(remplacement avec{ result })
resources_discover
Section intitulée « resources_discover »resources_discover existe dans les types d’extension et ExtensionRunner.
Note sur le runtime actuel : ExtensionRunner.emitResourcesDiscover(...) est implémenté, mais il n’existe aucun point d’appel AgentSession l’invoquant dans la base de code actuelle.
Détails de la création d’outils
Section intitulée « Détails de la création d’outils »registerTool utilise ToolDefinition depuis types.ts.
Signature execute actuelle :
execute( toolCallId, params, signal, onUpdate, ctx,): Promise<AgentToolResult>Modèle :
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) { // optional TUI render }, renderResult(result, options, theme, args) { // optional TUI render },});tool_call/tool_result intercepte tous les outils une fois le registre encapsulé dans sdk.ts, y compris les outils intégrés et les outils d’extension/personnalisés.
Points d’intégration de l’interface utilisateur
Section intitulée « Points d’intégration de l’interface utilisateur »ctx.ui implémente l’interface ExtensionUIContext. La prise en charge varie selon le mode.
Mode interactif (extension-ui-controller.ts)
Section intitulée « Mode interactif (extension-ui-controller.ts) »Pris en charge :
- dialogues :
select,confirm,input,editor - notifications/statut/texte de l’éditeur/saisie terminal/superpositions personnalisées
- liste/chargement des thèmes par nom (
setThemeprend en charge les noms de chaînes) - bascule d’expansion des outils
Méthodes sans effet dans ce contrôleur :
setFootersetHeadersetEditorComponent
À noter également : setWidget route actuellement vers le texte de la ligne de statut via setHookWidget(...).
Mode RPC (rpc-mode.ts)
Section intitulée « Mode RPC (rpc-mode.ts) »ctx.ui est soutenu par des événements RPC extension_ui_request :
- les méthodes de dialogue (
select,confirm,input,editor) effectuent des allers-retours vers les réponses client - les méthodes fire-and-forget émettent des requêtes (
notify,setStatus,setWidgetpour les tableaux de chaînes,setTitle,setEditorText)
Non pris en charge/sans effet dans l’implémentation RPC :
onTerminalInputcustomsetFooter,setHeader,setEditorComponentsetWorkingMessage- changement/chargement de thème (
setThemerenvoie une erreur) - les contrôles d’expansion des outils sont inactifs
Chemins print/headless/subagent
Section intitulée « Chemins print/headless/subagent »Lorsqu’aucun contexte UI n’est fourni à l’initialisation du runner, ctx.hasUI est false et les méthodes sont sans effet/retournent des valeurs par défaut.
Mode interactif en arrière-plan
Section intitulée « Mode interactif en arrière-plan »Le mode arrière-plan installe un objet de contexte UI non interactif. Dans l’implémentation actuelle, ctx.hasUI peut toujours être true tandis que les dialogues interactifs retournent des valeurs par défaut/comportement sans effet.
Modèles de session et d’état
Section intitulée « Modèles de session et d’état »Pour un état d’extension durable :
- Persistez avec
pi.appendEntry(customType, data). - Reconstruisez l’état depuis
ctx.sessionManager.getBranch()lors desession_start,session_branch,session_tree. - Maintenez les
detailsdes résultats d’outils structurés lorsque l’état doit être visible/reconstructible depuis l’historique des résultats d’outils.
Exemple de modèle de reconstruction :
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; } } // restore from latest});Points d’extension du rendu
Section intitulée « Points d’extension du rendu »Renderer de messages personnalisé
Section intitulée « Renderer de messages personnalisé »pi.registerMessageRenderer("my-type", (message, { expanded }, theme) => { // return pi-tui Component});Utilisé par le rendu interactif lorsque des messages personnalisés sont affichés.
Renderer d’appel/résultat d’outil
Section intitulée « Renderer d’appel/résultat d’outil »Fournissez renderCall / renderResult dans les définitions registerTool pour une visualisation personnalisée des outils dans le TUI.
Contraintes et pièges
Section intitulée « Contraintes et pièges »- Les actions runtime ne sont pas disponibles lors du chargement de l’extension.
- Les erreurs
tool_callbloquent l’exécution (échec fermé). - Les conflits de noms de commandes avec les commandes intégrées sont ignorés avec des diagnostics.
- Les raccourcis réservés sont ignorés (
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). - Traitez
ctx.reload()comme terminal pour le cadre du gestionnaire de commande en cours.
Extensions vs hooks vs custom-tools
Section intitulée « Extensions vs hooks vs custom-tools »Utilisez la bonne surface :
- Extensions (
src/extensibility/extensions/*) : système unifié (événements + outils + commandes + renderers + enregistrement de fournisseur). - Hooks (
src/extensibility/hooks/*) : API d’événements légacy séparée. - Custom-tools (
src/extensibility/custom-tools/*) : modules axés sur les outils ; lorsqu’ils sont chargés aux côtés des extensions, ils sont adaptés et passent toujours par les wrappers d’interception d’extension.
Si vous avez besoin d’un seul paquet qui gère la politique, les outils, l’expérience utilisateur des commandes et le rendu ensemble, utilisez les extensions.