]> git.giacomolli.uk - onechat/blobdiff - tools.go
Show stored images to the user
[onechat] / tools.go
index b4fb075b40a5cc7b6a6086417e1bce375a4bfc79..9f2905acb8b32ef61a6c96dbc3cc4365d086d4aa 100644 (file)
--- 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.
@@ -53,8 +53,9 @@ type ToolChat interface {
 // 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. A child has no WhatsApp link or child creation.
-// The toolbox of a subagent has none of them, so these are unknown tools there.
+// 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
@@ -66,13 +67,17 @@ type Toolbox struct {
        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)
@@ -88,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.
@@ -170,6 +189,36 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string {
                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
 
@@ -183,6 +232,16 @@ func (b *Toolbox) run(ctx context.Context, name, arguments string) string {
                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
 
@@ -331,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
@@ -484,12 +543,29 @@ type ToolAgentCreateArguments struct {
        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"`