X-Git-Url: http://git.giacomolli.uk/onechat/blobdiff_plain/d9fddc0f506eb4ead7049f5638674b2b8773fcaa..855a45e6448537c3eed1ad6ec7f1ca9d6caf91fc:/tools.go?ds=sidebyside diff --git a/tools.go b/tools.go index 1cb098a..a75514f 100644 --- a/tools.go +++ b/tools.go @@ -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. 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. The toolbox of a subagent has none of them, so these are +// unknown tools there. type Toolbox struct { log ToolLog chat ToolChat @@ -62,6 +64,10 @@ type Toolbox struct { whatsapp *WhatsApp schedules *Schedules workdir string + agentName string + messages *DaemonMessages + children *DaemonChildren + routes *Routes } // Call logs the call of the tool name with its JSON arguments, makes the log @@ -94,11 +100,15 @@ func (b *Toolbox) Call(ctx context.Context, name, arguments string) (string, err // run runs one tool and returns its output, or its error as text. func (b *Toolbox) run(ctx context.Context, name, arguments string) string { - var output string - var err error + var ( + output string + err error + ) + switch name { case "zoom": var args ToolZoomArguments + err = toolDecodeArguments(arguments, &args) switch { case err != nil: @@ -109,36 +119,42 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string { } case "date": var args ToolDateArguments + err = toolDecodeArguments(arguments, &args) if err == nil { output, err = b.date(args.ID) } case "shell": var args ToolShellArguments + err = toolDecodeArguments(arguments, &args) if err == nil { output = b.shell(ctx, args.Command) } case "read": var args ToolPathArguments + err = toolDecodeArguments(arguments, &args) if err == nil { output, err = b.read(args.Path) } case "write": var args ToolWriteArguments + err = toolDecodeArguments(arguments, &args) if err == nil { output, err = b.write(args.Path, args.Text) } case "edit": var args ToolEditArguments + err = toolDecodeArguments(arguments, &args) if err == nil { output, err = b.edit(args.Path, args.Old, args.New) } case "spawn": var args ToolSpawnArguments + err = toolDecodeArguments(arguments, &args) switch { case b.runs == nil: @@ -146,17 +162,62 @@ 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 + err = toolDecodeArguments(arguments, &args) 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 "whatsapp_find": var args ToolWhatsAppFindArguments + err = toolDecodeArguments(arguments, &args) switch { case b.whatsapp == nil: @@ -166,6 +227,7 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string { } case "whatsapp_send": var args ToolWhatsAppSendArguments + err = toolDecodeArguments(arguments, &args) switch { case b.whatsapp == nil: @@ -175,6 +237,7 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string { } case "schedule_set": var args ToolScheduleSetArguments + err = toolDecodeArguments(arguments, &args) switch { case b.schedules == nil: @@ -192,6 +255,7 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string { } case "schedule_remove": var args ToolScheduleRemoveArguments + err = toolDecodeArguments(arguments, &args) switch { case b.schedules == nil: @@ -210,8 +274,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 @@ -241,7 +312,9 @@ func (b *Toolbox) zoom(id, n int) (string, error) { } level := bits.TrailingZeros(uint(n)) - 1 + var lines []string + for _, index := range []int{2 * id / n, 2*id/n + 1} { node, built, err := b.chat.Node(level, index) if err != nil { @@ -298,7 +371,9 @@ func (b *Toolbox) shell(ctx context.Context, command string) string { err := cmd.Run() text := output.String() + var exit *exec.ExitError + switch { case ctx.Err() != nil: text += "\n[interrupted]" @@ -391,8 +466,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"` @@ -434,6 +510,23 @@ 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"` @@ -530,6 +623,7 @@ func toolClip(text string) string { func toolDecodeArguments(arguments string, args any) error { decoder := json.NewDecoder(strings.NewReader(arguments)) decoder.DisallowUnknownFields() + err := decoder.Decode(args) if err != nil { return fmt.Errorf("arguments: %w", err)