diff --git a/go.mod b/go.mod index 896d1bef..85f1b8c1 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.6.0 github.com/gptscript-ai/chat-completion-client v0.0.0-20240515050533-bdef9f2226a9 - github.com/gptscript-ai/tui v0.0.0-20240604045848-e01b0b7aab9f + github.com/gptscript-ai/tui v0.0.0-20240604233332-4a5ff43cdc58 github.com/hexops/autogold/v2 v2.2.1 github.com/hexops/valast v1.4.4 github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 @@ -62,7 +62,7 @@ require ( github.com/gookit/color v1.5.4 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/gptscript-ai/go-gptscript v0.0.0-20240604030145-39497c0575b3 // indirect + github.com/gptscript-ai/go-gptscript v0.0.0-20240604231423-7a845df843b1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hexops/autogold v1.3.1 // indirect diff --git a/go.sum b/go.sum index a443d0c7..f3d20688 100644 --- a/go.sum +++ b/go.sum @@ -172,10 +172,10 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gptscript-ai/chat-completion-client v0.0.0-20240515050533-bdef9f2226a9 h1:s6nL/aokB1sJTqVXEjN0zFI5CJa66ubw9g68VTMzEw0= github.com/gptscript-ai/chat-completion-client v0.0.0-20240515050533-bdef9f2226a9/go.mod h1:7P/o6/IWa1KqsntVf68hSnLKuu3+xuqm6lYhch1w4jo= -github.com/gptscript-ai/go-gptscript v0.0.0-20240604030145-39497c0575b3 h1:mXLpCzEg4DoOeFZt6w99QFh9n60UwpRGGG0c+aaT+5k= -github.com/gptscript-ai/go-gptscript v0.0.0-20240604030145-39497c0575b3/go.mod h1:h1yYzC0rgB5Kk7lwdba+Xs6cWkuJfLq6sPRna45OVG0= -github.com/gptscript-ai/tui v0.0.0-20240604045848-e01b0b7aab9f h1:7dCE0E/y3y3p1BPSQGQ4mtsz5cWWl0FbXfCCDCf57SI= -github.com/gptscript-ai/tui v0.0.0-20240604045848-e01b0b7aab9f/go.mod h1:ybD/8QfwaMiK2QUSnnTxgzrIaXRgipowU4pW1qxgNJ8= +github.com/gptscript-ai/go-gptscript v0.0.0-20240604231423-7a845df843b1 h1:SHoqsU8Ne2V4zfrFve9kQn4vcv4N4TItD6Oju+pzKV8= +github.com/gptscript-ai/go-gptscript v0.0.0-20240604231423-7a845df843b1/go.mod h1:h1yYzC0rgB5Kk7lwdba+Xs6cWkuJfLq6sPRna45OVG0= +github.com/gptscript-ai/tui v0.0.0-20240604233332-4a5ff43cdc58 h1:kbr6cY4VdvxAfJf+xk6x/gxDzmgZMkX2ZfLcyskGYsw= +github.com/gptscript-ai/tui v0.0.0-20240604233332-4a5ff43cdc58/go.mod h1:At6zmCk0XrJ2J1yo95fzbEDOGwaPPEMwxxcQGJx7IGE= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= diff --git a/pkg/chat/chat.go b/pkg/chat/chat.go index 35e025f4..a3fdb97a 100644 --- a/pkg/chat/chat.go +++ b/pkg/chat/chat.go @@ -2,6 +2,7 @@ package chat import ( "context" + "os" "github.com/fatih/color" "github.com/gptscript-ai/gptscript/pkg/runner" @@ -30,7 +31,7 @@ func getPrompt(prg types.Program, resp runner.ChatResponse) string { return color.GreenString("%s> ", name) } -func Start(ctx context.Context, prevState runner.ChatState, chatter Chatter, prg GetProgram, env []string, startInput string) error { +func Start(ctx context.Context, prevState runner.ChatState, chatter Chatter, prg GetProgram, env []string, startInput, chatStateSaveFile string) error { var ( prompter Prompter ) @@ -60,7 +61,7 @@ func Start(ctx context.Context, prevState runner.ChatState, chatter Chatter, prg if startInput != "" { input = startInput startInput = "" - } else if targetTool := prg.ToolSet[prg.EntryToolID]; !(prevState == nil && targetTool.Arguments == nil && targetTool.Instructions != "") { + } else if targetTool := prg.ToolSet[prg.EntryToolID]; !((prevState == nil || prevState == "") && targetTool.Arguments == nil && targetTool.Instructions != "") { // The above logic will skip prompting if this is the first loop and the chat expects no args input, ok, err = prompter.Readline() if !ok || err != nil { @@ -69,9 +70,15 @@ func Start(ctx context.Context, prevState runner.ChatState, chatter Chatter, prg } resp, err = chatter.Chat(ctx, prevState, prg, env, input) - if err != nil || resp.Done { + if err != nil { return err } + if resp.Done { + if chatStateSaveFile != "" { + _ = os.Remove(chatStateSaveFile) + } + return nil + } if resp.Content != "" { _, err := prompter.Printf("%s", color.RedString("< %s\n", resp.Content)) @@ -80,6 +87,10 @@ func Start(ctx context.Context, prevState runner.ChatState, chatter Chatter, prg } } + if chatStateSaveFile != "" { + _ = os.WriteFile(chatStateSaveFile, []byte(resp.Content), 0600) + } + prevState = resp.State prevResp = resp } diff --git a/pkg/cli/eval.go b/pkg/cli/eval.go index 0f9d0f85..db84dd6b 100644 --- a/pkg/cli/eval.go +++ b/pkg/cli/eval.go @@ -76,7 +76,7 @@ func (e *Eval) Run(cmd *cobra.Command, args []string) error { if e.Chat { return chat.Start(cmd.Context(), nil, runner, func() (types.Program, error) { return prg, nil - }, os.Environ(), toolInput) + }, os.Environ(), toolInput, "") } toolOutput, err := runner.Run(cmd.Context(), prg, opts.Env, toolInput) diff --git a/pkg/cli/gptscript.go b/pkg/cli/gptscript.go index d2515673..7e677289 100644 --- a/pkg/cli/gptscript.go +++ b/pkg/cli/gptscript.go @@ -65,12 +65,13 @@ type GPTScript struct { Ports string `usage:"The port range to use for ephemeral daemon ports (ex: 11000-12000)" hidden:"true"` CredentialContext string `usage:"Context name in which to store credentials" default:"default"` CredentialOverride string `usage:"Credentials to override (ex: --credential-override github.com/example/cred-tool:API_TOKEN=1234)"` - ChatState string `usage:"The chat state to continue, or null to start a new chat and return the state"` - ForceChat bool `usage:"Force an interactive chat session if even the top level tool is not a chat tool"` - ForceSequential bool `usage:"Force parallel calls to run sequentially"` + ChatState string `usage:"The chat state to continue, or null to start a new chat and return the state" local:"true"` + ForceChat bool `usage:"Force an interactive chat session if even the top level tool is not a chat tool" local:"true"` + ForceSequential bool `usage:"Force parallel calls to run sequentially" local:"true"` Workspace string `usage:"Directory to use for the workspace, if specified it will not be deleted on exit"` UI bool `usage:"Launch the UI" local:"true" name:"ui"` DisableTUI bool `usage:"Don't use chat TUI but instead verbose output" local:"true" name:"disable-tui"` + SaveChatStateFile string `usage:"A file to save the chat state to so that a conversation can be resumed with --chat-state" local:"true"` readData []byte } @@ -425,8 +426,18 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { return err } - if r.ChatState != "" { - resp, err := gptScript.Chat(cmd.Context(), r.ChatState, prg, gptOpt.Env, toolInput) + var chatState string + if r.ChatState != "" && r.ChatState != "null" && !strings.HasPrefix(r.ChatState, "{") { + data, err := os.ReadFile(r.ChatState) + if err != nil { + return fmt.Errorf("reading %s: %w", r.ChatState, err) + } + chatState = string(data) + } + + // This chat in a stateless mode + if r.SaveChatStateFile == "-" || r.SaveChatStateFile == "stdout" { + resp, err := gptScript.Chat(cmd.Context(), chatState, prg, gptOpt.Env, toolInput) if err != nil { return err } @@ -446,11 +457,13 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) (retErr error) { CacheDir: r.CacheDir, SubTool: r.SubTool, Workspace: r.Workspace, + SaveChatStateFile: r.SaveChatStateFile, + ChatState: chatState, }) } - return chat.Start(cmd.Context(), nil, gptScript, func() (types.Program, error) { + return chat.Start(cmd.Context(), chatState, gptScript, func() (types.Program, error) { return r.readProgram(ctx, gptScript, args) - }, gptOpt.Env, toolInput) + }, gptOpt.Env, toolInput, r.SaveChatStateFile) } s, err := gptScript.Run(cmd.Context(), prg, gptOpt.Env, toolInput) diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 57ea7b38..20a20ec8 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -132,7 +132,7 @@ func (r *Runner) Chat(ctx context.Context, prevState ChatState, prg types.Progra case *State: state = v case string: - if v != "null" { + if v != "null" && v != "" { state = &State{} if err := json.Unmarshal([]byte(v), state); err != nil { return resp, fmt.Errorf("failed to unmarshal chat state: %w", err)