Fantasy v0.21.0 natively includes gpt-5.5 and other newer models in its responsesModelIDs/responsesReasoningModelIDs lists, making our workaround unnecessary. - Delete responses_models.go (go:linkname hack + RegisterResponsesModels) - Delete responses_models_test.go - Replace isResponsesAPIModel/isResponsesReasoningModel heuristics with direct openai.IsResponsesModel/openai.IsResponsesReasoningModel calls - Remove RegisterResponsesModels calls from registry init/reload - Remove hack documentation from AGENTS.md - Update all deps (fantasy v0.21.0, smithy-go, ultraviolet, etc.)
6.4 KiB
KIT Agent Guidelines
Build/Test Commands
- Build:
go build -o output/kit ./cmd/kit - Test all:
go test -race ./... - Test single:
go test -race ./cmd -run TestScriptExecution - Lint:
go vet ./... - Format:
go fmt ./...
Code Style
- Imports: stdlib → third-party → local (blank lines between)
- Naming: camelCase (unexported), PascalCase (exported)
- Errors: Always check, wrap with
fmt.Errorf("context: %w", err) - Logging: Use
github.com/charmbracelet/logstructured logging - Types: Prefer
anyoverinterface{} - JSON: snake_case tags with
omitemptywhere appropriate - Context: First parameter for blocking operations
Architecture
- Multi-provider LLM support via
llm.Providerinterface - MCP client-server for tool integration
- Builtin servers: bash, fetch, todo, fs
- Extension system (
internal/extensions/): Yaegi-interpreted Go, 13 lifecycle events, custom tools/commands/widgets/overlays/editor interceptors - TUI (
internal/ui/): Bubble Tea v2 parent-child model (AppModel→InputComponent,StreamComponent, etc.) - Decoupling pattern:
cmd/root.gohas converter functions (e.g.widgetProviderForUI()) that bridgeinternal/extensions/types tointernal/ui/types — the UI never imports extensions directly - Public SDK (
pkg/kit/): The public-facing Go SDK for embedding Kit as a library. See rules below.
Public SDK (pkg/kit/) Rules
pkg/kit/ is the public API surface consumed by external Go developers. All exported symbols, types, function names, and godoc comments in this package are part of the SDK contract.
No Dependency Name Leakage
Internal dependency names (e.g. charm.land/fantasy, library-specific jargon) must not appear in:
- Exported function/method names — use generic terms (
LLM,Provider,Message) instead of library names - Exported type names — type aliases should use domain names (e.g.
LLMMessage, notFantasyMessage) - Godoc comments on exported symbols — these are visible in
go docoutput and pkg.go.dev - Struct field names and tags on exported types
Using dependency types directly in function bodies (private implementation) is fine — that's invisible to SDK consumers.
Naming Conventions for SDK Symbols
- Type aliases re-exporting dependency types: use
LLM*prefix (e.g.LLMMessage,LLMUsage,LLMResponse) - Conversion helpers: use
ConvertToLLM*/ConvertFromLLM*(not the dependency name) - Provider queries: use
GetLLMProviders(notGetFantasyProviders) - When wrapping internal methods, the
pkg/kit/name should be dependency-agnostic even if theinternal/method still uses the old name
Deprecation Pattern
When renaming a public SDK symbol, keep the old name as a deprecated wrapper for one release cycle:
// Deprecated: Use NewName instead.
func OldName() { return NewName() }
Key Patterns
Yaegi (Extension Interpreter) Gotchas
- No interfaces across boundary: All extension-facing API types must be concrete structs, never interfaces. Yaegi crashes on interface wrapper generation.
- Function field bug: Named function references assigned to struct fields return zero values across the interpreter boundary. Always use anonymous closure literals:
// WRONG: ctx.SetEditor(ext.EditorConfig{HandleKey: myHandler}) // RIGHT: ctx.SetEditor(ext.EditorConfig{HandleKey: func(k, t string) ext.EditorKeyAction { return myHandler(k, t) }}) - Symbol exports: Every new type exposed to extensions must be added to
internal/extensions/symbols.go
BubbleTea Integration
- No
prog.Send()from insideUpdate(): Callingprog.Send()synchronously within a BubbleTeaUpdate()handler deadlocks the event loop. Usego appInstance.NotifyWidgetUpdate()(async goroutine) instead. - Height measurement:
distributeHeight()inmodel.gomust measure using the same render path asView(). If an interceptor wraps rendering, measure with the wrapper too, or layout will mismatch. - Channel-based prompts: Extension prompt calls (PromptSelect, etc.) block on a
chan PromptResponse. Extension slash commands run in dedicated goroutines (nottea.Cmd) to avoid stalling BubbleTea's Cmd scheduler.
Extension State Management
- Thread-safe maps on Runner: Widget/header/footer/editor state lives on the Runner with
sync.RWMutex, queried by UI via callbacks - Context function fields: The
Contextstruct uses function fields (Print func(string),SetWidget func(WidgetConfig)) wired by closures incmd/root.go - Package-level vars in extensions: Yaegi supports package-level variables captured in closures — this is how extensions maintain state across event callbacks
Unicode in Widget Text
- Widget content renders through
lipgloss.Style.Render()which preserves ANSI escape codes - Use rune-based width calculations (
len([]rune(s))) not byte length (len(s)) when aligning box-drawing characters or multi-byte symbols
Testing
Interactive TUI Testing with tmux
Use tmux to test Kit interactively without blocking the agent:
tmux new-session -d -s kittest -x 120 -y 40 "output/kit -e examples/extensions/my-ext.go --no-session 2>kit_stderr.log"
sleep 3
tmux capture-pane -t kittest -p # read screen
tmux send-keys -t kittest '/command' Enter # send input
tmux kill-session -t kittest # cleanup
Non-Interactive Kit (Subprocess Spawning)
Extensions can spawn Kit as a subprocess for sub-agent patterns:
kit --quiet --no-session --no-extensions --system-prompt /path/to/prompt.txt --model provider/model "question"
Positional args are the prompt. @file args attach file content. Key flags: --quiet (stdout only, no TUI), --no-session (ephemeral), --no-extensions (prevent recursive loading), --system-prompt (string or file path).
External Repo Research
- ALWAYS use
btcato search external repos (e.g. iteratr, other reference codebases) - Never guess or manually search the filesystem for external projects
- Example:
btca ask -r https://github.com/user/repo -q "How does X work?" - See
.agents/skills/btca-cli/SKILL.mdfor full btca usage
BTCA Configured Resources
The following external repositories are configured in btca.config.jsonc for research:
- bubbletea
- lipgloss
- bubbles
- glamour
- fantasy
- catwalk
- crush
- pi
- iteratr
- yaegi
- acp-go-sdk
- opencode
- herald
- herald-md