]> git.giacomolli.uk - onechat/blobdiff - tools.go
Separate multiline Go statements
[onechat] / tools.go
index a75514f984964224f0e1075d06281cda3be79cb0..94885ff43c01d45538518a70c3fe3544cb211f69 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
 
        // 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.
        // 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.
 )
 
 // ToolLog is the log that the tools write their calls and results to.
@@ -54,8 +54,8 @@ type ToolChat interface {
 // 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
 // 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.
+// 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
 type Toolbox struct {
        log       ToolLog
        chat      ToolChat
@@ -68,13 +68,123 @@ type Toolbox struct {
        messages  *DaemonMessages
        children  *DaemonChildren
        routes    *Routes
        messages  *DaemonMessages
        children  *DaemonChildren
        routes    *Routes
+       images    *ImageStore
+       imageShow *ImageShow
+}
+
+// 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"`
+       Name *string `json:"name"`
+}
+
+// ToolDateArguments are the arguments of date.
+type ToolDateArguments struct {
+       ID int `json:"id"`
+}
+
+// ToolShellArguments are the arguments of shell.
+type ToolShellArguments struct {
+       Command string `json:"command"`
+}
+
+// ToolPathArguments are the arguments of read.
+type ToolPathArguments struct {
+       Path string `json:"path"`
+}
+
+// ToolWriteArguments are the arguments of write.
+type ToolWriteArguments struct {
+       Path string `json:"path"`
+       Text string `json:"text"`
+}
+
+// ToolEditArguments are the arguments of edit.
+type ToolEditArguments struct {
+       Path string `json:"path"`
+       Old  string `json:"old"`
+       New  string `json:"new"`
+}
+
+// ToolSpawnArguments are the arguments of spawn. A nil Effort is the effort of
+// the agent.
+type ToolSpawnArguments struct {
+       Tasks  []string `json:"tasks"`
+       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"`
+}
+
+// ToolWhatsAppSendArguments are the arguments of whatsapp_send.
+type ToolWhatsAppSendArguments struct {
+       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
+// and Cron is not nil.
+type ToolScheduleSetArguments struct {
+       Text string  `json:"text"`
+       At   *string `json:"at"`
+       Cron *string `json:"cron"`
+}
+
+// ToolScheduleRemoveArguments are the arguments of schedule_remove.
+type ToolScheduleRemoveArguments struct {
+       ID int `json:"id"`
+}
+
+// ToolHeadTail keeps the first and the last toolClipBytes bytes written to it,
+// and counts the bytes between them that it drops.
+type ToolHeadTail struct {
+       head    []byte
+       tail    []byte
+       dropped int
 }
 
 // Call logs the call of the tool name with its JSON arguments, makes the log
 }
 
 // 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)
        _, err := b.log.Append("tool", toolCallText(name, arguments))
        if err != nil {
                return "", fmt.Errorf("append tool call: %w", err)
@@ -90,12 +200,26 @@ func (b *Toolbox) Call(ctx context.Context, name, arguments string) (string, err
                output = toolClip(b.run(ctx, name, arguments))
        }
 
                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)
        }
 
        _, 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.
 }
 
 // run runs one tool and returns its output, or its error as text.
@@ -215,6 +339,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 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
 
        case "whatsapp_find":
                var args ToolWhatsAppFindArguments
 
@@ -233,7 +367,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:
                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
                }
        case "schedule_set":
                var args ToolScheduleSetArguments
@@ -360,10 +494,12 @@ func (b *Toolbox) shell(ctx context.Context, command string) string {
        cmd.SysProcAttr = &syscall.SysProcAttr{
                Setpgid: true,
        }
        cmd.SysProcAttr = &syscall.SysProcAttr{
                Setpgid: true,
        }
+
        cmd.Cancel = func() error {
                return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
        }
        cmd.Cancel = func() error {
                return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
        }
-       cmd.WaitDelay = toolShellWaitDelay
+
+       cmd.WaitDelay = toolShellOutputTimeout
 
        output := &ToolHeadTail{}
        cmd.Stdout = output
 
        output := &ToolHeadTail{}
        cmd.Stdout = output
@@ -466,106 +602,6 @@ func (b *Toolbox) path(path string) string {
        return filepath.Join(b.workdir, path)
 }
 
        return filepath.Join(b.workdir, path)
 }
 
-// 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"`
-       Name *string `json:"name"`
-}
-
-// ToolDateArguments are the arguments of date.
-type ToolDateArguments struct {
-       ID int `json:"id"`
-}
-
-// ToolShellArguments are the arguments of shell.
-type ToolShellArguments struct {
-       Command string `json:"command"`
-}
-
-// ToolPathArguments are the arguments of read.
-type ToolPathArguments struct {
-       Path string `json:"path"`
-}
-
-// ToolWriteArguments are the arguments of write.
-type ToolWriteArguments struct {
-       Path string `json:"path"`
-       Text string `json:"text"`
-}
-
-// ToolEditArguments are the arguments of edit.
-type ToolEditArguments struct {
-       Path string `json:"path"`
-       Old  string `json:"old"`
-       New  string `json:"new"`
-}
-
-// ToolSpawnArguments are the arguments of spawn. A nil Effort is the effort of
-// the agent.
-type ToolSpawnArguments struct {
-       Tasks  []string `json:"tasks"`
-       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"`
-}
-
-// ToolWhatsAppFindArguments are the arguments of whatsapp_find.
-type ToolWhatsAppFindArguments struct {
-       Name string `json:"name"`
-}
-
-// ToolWhatsAppSendArguments are the arguments of whatsapp_send.
-type ToolWhatsAppSendArguments struct {
-       ID   string `json:"id"`
-       Chat string `json:"chat"`
-       Text string `json:"text"`
-}
-
-// ToolScheduleSetArguments are the arguments of schedule_set. Exactly one of At
-// and Cron is not nil.
-type ToolScheduleSetArguments struct {
-       Text string  `json:"text"`
-       At   *string `json:"at"`
-       Cron *string `json:"cron"`
-}
-
-// ToolScheduleRemoveArguments are the arguments of schedule_remove.
-type ToolScheduleRemoveArguments struct {
-       ID int `json:"id"`
-}
-
-// ToolHeadTail keeps the first and the last toolClipBytes bytes written to it,
-// and counts the bytes between them that it drops.
-type ToolHeadTail struct {
-       head    []byte
-       tail    []byte
-       dropped int
-}
-
 // String returns the output, clipped when it is too long. Invalid UTF-8 becomes
 // the replacement character.
 func (h *ToolHeadTail) String() string {
 // String returns the output, clipped when it is too long. Invalid UTF-8 becomes
 // the replacement character.
 func (h *ToolHeadTail) String() string {