Skip to content

chore: allow nested gptscript to run sys.chat.finish #514

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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-20240531200700-af8e7ecf0379
github.com/gptscript-ai/tui v0.0.0-20240614062633-985091711b0a
github.com/gptscript-ai/tui v0.0.0-20240618230843-2b5961f3341b
github.com/hexops/autogold/v2 v2.2.1
github.com/hexops/valast v1.4.4
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ github.com/gptscript-ai/chat-completion-client v0.0.0-20240531200700-af8e7ecf037
github.com/gptscript-ai/chat-completion-client v0.0.0-20240531200700-af8e7ecf0379/go.mod h1:7P/o6/IWa1KqsntVf68hSnLKuu3+xuqm6lYhch1w4jo=
github.com/gptscript-ai/go-gptscript v0.0.0-20240613214812-8111c2b02d71 h1:WehkkausLuXI91ePpIVrzZ6eBmfFIU/HfNsSA1CHiwo=
github.com/gptscript-ai/go-gptscript v0.0.0-20240613214812-8111c2b02d71/go.mod h1:Dh6vYRAiVcyC3ElZIGzTvNF1FxtYwA07BHfSiFKQY7s=
github.com/gptscript-ai/tui v0.0.0-20240614062633-985091711b0a h1:LFsEDiIAx0Rq0V6aOMlRjXMMIfkA3uEhqqqjoggLlDQ=
github.com/gptscript-ai/tui v0.0.0-20240614062633-985091711b0a/go.mod h1:ZlyM+BRiD6mV04w+Xw2mXP1VKGEUbn8BvwrosWlplUo=
github.com/gptscript-ai/tui v0.0.0-20240618230843-2b5961f3341b h1:OJfmpDQ/6ffz5P4UdJJEd5xeqo2dfWnsg1YZLDqJWYo=
github.com/gptscript-ai/tui v0.0.0-20240618230843-2b5961f3341b/go.mod h1:ZlyM+BRiD6mV04w+Xw2mXP1VKGEUbn8BvwrosWlplUo=
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=
Expand Down
12 changes: 2 additions & 10 deletions pkg/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,14 +593,6 @@ func SysGetenv(_ context.Context, env []string, input string) (string, error) {
return value, nil
}

type ErrChatFinish struct {
Message string
}

func (e *ErrChatFinish) Error() string {
return fmt.Sprintf("CHAT FINISH: %s", e.Message)
}

func invalidArgument(input string, err error) string {
return fmt.Sprintf("Failed to parse arguments %s: %v", input, err)
}
Expand Down Expand Up @@ -640,11 +632,11 @@ func SysChatFinish(_ context.Context, _ []string, input string) (string, error)
Message string `json:"return,omitempty"`
}
if err := json.Unmarshal([]byte(input), &params); err != nil {
return "", &ErrChatFinish{
return "", &engine.ErrChatFinish{
Message: input,
}
}
return "", &ErrChatFinish{
return "", &engine.ErrChatFinish{
Message: params.Message,
}
}
Expand Down
27 changes: 24 additions & 3 deletions pkg/engine/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ var requiredFileExtensions = map[string]string{
"powershell": "*.ps1",
}

type outputWriter struct {
id string
progress chan<- types.CompletionStatus
buf bytes.Buffer
}

func (o *outputWriter) Write(p []byte) (n int, err error) {
o.buf.Write(p)
o.progress <- types.CompletionStatus{
CompletionID: o.id,
PartialResponse: &types.CompletionMessage{
Role: types.CompletionMessageRoleTypeAssistant,
Content: types.Text(o.buf.String()),
},
}
return len(p), nil
}

func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCategory ToolCategory) (cmdOut string, cmdErr error) {
id := counter.Next()

Expand Down Expand Up @@ -74,7 +92,10 @@ func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCate
output := &bytes.Buffer{}
all := &bytes.Buffer{}
cmd.Stderr = io.MultiWriter(all, os.Stderr)
cmd.Stdout = io.MultiWriter(all, output)
cmd.Stdout = io.MultiWriter(all, output, &outputWriter{
id: id,
progress: e.Progress,
})

if err := cmd.Run(); err != nil {
if toolCategory == NoCategory {
Expand All @@ -85,7 +106,7 @@ func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCate
return "", fmt.Errorf("ERROR: %s: %w", all, err)
}

return output.String(), nil
return output.String(), IsChatFinishMessage(output.String())
}

func (e *Engine) getRuntimeEnv(ctx context.Context, tool types.Tool, cmd, env []string) ([]string, error) {
Expand Down Expand Up @@ -161,7 +182,7 @@ func appendInputAsEnv(env []string, input string) []string {
}
}

env = appendEnv(env, "GPTSCRIPT_INPUT", input)
env = appendEnv(env, "GPTSCRIPT_INPUT_CONTENT", input)
return env
}

