Files
agents_and_robots/agents/commands.go
T
egutierrez d95ae173de feat: formato markdown en salida de comandos built-in
Mejora la salida de !help, !tools, !status e !info para usar markdown
nativo: encabezados en negrita, listas con bullet points, código inline
con backticks. Aprovecha el rendering HTML que ya hace el bot para que
los clientes Matrix muestren la información formateada correctamente.

No se cambia lógica de comandos, solo formato de texto de salida.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 15:46:13 +00:00

181 lines
5.1 KiB
Go

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["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 <nombre> [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 <nombre> [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.Execute(ctx, toolName, argsJSON)
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()
}
// 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
}