mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
e8e99b19a8
* Remove dead code: 5 unused symbols across internal packages
- internal/models: LoadModelSettingsFromConfig (zero refs)
- internal/prompts: PromptTemplate.ExpandWithArgs (zero refs)
- internal/app: NewMessageStore (tests migrated to NewMessageStoreWithMessages)
- internal/config: HasEnvVars (+ its test)
- internal/core: ContextWithSudoPassword (test migrated to context.WithValue)
* pkg/kit: use TreeManager alias in exported signatures
NewTreeManagerAdapter and InitTreeSession now spell their signatures with
the public kit.TreeManager alias instead of internal/session.TreeManager,
so go doc renders domain types rather than internal paths.
* Consolidate tool-kind classification into internal/extensions
coreToolKinds + toolKindFor were duplicated verbatim in
internal/extensions/wrapper.go and pkg/kit/events.go, risking silent
divergence between extension events and SDK events. Single source of
truth now lives in internal/extensions/toolkinds.go; pkg/kit re-exports
the constants.
* Consolidate Anthropic OAuth detection and usage-tracker refresh
The 'is the active Anthropic credential a stored OAuth token' check was
copy-pasted at 5 sites, all prefix-matching the magic string
'stored OAuth' produced in internal/auth. Now:
- internal/auth: new CredentialSourceOAuth constant + IsAnthropicOAuth()
- internal/ui: new UpdateUsageTrackerForModel(); CreateUsageTracker and
SetupCLI share lookupTrackableModel (SetupCLI no longer re-inlines the
tracker construction)
- cmd/root.go + cmd/extension_context.go: verbatim-duplicated tracker
refresh blocks replaced with ui.UpdateUsageTrackerForModel
- pkg/kit isAnthropicOAuth delegates to auth.IsAnthropicOAuth
- internal/models compares source against the constant
* pkg/kit: consolidate model-path helpers and argument tokenizer
- ExtractModelFromPath mis-parsed model IDs containing '/' (e.g.
'openrouter/meta/llama' -> 'meta'); it now delegates to
RemoveProviderFromModel and is deprecated alongside
ExtractProviderFromPath (-> GetCurrentProvider)
- parseFields delegated to prompts.ParseCommandArgs so extension argument
parsing and builtin prompt-template parsing share one quote/escape
grammar; ParseCommandArgs now also splits on tabs (superset of both
previous tokenizers)
* Unify the two {{variable}} template engines
internal/skills and pkg/kit/template_bridge each had their own grammar:
skills rejected '{{ name }}' (whitespace) but allowed digit-first names;
the bridge was the opposite. A template behaved differently depending on
whether it was loaded as a skill prompt or via the extension API.
internal/skills is now the single engine using the superset grammar
(\{\{\s*(\w+)\s*\}\}); pkg/kit ParseTemplate/RenderTemplate are thin
adapters over it. Expand is now regex-based so whitespace placeholders
expand consistently; missing variables are still left as-is.
* internal/ui: extract switchModel helper for model-switch flow
The model-selector handler (ModelSelectedMsg) and /model slash command
duplicated the full switch sequence (thinking-level fallback, setModel,
display-state update, preference persistence, ModelChange emit) and had
already drifted in ordering. Both now call a single switchModel method.
Display state is still updated directly (no prog.Send from Update).
* extbridge: extract shared BaseContext for extension wiring
cmd/extension_context.go and internal/acpserver/session.go each built a
giant extensions.Context literal, duplicating ~15 delegation closures
(GetContextStats, GetMessages, AppendEntry, options, SetModel core,
Complete, SpawnSubagent, ...) that had to be kept in sync by hand. New
data-access fields had to be wired in both places or ACP-mode extensions
silently got nil function fields.
extbridge.BaseContext now provides the headless half; both call sites
overlay only their UI-specific closures. As a side effect ACP mode gains
previously-missing APIs (state, tree navigation, skills, template
parsing, model resolution) that were nil before. The interactive TUI
keeps its exact SetModel/ReloadExtensions ordering via overrides.
* internal/tools: extract withOAuthRetry and marshalToolResult helpers
ExecuteTool repeated the OAuth-error/re-auth/retry stanza verbatim twice
(sync and task-augmented paths) and the marshal-and-wrap stanza four
times. Both are now single helpers with identical error strings, so a
fix to OAuth retry or error categorization applies everywhere at once.
* internal/ui: extract buildShareFile with defer-based cleanup
handleShareCommand repeated the close/remove/print/return cleanup chain
four times across its temp-file write error paths. File assembly now
lives in buildShareFile with a single deferred cleanup on error.
* cmd: extract flag validation, preference restore, and provider-URL routing from runNormalMode
runNormalMode opened with ~150 lines of policy logic (flag-combination
validation, persisted model/thinking-level preference restoration, and
two subtle --provider-url model-rewrite rules). These are now standalone
functions (validateModeFlags, restorePersistedPreferences,
applyProviderURLRouting) so the routing policy is independently readable
and testable. Behaviour unchanged; ordering preserved.
* fix: address review findings on SDK godoc and nil guard
- pkg/kit: remove internal package paths from exported godoc on
ParseTemplate and the ToolKind* constants (SDK doc surface must not
reference internal packages)
- internal/tools: guard marshalToolResult against a nil CallToolResult
(json.Marshal(nil) succeeds as 'null', then result.IsError panics if
a client returns nil result with nil error)
Skipped the TreeNode Children deep-copy suggestion: the slice already
comes from TreeManager.GetChildren which returns a fresh copy per call
into a throwaway intermediate, so no internal state is exposed.
128 lines
4.5 KiB
Go
128 lines
4.5 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mark3labs/kit/internal/auth"
|
|
"github.com/mark3labs/kit/internal/models"
|
|
)
|
|
|
|
// AgentInterface defines the minimal interface required from the agent package
|
|
// to avoid circular dependencies while still accessing necessary agent functionality.
|
|
type AgentInterface interface {
|
|
GetLoadingMessage() string
|
|
GetTools() []any // Using any to avoid importing tool types
|
|
GetLoadedServerNames() []string // Add this method for debug config
|
|
GetMCPToolCount() int // Tools loaded from external MCP servers
|
|
GetExtensionToolCount() int // Tools registered by extensions
|
|
}
|
|
|
|
// CLISetupOptions encapsulates all configuration parameters needed to initialize
|
|
// and set up a CLI instance, including display preferences, model information,
|
|
// and debugging settings.
|
|
type CLISetupOptions struct {
|
|
Agent AgentInterface
|
|
ModelString string
|
|
Debug bool
|
|
Quiet bool
|
|
ShowDebug bool // Whether to show debug config
|
|
ProviderAPIKey string // For OAuth detection
|
|
}
|
|
|
|
// parseModelName extracts provider and model name from model string
|
|
func parseModelName(modelString string) (provider, model string) {
|
|
p, m, err := models.ParseModelString(modelString)
|
|
if err != nil {
|
|
return "unknown", "unknown"
|
|
}
|
|
return p, m
|
|
}
|
|
|
|
// CreateUsageTracker creates a UsageTracker for the given model string and
|
|
// provider API key. It returns nil when usage tracking is unavailable (e.g.
|
|
// ollama or unrecognised models). This is used by the interactive TUI path
|
|
// which doesn't go through SetupCLI.
|
|
func CreateUsageTracker(modelString, providerAPIKey string) *UsageTracker {
|
|
modelInfo, provider := lookupTrackableModel(modelString)
|
|
if modelInfo == nil {
|
|
return nil
|
|
}
|
|
isOAuth := provider == "anthropic" && auth.IsAnthropicOAuth(providerAPIKey)
|
|
return NewUsageTracker(modelInfo, provider, 80, isOAuth)
|
|
}
|
|
|
|
// UpdateUsageTrackerForModel refreshes an existing tracker after a model
|
|
// switch so token counting and cost reporting use the new model's metadata.
|
|
// No-op for a nil tracker or untrackable models (unknown/ollama).
|
|
func UpdateUsageTrackerForModel(t *UsageTracker, modelString, providerAPIKey string) {
|
|
if t == nil {
|
|
return
|
|
}
|
|
modelInfo, provider := lookupTrackableModel(modelString)
|
|
if modelInfo == nil {
|
|
return
|
|
}
|
|
isOAuth := provider == "anthropic" && auth.IsAnthropicOAuth(providerAPIKey)
|
|
t.UpdateModelInfo(modelInfo, provider, isOAuth)
|
|
}
|
|
|
|
// lookupTrackableModel resolves a model string to registry metadata, returning
|
|
// nil for models without usage tracking support (unknown or ollama models).
|
|
func lookupTrackableModel(modelString string) (*models.ModelInfo, string) {
|
|
provider, model := parseModelName(modelString)
|
|
if provider == "unknown" || model == "unknown" || provider == "ollama" {
|
|
return nil, provider
|
|
}
|
|
return models.GetGlobalRegistry().LookupModel(provider, model), provider
|
|
}
|
|
|
|
// SetupCLI creates, configures, and initializes a CLI instance with the provided
|
|
// options. It sets up model display, usage tracking for supported providers, and
|
|
// shows initial loading information. Returns nil in quiet mode or an initialized
|
|
// CLI instance ready for user interaction.
|
|
func SetupCLI(opts *CLISetupOptions) (*CLI, error) {
|
|
if opts.Quiet {
|
|
return nil, nil // No CLI in quiet mode
|
|
}
|
|
|
|
cli, err := NewCLI(opts.Debug)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create CLI: %v", err)
|
|
}
|
|
|
|
// Parse model string for display and usage tracking
|
|
provider, model := parseModelName(opts.ModelString)
|
|
|
|
// Set the model name for consistent display
|
|
if model != "unknown" {
|
|
cli.SetModelName(model)
|
|
}
|
|
|
|
// Set up usage tracking for supported providers
|
|
if usageTracker := CreateUsageTracker(opts.ModelString, opts.ProviderAPIKey); usageTracker != nil {
|
|
cli.SetUsageTracker(usageTracker)
|
|
}
|
|
|
|
// Display model info (the system message block provides its own spacing).
|
|
if provider != "unknown" && model != "unknown" {
|
|
cli.DisplayInfo(fmt.Sprintf("Model loaded: %s (%s)", provider, model))
|
|
}
|
|
|
|
// Display loading message if available (e.g., GPU fallback info)
|
|
if loadingMessage := opts.Agent.GetLoadingMessage(); loadingMessage != "" {
|
|
cli.DisplayInfo(loadingMessage)
|
|
}
|
|
|
|
// Display extension tool count (only when > 0).
|
|
if extCount := opts.Agent.GetExtensionToolCount(); extCount > 0 {
|
|
cli.DisplayInfo(fmt.Sprintf("Loaded %d extension tools", extCount))
|
|
}
|
|
|
|
// Display MCP tool count (only when > 0).
|
|
if mcpCount := opts.Agent.GetMCPToolCount(); mcpCount > 0 {
|
|
cli.DisplayInfo(fmt.Sprintf("Loaded %d tools from MCP servers", mcpCount))
|
|
}
|
|
|
|
return cli, nil
|
|
}
|