mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
49f8b485be
* feat(extensions): add OnLLMUsage, SetState, enriched AgentEndEvent (#53) Three additive primitives to the extension API: - OnLLMUsage event: per-LLM-call token + cost deltas attributed to the specific model/provider used for each round-trip. Derived from the SDK StepFinishEvent in the extension bridge. Enables accurate budget enforcement between calls instead of only at turn boundaries. - ctx.SetState / GetState / DeleteState / ListState: session-scoped, last-write-wins key-value store backed by a sidecar file (<session>.ext-state.json) outside the conversation tree. Reads are O(1), writes don't grow the JSONL, and the store is not duplicated on fork. State is preserved across hot-reloads. - Enriched AgentEndEvent: ToolCallCount, ToolNames, LLMCallCount, token deltas (input/output/cache-read/cache-write), CostDelta, and DurationMs populated by a per-turn aggregator. Existing handlers reading only Response/StopReason are unaffected. Includes unit tests for the state store, LLMUsage registration, enriched AgentEndEvent, turn aggregator, llmUsageMeta, and sidecar path derivation. Adds examples/extensions/usage-budget.go demoing all three primitives together. Documents the additions in README, the docs site (extensions overview, capabilities, examples), and the kit-extensions and kit-sdk skill guides. Fixes #53 * fix(extensions): address review feedback on state store and llmUsageMeta - Serialize SetState/DeleteState saver invocations through a new saverMu so overlapping atomic-rename writes can no longer race on the shared .tmp file and persist an older snapshot after a newer one. - LoadStateFromFile now clears the in-memory store when the sidecar is missing or empty, matching the documented "replace … with its contents" contract. This makes session-switching safe by preventing keys from a prior session leaking into a new one. Tests updated to cover both the missing-file and empty-file cases. - llmUsageMeta now detects Anthropic OAuth credentials and returns Cost=0, matching the comment and the existing usage_tracker behavior for OAuth users. Mirrors the OAuth detection already used in cmd/extension_context.go. - Document the single-in-flight-turn assumption baked into the per-turn aggregator with a clear migration path (per-turn ID) for if concurrent turns ever become a supported use case. * fix(extensions): release saverMu on panic in state store Extract a runSaver helper that locks saverMu and defers Unlock before invoking the persistence callback. Without the deferred Unlock, a panic inside the saver (e.g. disk full mid-write) would leave saverMu held forever and deadlock the next SetState/DeleteState. Both SetState and DeleteState now route through the helper. New TestRunner_State_Saver PanicReleasesSaverMu reproduces the deadlock window with a 2s deadline and proves the mutex is released after a panic.
533 lines
17 KiB
Markdown
533 lines
17 KiB
Markdown
---
|
|
title: Capabilities
|
|
description: All extension capabilities — lifecycle events, tools, commands, widgets, and more.
|
|
---
|
|
|
|
# Extension Capabilities
|
|
|
|
## Lifecycle events
|
|
|
|
Extensions can hook into 27 lifecycle events:
|
|
|
|
| Event | Description |
|
|
|-------|-------------|
|
|
| `OnSessionStart` | Session initialized |
|
|
| `OnSessionShutdown` | Session ending |
|
|
| `OnBeforeAgentStart` | Before the agent loop begins |
|
|
| `OnAgentStart` | Agent loop started |
|
|
| `OnAgentEnd` | Agent loop completed (carries per-turn aggregates: tool counts, token deltas, cost, duration) |
|
|
| `OnLLMUsage` | Per-LLM-call token + cost delta (fires once per provider round-trip) |
|
|
| `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(e ext.AgentEndEvent, ctx ext.Context) {
|
|
// Per-turn aggregates populated by Kit's runtime — no parallel
|
|
// bookkeeping required in the handler.
|
|
ctx.PrintInfo(fmt.Sprintf(
|
|
"Turn finished: %d tool calls (%v), %d LLM round-trips, $%.4f, %dms",
|
|
e.ToolCallCount, e.ToolNames, e.LLMCallCount, e.CostDelta, e.DurationMs,
|
|
))
|
|
})
|
|
|
|
// Per-LLM-call usage — fires multiple times per turn (once per round-trip).
|
|
// Use for accurate budget enforcement between calls.
|
|
api.OnLLMUsage(func(e ext.LLMUsageEvent, ctx ext.Context) {
|
|
ctx.PrintInfo(fmt.Sprintf(
|
|
"%s/%s step=%d tokens=↑%d ↓%d cost=$%.4f (%s)",
|
|
e.Provider, e.Model, e.StepNumber,
|
|
e.InputTokens, e.OutputTokens, e.Cost, e.FinishReason,
|
|
))
|
|
})
|
|
```
|
|
|
|
**`AgentEndEvent` fields** (in addition to `Response` and `StopReason`):
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `ToolCallCount` | `int` | Total tool invocations during the turn |
|
|
| `ToolNames` | `[]string` | Tool names in call order (duplicates preserved) |
|
|
| `LLMCallCount` | `int` | LLM round-trips / tool-loop iterations |
|
|
| `InputTokensDelta` | `int` | Sum of input tokens across all LLM calls this turn |
|
|
| `OutputTokensDelta` | `int` | Sum of output tokens across all LLM calls this turn |
|
|
| `CacheReadTokensDelta` | `int` | Sum of cache-read tokens this turn |
|
|
| `CacheWriteTokensDelta` | `int` | Sum of cache-write tokens this turn |
|
|
| `CostDelta` | `float64` | Cost in USD (zero when pricing is unknown or OAuth credentials) |
|
|
| `DurationMs` | `int64` | Wall-clock time from `AgentStart` to `AgentEnd` |
|
|
|
|
**`LLMUsageEvent` fields**:
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `InputTokens` / `OutputTokens` | `int` | Per-call token deltas |
|
|
| `CacheReadTokens` / `CacheWriteTokens` | `int` | Per-call cache token deltas |
|
|
| `Cost` | `float64` | Per-call USD cost (zero when pricing unknown) |
|
|
| `Model` / `Provider` | `string` | Model used for this specific call — may differ from earlier calls if `ctx.SetModel` was called mid-turn |
|
|
| `StepNumber` | `int` | Zero-based step index within the turn |
|
|
| `FinishReason` | `string` | Provider finish reason for this call (`"stop"`, `"tool_calls"`, `"length"`, ...) |
|
|
| `RequestID` | `string` | Optional provider correlation id (may be empty) |
|
|
|
|
## 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
|
|
})
|
|
```
|
|
|
|
## Session state
|
|
|
|
Last-write-wins key-value store, scoped to the current session and persisted to a sidecar file (`<session>.ext-state.json`) outside the conversation tree:
|
|
|
|
```go
|
|
ctx.SetState("myext:budget-cap", "10.00")
|
|
|
|
if cap, ok := ctx.GetState("myext:budget-cap"); ok {
|
|
// ...
|
|
}
|
|
|
|
ctx.DeleteState("myext:budget-cap")
|
|
keys := ctx.ListState() // []string, unspecified order
|
|
```
|
|
|
|
Reads are O(1) (no branch walk), writes don't grow the session JSONL, and the store is not duplicated when the conversation forks. State is invisible to the LLM and survives session resume.
|
|
|
|
### When to use which persistence primitive
|
|
|
|
| Need | Use | Why |
|
|
|------|-----|-----|
|
|
| Snapshot state ("current value of X") | `SetState` / `GetState` | O(1) reads, sidecar file, last-write-wins |
|
|
| Audit log / event history | `AppendEntry` / `GetEntries` | Append-only, lives in conversation tree, fork-aware |
|
|
| One-shot per-turn signal | Enriched `AgentEndEvent` fields | No persistence needed; runtime tracks it for you |
|
|
| Per-LLM-call observation | `OnLLMUsage` event | Already attributed to model/provider/step |
|
|
|
|
Using `AppendEntry` for snapshot state has a cost: it's O(branch_length) to read, fsyncs into the JSONL on every write, and the entry list duplicates on every fork. Prefer `SetState` for "what's the current value of X?"-style data.
|
|
|
|
For ephemeral / in-memory sessions (no JSONL path) the state lives only in memory for the lifetime of the runner.
|
|
|
|
## 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"
|
|
```
|