X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/723f6e18adc6482ed913e1618c8a76e981720c2b..80e348724041a694002fc38f57e8868b9b9a2be9:/tools.go?ds=inline diff --git a/tools.go b/tools.go index a75514f..11e208d 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. @@ -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 -// 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 @@ -68,13 +68,123 @@ type Toolbox struct { 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 -// 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) @@ -90,12 +200,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. @@ -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 "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 @@ -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: - 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 @@ -363,7 +497,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 @@ -466,106 +600,6 @@ 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 -// 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 {