Documentation ¶
Index ¶
- Variables
- func FirstLine(lines []string) string
- func FirstLineBytes(data []byte) string
- func FirstLineString(s string) string
- func RandomCatchphrase() string
- type FileInfo
- type ListFilesParams
- type LookAtImageParams
- type LookAtRealWorldParams
- type RunAppleScriptParams
- type RunPowerShellCmdParams
- type RunPythonParams
- type RunShellCmdParams
- type SliceFileParams
- type SpeakOutLoudParams
- type SpliceFileParams
- type TakeScreenshotParams
Constants ¶
This section is empty.
Variables ¶
View Source
var ListFiles = tools.Func( "List files", "Lists some of the contents in the specified directory. Don't use this on files. Don't use a depth higher than 2 unless you're really sure.", "list_files", func(r tools.Runner, p ListFilesParams) tools.Result { if p.Depth < 1 { p.Depth = 1 } p.Path = expandPath(p.Path) items := make(map[string]FileInfo) entries := 0 err := filepath.WalkDir(p.Path, func(path string, d os.DirEntry, err error) error { if err != nil { return err } relPath, _ := filepath.Rel(p.Path, path) if relPath == "." { return nil } depth := len(strings.Split(relPath, string(os.PathSeparator))) if depth > p.Depth { return filepath.SkipDir } entries++ if entries >= 1_000 { if d.IsDir() { return filepath.SkipDir } else { return nil } } if d.IsDir() { subItems, _ := os.ReadDir(path) items[relPath] = FileInfo{ Type: "directory", Count: len(subItems), ContentsSkipped: depth == p.Depth, } switch d.Name() { case ".git", "node_modules": return filepath.SkipDir } } else { file, _ := os.Open(path) scanner := bufio.NewScanner(file) lines := 0 for scanner.Scan() { lines++ } items[relPath] = FileInfo{ Type: "file", Lines: lines, } file.Close() } return nil }) label := fmt.Sprintf("List files in `%s`", p.Path) if err != nil { return tools.Error(label, err) } result := map[string]any{ "items": items, "totalEntries": entries, } if entries > 1_000 { result["note"] = fmt.Sprintf("There were %d entries, but we could only include 1000.", entries) } return tools.Success(label, result) }, )
View Source
var LookAtImage = tools.Func( "Look at image", "Displays an image from the specified path. Use this to view an image file.", "look_at_image", func(r tools.Runner, p LookAtImageParams) tools.Result { p.Path = expandPath(p.Path) label := fmt.Sprintf("Look at image `%s`", filepath.Base(p.Path)) if _, err := os.Stat(p.Path); os.IsNotExist(err) { return tools.Error(label, fmt.Errorf("file does not exist: %s", p.Path)) } var rb tools.ResultBuilder if err := rb.AddImage(p.Path, p.HighQuality); err != nil { return tools.Error(label, err) } content := map[string]string{ "message": fmt.Sprintf("You will receive %s from the user as an automated message.", filepath.Base(p.Path)), } return rb.Success(label, content) }, )
View Source
var LookAtRealWorld = tools.Func( "Look at real world", "Takes a photo of the real world with the camera. Optionally pans/tilts first (camera has a 360 view). If you're looking for something, search by tilting/panning, taking a low-resolution image, and look at the result. If you don't see what you want, do another search pass, otherwise you can choose to get the high-resolution image if you want to see more.", "look_at_real_world", func(r tools.Runner, p LookAtRealWorldParams) tools.Result { device, err := onvif.NewDevice(onvif.DeviceParams{ Xaddr: os.Getenv("CAMERA_ONVIF"), Username: os.Getenv("CAMERA_USERNAME"), Password: os.Getenv("CAMERA_PASSWORD"), }) if err != nil { return tools.Error("Look at real world", fmt.Errorf("failed to connect to camera: %v", err)) } profile, err := getDefaultProfile(device) if err != nil { return tools.Error("Look at real world", fmt.Errorf("failed to get metadata about camera: %w", err)) } if p.RelativePan != 0 || p.RelativeTilt != 0 { err := relativeMove(device, profile.Token, p.RelativePan, p.RelativeTilt, 0) if err != nil { return tools.Error("Look at real world", fmt.Errorf("failed to pan/tilt camera: %v", err)) } } photoPath, err := takePhoto() if err != nil { return tools.Error("Look at real world", fmt.Errorf("failed to get photo path: %v", err)) } defer os.Remove(photoPath) var rb tools.ResultBuilder rb.AddImage(photoPath, p.HighQuality) return rb.Success("Look at real world", fmt.Sprintf("You will receive the photo (%s) from the user as an automated message.", filepath.Base(photoPath))) }, )
View Source
var RunAppleScript = tools.Func( "Run AppleScript", "Run AppleScript (osascript) on the user's macOS and return the output", "run_apple_script", func(r tools.Runner, p RunAppleScriptParams) tools.Result { if len(p.ScriptLines) == 0 { return tools.Error("Run AppleScript failed", errors.New("missing script lines")) } // Run the shell command and capture the output or error. var args []string for _, line := range p.ScriptLines { args = append(args, "-e", line) } cmd := exec.Command("osascript", args...) output, err := cmd.CombinedOutput() if err != nil { return tools.Error(FirstLine(p.ScriptLines), fmt.Errorf("%w: %s", err, output)) } return tools.Success(FirstLine(p.ScriptLines), map[string]any{ "output": string(output), }) })
View Source
var RunPowerShellCmd = tools.Func( "Run PowerShell command", "Run a shell command on the user's computer (a Windows machine) and return the output", "run_powershell_cmd", func(r tools.Runner, p RunPowerShellCmdParams) tools.Result { cmd := exec.Command("powershell", "-Command", p.Command) output, err := cmd.CombinedOutput() if err != nil { return tools.Error(p.Command, fmt.Errorf("%w: %s", err, output)) } return tools.Success(p.Command, map[string]any{ "output": string(output), }) })
View Source
var RunPython = tools.Func( "Run Python", "Run Python on the user's computer and return the output", "run_python", func(r tools.Runner, p RunPythonParams) tools.Result { if len(p.Statements) == 0 { return tools.Error("Run Python failed", errors.New("missing Python statements")) } pythonExecutable := findPythonExecutable() if pythonExecutable == "" { return tools.Error("Run Python failed", errors.New("could not find Python executable")) } cmd := exec.Command(pythonExecutable) statementsJSON, err := json.Marshal(p.Statements) if err != nil { return tools.Error("Run Python failed", err) } cmd.Stdin = strings.NewReader(fmt.Sprintf(interpreterWrapper, statementsJSON)) output, err := cmd.CombinedOutput() if err != nil { return tools.Error(FirstLine(p.Statements), fmt.Errorf("%w: %s", err, output)) } return tools.Success(FirstLine(p.Statements), map[string]any{ "output": string(output), }) })
View Source
var RunShellCmd = tools.Func( "Run shell command", "Run a shell command on the user's computer and return the output", "run_shell_cmd", func(r tools.Runner, p RunShellCmdParams) tools.Result { cmd := exec.Command("sh", "-c", p.Command) output, err := cmd.CombinedOutput() if err != nil { return tools.Error(p.Command, fmt.Errorf("%w: %s", err, output)) } if len(output) > 1_000 { tmpDstFile, err := os.CreateTemp("", "tmp-") if err != nil { return tools.Error(p.Command, err) } defer tmpDstFile.Close() _, err = tmpDstFile.Write(output) if err != nil { return tools.Error(p.Command, err) } return tools.Success(p.Command, map[string]any{ "outputType": "file", "filePath": tmpDstFile.Name(), "fileSize": len(output), "firstLine": FirstLineBytes(output), "note": "The output was too long to fit here. It's been saved to a file. Prefer to immediately read the most relevant parts of this file instead of telling the user about it.", }) } return tools.Success(p.Command, map[string]any{ "outputType": "text", "output": string(output), }) })
View Source
var SliceFile = tools.Func( "Read file", "Read a slice of the lines in the specified file, if we imagine the file as a zero-indexed array of lines. Returns a JavaScript array value where each line is prefixed with its index in this imaginary array as a comment.", "slice_file", func(r tools.Runner, p SliceFileParams) tools.Result { p.Path = expandPath(p.Path) file, err := os.Open(p.Path) if err != nil { return tools.Error(p.Path, fmt.Errorf("failed to open file: %v", err)) } defer file.Close() var lines []string scanner := bufio.NewScanner(file) for scanner.Scan() { lines = append(lines, scanner.Text()) } if err := scanner.Err(); err != nil { return tools.Error(p.Path, err) } start := p.Start if start < 0 { start = len(lines) + start } if start < 0 { start = 0 } end := len(lines) if p.End != nil { end = *p.End } if end > len(lines) { end = len(lines) } slicedLines := make([]map[string]string, 0, end-start) for i := start; i < end; i++ { line := lines[i] slicedLines = append(slicedLines, map[string]string{ fmt.Sprintf("%d", i): line, }) } remainingLines := len(lines) - end result := map[string]any{ "filePath": p.Path, "slicedLines": slicedLines, "remainingLines": remainingLines, } var description string if end-start < 1 { return tools.Error(p.Path, fmt.Errorf("failed to read line %d from %q", start+1, p.Path)) } else if start == end-1 { description = fmt.Sprintf("Read line %d from %q", start+1, p.Path) } else { description = fmt.Sprintf("Read lines %d-%d from %q", start+1, end, p.Path) } return tools.Success(description, result) })
View Source
var SpeakOutLoud = tools.Func( "Speak out loud", "Speak out loud to the user using TTS", "speak_out_loud", func(r tools.Runner, p SpeakOutLoudParams) tools.Result { tts.Speak(p.Message) numWords := len(reWords.FindAllString(p.Message, -1)) return tools.Success(fmt.Sprintf("Spoke %d words", numWords), map[string]any{"success": true}) })
View Source
var SpliceFile = tools.Func( "Update file", "Delete and/or replace a slice of the lines in the specified file, if we imagine the file as a zero-indexed array of lines.", "splice_file", func(r tools.Runner, p SpliceFileParams) tools.Result { p.Path = expandPath(p.Path) file, err := os.OpenFile(p.Path, os.O_RDWR|os.O_CREATE, 0644) if err != nil { return tools.Error(p.Path, fmt.Errorf("failed to open %q: %w", p.Path, err)) } defer file.Close() var result strings.Builder scanner := bufio.NewScanner(file) var i int for { if i == p.Start { for _, line := range p.InsertLines { result.WriteString(line + "\n") } } if scanner.Scan() { if i < p.Start || i >= p.Start+p.DeleteCount { result.WriteString(scanner.Text() + "\n") } } else if err := scanner.Err(); err != nil { return tools.Error(p.Path, fmt.Errorf("failed to read file: %w", err)) } else if i < p.Start { return tools.Error(p.Path, fmt.Errorf("file has less than %d lines", p.Start+1)) } else { break } i++ } if i > 0 { backupPath := fmt.Sprintf("%s.%d.bak", p.Path, time.Now().Unix()) if err := copyFile(p.Path, backupPath); err != nil { return tools.Error(p.Path, fmt.Errorf("failed to create backup: %w", err)) } } if err := writeFileAtomically(p.Path, strings.NewReader(result.String())); err != nil { return tools.Error(p.Path, fmt.Errorf("failed to write updated content: %w", err)) } var description string var action string if p.DeleteCount > 0 && len(p.InsertLines) > 0 { if p.DeleteCount == len(p.InsertLines) { description = fmt.Sprintf("Replaced %s in %q", line(p.DeleteCount), p.Path) action = "replaced" } else { description = fmt.Sprintf("Replaced %s with %s in %q", line(p.DeleteCount), line(len(p.InsertLines)), p.Path) action = "replaced" } } else if p.DeleteCount > 0 { description = fmt.Sprintf("Deleted %s from %q", line(p.DeleteCount), p.Path) action = "deleted" } else if len(p.InsertLines) > 0 { description = fmt.Sprintf("Added %s to %q", line(len(p.InsertLines)), p.Path) action = "added" } return tools.Success(description, map[string]interface{}{ "path": p.Path, "action": action, "deleteCount": p.DeleteCount, "insertCount": len(p.InsertLines), }) })
View Source
var TakeScreenshot = tools.Func( "Take screenshot", "Takes a screenshot of the user's screen. Use this if the user refers to something you can't see.", "take_screenshot", func(r tools.Runner, params TakeScreenshotParams) tools.Result { screenshotPath := fmt.Sprintf("%s/screenshot_%d.png", os.TempDir(), time.Now().UnixNano()) var cmd *exec.Cmd if runtime.GOOS == "windows" { cmd = exec.Command("powershell", "-command", fmt.Sprintf("Add-Type -AssemblyName System.Windows.Forms; $bmp = New-Object System.Drawing.Bitmap([System.Windows.Forms.SystemInformation]::VirtualScreen.Width, [System.Windows.Forms.SystemInformation]::VirtualScreen.Height); $graph = [System.Drawing.Graphics]::FromImage($bmp); $graph.CopyFromScreen([System.Windows.Forms.SystemInformation]::VirtualScreen.Location, [System.Drawing.Point]::Empty, $bmp.Size); $bmp.Save('%s');", screenshotPath)) } else if runtime.GOOS == "darwin" { cmd = exec.Command("screencapture", "-x", screenshotPath) } else { return tools.Error("Take screenshot", fmt.Errorf("unsupported platform %s", runtime.GOOS)) } output, err := cmd.CombinedOutput() if err != nil { return tools.Error("Take screenshot", fmt.Errorf("%w: %s", err, output)) } defer os.Remove(screenshotPath) var rb tools.ResultBuilder if err := rb.AddImage(screenshotPath, true); err != nil { return tools.Error("Take screenshot", fmt.Errorf("failed to add image: %w", err)) } fileName := filepath.Base(screenshotPath) return rb.Success("Take screenshot", map[string]any{ "message": fmt.Sprintf("You will receive %s from the user as an automated message.", fileName), "fileName": fileName, }) }, )
Functions ¶
func FirstLineBytes ¶
func FirstLineString ¶
func RandomCatchphrase ¶
func RandomCatchphrase() string
Types ¶
type ListFilesParams ¶
type LookAtImageParams ¶
type LookAtRealWorldParams ¶
type LookAtRealWorldParams struct { RelativePan float64 `json:"relative_pan,omitempty" description:"A value from -1.0 (right) to 1.0 (left) indicating how much to pan."` RelativeTilt float64 `json:"relative_tilt,omitempty" description:"A value from -1.0 (up) to 1.0 (down) indicating how much to tilt."` HighQuality bool `json:"high_quality,omitempty" description:"Use true if you want a high-resolution photo."` }
type RunAppleScriptParams ¶
type RunAppleScriptParams struct {
ScriptLines []string `json:"script_lines" description:"One or more statements of valid AppleScript"`
}
type RunPowerShellCmdParams ¶
type RunPowerShellCmdParams struct {
Command string `json:"command"`
}
type RunPythonParams ¶
type RunPythonParams struct {
Statements []string `` /* 132-byte string literal not displayed */
}
type RunShellCmdParams ¶
type RunShellCmdParams struct {
Command string `json:"command"`
}
type SliceFileParams ¶
type SliceFileParams struct { Path string `json:"path" description:"The path to the file to read (don't use this on directories)."` Start int `json:"start" description:"The start index of the slice to get. Can be negative to start from the end."` End *int `` /* 163-byte string literal not displayed */ }
type SpeakOutLoudParams ¶
type SpeakOutLoudParams struct {
Message string `json:"message"`
}
type SpliceFileParams ¶
type SpliceFileParams struct { Path string `json:"path" description:"The path to the file to update."` Start int `json:"start" description:"The start index of the slice to delete and optionally replace."` DeleteCount int `json:"deleteCount,omitempty" description:"The number of lines to delete from the slice."` InsertLines []string `json:"insertLines,omitempty" description:"The lines to insert at the start of the slice."` }
type TakeScreenshotParams ¶
type TakeScreenshotParams struct { }
Click to show internal directories.
Click to hide internal directories.