diff --git a/examples/bob.gpt b/examples/bob.gpt new file mode 100644 index 00000000..6f828e97 --- /dev/null +++ b/examples/bob.gpt @@ -0,0 +1,9 @@ +tools: bob + +Ask Bob how he is doing and let me know exactly what he said. + +--- +name: bob +description: I'm Bob, a friendly guy. + +When asked how I am doing, respond with "I'm doing great fellow friendly AI tool!" diff --git a/examples/sqlite-download.gpt b/examples/sqlite-download.gpt new file mode 100644 index 00000000..6364bc35 --- /dev/null +++ b/examples/sqlite-download.gpt @@ -0,0 +1,9 @@ +tools: sys.download, sys.exec + +Download https://www.sqlitetutorial.net/wp-content/uploads/2018/03/chinook.zip to the +local directory. Expand the archive as there is a sqlite database in it. + +First inspect the schema of the database to understand the table structure. + +Form and run a SQL query to find the artist with the most number of albums and output +the result of that. diff --git a/pkg/builtin/builtin.go b/pkg/builtin/builtin.go index 7b132605..c20aace9 100644 --- a/pkg/builtin/builtin.go +++ b/pkg/builtin/builtin.go @@ -3,6 +3,7 @@ package builtin import ( "context" "encoding/json" + "errors" "fmt" "io" "io/fs" @@ -80,6 +81,13 @@ var tools = map[string]types.Tool{ "name", "The environment variable name to lookup"), BuiltinFunc: SysGetenv, }, + "sys.download": { + Description: "Downloads a URL, saving the contents to disk at a given location", + Arguments: types.ObjectSchema( + "url", "The URL to download, either http or https.", + "location", "(optional) The on disk location to store the file. If no location is specified a temp location will be used. If the target file already exists it will not be overwritten and will fail."), + BuiltinFunc: SysDownload, + }, } func SysProgram() *types.Program { @@ -308,3 +316,60 @@ func SysAbort(ctx context.Context, env []string, input string) (string, error) { } return "", fmt.Errorf("ABORT: %s", params.Message) } + +func SysDownload(ctx context.Context, env []string, input string) (string, error) { + var params struct { + URL string `json:"url,omitempty"` + Location string `json:"location,omitempty"` + } + if err := json.Unmarshal([]byte(input), ¶ms); err != nil { + return "", err + } + + checkExists := true + if params.Location == "" { + f, err := os.CreateTemp("", "gpt-download") + if err != nil { + return "", err + } + if err := f.Close(); err != nil { + return "", err + } + checkExists = false + params.Location = f.Name() + } + + if checkExists { + if _, err := os.Stat(params.Location); err == nil { + return "", fmt.Errorf("file %s already exists and can not be overwritten: %w", params.Location, err) + } else if err != nil && !errors.Is(err, fs.ErrNotExist) { + return "", err + } + } + + log.Infof("download [%s] to [%s]", params.URL, params.Location) + resp, err := http.Get(params.URL) + if err != nil { + return "", err + } + defer func() { + _, _ = io.ReadAll(resp.Body) + _ = resp.Body.Close() + }() + + if resp.StatusCode > 299 { + return "", fmt.Errorf("invalid status code [%d] downloading [%s]: %s", resp.StatusCode, params.URL, resp.Status) + } + + f, err := os.Create(params.Location) + if err != nil { + return "", fmt.Errorf("failed to create [%s]: %w", params.Location, err) + } + defer f.Close() + + if _, err := io.Copy(f, resp.Body); err != nil { + return "", fmt.Errorf("failed copying data from [%s] to [%s]: %w", params.URL, params.Location, err) + } + + return params.Location, nil +}