mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
e830bf87ca
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.)
121 lines
6.4 KiB
Markdown
121 lines
6.4 KiB
Markdown
# 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/log` structured logging
|
|
- **Types**: Prefer `any` over `interface{}`
|
|
- **JSON**: snake_case tags with `omitempty` where appropriate
|
|
- **Context**: First parameter for blocking operations
|
|
|
|
## Architecture
|
|
- Multi-provider LLM support via `llm.Provider` interface
|
|
- 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.go` has converter functions (e.g. `widgetProviderForUI()`) that bridge `internal/extensions/` types to `internal/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`, not `FantasyMessage`)
|
|
- **Godoc comments** on exported symbols — these are visible in `go doc` output 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` (not `GetFantasyProviders`)
|
|
- When wrapping internal methods, the `pkg/kit/` name should be dependency-agnostic even if the `internal/` 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:
|
|
```go
|
|
// 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:
|
|
```go
|
|
// 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 inside `Update()`**: Calling `prog.Send()` synchronously within a BubbleTea `Update()` handler deadlocks the event loop. Use `go appInstance.NotifyWidgetUpdate()` (async goroutine) instead.
|
|
- **Height measurement**: `distributeHeight()` in `model.go` must measure using the same render path as `View()`. 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 (not `tea.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 `Context` struct uses function fields (`Print func(string)`, `SetWidget func(WidgetConfig)`) wired by closures in `cmd/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:
|
|
```bash
|
|
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:
|
|
```bash
|
|
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 `btca`** to 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.md` for 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
|