formatting

This commit is contained in:
Ed Zynda
2025-06-10 01:21:17 +03:00
parent 33e3e4fbb0
commit 13ede07ea5
10 changed files with 139 additions and 142 deletions
+29 -31
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
}
+2 -2
View File
@@ -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)
}
}
+6 -6
View File
@@ -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
}
}
+2 -2
View File
@@ -30,8 +30,8 @@ func (c *CLI) CreateCallbackHandler() callbacks.Handler {
return ctx
},
}
return utilCallbacks.NewHandlerHelper().
Tool(toolHandler).
Handler()
}
}
+17 -18
View File
@@ -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
View File
@@ -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 {
"",
),
)
}
}
+1 -1
View File
@@ -124,4 +124,4 @@ func (s *Spinner) Start() {
func (s *Spinner) Stop() {
s.cancel()
<-s.done
}
}
+1 -1
View File
@@ -294,4 +294,4 @@ func toMarkdown(content string, width int) string {
r := GetMarkdownRenderer(width)
rendered, _ := r.Render(content)
return rendered
}
}