From 80d0e9564c2d98c4f55b77b5d15d74e5242aa343 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Wed, 3 Apr 2024 18:51:50 -0700 Subject: [PATCH 1/2] feat: add sys.ls --- pkg/builtin/builtin.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pkg/builtin/builtin.go b/pkg/builtin/builtin.go index 2cbe6c7e..8e950c97 100644 --- a/pkg/builtin/builtin.go +++ b/pkg/builtin/builtin.go @@ -25,6 +25,14 @@ import ( ) var tools = map[string]types.Tool{ + "sys.ls": { + Parameters: types.Parameters{ + Description: "Lists the contents of a directory", + Arguments: types.ObjectSchema( + "dir", "The directory to list"), + }, + BuiltinFunc: SysLs, + }, "sys.read": { Parameters: types.Parameters{ Description: "Reads the contents of a file", @@ -268,6 +276,37 @@ func SysExec(ctx context.Context, env []string, input string) (string, error) { return string(out), err } +func SysLs(_ context.Context, _ []string, input string) (string, error) { + var params struct { + Dir string `json:"dir,omitempty"` + } + if err := json.Unmarshal([]byte(input), ¶ms); err != nil { + return "", err + } + + if params.Dir == "" { + params.Dir = "." + } + + entries, err := os.ReadDir(params.Dir) + if errors.Is(err, fs.ErrNotExist) { + return "", fmt.Errorf("directory does not exist: %s", params.Dir) + } else if err != nil { + return "", err + } + + var result []string + for _, entry := range entries { + if entry.IsDir() { + result = append(result, entry.Name()+"/") + } else { + result = append(result, entry.Name()) + } + } + + return strings.Join(result, "\n"), nil +} + func SysRead(ctx context.Context, env []string, input string) (string, error) { var params struct { Filename string `json:"filename,omitempty"` From 4ca1bca00a4917bc2964ba717091167eda0ad5c1 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Thu, 4 Apr 2024 11:08:33 -0700 Subject: [PATCH 2/2] change: add hidden daemon and ports options --- pkg/builtin/builtin.go | 9 +++++++-- pkg/cli/eval.go | 6 +++++- pkg/cli/gptscript.go | 43 ++++++++++++++++++++++++++++++++++++++---- pkg/engine/daemon.go | 5 +++++ pkg/runner/runner.go | 23 ++++++++++++++++++++-- 5 files changed, 77 insertions(+), 9 deletions(-) diff --git a/pkg/builtin/builtin.go b/pkg/builtin/builtin.go index 8e950c97..e696e4c1 100644 --- a/pkg/builtin/builtin.go +++ b/pkg/builtin/builtin.go @@ -290,7 +290,7 @@ func SysLs(_ context.Context, _ []string, input string) (string, error) { entries, err := os.ReadDir(params.Dir) if errors.Is(err, fs.ErrNotExist) { - return "", fmt.Errorf("directory does not exist: %s", params.Dir) + return fmt.Sprintf("directory does not exist: %s", params.Dir), nil } else if err != nil { return "", err } @@ -321,10 +321,15 @@ func SysRead(ctx context.Context, env []string, input string) (string, error) { log.Debugf("Reading file %s", params.Filename) data, err := os.ReadFile(params.Filename) - if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return fmt.Sprintf("The file %s does not exist", params.Filename), nil + } else if err != nil { return "", err } + if len(data) == 0 { + return fmt.Sprintf("The file %s has no contents", params.Filename), nil + } return string(data), nil } diff --git a/pkg/cli/eval.go b/pkg/cli/eval.go index bb7e57c8..c9491177 100644 --- a/pkg/cli/eval.go +++ b/pkg/cli/eval.go @@ -51,7 +51,11 @@ func (e *Eval) Run(cmd *cobra.Command, args []string) error { return err } - opts := e.gptscript.NewGPTScriptOpts() + opts, err := e.gptscript.NewGPTScriptOpts() + if err != nil { + return err + } + runner, err := gptscript.New(&opts) if err != nil { return err diff --git a/pkg/cli/gptscript.go b/pkg/cli/gptscript.go index fef354f2..dc91adf8 100644 --- a/pkg/cli/gptscript.go +++ b/pkg/cli/gptscript.go @@ -6,6 +6,7 @@ import ( "io" "os" "sort" + "strconv" "strings" "github.com/acorn-io/cmd" @@ -50,6 +51,8 @@ type GPTScript struct { Server bool `usage:"Start server" local:"true"` ListenAddress string `usage:"Server listen address" default:"127.0.0.1:9090" local:"true"` Chdir string `usage:"Change current working directory" short:"C"` + Daemon bool `usage:"Run tool as a daemon" local:"true" hidden:"true"` + Ports string `usage:"The port range to use for ephemeral daemon ports (ex: 11000-12000)" hidden:"true"` } func New() *cobra.Command { @@ -67,14 +70,33 @@ func (r *GPTScript) NewRunContext(cmd *cobra.Command) context.Context { return ctx } -func (r *GPTScript) NewGPTScriptOpts() gptscript.Options { - return gptscript.Options{ +func (r *GPTScript) NewGPTScriptOpts() (gptscript.Options, error) { + opts := gptscript.Options{ Cache: cache.Options(r.CacheOptions), OpenAI: openai.Options(r.OpenAIOptions), Monitor: monitor.Options(r.DisplayOptions), Quiet: r.Quiet, Env: os.Environ(), } + + if r.Ports != "" { + start, end, _ := strings.Cut(r.Ports, "-") + startNum, err := strconv.ParseInt(strings.TrimSpace(start), 10, 64) + if err != nil { + return gptscript.Options{}, fmt.Errorf("invalid port range: %s", r.Ports) + } + var endNum int64 + if end != "" { + endNum, err = strconv.ParseInt(strings.TrimSpace(end), 10, 64) + if err != nil { + return gptscript.Options{}, fmt.Errorf("invalid port range: %s", r.Ports) + } + } + opts.Runner.StartPort = startNum + opts.Runner.EndPort = endNum + } + + return opts, nil } func (r *GPTScript) Customize(cmd *cobra.Command) { @@ -205,8 +227,12 @@ func (r *GPTScript) PrintOutput(toolInput, toolOutput string) (err error) { return } -func (r *GPTScript) Run(cmd *cobra.Command, args []string) error { - gptOpt := r.NewGPTScriptOpts() +func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { + gptOpt, err := r.NewGPTScriptOpts() + if err != nil { + return err + } + ctx := cmd.Context() if r.Server { @@ -236,6 +262,15 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) error { return err } + if r.Daemon { + prg = prg.SetBlocking() + defer func() { + if retErr == nil { + <-ctx.Done() + } + }() + } + if r.ListTools { return r.listTools(ctx, gptScript, prg) } diff --git a/pkg/engine/daemon.go b/pkg/engine/daemon.go index a2911512..c5863833 100644 --- a/pkg/engine/daemon.go +++ b/pkg/engine/daemon.go @@ -25,6 +25,11 @@ type Ports struct { daemonWG sync.WaitGroup } +func (p *Ports) SetPorts(start, end int64) { + p.startPort = start + p.endPort = end +} + func (p *Ports) CloseDaemons() { p.daemonLock.Lock() if p.daemonCtx == nil { diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 77636076..c717c78b 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -26,16 +26,26 @@ type Monitor interface { type Options struct { MonitorFactory MonitorFactory `usage:"-"` RuntimeManager engine.RuntimeManager `usage:"-"` + StartPort int64 `usage:"-"` + EndPort int64 `usage:"-"` } func complete(opts ...Options) (result Options) { for _, opt := range opts { result.MonitorFactory = types.FirstSet(opt.MonitorFactory, result.MonitorFactory) result.RuntimeManager = types.FirstSet(opt.RuntimeManager, result.RuntimeManager) + result.StartPort = types.FirstSet(opt.StartPort, result.StartPort) + result.EndPort = types.FirstSet(opt.EndPort, result.EndPort) } if result.MonitorFactory == nil { result.MonitorFactory = noopFactory{} } + if result.EndPort == 0 { + result.EndPort = result.StartPort + } + if result.StartPort == 0 { + result.StartPort = result.EndPort + } return } @@ -49,11 +59,20 @@ type Runner struct { func New(client engine.Model, opts ...Options) (*Runner, error) { opt := complete(opts...) - return &Runner{ + runner := &Runner{ c: client, factory: opt.MonitorFactory, runtimeManager: opt.RuntimeManager, - }, nil + } + + if opt.StartPort != 0 { + if opt.EndPort < opt.StartPort { + return nil, fmt.Errorf("invalid port range: %d-%d", opt.StartPort, opt.EndPort) + } + runner.ports.SetPorts(opt.StartPort, opt.EndPort) + } + + return runner, nil } func (r *Runner) Close() {