X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/2a276c273c21a1752d541bf87e074c5d1c27e30f..b1be0ee8530df5db8973bfe355f2f6d3149383a0:/tools.go diff --git a/tools.go b/tools.go index 0811981..621cd84 100644 --- a/tools.go +++ b/tools.go @@ -27,9 +27,9 @@ const ( // output, enough for toolClipCharacters/2 characters of 4 bytes. toolClipBytes = 4 * toolClipCharacters / 2 - // toolShellWaitDelay is how long a command can keep its output open + // toolShellOutputTimeout is how long a command can keep its output open // after it ends or after its kill. - toolShellWaitDelay = 2 * time.Second + toolShellOutputTimeout = 2 * time.Second ) // ToolLog is the log that the tools write their calls and results to. @@ -49,11 +49,13 @@ type ToolChat interface { // the log as it happens: the call as a tool message, the result as an echo // message. The call is durable before its tool runs, so the log never misses an // action outside the agent. Commands and files run as the user in the working -// directory, with no sandbox. Zoom with a name reads the log of a subagent. The -// toolbox of the agent has the runs of its subagents for spawn and tell, the -// WhatsApp link for the WhatsApp tools, and the schedules for the schedule -// tools. A child has no WhatsApp link. The toolbox of a subagent has none of -// them, so these are unknown tools there. +// directory, with no sandbox. Zoom with a name reads a child's view or a +// subagent's log. The toolbox of the agent has the runs of its subagents for +// spawn and tell, the WhatsApp link for the WhatsApp tools, and the schedules +// for the schedule tools. Each permanent agent has the parent-child queue. Only +// the root has child creation and source routes. A child has neither of them +// nor a WhatsApp link. Only the root can show images to the user. The toolbox +// of a subagent has none of them, so these are unknown tools there. type Toolbox struct { log ToolLog chat ToolChat @@ -62,13 +64,20 @@ type Toolbox struct { whatsapp *WhatsApp schedules *Schedules workdir string + agentName string + messages *DaemonMessages + children *DaemonChildren + routes *Routes + images *ImageStore + imageShow *ImageShow } // Call logs the call of the tool name with its JSON arguments, makes the log -// durable, runs the tool, and logs its clipped output, which it returns. A -// failed tool gives its error as the output. An error of Call itself is an -// error of the log. When ctx ends before the flush, the tool does not run. -func (b *Toolbox) Call(ctx context.Context, name, arguments string) (string, error) { +// durable, runs the tool, and logs its clipped text. It returns that text or +// the text and image parts of a single-message zoom. Pixels never enter the +// log. A failed tool gives its error as the output. An error of Call itself is +// an error of the log. When ctx ends before the flush, the tool does not run. +func (b *Toolbox) Call(ctx context.Context, name, arguments string) (any, error) { _, err := b.log.Append("tool", toolCallText(name, arguments)) if err != nil { return "", fmt.Errorf("append tool call: %w", err) @@ -84,12 +93,26 @@ func (b *Toolbox) Call(ctx context.Context, name, arguments string) (string, err output = toolClip(b.run(ctx, name, arguments)) } + var ( + result any = output + resultErr error + ) + + if err == nil { + result, resultErr = imageToolOutput(b, name, arguments, output) + } + + if resultErr != nil { + output = "error: " + resultErr.Error() + result = output + } + _, err = b.log.Append("echo", output) if err != nil { return "", fmt.Errorf("append tool result: %w", err) } - return output, nil + return result, nil } // run runs one tool and returns its output, or its error as text. @@ -156,6 +179,46 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string { case err == nil: output, err = b.runs.Spawn(ctx, args.Tasks, args.Effort) } + case "agent_create": + var args ToolAgentCreateArguments + + err = toolDecodeArguments(arguments, &args) + switch { + case b.children == nil: + err = fmt.Errorf("unknown tool %q", name) + case err == nil: + output, err = b.children.Create(args.Name, args.Responsibility) + } + case "route", "unroute", "routes": + var args ToolRouteArguments + + switch name { + case "routes": + err = toolDecodeArguments(arguments, &struct{}{}) + case "unroute": + var source ToolRouteSourceArguments + + err = toolDecodeArguments(arguments, &source) + args.Source = source.Source + default: + err = toolDecodeArguments(arguments, &args) + } + + switch { + case b.routes == nil: + err = fmt.Errorf("unknown tool %q", name) + case err != nil: + case name == "routes": + output = b.routes.List() + case name == "route" && (b.messages == nil || b.messages.Child(b.agentName, args.Agent) == nil): + err = errors.New("the agent must be a child of the root") + default: + err = b.routes.Set(args.Source, args.Agent) + output = "Route removed." + if name == "route" { + output = "Route set." + } + } case "tell": var args ToolTellArguments @@ -163,9 +226,22 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string { switch { case b.runs == nil: err = fmt.Errorf("unknown tool %q", name) + case err == nil && b.messages != nil && b.messages.Child(b.agentName, args.Name) != nil: + err = b.messages.Tell(b.agentName, args.Name, args.Message) + output = "Sent. " + args.Name + "'s reply will reach you as a message." case err == nil: output, err = b.runs.Tell(ctx, args.Name, args.Message) } + case "image_show": + var args ToolImageShowArguments + + err = toolDecodeArguments(arguments, &args) + switch { + case b.imageShow == nil: + err = fmt.Errorf("unknown tool %q", name) + case err == nil: + output, err = b.imageShow.Show(ctx, args.Image, args.Caption) + } case "whatsapp_find": var args ToolWhatsAppFindArguments @@ -184,7 +260,7 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string { case b.whatsapp == nil: err = fmt.Errorf("unknown tool %q", name) case err == nil: - output, err = b.whatsapp.Send(ctx, args.ID, args.Chat, args.Text) + output, err = b.whatsapp.Send(ctx, args.ID, args.Chat, args.Text, args.Image) } case "schedule_set": var args ToolScheduleSetArguments @@ -225,8 +301,15 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string { return output } -// zoomAgent returns the whole log of the subagent name. +// zoomAgent returns a child's view or the whole log of a subagent. func (b *Toolbox) zoomAgent(ctx context.Context, name string) (string, error) { + if b.messages != nil { + child := b.messages.Child(b.agentName, name) + if child != nil { + return strings.Join(child.memory.ViewLines(nil), ""), nil + } + } + subagent, err := b.subagents.Get(ctx, name) if err != nil { return "", err @@ -307,7 +390,7 @@ func (b *Toolbox) shell(ctx context.Context, command string) string { cmd.Cancel = func() error { return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) } - cmd.WaitDelay = toolShellWaitDelay + cmd.WaitDelay = toolShellOutputTimeout output := &ToolHeadTail{} cmd.Stdout = output @@ -410,8 +493,9 @@ func (b *Toolbox) path(path string) string { return filepath.Join(b.workdir, path) } -// ToolZoomArguments are the arguments of zoom. A Name that is not nil names -// the subagent whose whole log zoom gives, and then ID and N are not used. +// ToolZoomArguments are the arguments of zoom. A Name that is not nil names the +// child whose view or the subagent whose whole log zoom gives. ID and N are +// then not used. type ToolZoomArguments struct { ID int `json:"id"` N int `json:"n"` @@ -453,12 +537,35 @@ type ToolSpawnArguments struct { Effort *string `json:"effort"` } +// ToolAgentCreateArguments are the arguments of agent_create. +type ToolAgentCreateArguments struct { + Name string `json:"name"` + Responsibility string `json:"responsibility"` +} + +// ToolRouteArguments are the arguments of route. +type ToolRouteArguments struct { + Source string `json:"source"` + Agent string `json:"agent"` +} + +// ToolRouteSourceArguments are the arguments of unroute. +type ToolRouteSourceArguments struct { + Source string `json:"source"` +} + // ToolTellArguments are the arguments of tell. type ToolTellArguments struct { Name string `json:"name"` Message string `json:"message"` } +// ToolImageShowArguments are the arguments of image_show. +type ToolImageShowArguments struct { + Image string `json:"image"` + Caption string `json:"caption"` +} + // ToolWhatsAppFindArguments are the arguments of whatsapp_find. type ToolWhatsAppFindArguments struct { Name string `json:"name"` @@ -466,9 +573,10 @@ type ToolWhatsAppFindArguments struct { // ToolWhatsAppSendArguments are the arguments of whatsapp_send. type ToolWhatsAppSendArguments struct { - ID string `json:"id"` - Chat string `json:"chat"` - Text string `json:"text"` + ID string `json:"id"` + Chat string `json:"chat"` + Text string `json:"text"` + Image string `json:"image"` } // ToolScheduleSetArguments are the arguments of schedule_set. Exactly one of At