package agents import ( "context" "fmt" "strings" "time" "github.com/enmanuel/agents/pkg/command" "github.com/enmanuel/agents/pkg/decision" ) // registerBuiltinCommands registers all built-in command handlers. func (a *Agent) registerBuiltinCommands() { a.commands["help"] = a.cmdHelp a.commands["tools"] = a.cmdTools a.commands["tool"] = a.cmdTool a.commands["ping"] = a.cmdPing a.commands["status"] = a.cmdStatus a.commands["info"] = a.cmdInfo a.commands["clear"] = a.cmdClear a.commands["prompts"] = a.cmdPrompts a.commands["version"] = a.cmdVersion } // cmdHelp lists all available commands (built-in + agent-specific). func (a *Agent) cmdHelp(_ context.Context, _ decision.MessageContext) string { var b strings.Builder b.WriteString("**Comandos disponibles:**\n\n") // Built-in commands for _, spec := range command.Builtins() { if spec.Hidden { continue } writeSpec(&b, spec) } // Agent-specific commands (registered via RegisterCommand) if len(a.customSpecs) > 0 { b.WriteString("\n**Comandos del agente:**\n\n") for _, spec := range a.customSpecs { if spec.Hidden { continue } writeSpec(&b, spec) } } return b.String() } // writeSpec formats a single command spec for the help output. func writeSpec(b *strings.Builder, spec command.Spec) { aliases := "" if len(spec.Aliases) > 0 { aliases = " (" + strings.Join(prefixAll(spec.Aliases, "!"), ", ") + ")" } usage := spec.Usage if usage == "" { usage = "!" + spec.Name } fmt.Fprintf(b, "- `%s`%s — %s\n", usage, aliases, spec.Description) } // cmdTools lists all tools registered in the agent's tool registry. func (a *Agent) cmdTools(_ context.Context, _ decision.MessageContext) string { names := a.toolReg.Names() if len(names) == 0 { return "No hay tools registradas." } var b strings.Builder fmt.Fprintf(&b, "**Tools disponibles (%d):**\n\n", len(names)) for _, name := range names { t, _ := a.toolReg.Get(name) fmt.Fprintf(&b, "- **%s** — %s\n", t.Def.Name, t.Def.Description) for _, p := range t.Def.Parameters { req := "" if p.Required { req = " *(requerido)*" } fmt.Fprintf(&b, " - `%s`: %s%s\n", p.Name, p.Description, req) } } b.WriteString("\nUso: `!tool [key=value ...]`") return b.String() } // cmdTool executes a tool directly with key=value args. func (a *Agent) cmdTool(ctx context.Context, msgCtx decision.MessageContext) string { if len(msgCtx.Args) == 0 { return "Uso: `!tool [key=value ...]`\nUsa `!tools` para ver tools disponibles." } toolName := msgCtx.Args[0] if _, ok := a.toolReg.Get(toolName); !ok { return fmt.Sprintf("Tool %q no encontrada. Usa `!tools` para ver tools disponibles.", toolName) } // Parse remaining args as key=value parsed := command.ParseArgs(msgCtx.Args[1:]) argsJSON := command.ArgsToJSON(parsed.Named) a.logger.Info("executing tool via command", "tool", toolName, "args", argsJSON, ) result := a.toolReg.ExecuteForRoom(ctx, toolName, argsJSON, msgCtx.RoomID) if result.Err != nil { return fmt.Sprintf("Error ejecutando %s: %s", toolName, result.Err) } return fmt.Sprintf("%s:\n%s", toolName, result.Output) } // cmdPing responds with pong and timestamp. func (a *Agent) cmdPing(_ context.Context, _ decision.MessageContext) string { return fmt.Sprintf("pong — %s", time.Now().Format(time.RFC3339)) } // cmdStatus shows agent uptime and active rooms. func (a *Agent) cmdStatus(_ context.Context, _ decision.MessageContext) string { uptime := time.Since(a.startTime).Truncate(time.Second) a.windowsMu.RLock() roomCount := len(a.windows) a.windowsMu.RUnlock() var b strings.Builder fmt.Fprintf(&b, "**Estado de %s:**\n\n", a.cfg.Agent.Name) fmt.Fprintf(&b, "- **Uptime:** %s\n", uptime) fmt.Fprintf(&b, "- **Rooms activos:** %d\n", roomCount) fmt.Fprintf(&b, "- **Window size:** %d\n", a.windowSize) fmt.Fprintf(&b, "- **Tools:** %d\n", a.toolReg.Len()) if a.llm != nil { fmt.Fprintf(&b, "- **LLM:** %s/%s\n", a.cfg.LLM.Primary.Provider, a.cfg.LLM.Primary.Model) } else { b.WriteString("- **LLM:** no configurado\n") } return b.String() } // cmdInfo shows agent name, version, and description. func (a *Agent) cmdInfo(_ context.Context, _ decision.MessageContext) string { var b strings.Builder fmt.Fprintf(&b, "- **Nombre:** %s\n", a.cfg.Agent.Name) fmt.Fprintf(&b, "- **ID:** %s\n", a.cfg.Agent.ID) if a.cfg.Agent.Version != "" { fmt.Fprintf(&b, "- **Version:** %s\n", a.cfg.Agent.Version) } fmt.Fprintf(&b, "- **Descripcion:** %s\n", a.cfg.Agent.Description) return b.String() } // cmdPrompts lists available prompt-commands. func (a *Agent) cmdPrompts(_ context.Context, _ decision.MessageContext) string { if len(a.promptCmds) == 0 { return "No hay prompt-commands disponibles." } var b strings.Builder fmt.Fprintf(&b, "**Prompt-commands disponibles (%d):**\n\n", len(a.promptCmds)) for name := range a.promptCmds { fmt.Fprintf(&b, "- `!%s`\n", name) } b.WriteString("\nUso: `! [detalles adicionales...]`") return b.String() } // cmdClear clears the conversation window for the current room. func (a *Agent) cmdClear(_ context.Context, msgCtx decision.MessageContext) string { a.ClearWindow(msgCtx.RoomID) return "Ventana de conversacion limpiada." } // cmdVersion shows the agent version. func (a *Agent) cmdVersion(_ context.Context, _ decision.MessageContext) string { v := a.cfg.Agent.Version if v == "" { v = "sin version" } return fmt.Sprintf("%s %s", a.cfg.Agent.Name, v) } // prefixAll adds a prefix to each string in a slice. func prefixAll(ss []string, prefix string) []string { out := make([]string, len(ss)) for i, s := range ss { out[i] = prefix + s } return out }