Files
kit/extensions/capabilities/index.html
T

472 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Capabilities | Kit</title>
<meta name="description" content="All extension capabilities — lifecycle events, tools, commands, widgets, and more.">
<link rel="canonical" href="/extensions/capabilities">
<link rel="stylesheet" href="/assets/index-Di_r5hA0.css">
<script type="module" src="/assets/index-BbQ_p9l4.js"></script>
<script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","headline":"Capabilities","description":"All extension capabilities — lifecycle events, tools, commands, widgets, and more.","url":"https://go-kit.dev/extensions/capabilities","isPartOf":{"@type":"WebSite","name":"Kit","url":"https://go-kit.dev"}}</script>
</head>
<body>
<div id="tome-root"></div>
<div data-pagefind-body style="display:none"><h1>Capabilities</h1>
# Extension Capabilities
## Lifecycle events
Extensions can hook into 26 lifecycle events:
| Event | Description |
|-------|-------------|
| `OnSessionStart` | Session initialized |
| `OnSessionShutdown` | Session ending |
| `OnBeforeAgentStart` | Before the agent loop begins |
| `OnAgentStart` | Agent loop started |
| `OnAgentEnd` | Agent loop completed |
| `OnToolCall` | Tool call requested by the model |
| `OnToolCallInputStart` | LLM began generating tool call arguments (tool name known, args streaming) |
| `OnToolCallInputDelta` | Streamed JSON fragment of tool call arguments |
| `OnToolCallInputEnd` | Tool argument streaming complete, before execution begins |
| `OnToolExecutionStart` | Tool execution beginning |
| `OnToolOutput` | Streaming tool output chunk (for long-running tools) |
| `OnToolExecutionEnd` | Tool execution completed |
| `OnToolResult` | Tool result returned |
| `OnInput` | User input received |
| `OnMessageStart` | Assistant message started |
| `OnMessageUpdate` | Streaming text chunk received |
| `OnMessageEnd` | Assistant message completed |
| `OnModelChange` | Model switched |
| `OnContextPrepare` | Context being assembled for the model |
| `OnBeforeFork` | Before forking a conversation branch |
| `OnBeforeSessionSwitch` | Before switching sessions |
| `OnBeforeCompact` | Before conversation compaction |
| `OnCustomEvent` | Custom inter-extension event received |
| `OnSubagentStart` | Subagent spawned by the main agent |
| `OnSubagentChunk` | Real-time output from subagent (text, tool calls, results) |
| `OnSubagentEnd` | Subagent completed with final response/error |
### Example
```go
api.OnToolCall(func(event ext.ToolCallEvent, ctx ext.Context) {
ctx.PrintInfo("Calling tool: " + event.Name)
})
api.OnAgentEnd(func(_ ext.AgentEndEvent, ctx ext.Context) {
ctx.PrintInfo("Agent finished")
})
```
## Tools
Register custom tools that the LLM can invoke:
```go
api.RegisterTool(ext.ToolDef{
Name: "weather",
Description: "Get current weather for a location",
Parameters: map[string]ext.ParameterDef{
"city": {Type: "string", Description: "City name", Required: true},
},
Handler: func(ctx ext.Context, params map[string]any) (string, error) {
city := params["city"].(string)
return "Sunny, 72°F in " + city, nil
},
})
```
## Commands
Register slash commands that users can invoke directly:
```go
api.RegisterCommand(ext.CommandDef{
Name: "stats",
Description: "Show context statistics",
Handler: func(ctx ext.Context, args string) {
stats := ctx.GetContextStats()
ctx.PrintInfo(fmt.Sprintf("Tokens: %d", stats.TotalTokens))
},
})
```
## Widgets
Add persistent status displays above or below the input area:
```go
ctx.SetWidget(ext.WidgetConfig{
ID: "token-count",
Position: "bottom",
Content: ext.WidgetContent{Text: "Tokens: 1,234"},
})
// Update later
ctx.SetWidget(ext.WidgetConfig{
ID: "token-count",
Position: "bottom",
Content: ext.WidgetContent{Text: "Tokens: 2,456"},
})
// Remove
ctx.RemoveWidget("token-count")
```
## Headers and footers
Persistent content above and below the conversation:
```go
ctx.SetHeader(ext.HeaderFooterConfig{
Content: ext.WidgetContent{Text: "Project: my-app | Branch: main"},
})
ctx.SetFooter(ext.HeaderFooterConfig{
Content: ext.WidgetContent{Text: "Plan Mode (read-only)"},
})
```
## Status bar
Custom status bar entries:
```go
ctx.SetStatus("mode", "Planning")
ctx.RemoveStatus("mode")
```
## Shortcuts
Global keyboard shortcuts:
```go
api.RegisterShortcut(ext.ShortcutDef{
Key: "ctrl+t",
Description: "Toggle plan mode",
}, func(ctx ext.Context) {
// handle shortcut
})
```
## Overlays
Modal dialogs with markdown content:
```go
ctx.ShowOverlay(ext.OverlayConfig{
Title: "Help",
Content: "# Keyboard Shortcuts\n\n- **ctrl+t** — Toggle plan mode\n- **ctrl+s** — Save session",
})
```
## Tool renderers
Customize how specific tool calls are displayed in the TUI:
```go
api.RegisterToolRenderer(ext.ToolRenderConfig{
ToolName: "bash",
Render: func(name, args, result string, isError bool) string {
return "$ " + args + "\n" + result
},
})
```
## Message renderers
Custom rendering for assistant messages:
```go
api.RegisterMessageRenderer(ext.MessageRendererConfig{
Name: "custom",
Render: func(content string) string {
return ">> " + content
},
})
```
## Editor interceptors
Handle key events and wrap the editor's rendering:
```go
ctx.SetEditor(ext.EditorConfig{
HandleKey: func(key, text string) ext.EditorKeyAction {
if key == "escape" {
return ext.EditorKeyAction{Handled: true}
}
return ext.EditorKeyAction{Handled: false}
},
})
```
## Interactive prompts
Select, confirm, input, and multi-select dialogs:
```go
// Single select
response := ctx.PromptSelect(ext.PromptSelectConfig{
Title: "Choose a model",
Options: []string{"claude-sonnet", "gpt-4o", "llama3"},
})
// Confirm
confirmed := ctx.PromptConfirm(ext.PromptConfirmConfig{
Title: "Delete this file?",
})
// Text input
name := ctx.PromptInput(ext.PromptInputConfig{
Title: "Enter project name",
Placeholder: "my-project",
})
```
## Options
Register configurable extension options:
```go
api.RegisterOption(ext.OptionDef{
Name: "auto-commit",
Description: "Automatically commit on shutdown",
DefaultValue: "false",
})
```
## Subagents
Spawn in-process child Kit instances:
```go
result := ctx.SpawnSubagent(ext.SubagentConfig{
Task: "Analyze the test files and summarize coverage",
Model: "anthropic/claude-haiku-latest",
SystemPrompt: "You are a test analysis expert.",
})
```
### Monitoring subagents spawned by the main agent
When the LLM uses the built-in `subagent` tool, extensions can monitor the subagent's activity in real-time using three lifecycle events:
```go
// Subagent started
api.OnSubagentStart(func(e ext.SubagentStartEvent, ctx ext.Context) {
// e.ToolCallID — unique ID for this subagent invocation
// e.Task — the task/prompt sent to the subagent
ctx.PrintInfo(fmt.Sprintf("Subagent started: %s", e.Task))
})
// Real-time streaming output from subagent
api.OnSubagentChunk(func(e ext.SubagentChunkEvent, ctx ext.Context) {
// e.ToolCallID — matches the start event
// e.Task — task description
// e.ChunkType — "text", "tool_call", "tool_execution_start", "tool_result"
// e.Content — text content (for text chunks)
// e.ToolName — tool name (for tool-related chunks)
// e.IsError — true if tool result is an error
switch e.ChunkType {
case "text":
// Streaming text output
case "tool_call":
// Subagent is calling a tool
case "tool_execution_start":
// Tool execution started
case "tool_result":
// Tool execution completed (check e.IsError)
}
})
// Subagent completed
api.OnSubagentEnd(func(e ext.SubagentEndEvent, ctx ext.Context) {
// e.ToolCallID — matches start event
// e.Task — task description
// e.Response — final response from subagent
// e.ErrorMsg — error message if subagent failed
if e.ErrorMsg != "" {
ctx.PrintError(fmt.Sprintf("Subagent failed: %s", e.ErrorMsg))
} else {
ctx.PrintInfo(fmt.Sprintf("Subagent completed: %s", e.Response))
}
})
```
This enables building widgets that display real-time subagent activity.
## LLM completion
Make direct model calls without going through the agent loop:
```go
response := ctx.Complete(ext.CompleteRequest{
Prompt: "Summarize this in one sentence: " + content,
})
```
## Themes
Register and switch color themes at runtime:
```go
// Register a custom theme
ctx.RegisterTheme("neon", ext.ThemeColorConfig{
Primary: ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
Secondary: ext.ThemeColor{Light: "#0088CC", Dark: "#00FFFF"},
Success: ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
Warning: ext.ThemeColor{Light: "#CCAA00", Dark: "#FFFF00"},
Error: ext.ThemeColor{Light: "#CC0033", Dark: "#FF0055"},
Info: ext.ThemeColor{Light: "#0088CC", Dark: "#00CCFF"},
Text: ext.ThemeColor{Light: "#111111", Dark: "#F0F0F0"},
Background: ext.ThemeColor{Light: "#F0F0F0", Dark: "#0A0A14"},
})
// Switch to it
ctx.SetTheme("neon")
// List all available themes
names := ctx.ListThemes()
```
See [Themes](/themes) for the full theme file format, built-in themes, and color reference.
## Custom events
Inter-extension communication:
```go
// Emit
ctx.EmitCustomEvent("my-extension:data-ready", payload)
// Listen
api.OnCustomEvent("my-extension:data-ready", func(data any, ctx ext.Context) {
// handle event
})
```
## Bridged SDK APIs
Extensions can access powerful internal SDK capabilities that enable advanced features like conversation tree navigation, dynamic skill loading, template parsing, and model resolution.
### Tree Navigation
Navigate the conversation tree, summarize branches, and implement "fresh context" loops:
```go
// Get a specific node by ID with full metadata and children
node := ctx.GetTreeNode("entry-id")
// node.ID, node.ParentID, node.Type ("message"/"branch_summary"/etc)
// node.Role, node.Content, node.Model, node.Children ([]string)
// Get the current branch from root to leaf
branch := ctx.GetCurrentBranch() // []ext.TreeNode
// Get child entry IDs of a node
children := ctx.GetChildren("entry-id") // []string
// Navigate/fork to a different entry in the tree
result := ctx.NavigateTo("entry-id") // ext.TreeNavigationResult{Success, Error}
// Summarize a range of the branch using LLM
summary := ctx.SummarizeBranch("from-id", "to-id") // string
// Collapse a branch range into a summary entry (fresh context primitive)
result := ctx.CollapseBranch("from-id", "to-id", "summary text")
```
### Skill Loading
Load and inject skills dynamically at runtime:
```go
// Discover skills from standard locations
result := ctx.DiscoverSkills() // ext.SkillLoadResult{Skills, Error}
// Standard locations: ~/.config/kit/skills/, .kit/skills/, .agents/skills/
// Load a specific skill file
skill, err := ctx.LoadSkill("/path/to/skill.md") // (*ext.Skill, error string)
// skill.Name, skill.Description, skill.Content, skill.Tags, skill.When
// Load all skills from a directory
result := ctx.LoadSkillsFromDir("/path/to/skills") // ext.SkillLoadResult
// Inject a skill as context (pre-loads for next turn)
err := ctx.InjectSkillAsContext("skill-name") // error string
// Inject a skill file directly
err := ctx.InjectRawSkillAsContext("/path/to/skill.md") // error string
// Get all discovered skills
skills := ctx.GetAvailableSkills() // []ext.Skill
```
### Template Parsing
Parse and render templates with variable substitution:
```go
// Parse a template to extract {{variables}}
tpl := ctx.ParseTemplate("name", "Hello {{name}}, welcome to {{place}}!")
// tpl.Name, tpl.Content, tpl.Variables ([]string)
// Render a template with variable values
vars := map[string]string{"name": "Alice", "place": "Kit"}
rendered := ctx.RenderTemplate(tpl, vars) // "Hello Alice, welcome to Kit!"
// Parse command-line style arguments
pattern := ext.ArgumentPattern{
Positional: []string{"command", "target"}, // $1, $2
Rest: "args", // $@
Flags: map[string]string{"--loop": "loop", "-f": "force"},
}
result := ctx.ParseArguments("deploy staging --loop 5", pattern)
// result.Vars["command"] = "deploy"
// result.Vars["target"] = "staging"
// result.Flags["--loop"] = "5"
// Simple positional argument parsing ($1, $2, $@)
args := ctx.SimpleParseArguments("deploy staging --force", 2)
// args[0] = "deploy staging --force" (full input)
// args[1] = "deploy" ($1)
// args[2] = "staging" ($2)
// args[3] = "--force" ($@)
// Evaluate model conditionals with wildcards
matches := ctx.EvaluateModelConditional("claude-*") // bool
// Patterns: * matches any, ? matches single char, comma = OR
// Render content with <if-model> conditionals
content := `<if-model is="claude-*">Hi Claude<else>Hi there</if-model>`
rendered := ctx.RenderWithModelConditionals(content) // based on current model
```
### Model Resolution
Resolve model fallback chains and query capabilities:
```go
// Resolve a chain of model preferences (tries each until available)
result := ctx.ResolveModelChain([]string{
"anthropic/claude-opus-4",
"anthropic/claude-sonnet-4",
"openai/gpt-4o",
})
// result.Model (selected), result.Capabilities, result.Attempted, result.Error
// Get capabilities for a specific model
caps, err := ctx.GetModelCapabilities("anthropic/claude-sonnet-4")
// caps.Provider, caps.ModelID, caps.ContextLimit, caps.Reasoning, caps.Streaming
// Check if a model is available (provider exists)
available := ctx.CheckModelAvailable("anthropic/claude-sonnet-4") // bool
// Get current provider/model ID
provider := ctx.GetCurrentProvider() // "anthropic"
modelID := ctx.GetCurrentModelID() // "claude-sonnet-4"
```</div>
</body>
</html>