mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
formatting
This commit is contained in:
+29
-31
@@ -139,7 +139,7 @@ func runNormalMode(ctx context.Context) error {
|
||||
// Load configuration
|
||||
var mcpConfig *config.Config
|
||||
var err error
|
||||
|
||||
|
||||
if scriptMCPConfig != nil {
|
||||
// Use script-provided config
|
||||
mcpConfig = scriptMCPConfig
|
||||
@@ -232,7 +232,7 @@ func runNormalMode(ctx context.Context) error {
|
||||
if agentMaxSteps == 0 {
|
||||
agentMaxSteps = 1000 // Set a high limit for "unlimited"
|
||||
}
|
||||
|
||||
|
||||
agentConfig := &agent.AgentConfig{
|
||||
ModelConfig: modelConfig,
|
||||
MCPConfig: mcpConfig,
|
||||
@@ -288,17 +288,17 @@ func runNormalMode(ctx context.Context) error {
|
||||
|
||||
// Main interaction logic
|
||||
var messages []*schema.Message
|
||||
|
||||
|
||||
// Check if running in non-interactive mode
|
||||
if promptFlag != "" {
|
||||
return runNonInteractiveMode(ctx, mcpAgent, cli, promptFlag, modelName, messages, quietFlag)
|
||||
}
|
||||
|
||||
|
||||
// Quiet mode is not allowed in interactive mode
|
||||
if quietFlag {
|
||||
return fmt.Errorf("--quiet flag can only be used with --prompt/-p")
|
||||
}
|
||||
|
||||
|
||||
return runInteractiveMode(ctx, mcpAgent, cli, serverNames, toolNames, modelName, messages)
|
||||
}
|
||||
|
||||
@@ -315,13 +315,13 @@ func runNonInteractiveMode(ctx context.Context, mcpAgent *agent.Agent, cli *ui.C
|
||||
// Get agent response with controlled spinner that stops for tool call display
|
||||
var response *schema.Message
|
||||
var currentSpinner *ui.Spinner
|
||||
|
||||
|
||||
// Start initial spinner (skip if quiet)
|
||||
if !quiet && cli != nil {
|
||||
currentSpinner = ui.NewSpinner("Thinking...")
|
||||
currentSpinner.Start()
|
||||
}
|
||||
|
||||
|
||||
response, err := mcpAgent.GenerateWithLoop(ctx, messages,
|
||||
// Tool call handler - called when a tool is about to be executed
|
||||
func(toolName, toolArgs string) {
|
||||
@@ -385,7 +385,7 @@ func runNonInteractiveMode(ctx context.Context, mcpAgent *agent.Agent, cli *ui.C
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
// Make sure spinner is stopped if still running
|
||||
if !quiet && cli != nil && currentSpinner != nil {
|
||||
currentSpinner.Stop()
|
||||
@@ -454,11 +454,11 @@ func runInteractiveMode(ctx context.Context, mcpAgent *agent.Agent, cli *ui.CLI,
|
||||
// Get agent response with controlled spinner that stops for tool call display
|
||||
var response *schema.Message
|
||||
var currentSpinner *ui.Spinner
|
||||
|
||||
|
||||
// Start initial spinner
|
||||
currentSpinner = ui.NewSpinner("Thinking...")
|
||||
currentSpinner.Start()
|
||||
|
||||
|
||||
response, err = mcpAgent.GenerateWithLoop(ctx, messages,
|
||||
// Tool call handler - called when a tool is about to be executed
|
||||
func(toolName, toolArgs string) {
|
||||
@@ -511,7 +511,7 @@ func runInteractiveMode(ctx context.Context, mcpAgent *agent.Agent, cli *ui.CLI,
|
||||
currentSpinner.Start()
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
// Make sure spinner is stopped if still running
|
||||
if currentSpinner != nil {
|
||||
currentSpinner.Stop()
|
||||
@@ -531,17 +531,15 @@ func runInteractiveMode(ctx context.Context, mcpAgent *agent.Agent, cli *ui.CLI,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// runScriptMode handles script mode execution
|
||||
func runScriptMode(ctx context.Context) error {
|
||||
var scriptFile string
|
||||
|
||||
|
||||
// Determine script file from arguments
|
||||
// When called via shebang, the script file is the first non-flag argument
|
||||
// When called with --script flag, we need to find the script file in args
|
||||
args := os.Args[1:]
|
||||
|
||||
|
||||
// Filter out flags to find the script file
|
||||
for _, arg := range args {
|
||||
if arg == "--script" {
|
||||
@@ -556,17 +554,17 @@ func runScriptMode(ctx context.Context) error {
|
||||
scriptFile = arg
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
if scriptFile == "" {
|
||||
return fmt.Errorf("script mode requires a script file argument")
|
||||
}
|
||||
|
||||
|
||||
// Parse the script file
|
||||
scriptConfig, err := parseScriptFile(scriptFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse script file: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Override the global configFile and promptFlag with script values
|
||||
originalConfigFile := configFile
|
||||
originalPromptFlag := promptFlag
|
||||
@@ -580,7 +578,7 @@ func runScriptMode(ctx context.Context) error {
|
||||
originalGoogleAPIKey := googleAPIKey
|
||||
originalOpenAIURL := openaiBaseURL
|
||||
originalAnthropicURL := anthropicBaseURL
|
||||
|
||||
|
||||
// Create config from script or load normal config
|
||||
var mcpConfig *config.Config
|
||||
if len(scriptConfig.MCPServers) > 0 {
|
||||
@@ -627,10 +625,10 @@ func runScriptMode(ctx context.Context) error {
|
||||
mcpConfig.Prompt = scriptConfig.Prompt
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Override the global config for normal mode
|
||||
scriptMCPConfig = mcpConfig
|
||||
|
||||
|
||||
// Apply script configuration to global flags
|
||||
if mcpConfig.Prompt != "" {
|
||||
promptFlag = mcpConfig.Prompt
|
||||
@@ -665,7 +663,7 @@ func runScriptMode(ctx context.Context) error {
|
||||
if mcpConfig.AnthropicURL != "" {
|
||||
anthropicBaseURL = mcpConfig.AnthropicURL
|
||||
}
|
||||
|
||||
|
||||
// Restore original values after execution
|
||||
defer func() {
|
||||
configFile = originalConfigFile
|
||||
@@ -682,7 +680,7 @@ func runScriptMode(ctx context.Context) error {
|
||||
anthropicBaseURL = originalAnthropicURL
|
||||
scriptMCPConfig = nil
|
||||
}()
|
||||
|
||||
|
||||
// Now run the normal execution path which will use our overridden config
|
||||
return runNormalMode(ctx)
|
||||
}
|
||||
@@ -694,9 +692,9 @@ func parseScriptFile(filename string) (*config.Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
|
||||
// Skip shebang line if present
|
||||
if scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
@@ -705,7 +703,7 @@ func parseScriptFile(filename string) (*config.Config, error) {
|
||||
return parseScriptContent(line + "\n" + readRemainingLines(scanner))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Read the rest of the file
|
||||
content := readRemainingLines(scanner)
|
||||
return parseScriptContent(content)
|
||||
@@ -723,20 +721,20 @@ func readRemainingLines(scanner *bufio.Scanner) string {
|
||||
// parseScriptContent parses the content to extract YAML frontmatter
|
||||
func parseScriptContent(content string) (*config.Config, error) {
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
|
||||
// Find YAML frontmatter
|
||||
var yamlLines []string
|
||||
|
||||
|
||||
for _, line := range lines {
|
||||
yamlLines = append(yamlLines, line)
|
||||
}
|
||||
|
||||
|
||||
// Parse YAML
|
||||
yamlContent := strings.Join(yamlLines, "\n")
|
||||
var scriptConfig config.Config
|
||||
if err := yaml.Unmarshal([]byte(yamlContent), &scriptConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse YAML: %v", err)
|
||||
}
|
||||
|
||||
|
||||
return &scriptConfig, nil
|
||||
}
|
||||
}
|
||||
|
||||
+20
-20
@@ -31,12 +31,12 @@ type MessageModifier func(ctx context.Context, input []*schema.Message) []*schem
|
||||
|
||||
// AgentConfig is the config for agent.
|
||||
type AgentConfig struct {
|
||||
ModelConfig *models.ProviderConfig
|
||||
MCPConfig *config.Config
|
||||
SystemPrompt string
|
||||
MaxSteps int
|
||||
MessageWindow int
|
||||
|
||||
ModelConfig *models.ProviderConfig
|
||||
MCPConfig *config.Config
|
||||
SystemPrompt string
|
||||
MaxSteps int
|
||||
MessageWindow int
|
||||
|
||||
// MessageModifier.
|
||||
// modify the input messages before the model is called, it's useful when you want to add some system prompt or other messages.
|
||||
MessageModifier MessageModifier
|
||||
@@ -146,7 +146,7 @@ func NewAgent(ctx context.Context, config *AgentConfig) (*Agent, error) {
|
||||
|
||||
// Only set up tools if we have any
|
||||
hasTools := len(toolsConfig.Tools) > 0
|
||||
|
||||
|
||||
if hasTools {
|
||||
if toolInfos, err = genToolInfos(ctx, toolsConfig); err != nil {
|
||||
return nil, err
|
||||
@@ -185,7 +185,7 @@ func NewAgent(ctx context.Context, config *AgentConfig) (*Agent, error) {
|
||||
if len(state.Messages) > 0 && state.Messages[0].Role == schema.System {
|
||||
hasSystemMessage = true
|
||||
}
|
||||
|
||||
|
||||
if !hasSystemMessage {
|
||||
systemMsg := schema.SystemMessage(config.SystemPrompt)
|
||||
state.Messages = append([]*schema.Message{systemMsg}, state.Messages...)
|
||||
@@ -368,9 +368,9 @@ func (a *Agent) Stream(ctx context.Context, input []*schema.Message, opts ...com
|
||||
}
|
||||
|
||||
// GenerateWithLoop processes messages with a custom loop that displays tool calls in real-time
|
||||
func (a *Agent) GenerateWithLoop(ctx context.Context, messages []*schema.Message,
|
||||
func (a *Agent) GenerateWithLoop(ctx context.Context, messages []*schema.Message,
|
||||
onToolCall ToolCallHandler, onToolExecution ToolExecutionHandler, onToolResult ToolResultHandler, onResponse ResponseHandler, onToolCallContent ToolCallContentHandler) (*schema.Message, error) {
|
||||
|
||||
|
||||
// Create a copy of messages to avoid modifying the original
|
||||
workingMessages := make([]*schema.Message, len(messages))
|
||||
copy(workingMessages, messages)
|
||||
@@ -381,7 +381,7 @@ func (a *Agent) GenerateWithLoop(ctx context.Context, messages []*schema.Message
|
||||
if len(workingMessages) > 0 && workingMessages[0].Role == schema.System {
|
||||
hasSystemMessage = true
|
||||
}
|
||||
|
||||
|
||||
if !hasSystemMessage {
|
||||
systemMsg := schema.SystemMessage(a.systemPrompt)
|
||||
workingMessages = append([]*schema.Message{systemMsg}, workingMessages...)
|
||||
@@ -392,7 +392,7 @@ func (a *Agent) GenerateWithLoop(ctx context.Context, messages []*schema.Message
|
||||
availableTools := a.toolManager.GetTools()
|
||||
var toolInfos []*schema.ToolInfo
|
||||
toolMap := make(map[string]tool.BaseTool)
|
||||
|
||||
|
||||
for _, t := range availableTools {
|
||||
info, err := t.Info(ctx)
|
||||
if err != nil {
|
||||
@@ -419,7 +419,7 @@ func (a *Agent) GenerateWithLoop(ctx context.Context, messages []*schema.Message
|
||||
if response.Content != "" && onToolCallContent != nil {
|
||||
onToolCallContent(response.Content)
|
||||
}
|
||||
|
||||
|
||||
// Handle tool calls
|
||||
for _, toolCall := range response.ToolCalls {
|
||||
// Notify about tool call
|
||||
@@ -433,26 +433,26 @@ func (a *Agent) GenerateWithLoop(ctx context.Context, messages []*schema.Message
|
||||
if onToolExecution != nil {
|
||||
onToolExecution(toolCall.Function.Name, true)
|
||||
}
|
||||
|
||||
|
||||
output, err := selectedTool.(tool.InvokableTool).InvokableRun(ctx, toolCall.Function.Arguments)
|
||||
|
||||
|
||||
// Notify tool execution end
|
||||
if onToolExecution != nil {
|
||||
onToolExecution(toolCall.Function.Name, false)
|
||||
}
|
||||
|
||||
|
||||
if err != nil {
|
||||
errorMsg := fmt.Sprintf("Tool execution error: %v", err)
|
||||
toolMessage := schema.ToolMessage(errorMsg, toolCall.ID)
|
||||
workingMessages = append(workingMessages, toolMessage)
|
||||
|
||||
|
||||
if onToolResult != nil {
|
||||
onToolResult(toolCall.Function.Name, toolCall.Function.Arguments, errorMsg, true)
|
||||
}
|
||||
} else {
|
||||
toolMessage := schema.ToolMessage(output, toolCall.ID)
|
||||
workingMessages = append(workingMessages, toolMessage)
|
||||
|
||||
|
||||
if onToolResult != nil {
|
||||
onToolResult(toolCall.Function.Name, toolCall.Function.Arguments, output, false)
|
||||
}
|
||||
@@ -461,7 +461,7 @@ func (a *Agent) GenerateWithLoop(ctx context.Context, messages []*schema.Message
|
||||
errorMsg := fmt.Sprintf("Tool not found: %s", toolCall.Function.Name)
|
||||
toolMessage := schema.ToolMessage(errorMsg, toolCall.ID)
|
||||
workingMessages = append(workingMessages, toolMessage)
|
||||
|
||||
|
||||
if onToolResult != nil {
|
||||
onToolResult(toolCall.Function.Name, toolCall.Function.Arguments, errorMsg, true)
|
||||
}
|
||||
@@ -493,4 +493,4 @@ func (a *Agent) Close() error {
|
||||
// ExportGraph exports the underlying graph from Agent, along with the []compose.GraphAddNodeOpt to be used when adding this graph to another graph.
|
||||
func (a *Agent) ExportGraph() (compose.AnyGraph, []compose.GraphAddNodeOpt) {
|
||||
return a.graph, a.graphAddNodeOpts
|
||||
}
|
||||
}
|
||||
|
||||
+29
-29
@@ -10,28 +10,28 @@ import (
|
||||
|
||||
// MCPServerConfig represents configuration for an MCP server
|
||||
type MCPServerConfig struct {
|
||||
Command string `json:"command,omitempty"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Headers []string `json:"headers,omitempty"`
|
||||
AllowedTools []string `json:"allowedTools,omitempty"`
|
||||
Command string `json:"command,omitempty"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Headers []string `json:"headers,omitempty"`
|
||||
AllowedTools []string `json:"allowedTools,omitempty"`
|
||||
ExcludedTools []string `json:"excludedTools,omitempty"`
|
||||
}
|
||||
|
||||
// Config represents the application configuration
|
||||
type Config struct {
|
||||
MCPServers map[string]MCPServerConfig `json:"mcpServers" yaml:"mcpServers"`
|
||||
Model string `json:"model,omitempty" yaml:"model,omitempty"`
|
||||
MaxSteps int `json:"max-steps,omitempty" yaml:"max-steps,omitempty"`
|
||||
MessageWindow int `json:"message-window,omitempty" yaml:"message-window,omitempty"`
|
||||
Debug bool `json:"debug,omitempty" yaml:"debug,omitempty"`
|
||||
SystemPrompt string `json:"system-prompt,omitempty" yaml:"system-prompt,omitempty"`
|
||||
OpenAIAPIKey string `json:"openai-api-key,omitempty" yaml:"openai-api-key,omitempty"`
|
||||
AnthropicAPIKey string `json:"anthropic-api-key,omitempty" yaml:"anthropic-api-key,omitempty"`
|
||||
GoogleAPIKey string `json:"google-api-key,omitempty" yaml:"google-api-key,omitempty"`
|
||||
OpenAIURL string `json:"openai-url,omitempty" yaml:"openai-url,omitempty"`
|
||||
AnthropicURL string `json:"anthropic-url,omitempty" yaml:"anthropic-url,omitempty"`
|
||||
Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"`
|
||||
MCPServers map[string]MCPServerConfig `json:"mcpServers" yaml:"mcpServers"`
|
||||
Model string `json:"model,omitempty" yaml:"model,omitempty"`
|
||||
MaxSteps int `json:"max-steps,omitempty" yaml:"max-steps,omitempty"`
|
||||
MessageWindow int `json:"message-window,omitempty" yaml:"message-window,omitempty"`
|
||||
Debug bool `json:"debug,omitempty" yaml:"debug,omitempty"`
|
||||
SystemPrompt string `json:"system-prompt,omitempty" yaml:"system-prompt,omitempty"`
|
||||
OpenAIAPIKey string `json:"openai-api-key,omitempty" yaml:"openai-api-key,omitempty"`
|
||||
AnthropicAPIKey string `json:"anthropic-api-key,omitempty" yaml:"anthropic-api-key,omitempty"`
|
||||
GoogleAPIKey string `json:"google-api-key,omitempty" yaml:"google-api-key,omitempty"`
|
||||
OpenAIURL string `json:"openai-url,omitempty" yaml:"openai-url,omitempty"`
|
||||
AnthropicURL string `json:"anthropic-url,omitempty" yaml:"anthropic-url,omitempty"`
|
||||
Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates the configuration
|
||||
@@ -52,24 +52,24 @@ type SystemPromptConfig struct {
|
||||
// LoadMCPConfig loads MCP configuration from file
|
||||
func LoadMCPConfig(configFile string) (*Config, error) {
|
||||
v := viper.New()
|
||||
|
||||
|
||||
if configFile == "" {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting home directory: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Try .mcphost files first (new format), then .mcp files (backwards compatibility)
|
||||
configNames := []string{".mcphost", ".mcp"}
|
||||
configTypes := []string{"yaml", "json"}
|
||||
|
||||
|
||||
var configFound bool
|
||||
for _, configName := range configNames {
|
||||
for _, configType := range configTypes {
|
||||
v.SetConfigName(configName)
|
||||
v.SetConfigType(configType)
|
||||
v.AddConfigPath(homeDir)
|
||||
|
||||
|
||||
if err := v.ReadInConfig(); err == nil {
|
||||
configFound = true
|
||||
break
|
||||
@@ -79,7 +79,7 @@ func LoadMCPConfig(configFile string) (*Config, error) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if !configFound {
|
||||
// Create default config file
|
||||
if err := createDefaultConfig(homeDir); err != nil {
|
||||
@@ -88,7 +88,7 @@ func LoadMCPConfig(configFile string) (*Config, error) {
|
||||
MCPServers: make(map[string]MCPServerConfig),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
// Try to load the newly created config
|
||||
v.SetConfigName(".mcphost")
|
||||
v.SetConfigType("yaml")
|
||||
@@ -129,7 +129,7 @@ func LoadSystemPrompt(filePath string) (string, error) {
|
||||
|
||||
v := viper.New()
|
||||
v.SetConfigFile(filePath)
|
||||
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return "", fmt.Errorf("error reading system prompt file: %v", err)
|
||||
}
|
||||
@@ -145,14 +145,14 @@ func LoadSystemPrompt(filePath string) (string, error) {
|
||||
// createDefaultConfig creates a default .mcphost.yml file in the user's home directory
|
||||
func createDefaultConfig(homeDir string) error {
|
||||
configPath := filepath.Join(homeDir, ".mcphost.yml")
|
||||
|
||||
|
||||
// Create the file
|
||||
file, err := os.Create(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating config file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
|
||||
// Write a clean YAML template
|
||||
content := `# MCPHost Configuration File
|
||||
# All command-line flags can be configured here
|
||||
@@ -184,11 +184,11 @@ mcpServers:
|
||||
# openai-url: "https://api.openai.com/v1"
|
||||
# anthropic-url: "https://api.anthropic.com"
|
||||
`
|
||||
|
||||
|
||||
_, err = file.WriteString(content)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing config content: %v", err)
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/eino/components/model"
|
||||
"github.com/cloudwego/eino-ext/components/model/claude"
|
||||
"github.com/cloudwego/eino-ext/components/model/gemini"
|
||||
"github.com/cloudwego/eino-ext/components/model/ollama"
|
||||
"github.com/cloudwego/eino-ext/components/model/openai"
|
||||
"github.com/cloudwego/eino/components/model"
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
@@ -130,4 +130,4 @@ func createOllamaProvider(ctx context.Context, config *ProviderConfig, modelName
|
||||
}
|
||||
|
||||
return ollama.NewChatModel(ctx, ollamaConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func (m *MCPToolManager) LoadTools(ctx context.Context, config *config.Config) e
|
||||
if !m.shouldIncludeTool(mcpTool.Name, serverConfig) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
einoTool := &MCPTool{
|
||||
client: client,
|
||||
toolInfo: &mcpTool,
|
||||
@@ -159,7 +159,7 @@ func (m *MCPToolManager) shouldIncludeTool(toolName string, serverConfig config.
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// If excludedTools is specified, exclude tools in the list
|
||||
if len(serverConfig.ExcludedTools) > 0 {
|
||||
for _, excludedTool := range serverConfig.ExcludedTools {
|
||||
@@ -168,7 +168,7 @@ func (m *MCPToolManager) shouldIncludeTool(toolName string, serverConfig config.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Include by default
|
||||
return true
|
||||
}
|
||||
@@ -183,12 +183,12 @@ func (m *MCPToolManager) createMCPClient(ctx context.Context, serverName string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
// Start the SSE client
|
||||
if err := sseClient.Start(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to start SSE client: %v", err)
|
||||
}
|
||||
|
||||
|
||||
return sseClient, nil
|
||||
}
|
||||
|
||||
@@ -205,4 +205,4 @@ func (m *MCPToolManager) initializeClient(ctx context.Context, client client.MCP
|
||||
|
||||
_, err := client.Initialize(ctx, initRequest)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ func (c *CLI) CreateCallbackHandler() callbacks.Handler {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
return utilCallbacks.NewHandlerHelper().
|
||||
Tool(toolHandler).
|
||||
Handler()
|
||||
}
|
||||
}
|
||||
|
||||
+17
-18
@@ -32,7 +32,7 @@ func NewCLI() (*CLI, error) {
|
||||
cli.updateSize()
|
||||
cli.messageRenderer = NewMessageRenderer(cli.width)
|
||||
cli.messageContainer = NewMessageContainer(cli.width, cli.height-4) // Reserve space for input and help
|
||||
|
||||
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@ func (c *CLI) GetPrompt() (string, error) {
|
||||
BorderForeground(mutedColor).
|
||||
MarginTop(1).
|
||||
MarginBottom(1)
|
||||
|
||||
|
||||
// Render the divider
|
||||
fmt.Print(dividerStyle.Render(""))
|
||||
|
||||
|
||||
var prompt string
|
||||
err := huh.NewForm(huh.NewGroup(huh.NewText().
|
||||
Title("Enter your prompt (Type /help for commands, Ctrl+C to quit)").
|
||||
@@ -73,11 +73,11 @@ func (c *CLI) GetPrompt() (string, error) {
|
||||
func (c *CLI) ShowSpinner(message string, action func() error) error {
|
||||
spinner := NewSpinner(message)
|
||||
spinner.Start()
|
||||
|
||||
|
||||
err := action()
|
||||
|
||||
|
||||
spinner.Stop()
|
||||
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func (c *CLI) DisplayAssistantMessageWithModel(message, modelName string) error
|
||||
// DisplayToolCallMessage displays a tool call in progress
|
||||
func (c *CLI) DisplayToolCallMessage(toolName, toolArgs string) {
|
||||
msg := c.messageRenderer.RenderToolCallMessage(toolName, toolArgs, time.Now())
|
||||
|
||||
|
||||
// Always display immediately - spinner management is handled externally
|
||||
c.messageContainer.AddMessage(msg)
|
||||
c.displayContainer()
|
||||
@@ -113,7 +113,7 @@ func (c *CLI) DisplayToolCallMessage(toolName, toolArgs string) {
|
||||
// DisplayToolMessage displays a tool call message
|
||||
func (c *CLI) DisplayToolMessage(toolName, toolArgs, toolResult string, isError bool) {
|
||||
msg := c.messageRenderer.RenderToolMessage(toolName, toolArgs, toolResult, isError)
|
||||
|
||||
|
||||
// Always display immediately - spinner management is handled externally
|
||||
c.messageContainer.AddMessage(msg)
|
||||
c.displayContainer()
|
||||
@@ -123,7 +123,7 @@ func (c *CLI) DisplayToolMessage(toolName, toolArgs, toolResult string, isError
|
||||
func (c *CLI) DisplayStreamingMessage(reader *schema.StreamReader[*schema.Message]) error {
|
||||
// For streaming, we'll collect the content and then display it
|
||||
var content strings.Builder
|
||||
|
||||
|
||||
for {
|
||||
msg, err := reader.Recv()
|
||||
if err == io.EOF {
|
||||
@@ -175,7 +175,7 @@ You can also just type your message to chat with the AI assistant.`
|
||||
func (c *CLI) DisplayTools(tools []string) {
|
||||
var content strings.Builder
|
||||
content.WriteString("## Available Tools\n\n")
|
||||
|
||||
|
||||
if len(tools) == 0 {
|
||||
content.WriteString("No tools are currently available.")
|
||||
} else {
|
||||
@@ -183,7 +183,7 @@ func (c *CLI) DisplayTools(tools []string) {
|
||||
content.WriteString(fmt.Sprintf("%d. `%s`\n", i+1, tool))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Display as a system message
|
||||
msg := c.messageRenderer.RenderSystemMessage(content.String(), time.Now())
|
||||
c.messageContainer.AddMessage(msg)
|
||||
@@ -194,7 +194,7 @@ func (c *CLI) DisplayTools(tools []string) {
|
||||
func (c *CLI) DisplayServers(servers []string) {
|
||||
var content strings.Builder
|
||||
content.WriteString("## Configured MCP Servers\n\n")
|
||||
|
||||
|
||||
if len(servers) == 0 {
|
||||
content.WriteString("No MCP servers are currently configured.")
|
||||
} else {
|
||||
@@ -202,7 +202,7 @@ func (c *CLI) DisplayServers(servers []string) {
|
||||
content.WriteString(fmt.Sprintf("%d. `%s`\n", i+1, server))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Display as a system message
|
||||
msg := c.messageRenderer.RenderSystemMessage(content.String(), time.Now())
|
||||
c.messageContainer.AddMessage(msg)
|
||||
@@ -213,7 +213,7 @@ func (c *CLI) DisplayServers(servers []string) {
|
||||
func (c *CLI) DisplayHistory(messages []*schema.Message) {
|
||||
// Create a temporary container for history
|
||||
historyContainer := NewMessageContainer(c.width, c.height-4)
|
||||
|
||||
|
||||
for _, msg := range messages {
|
||||
switch msg.Role {
|
||||
case schema.User:
|
||||
@@ -224,7 +224,7 @@ func (c *CLI) DisplayHistory(messages []*schema.Message) {
|
||||
historyContainer.AddMessage(uiMsg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fmt.Println("\nConversation History:")
|
||||
fmt.Println(historyContainer.Render())
|
||||
}
|
||||
@@ -282,10 +282,10 @@ func (c *CLI) updateSize() {
|
||||
c.height = 24 // Fallback height
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
c.width = width
|
||||
c.height = height
|
||||
|
||||
|
||||
// Update renderers if they exist
|
||||
if c.messageRenderer != nil {
|
||||
c.messageRenderer.SetWidth(c.width)
|
||||
@@ -294,4 +294,3 @@ func (c *CLI) updateSize() {
|
||||
c.messageContainer.SetSize(c.width, c.height-4)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-32
@@ -22,12 +22,12 @@ const (
|
||||
|
||||
// UIMessage represents a rendered message for display
|
||||
type UIMessage struct {
|
||||
ID string
|
||||
Type MessageType
|
||||
Position int
|
||||
Height int
|
||||
Content string
|
||||
Timestamp time.Time
|
||||
ID string
|
||||
Type MessageType
|
||||
Position int
|
||||
Height int
|
||||
Content string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// Color constants
|
||||
@@ -61,7 +61,7 @@ func (r *MessageRenderer) SetWidth(width int) {
|
||||
// RenderUserMessage renders a user message with proper styling
|
||||
func (r *MessageRenderer) RenderUserMessage(content string, timestamp time.Time) UIMessage {
|
||||
baseStyle := lipgloss.NewStyle()
|
||||
|
||||
|
||||
// Create the main message style with border
|
||||
style := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -74,7 +74,7 @@ func (r *MessageRenderer) RenderUserMessage(content string, timestamp time.Time)
|
||||
// Format timestamp
|
||||
timeStr := timestamp.Local().Format("02 Jan 2006 03:04 PM")
|
||||
username := "You"
|
||||
|
||||
|
||||
// Create info line
|
||||
info := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -83,7 +83,7 @@ func (r *MessageRenderer) RenderUserMessage(content string, timestamp time.Time)
|
||||
|
||||
// Render the message content
|
||||
messageContent := r.renderMarkdown(content, r.width-2)
|
||||
|
||||
|
||||
// Combine content and info
|
||||
parts := []string{
|
||||
strings.TrimSuffix(messageContent, "\n"),
|
||||
@@ -105,7 +105,7 @@ func (r *MessageRenderer) RenderUserMessage(content string, timestamp time.Time)
|
||||
// RenderAssistantMessage renders an assistant message with proper styling
|
||||
func (r *MessageRenderer) RenderAssistantMessage(content string, timestamp time.Time, modelName string) UIMessage {
|
||||
baseStyle := lipgloss.NewStyle()
|
||||
|
||||
|
||||
// Create the main message style with border
|
||||
style := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -120,7 +120,7 @@ func (r *MessageRenderer) RenderAssistantMessage(content string, timestamp time.
|
||||
if modelName == "" {
|
||||
modelName = "Assistant"
|
||||
}
|
||||
|
||||
|
||||
// Create info line
|
||||
info := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -129,7 +129,7 @@ func (r *MessageRenderer) RenderAssistantMessage(content string, timestamp time.
|
||||
|
||||
// Render the message content
|
||||
messageContent := r.renderMarkdown(content, r.width-2)
|
||||
|
||||
|
||||
// Handle empty content
|
||||
if strings.TrimSpace(content) == "" {
|
||||
messageContent = baseStyle.
|
||||
@@ -137,7 +137,7 @@ func (r *MessageRenderer) RenderAssistantMessage(content string, timestamp time.
|
||||
Foreground(mutedColor).
|
||||
Render("*Finished without output*")
|
||||
}
|
||||
|
||||
|
||||
// Combine content and info
|
||||
parts := []string{
|
||||
strings.TrimSuffix(messageContent, "\n"),
|
||||
@@ -159,7 +159,7 @@ func (r *MessageRenderer) RenderAssistantMessage(content string, timestamp time.
|
||||
// RenderSystemMessage renders a system message (help, tools, etc.) with proper styling
|
||||
func (r *MessageRenderer) RenderSystemMessage(content string, timestamp time.Time) UIMessage {
|
||||
baseStyle := lipgloss.NewStyle()
|
||||
|
||||
|
||||
// Create the main message style with border
|
||||
style := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -171,7 +171,7 @@ func (r *MessageRenderer) RenderSystemMessage(content string, timestamp time.Tim
|
||||
|
||||
// Format timestamp
|
||||
timeStr := timestamp.Local().Format("02 Jan 2006 03:04 PM")
|
||||
|
||||
|
||||
// Create info line with MCPHost label
|
||||
info := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -180,7 +180,7 @@ func (r *MessageRenderer) RenderSystemMessage(content string, timestamp time.Tim
|
||||
|
||||
// Render the message content with markdown
|
||||
messageContent := r.renderMarkdown(content, r.width-2)
|
||||
|
||||
|
||||
// Handle empty content
|
||||
if strings.TrimSpace(content) == "" {
|
||||
messageContent = baseStyle.
|
||||
@@ -188,7 +188,7 @@ func (r *MessageRenderer) RenderSystemMessage(content string, timestamp time.Tim
|
||||
Foreground(mutedColor).
|
||||
Render("*No content*")
|
||||
}
|
||||
|
||||
|
||||
// Combine content and info
|
||||
parts := []string{
|
||||
strings.TrimSuffix(messageContent, "\n"),
|
||||
@@ -210,7 +210,7 @@ func (r *MessageRenderer) RenderSystemMessage(content string, timestamp time.Tim
|
||||
// RenderErrorMessage renders an error message with proper styling
|
||||
func (r *MessageRenderer) RenderErrorMessage(errorMsg string, timestamp time.Time) UIMessage {
|
||||
baseStyle := lipgloss.NewStyle()
|
||||
|
||||
|
||||
// Create the main message style with border
|
||||
style := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -222,7 +222,7 @@ func (r *MessageRenderer) RenderErrorMessage(errorMsg string, timestamp time.Tim
|
||||
|
||||
// Format timestamp
|
||||
timeStr := timestamp.Local().Format("02 Jan 2006 03:04 PM")
|
||||
|
||||
|
||||
// Create info line with Error label
|
||||
info := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -234,7 +234,7 @@ func (r *MessageRenderer) RenderErrorMessage(errorMsg string, timestamp time.Tim
|
||||
Foreground(errorColor).
|
||||
Bold(true).
|
||||
Render(fmt.Sprintf("❌ %s", errorMsg))
|
||||
|
||||
|
||||
// Combine content and info
|
||||
parts := []string{
|
||||
errorContent,
|
||||
@@ -256,7 +256,7 @@ func (r *MessageRenderer) RenderErrorMessage(errorMsg string, timestamp time.Tim
|
||||
// RenderToolCallMessage renders a tool call in progress with proper styling
|
||||
func (r *MessageRenderer) RenderToolCallMessage(toolName, toolArgs string, timestamp time.Time) UIMessage {
|
||||
baseStyle := lipgloss.NewStyle()
|
||||
|
||||
|
||||
// Create the main message style with border
|
||||
style := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -268,7 +268,7 @@ func (r *MessageRenderer) RenderToolCallMessage(toolName, toolArgs string, times
|
||||
|
||||
// Format timestamp
|
||||
timeStr := timestamp.Local().Format("02 Jan 2006 03:04 PM")
|
||||
|
||||
|
||||
// Create header with tool icon and name
|
||||
toolIcon := "🔧"
|
||||
header := baseStyle.
|
||||
@@ -313,7 +313,7 @@ func (r *MessageRenderer) RenderToolCallMessage(toolName, toolArgs string, times
|
||||
// RenderToolMessage renders a tool call message with proper styling
|
||||
func (r *MessageRenderer) RenderToolMessage(toolName, toolArgs, toolResult string, isError bool) UIMessage {
|
||||
baseStyle := lipgloss.NewStyle()
|
||||
|
||||
|
||||
// Create the main message style with border
|
||||
style := baseStyle.
|
||||
Width(r.width - 1).
|
||||
@@ -348,7 +348,7 @@ func (r *MessageRenderer) RenderToolMessage(toolName, toolArgs, toolResult strin
|
||||
// Combine parts
|
||||
headerLine := lipgloss.JoinHorizontal(lipgloss.Left, toolNameText, argsText)
|
||||
parts := []string{headerLine}
|
||||
|
||||
|
||||
if resultContent != "" {
|
||||
parts = append(parts, strings.TrimSuffix(resultContent, "\n"))
|
||||
}
|
||||
@@ -373,25 +373,25 @@ func (r *MessageRenderer) formatToolArgs(args string) string {
|
||||
args = strings.TrimSuffix(args, "}")
|
||||
args = strings.TrimSpace(args)
|
||||
}
|
||||
|
||||
|
||||
// If it's empty after cleanup, return a placeholder
|
||||
if args == "" {
|
||||
return "(no arguments)"
|
||||
}
|
||||
|
||||
|
||||
// Truncate if too long
|
||||
maxLen := 100
|
||||
if len(args) > maxLen {
|
||||
return args[:maxLen] + "..."
|
||||
}
|
||||
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
// formatToolResult formats tool results based on tool type
|
||||
func (r *MessageRenderer) formatToolResult(toolName, result string, width int) string {
|
||||
baseStyle := lipgloss.NewStyle()
|
||||
|
||||
|
||||
// Truncate very long results
|
||||
maxLines := 10
|
||||
lines := strings.Split(result, "\n")
|
||||
@@ -416,11 +416,11 @@ func (r *MessageRenderer) formatToolResult(toolName, result string, width int) s
|
||||
func (r *MessageRenderer) truncateText(text string, maxWidth int) string {
|
||||
// Replace newlines with spaces for single-line display
|
||||
text = strings.ReplaceAll(text, "\n", " ")
|
||||
|
||||
|
||||
if lipgloss.Width(text) <= maxWidth {
|
||||
return text
|
||||
}
|
||||
|
||||
|
||||
// Simple truncation - could be improved with proper unicode handling
|
||||
for i := len(text) - 1; i >= 0; i-- {
|
||||
truncated := text[:i] + "..."
|
||||
@@ -428,7 +428,7 @@ func (r *MessageRenderer) truncateText(text string, maxWidth int) string {
|
||||
return truncated
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return "..."
|
||||
}
|
||||
|
||||
@@ -524,4 +524,4 @@ func (c *MessageContainer) renderEmptyState() string {
|
||||
"",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,4 +124,4 @@ func (s *Spinner) Start() {
|
||||
func (s *Spinner) Stop() {
|
||||
s.cancel()
|
||||
<-s.done
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,4 +294,4 @@ func toMarkdown(content string, width int) string {
|
||||
r := GetMarkdownRenderer(width)
|
||||
rendered, _ := r.Render(content)
|
||||
return rendered
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user