GetStartupInfo returns map[string]any, and type-asserting tools/skills entries without checking ok is fragile. While the current implementation always stores the correct types, a future refactor could cause silent nil dereference. Add ok checks with explicit nil fallback.
176 lines
3.9 KiB
Go
176 lines
3.9 KiB
Go
package agent
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/ergochat/readline"
|
|
|
|
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
|
"github.com/sipeed/picoclaw/pkg/agent"
|
|
"github.com/sipeed/picoclaw/pkg/bus"
|
|
"github.com/sipeed/picoclaw/pkg/logger"
|
|
"github.com/sipeed/picoclaw/pkg/providers"
|
|
)
|
|
|
|
func agentCmd(message, sessionKey, model string, debug bool) error {
|
|
if sessionKey == "" {
|
|
sessionKey = "cli:default"
|
|
}
|
|
|
|
cfg, err := internal.LoadConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("error loading config: %w", err)
|
|
}
|
|
|
|
logger.ConfigureFromEnv()
|
|
|
|
if debug {
|
|
logger.SetLevel(logger.DEBUG)
|
|
fmt.Println("🔍 Debug mode enabled")
|
|
}
|
|
|
|
if model != "" {
|
|
cfg.Agents.Defaults.ModelName = model
|
|
}
|
|
|
|
provider, modelID, err := providers.CreateProvider(cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating provider: %w", err)
|
|
}
|
|
|
|
// Use the resolved model ID from provider creation
|
|
if modelID != "" {
|
|
cfg.Agents.Defaults.ModelName = modelID
|
|
}
|
|
|
|
msgBus := bus.NewMessageBus()
|
|
defer msgBus.Close()
|
|
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
|
defer agentLoop.Close()
|
|
|
|
// Print agent startup info (only for interactive mode)
|
|
startupInfo := agentLoop.GetStartupInfo()
|
|
toolsInfo, ok := startupInfo["tools"].(map[string]any)
|
|
if !ok {
|
|
toolsInfo = nil
|
|
}
|
|
skillsInfo, ok := startupInfo["skills"].(map[string]any)
|
|
if !ok {
|
|
skillsInfo = nil
|
|
}
|
|
logFields := map[string]any{}
|
|
if toolsInfo != nil {
|
|
logFields["tools_count"] = toolsInfo["count"]
|
|
}
|
|
if skillsInfo != nil {
|
|
logFields["skills_total"] = skillsInfo["total"]
|
|
logFields["skills_available"] = skillsInfo["available"]
|
|
}
|
|
logger.InfoCF("agent", "Agent initialized", logFields)
|
|
|
|
if message != "" {
|
|
ctx := context.Background()
|
|
response, err := agentLoop.ProcessDirect(ctx, message, sessionKey)
|
|
if err != nil {
|
|
return fmt.Errorf("error processing message: %w", err)
|
|
}
|
|
fmt.Printf("\n%s %s\n", internal.Logo, response)
|
|
return nil
|
|
}
|
|
|
|
fmt.Printf("%s Interactive mode (Ctrl+C to exit)\n\n", internal.Logo)
|
|
interactiveMode(agentLoop, sessionKey)
|
|
|
|
return nil
|
|
}
|
|
|
|
func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
|
prompt := fmt.Sprintf("%s You: ", internal.Logo)
|
|
|
|
rl, err := readline.NewEx(&readline.Config{
|
|
Prompt: prompt,
|
|
HistoryFile: filepath.Join(os.TempDir(), ".picoclaw_history"),
|
|
HistoryLimit: 100,
|
|
InterruptPrompt: "^C",
|
|
EOFPrompt: "exit",
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("Error initializing readline: %v\n", err)
|
|
fmt.Println("Falling back to simple input mode...")
|
|
simpleInteractiveMode(agentLoop, sessionKey)
|
|
return
|
|
}
|
|
defer rl.Close()
|
|
|
|
for {
|
|
line, err := rl.Readline()
|
|
if err != nil {
|
|
if err == readline.ErrInterrupt || err == io.EOF {
|
|
fmt.Println("\nGoodbye!")
|
|
return
|
|
}
|
|
fmt.Printf("Error reading input: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
input := strings.TrimSpace(line)
|
|
if input == "" {
|
|
continue
|
|
}
|
|
|
|
if input == "exit" || input == "quit" {
|
|
fmt.Println("Goodbye!")
|
|
return
|
|
}
|
|
|
|
ctx := context.Background()
|
|
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
fmt.Printf("\n%s %s\n\n", internal.Logo, response)
|
|
}
|
|
}
|
|
|
|
func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
|
reader := bufio.NewReader(os.Stdin)
|
|
for {
|
|
fmt.Print(fmt.Sprintf("%s You: ", internal.Logo))
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
fmt.Println("\nGoodbye!")
|
|
return
|
|
}
|
|
fmt.Printf("Error reading input: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
input := strings.TrimSpace(line)
|
|
if input == "" {
|
|
continue
|
|
}
|
|
|
|
if input == "exit" || input == "quit" {
|
|
fmt.Println("Goodbye!")
|
|
return
|
|
}
|
|
|
|
ctx := context.Background()
|
|
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
fmt.Printf("\n%s %s\n\n", internal.Logo, response)
|
|
}
|
|
}
|