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.
235 lines
8.8 KiB
Go
235 lines
8.8 KiB
Go
package extbridge
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/mark3labs/kit/internal/extensions"
|
|
kit "github.com/mark3labs/kit/pkg/kit"
|
|
)
|
|
|
|
// BaseContext returns an extensions.Context populated with the headless,
|
|
// TUI-independent delegation fields: data access, state, options,
|
|
// model/tool management, completions, subagents, tree navigation, skills,
|
|
// template parsing, and model resolution.
|
|
//
|
|
// Callers overlay their UI-specific fields (print routes, widgets, prompts,
|
|
// editor, TUI-aware SetModel/ReloadExtensions, etc.) on the returned value:
|
|
// cmd/extension_context.go for the interactive TUI and
|
|
// internal/acpserver/session.go for headless ACP mode. Keeping the shared
|
|
// half here means a new data-access Context field only has to be wired once.
|
|
//
|
|
// ctx is used for subagent spawns; pass a long-lived context (not a
|
|
// per-request one) so later spawns aren't cancelled prematurely.
|
|
func BaseContext(ctx context.Context, kitInstance *kit.Kit) extensions.Context {
|
|
return extensions.Context{
|
|
// -------------------------------------------------------------------
|
|
// Data access
|
|
// -------------------------------------------------------------------
|
|
GetContextStats: func() extensions.ContextStats {
|
|
s := kitInstance.GetContextStats()
|
|
return extensions.ContextStats{
|
|
EstimatedTokens: s.EstimatedTokens,
|
|
ContextLimit: s.ContextLimit,
|
|
UsagePercent: s.UsagePercent,
|
|
MessageCount: s.MessageCount,
|
|
}
|
|
},
|
|
GetMessages: func() []extensions.SessionMessage {
|
|
return kitInstance.Extensions().GetSessionMessages()
|
|
},
|
|
GetSessionPath: func() string {
|
|
return kitInstance.GetSessionPath()
|
|
},
|
|
AppendEntry: func(entryType string, data string) (string, error) {
|
|
return kitInstance.Extensions().AppendEntry(entryType, data)
|
|
},
|
|
GetEntries: func(entryType string) []extensions.ExtensionEntry {
|
|
return kitInstance.Extensions().GetEntries(entryType)
|
|
},
|
|
|
|
// -------------------------------------------------------------------
|
|
// Extension state
|
|
// -------------------------------------------------------------------
|
|
SetState: func(key string, value string) {
|
|
kitInstance.Extensions().SetState(key, value)
|
|
},
|
|
GetState: func(key string) (string, bool) {
|
|
return kitInstance.Extensions().GetState(key)
|
|
},
|
|
DeleteState: func(key string) {
|
|
kitInstance.Extensions().DeleteState(key)
|
|
},
|
|
ListState: func() []string {
|
|
return kitInstance.Extensions().ListState()
|
|
},
|
|
|
|
// -------------------------------------------------------------------
|
|
// Options, model, and tool management
|
|
// -------------------------------------------------------------------
|
|
GetOption: func(name string) string {
|
|
return kitInstance.Extensions().GetOption(name)
|
|
},
|
|
SetOption: func(name string, value string) {
|
|
kitInstance.Extensions().SetOption(name, value)
|
|
},
|
|
// Headless model switch. The interactive TUI overrides this with a
|
|
// version that also notifies the TUI and refreshes the usage tracker.
|
|
SetModel: func(modelString string) error {
|
|
previousModel := kitInstance.Extensions().GetContext().Model
|
|
if err := kitInstance.SetModel(context.Background(), modelString); err != nil {
|
|
return err
|
|
}
|
|
kitInstance.Extensions().UpdateContextModel(modelString)
|
|
kitInstance.Extensions().EmitModelChange(modelString, previousModel, "extension")
|
|
return nil
|
|
},
|
|
GetAvailableModels: func() []extensions.ModelInfoEntry {
|
|
return kitInstance.GetAvailableModels()
|
|
},
|
|
EmitCustomEvent: func(name string, data string) {
|
|
kitInstance.Extensions().EmitCustomEvent(name, data)
|
|
},
|
|
GetAllTools: func() []extensions.ToolInfo {
|
|
return kitInstance.Extensions().GetToolInfos()
|
|
},
|
|
SetActiveTools: func(names []string) {
|
|
kitInstance.Extensions().SetActiveTools(names)
|
|
},
|
|
// Headless reload. The interactive TUI overrides this to also
|
|
// refresh widgets/status/commands.
|
|
ReloadExtensions: func() error {
|
|
return kitInstance.Extensions().Reload()
|
|
},
|
|
|
|
// -------------------------------------------------------------------
|
|
// LLM completions and subagents
|
|
// -------------------------------------------------------------------
|
|
Complete: func(req extensions.CompleteRequest) (extensions.CompleteResponse, error) {
|
|
return kitInstance.ExecuteCompletion(context.Background(), req)
|
|
},
|
|
SpawnSubagent: func(config extensions.SubagentConfig) (*extensions.SubagentHandle, *extensions.SubagentResult, error) {
|
|
return SpawnSubagent(ctx, kitInstance, config)
|
|
},
|
|
|
|
// -------------------------------------------------------------------
|
|
// Tree Navigation API
|
|
// -------------------------------------------------------------------
|
|
GetTreeNode: func(entryID string) *extensions.TreeNode {
|
|
node := kitInstance.GetTreeNode(entryID)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
return &extensions.TreeNode{
|
|
ID: node.ID,
|
|
ParentID: node.ParentID,
|
|
Type: node.Type,
|
|
Role: node.Role,
|
|
Content: node.Content,
|
|
Model: node.Model,
|
|
Provider: node.Provider,
|
|
Timestamp: node.Timestamp,
|
|
Children: node.Children,
|
|
}
|
|
},
|
|
GetCurrentBranch: func() []extensions.TreeNode {
|
|
nodes := kitInstance.GetCurrentBranch()
|
|
result := make([]extensions.TreeNode, len(nodes))
|
|
for i, n := range nodes {
|
|
result[i] = extensions.TreeNode{
|
|
ID: n.ID,
|
|
ParentID: n.ParentID,
|
|
Type: n.Type,
|
|
Role: n.Role,
|
|
Content: n.Content,
|
|
Model: n.Model,
|
|
Provider: n.Provider,
|
|
Timestamp: n.Timestamp,
|
|
Children: n.Children,
|
|
}
|
|
}
|
|
return result
|
|
},
|
|
GetChildren: func(parentID string) []string {
|
|
return kitInstance.GetChildren(parentID)
|
|
},
|
|
NavigateTo: func(entryID string) extensions.TreeNavigationResult {
|
|
err := kitInstance.NavigateTo(entryID)
|
|
if err != nil {
|
|
return extensions.TreeNavigationResult{Success: false, Error: err.Error()}
|
|
}
|
|
return extensions.TreeNavigationResult{Success: true}
|
|
},
|
|
SummarizeBranch: func(fromID, toID string) string {
|
|
summary, _ := kitInstance.SummarizeBranch(fromID, toID)
|
|
return summary
|
|
},
|
|
CollapseBranch: func(fromID, toID, summary string) extensions.TreeNavigationResult {
|
|
err := kitInstance.CollapseBranch(fromID, toID, summary)
|
|
if err != nil {
|
|
return extensions.TreeNavigationResult{Success: false, Error: err.Error()}
|
|
}
|
|
return extensions.TreeNavigationResult{Success: true}
|
|
},
|
|
|
|
// -------------------------------------------------------------------
|
|
// Skill Loading API (context-injection variants are TUI-specific and
|
|
// wired by the interactive overlay)
|
|
// -------------------------------------------------------------------
|
|
LoadSkill: func(path string) (*extensions.Skill, string) {
|
|
s, err := kitInstance.LoadSkillForExtension(path)
|
|
return s, err
|
|
},
|
|
LoadSkillsFromDir: func(dir string) extensions.SkillLoadResult {
|
|
return kitInstance.LoadSkillsFromDirForExtension(dir)
|
|
},
|
|
DiscoverSkills: func() extensions.SkillLoadResult {
|
|
skills := kitInstance.DiscoverSkillsForExtension()
|
|
return extensions.SkillLoadResult{Skills: skills}
|
|
},
|
|
GetAvailableSkills: func() []extensions.Skill {
|
|
return kitInstance.DiscoverSkillsForExtension()
|
|
},
|
|
|
|
// -------------------------------------------------------------------
|
|
// Template Parsing API
|
|
// -------------------------------------------------------------------
|
|
ParseTemplate: func(name, content string) extensions.PromptTemplate {
|
|
return kit.ParseTemplate(name, content)
|
|
},
|
|
RenderTemplate: func(tpl extensions.PromptTemplate, vars map[string]string) string {
|
|
return kit.RenderTemplate(tpl, vars)
|
|
},
|
|
ParseArguments: func(input string, pattern extensions.ArgumentPattern) extensions.ParseResult {
|
|
return kit.ParseArguments(input, pattern)
|
|
},
|
|
SimpleParseArguments: func(input string, count int) []string {
|
|
return kit.SimpleParseArguments(input, count)
|
|
},
|
|
EvaluateModelConditional: func(condition string) bool {
|
|
return kit.EvaluateModelConditional(kitInstance.Extensions().GetContext().Model, condition)
|
|
},
|
|
RenderWithModelConditionals: func(content string) string {
|
|
return kit.RenderWithModelConditionals(content, kitInstance.Extensions().GetContext().Model)
|
|
},
|
|
|
|
// -------------------------------------------------------------------
|
|
// Model Resolution API
|
|
// -------------------------------------------------------------------
|
|
ResolveModelChain: func(preferences []string) extensions.ModelResolutionResult {
|
|
return kit.ResolveModelChain(preferences)
|
|
},
|
|
GetModelCapabilities: func(model string) (extensions.ModelCapabilities, string) {
|
|
return kit.GetModelCapabilities(model)
|
|
},
|
|
CheckModelAvailable: func(model string) bool {
|
|
return kit.CheckModelAvailable(model)
|
|
},
|
|
GetCurrentProvider: func() string {
|
|
return kit.GetCurrentProvider(kitInstance.Extensions().GetContext().Model)
|
|
},
|
|
GetCurrentModelID: func() string {
|
|
return kit.GetCurrentModelID(kitInstance.Extensions().GetContext().Model)
|
|
},
|
|
}
|
|
}
|