mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
combine startup info into single system message block
Merge Context, Skills, and tool counts into one KIT System block instead of separate styled sections. Add separate MCP and extension tool counts to Agent, only displaying each when > 0.
This commit is contained in:
+28
-18
@@ -85,6 +85,14 @@ func (a *agentUIAdapter) GetLoadedServerNames() []string {
|
||||
return a.agent.GetLoadedServerNames()
|
||||
}
|
||||
|
||||
func (a *agentUIAdapter) GetMCPToolCount() int {
|
||||
return a.agent.GetMCPToolCount()
|
||||
}
|
||||
|
||||
func (a *agentUIAdapter) GetExtensionToolCount() int {
|
||||
return a.agent.GetExtensionToolCount()
|
||||
}
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands.
|
||||
// This is the main entry point for the KIT CLI application, providing
|
||||
// an interface to interact with various AI models through a unified interface
|
||||
@@ -365,7 +373,7 @@ func runNormalMode(ctx context.Context) error {
|
||||
|
||||
// Extract agent + metadata for display and app options.
|
||||
mcpAgent := kitInstance.GetAgent()
|
||||
parsedProvider, modelName, serverNames, toolNames := CollectAgentMetadata(mcpAgent, mcpConfig)
|
||||
parsedProvider, modelName, serverNames, toolNames, mcpToolCount, extensionToolCount := CollectAgentMetadata(mcpAgent, mcpConfig)
|
||||
|
||||
// Create CLI for non-interactive mode only.
|
||||
var cli *ui.CLI
|
||||
@@ -456,7 +464,7 @@ func runNormalMode(ctx context.Context) error {
|
||||
|
||||
// Check if running in non-interactive mode
|
||||
if promptFlag != "" {
|
||||
return runNonInteractiveModeApp(ctx, appInstance, cli, promptFlag, quietFlag, noExitFlag, modelName, parsedProvider, mcpAgent.GetLoadingMessage(), serverNames, toolNames, usageTracker, extCommands, contextPaths, skillItems)
|
||||
return runNonInteractiveModeApp(ctx, appInstance, cli, promptFlag, quietFlag, noExitFlag, modelName, parsedProvider, mcpAgent.GetLoadingMessage(), serverNames, toolNames, mcpToolCount, extensionToolCount, usageTracker, extCommands, contextPaths, skillItems)
|
||||
}
|
||||
|
||||
// Quiet mode is not allowed in interactive mode
|
||||
@@ -464,7 +472,7 @@ func runNormalMode(ctx context.Context) error {
|
||||
return fmt.Errorf("--quiet flag can only be used with --prompt/-p")
|
||||
}
|
||||
|
||||
return runInteractiveModeBubbleTea(ctx, appInstance, modelName, parsedProvider, mcpAgent.GetLoadingMessage(), serverNames, toolNames, usageTracker, extCommands, contextPaths, skillItems)
|
||||
return runInteractiveModeBubbleTea(ctx, appInstance, modelName, parsedProvider, mcpAgent.GetLoadingMessage(), serverNames, toolNames, mcpToolCount, extensionToolCount, usageTracker, extCommands, contextPaths, skillItems)
|
||||
}
|
||||
|
||||
// runNonInteractiveModeApp executes a single prompt via the app layer and exits,
|
||||
@@ -477,7 +485,7 @@ func runNormalMode(ctx context.Context) error {
|
||||
//
|
||||
// When --no-exit is set, after the prompt completes the interactive BubbleTea
|
||||
// TUI is started so the user can continue the conversation.
|
||||
func runNonInteractiveModeApp(ctx context.Context, appInstance *app.App, cli *ui.CLI, prompt string, quiet, noExit bool, modelName, providerName, loadingMessage string, serverNames, toolNames []string, usageTracker *ui.UsageTracker, extCommands []ui.ExtensionCommand, contextPaths []string, skillItems []ui.SkillItem) error {
|
||||
func runNonInteractiveModeApp(ctx context.Context, appInstance *app.App, cli *ui.CLI, prompt string, quiet, noExit bool, modelName, providerName, loadingMessage string, serverNames, toolNames []string, mcpToolCount, extensionToolCount int, usageTracker *ui.UsageTracker, extCommands []ui.ExtensionCommand, contextPaths []string, skillItems []ui.SkillItem) error {
|
||||
if quiet {
|
||||
// Quiet mode: no intermediate display, just print final response.
|
||||
if err := appInstance.RunOnce(ctx, prompt); err != nil {
|
||||
@@ -503,7 +511,7 @@ func runNonInteractiveModeApp(ctx context.Context, appInstance *app.App, cli *ui
|
||||
|
||||
// If --no-exit was requested, hand off to the interactive TUI.
|
||||
if noExit {
|
||||
return runInteractiveModeBubbleTea(ctx, appInstance, modelName, providerName, loadingMessage, serverNames, toolNames, usageTracker, extCommands, contextPaths, skillItems)
|
||||
return runInteractiveModeBubbleTea(ctx, appInstance, modelName, providerName, loadingMessage, serverNames, toolNames, mcpToolCount, extensionToolCount, usageTracker, extCommands, contextPaths, skillItems)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -520,7 +528,7 @@ func runNonInteractiveModeApp(ctx context.Context, appInstance *app.App, cli *ui
|
||||
// 4. Calls program.Run() which blocks until the user quits (Ctrl+C or /quit).
|
||||
//
|
||||
// SetupCLI is not used for interactive mode; the TUI (AppModel) handles its own rendering.
|
||||
func runInteractiveModeBubbleTea(_ context.Context, appInstance *app.App, modelName, providerName, loadingMessage string, serverNames, toolNames []string, usageTracker *ui.UsageTracker, extCommands []ui.ExtensionCommand, contextPaths []string, skillItems []ui.SkillItem) error {
|
||||
func runInteractiveModeBubbleTea(_ context.Context, appInstance *app.App, modelName, providerName, loadingMessage string, serverNames, toolNames []string, mcpToolCount, extensionToolCount int, usageTracker *ui.UsageTracker, extCommands []ui.ExtensionCommand, contextPaths []string, skillItems []ui.SkillItem) error {
|
||||
// Determine terminal size; fall back gracefully.
|
||||
termWidth, termHeight, err := term.GetSize(int(os.Stdout.Fd()))
|
||||
if err != nil || termWidth == 0 {
|
||||
@@ -529,18 +537,20 @@ func runInteractiveModeBubbleTea(_ context.Context, appInstance *app.App, modelN
|
||||
}
|
||||
|
||||
appModel := ui.NewAppModel(appInstance, ui.AppModelOptions{
|
||||
CompactMode: viper.GetBool("compact"),
|
||||
ModelName: modelName,
|
||||
ProviderName: providerName,
|
||||
LoadingMessage: loadingMessage,
|
||||
Width: termWidth,
|
||||
Height: termHeight,
|
||||
ServerNames: serverNames,
|
||||
ToolNames: toolNames,
|
||||
UsageTracker: usageTracker,
|
||||
ExtensionCommands: extCommands,
|
||||
ContextPaths: contextPaths,
|
||||
SkillItems: skillItems,
|
||||
CompactMode: viper.GetBool("compact"),
|
||||
ModelName: modelName,
|
||||
ProviderName: providerName,
|
||||
LoadingMessage: loadingMessage,
|
||||
Width: termWidth,
|
||||
Height: termHeight,
|
||||
ServerNames: serverNames,
|
||||
ToolNames: toolNames,
|
||||
MCPToolCount: mcpToolCount,
|
||||
ExtensionToolCount: extensionToolCount,
|
||||
UsageTracker: usageTracker,
|
||||
ExtensionCommands: extCommands,
|
||||
ContextPaths: contextPaths,
|
||||
SkillItems: skillItems,
|
||||
})
|
||||
|
||||
// Print startup info to stdout before Bubble Tea takes over the screen.
|
||||
|
||||
+6
-2
@@ -13,7 +13,8 @@ import (
|
||||
|
||||
// CollectAgentMetadata extracts model display info and tool/server name lists
|
||||
// from the agent, used to populate app.Options and UI setup.
|
||||
func CollectAgentMetadata(mcpAgent *agent.Agent, mcpConfig *config.Config) (provider, modelName string, serverNames, toolNames []string) {
|
||||
// It also returns the number of MCP tools and extension tools separately.
|
||||
func CollectAgentMetadata(mcpAgent *agent.Agent, mcpConfig *config.Config) (provider, modelName string, serverNames, toolNames []string, mcpToolCount, extensionToolCount int) {
|
||||
modelString := viper.GetString("model")
|
||||
provider, modelName, _ = kit.ParseModelString(modelString)
|
||||
if modelName == "" {
|
||||
@@ -29,7 +30,10 @@ func CollectAgentMetadata(mcpAgent *agent.Agent, mcpConfig *config.Config) (prov
|
||||
toolNames = append(toolNames, info.Name)
|
||||
}
|
||||
|
||||
return provider, modelName, serverNames, toolNames
|
||||
mcpToolCount = mcpAgent.GetMCPToolCount()
|
||||
extensionToolCount = mcpAgent.GetExtensionToolCount()
|
||||
|
||||
return provider, modelName, serverNames, toolNames, mcpToolCount, extensionToolCount
|
||||
}
|
||||
|
||||
// BuildAppOptions constructs the app.Options struct from the current state.
|
||||
|
||||
@@ -455,6 +455,19 @@ func (a *Agent) GetTools() []fantasy.AgentTool {
|
||||
return allTools
|
||||
}
|
||||
|
||||
// GetMCPToolCount returns the number of tools loaded from external MCP servers.
|
||||
func (a *Agent) GetMCPToolCount() int {
|
||||
if a.toolManager == nil {
|
||||
return 0
|
||||
}
|
||||
return len(a.toolManager.GetTools())
|
||||
}
|
||||
|
||||
// GetExtensionToolCount returns the number of tools registered by extensions.
|
||||
func (a *Agent) GetExtensionToolCount() int {
|
||||
return len(a.extraTools)
|
||||
}
|
||||
|
||||
// GetLoadingMessage returns the loading message from provider creation.
|
||||
func (a *Agent) GetLoadingMessage() string {
|
||||
return a.loadingMessage
|
||||
|
||||
+11
-3
@@ -14,6 +14,8 @@ 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
|
||||
@@ -120,9 +122,15 @@ func SetupCLI(opts *CLISetupOptions) (*CLI, error) {
|
||||
cli.DisplayInfo(loadingMessage)
|
||||
}
|
||||
|
||||
// Display tool count
|
||||
tools := opts.Agent.GetTools()
|
||||
cli.DisplayInfo(fmt.Sprintf("Loaded %d tools from MCP servers", len(tools)))
|
||||
// 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
|
||||
}
|
||||
|
||||
+42
-35
@@ -111,6 +111,12 @@ type AppModelOptions struct {
|
||||
|
||||
// SkillItems lists loaded skills for the [Skills] startup section.
|
||||
SkillItems []SkillItem
|
||||
|
||||
// MCPToolCount is the number of tools loaded from external MCP servers.
|
||||
MCPToolCount int
|
||||
|
||||
// ExtensionToolCount is the number of tools registered by extensions.
|
||||
ExtensionToolCount int
|
||||
}
|
||||
|
||||
// AppModel is the root Bubble Tea model for the interactive TUI. It owns the
|
||||
@@ -192,6 +198,11 @@ type AppModel struct {
|
||||
contextPaths []string
|
||||
skillItems []SkillItem
|
||||
|
||||
// mcpToolCount and extensionToolCount track tool counts by source for
|
||||
// the startup info display.
|
||||
mcpToolCount int
|
||||
extensionToolCount int
|
||||
|
||||
// width and height track the terminal dimensions.
|
||||
width int
|
||||
height int
|
||||
@@ -261,9 +272,11 @@ func NewAppModel(appCtrl AppController, opts AppModelOptions) *AppModel {
|
||||
// Store extension commands for dispatch.
|
||||
m.extensionCommands = opts.ExtensionCommands
|
||||
|
||||
// Store context/skills metadata for startup display.
|
||||
// Store context/skills metadata and tool counts for startup display.
|
||||
m.contextPaths = opts.ContextPaths
|
||||
m.skillItems = opts.SkillItems
|
||||
m.mcpToolCount = opts.MCPToolCount
|
||||
m.extensionToolCount = opts.ExtensionToolCount
|
||||
|
||||
// Wire up child components now that we have the concrete implementations.
|
||||
m.input = NewInputComponent(width, "Enter your prompt (Type /help for commands, Ctrl+C to quit)", appCtrl)
|
||||
@@ -306,24 +319,12 @@ func (m *AppModel) Init() tea.Cmd {
|
||||
return tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// PrintStartupInfo writes startup messages (model loaded, context, skills,
|
||||
// tool count) to stdout. Call this before program.Run() so the messages are
|
||||
// PrintStartupInfo prints the startup banner (model name, context, skills,
|
||||
// tool counts) to stdout. Call this before program.Run() so the messages are
|
||||
// visible above the Bubble Tea managed region.
|
||||
//
|
||||
// The output matches the Pi SDK's startup display:
|
||||
//
|
||||
// [Context]
|
||||
// ~/Workspace/project/AGENTS.md
|
||||
//
|
||||
// [Skills]
|
||||
// project
|
||||
// ~/Workspace/project/.agents/skills/foo/SKILL.md
|
||||
// All startup information is rendered inside a single system message block.
|
||||
func (m *AppModel) PrintStartupInfo() {
|
||||
theme := GetTheme()
|
||||
headerStyle := lipgloss.NewStyle().Foreground(theme.Warning)
|
||||
dimStyle := lipgloss.NewStyle().Foreground(theme.Muted)
|
||||
boldStyle := lipgloss.NewStyle().Foreground(theme.Text).Bold(true)
|
||||
|
||||
render := func(text string) string {
|
||||
if m.compactMode {
|
||||
return m.compactRdr.RenderSystemMessage(text, time.Now()).Content
|
||||
@@ -333,40 +334,46 @@ func (m *AppModel) PrintStartupInfo() {
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// Build the combined startup content.
|
||||
var lines []string
|
||||
|
||||
if m.providerName != "" && m.modelName != "" {
|
||||
fmt.Println(render(fmt.Sprintf("Model loaded: %s (%s)", m.providerName, m.modelName)))
|
||||
lines = append(lines, fmt.Sprintf("Model loaded: %s (%s)", m.providerName, m.modelName))
|
||||
}
|
||||
|
||||
if m.loadingMessage != "" {
|
||||
fmt.Println(render(m.loadingMessage))
|
||||
lines = append(lines, m.loadingMessage)
|
||||
}
|
||||
|
||||
// [Context] section — loaded AGENTS.md files.
|
||||
// Context — loaded AGENTS.md files.
|
||||
if len(m.contextPaths) > 0 {
|
||||
fmt.Println()
|
||||
fmt.Println(headerStyle.Render("[Context]"))
|
||||
for _, p := range m.contextPaths {
|
||||
fmt.Println(dimStyle.Render(" " + tildeHome(p)))
|
||||
lines = append(lines, fmt.Sprintf("Context: %s", tildeHome(p)))
|
||||
}
|
||||
}
|
||||
|
||||
// [Skills] section — loaded skills grouped by source.
|
||||
// Skills — listed by name.
|
||||
if len(m.skillItems) > 0 {
|
||||
fmt.Println()
|
||||
fmt.Println(headerStyle.Render("[Skills]"))
|
||||
// Group by source and display.
|
||||
var currentSource string
|
||||
for _, si := range m.skillItems {
|
||||
if si.Source != currentSource {
|
||||
currentSource = si.Source
|
||||
fmt.Println(boldStyle.Render(" " + currentSource))
|
||||
}
|
||||
fmt.Println(dimStyle.Render(" " + tildeHome(si.Path)))
|
||||
names := make([]string, len(m.skillItems))
|
||||
for i, si := range m.skillItems {
|
||||
names[i] = si.Name
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("Skills: %s", strings.Join(names, ", ")))
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println(render(fmt.Sprintf("Loaded %d tools from MCP servers", len(m.toolNames))))
|
||||
// Extension tool count (only shown when > 0).
|
||||
if m.extensionToolCount > 0 {
|
||||
lines = append(lines, fmt.Sprintf("Loaded %d extension tools", m.extensionToolCount))
|
||||
}
|
||||
|
||||
// MCP tool count (only shown when > 0).
|
||||
if m.mcpToolCount > 0 {
|
||||
lines = append(lines, fmt.Sprintf("Loaded %d tools from MCP servers", m.mcpToolCount))
|
||||
}
|
||||
|
||||
if len(lines) > 0 {
|
||||
fmt.Println(render(strings.Join(lines, "\n\n")))
|
||||
}
|
||||
}
|
||||
|
||||
// tildeHome replaces the user's home directory prefix with ~ for display.
|
||||
|
||||
Reference in New Issue
Block a user