Expand Down
16 changes: 16 additions & 0 deletions pkg/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Model interface {

type RuntimeManager interface {
GetContext(ctx context.Context, tool types.Tool, cmd, env []string) (string, []string, error)
EnsureCredentialHelpers(ctx context.Context) error
SetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig, env []string) error
}

Expand Down Expand Up @@ -105,6 +106,21 @@ type InputContext struct {
Content string `json:"content,omitempty"`
}

type ErrChatFinish struct {
Message string
}

func (e *ErrChatFinish) Error() string {
return fmt.Sprintf("CHAT FINISH: %s", e.Message)
}

func IsChatFinishMessage(msg string) error {
if msg, ok := strings.CutPrefix(msg, "CHAT FINISH: "); ok {
return &ErrChatFinish{Message: msg}
}
return nil
}

func (c *Context) ParentID() string {
if c.Parent == nil {
return ""
Expand Down
10 changes: 10 additions & 0 deletions pkg/input/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/gptscript-ai/gptscript/pkg/loader"
"github.com/gptscript-ai/gptscript/pkg/types"
)

func FromArgs(args []string) string {
Expand All @@ -31,6 +33,14 @@ func FromFile(file string) (string, error) {
}
return string(data), nil
} else if file != "" {
if s, err := os.Stat(file); err == nil && s.IsDir() {
for _, ext := range types.DefaultFiles {
if _, err := os.Stat(filepath.Join(file, ext)); err == nil {
file = filepath.Join(file, ext)
break
}
}
}
log.Debugf("reading file %s", file)
data, err := os.ReadFile(file)
if err != nil {
Expand Down
30 changes: 12 additions & 18 deletions pkg/monitor/display.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,22 @@ import (
)

type Options struct {
DisplayProgress bool `usage:"-"`
DumpState string `usage:"Dump the internal execution state to a file"`
DebugMessages bool `usage:"Enable logging of chat completion calls"`
DumpState string `usage:"Dump the internal execution state to a file"`
DebugMessages bool `usage:"Enable logging of chat completion calls"`
}

func Complete(opts ...Options) (result Options) {
for _, opt := range opts {
result.DumpState = types.FirstSet(opt.DumpState, result.DumpState)
result.DisplayProgress = types.FirstSet(opt.DisplayProgress, result.DisplayProgress)
result.DebugMessages = types.FirstSet(opt.DebugMessages, result.DebugMessages)
}
return
}

type Console struct {
dumpState string
displayProgress bool
printMessages bool
callLock sync.Mutex
dumpState string
printMessages bool
callLock sync.Mutex
}

var (
Expand All @@ -47,7 +44,7 @@ var (

func (c *Console) Start(_ context.Context, prg *types.Program, _ []string, input string) (runner.Monitor, error) {
id := counter.Next()
mon := newDisplay(c.dumpState, c.displayProgress, c.printMessages)
mon := newDisplay(c.dumpState, c.printMessages)
mon.callLock = &c.callLock
mon.dump.ID = fmt.Sprint(id)
mon.dump.Program = prg
Expand Down Expand Up @@ -315,23 +312,20 @@ func (d *display) Stop(output string, err error) {
func NewConsole(opts ...Options) *Console {
opt := Complete(opts...)
return &Console{
dumpState: opt.DumpState,
displayProgress: opt.DisplayProgress,
printMessages: opt.DebugMessages,
dumpState: opt.DumpState,
printMessages: opt.DebugMessages,
}
}

func newDisplay(dumpState string, progress, printMessages bool) *display {
func newDisplay(dumpState string, printMessages bool) *display {
display := &display{
dumpState: dumpState,
callIDMap: make(map[string]string),
printMessages: printMessages,
}
if progress {
display.livePrinter = &livePrinter{
lastContent: map[string]string{},
callIDMap: display.callIDMap,
}
display.livePrinter = &livePrinter{
lastContent: map[string]string{},
callIDMap: display.callIDMap,
}
return display
}
Expand Down
20 changes: 20 additions & 0 deletions pkg/mvl/log.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mvl

import (
"context"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -156,6 +157,25 @@ func (l *Logger) Fields(kv ...any) *Logger {
}
}

type InfoLogger interface {
Infof(msg string, args ...any)
}

type infoKey struct{}

func WithInfo(ctx context.Context, logger InfoLogger) context.Context {
return context.WithValue(ctx, infoKey{}, logger)
}

func (l *Logger) InfofCtx(ctx context.Context, msg string, args ...any) {
il, ok := ctx.Value(infoKey{}).(InfoLogger)
if ok {
il.Infof(msg, args...)
return
}
l.log.WithFields(l.fields).Infof(msg, args...)
}

func (l *Logger) Infof(msg string, args ...any) {
l.log.WithFields(l.fields).Infof(msg, args...)
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/openai/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,8 @@ func appendMessage(msg types.CompletionMessage, response openai.ChatCompletionSt
if tc.ToolCall.Function.Name != tool.Function.Name {
tc.ToolCall.Function.Name += tool.Function.Name
}
// OpenAI like to sometimes add this prefix for no good reason
tc.ToolCall.Function.Name = strings.TrimPrefix(tc.ToolCall.Function.Name, "namespace.")
tc.ToolCall.Function.Arguments += tool.Function.Arguments

msg.Content[idx] = tc
Expand Down
46 changes: 40 additions & 6 deletions pkg/repos/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"

"github.com/BurntSushi/locker"
"github.com/gptscript-ai/gptscript/pkg/config"
Expand Down Expand Up @@ -43,11 +44,19 @@ func (n noopRuntime) Setup(_ context.Context, _, _ string, _ []string) ([]string
}

type Manager struct {
storageDir string
gitDir string
runtimeDir string
credHelperDirs credentials.CredentialHelperDirs
runtimes []Runtime
storageDir string
gitDir string
runtimeDir string
credHelperDirs credentials.CredentialHelperDirs
runtimes []Runtime
credHelperConfig *credHelperConfig
}

type credHelperConfig struct {
lock sync.Mutex
initialized bool
cliCfg *config.CLIConfig
env []string
}

func New(cacheDir string, runtimes ...Runtime) *Manager {
Expand All @@ -61,7 +70,32 @@ func New(cacheDir string, runtimes ...Runtime) *Manager {
}
}

func (m *Manager) SetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig, env []string) error {
func (m *Manager) EnsureCredentialHelpers(ctx context.Context) error {
if m.credHelperConfig == nil {
return nil
}
m.credHelperConfig.lock.Lock()
defer m.credHelperConfig.lock.Unlock()

if !m.credHelperConfig.initialized {
if err := m.deferredSetUpCredentialHelpers(ctx, m.credHelperConfig.cliCfg, m.credHelperConfig.env); err != nil {
return err
}
m.credHelperConfig.initialized = true
}

return nil
}

func (m *Manager) SetUpCredentialHelpers(_ context.Context, cliCfg *config.CLIConfig, env []string) error {
m.credHelperConfig = &credHelperConfig{
cliCfg: cliCfg,
env: env,
}
return nil
}

func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig, env []string) error {
var (
helperName = cliCfg.CredentialsStore
suffix string
Expand Down
6 changes: 3 additions & 3 deletions pkg/repos/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func Checkout(ctx context.Context, base, repo, commit, toDir string) error {
return err
}

log.Infof("Checking out %s to %s", commit, toDir)
log.InfofCtx(ctx, "Checking out %s to %s", commit, toDir)
return gitWorktreeAdd(ctx, gitDir(base, repo), toDir, commit)
}

Expand All @@ -46,11 +46,11 @@ func Fetch(ctx context.Context, base, repo, commit string) error {
if found, err := exists(gitDir); err != nil {
return err
} else if !found {
log.Infof("Cloning %s", repo)
log.InfofCtx(ctx, "Cloning %s", repo)
if err := cloneBare(ctx, repo, gitDir); err != nil {
return err
}
}
log.Infof("Fetching %s at %s", commit, repo)
log.InfofCtx(ctx, "Fetching %s at %s", commit, repo)
return fetchCommit(ctx, gitDir, commit)
}
6 changes: 3 additions & 3 deletions pkg/repos/runtimes/golang/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (r *Runtime) BuildCredentialHelper(ctx context.Context, helperName string,
}
newEnv := runtimeEnv.AppendPath(env, binPath)

log.Infof("Building credential helper %s", helperName)
log.InfofCtx(ctx, "Building credential helper %s", helperName)
cmd := debugcmd.New(ctx, filepath.Join(binPath, "go"),
"build", "-buildvcs=false", "-o",
filepath.Join(credHelperDirs.BinDir, "gptscript-credential-"+helperName+suffix),
Expand Down Expand Up @@ -103,7 +103,7 @@ func stripGo(env []string) (result []string) {
}

func (r *Runtime) runBuild(ctx context.Context, toolSource, binDir string, env []string) error {
log.Infof("Running go build in %s", toolSource)
log.InfofCtx(ctx, "Running go build in %s", toolSource)
cmd := debugcmd.New(ctx, filepath.Join(binDir, "go"), "build", "-buildvcs=false", "-o", artifactName())
cmd.Env = stripGo(env)
cmd.Dir = toolSource
Expand Down Expand Up @@ -134,7 +134,7 @@ func (r *Runtime) getRuntime(ctx context.Context, cwd string) (string, error) {
return "", err
}

log.Infof("Downloading Go %s", r.Version)
log.InfofCtx(ctx, "Downloading Go %s", r.Version)
tmp := target + ".download"
defer os.RemoveAll(tmp)

Expand Down
Loading