Skip to content

Commit e06fa92

Browse files
Merge pull request #514 from ibuildthecloud/main
chore: allow nested gptscript to run sys.chat.finish
2 parents 837736a + e83a215 commit e06fa92

40 files changed

+507
-141
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ require (
1616
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
1717
github.com/google/uuid v1.6.0
1818
github.com/gptscript-ai/chat-completion-client v0.0.0-20240531200700-af8e7ecf0379
19-
github.com/gptscript-ai/tui v0.0.0-20240614062633-985091711b0a
19+
github.com/gptscript-ai/tui v0.0.0-20240618230843-2b5961f3341b
2020
github.com/hexops/autogold/v2 v2.2.1
2121
github.com/hexops/valast v1.4.4
2222
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ github.com/gptscript-ai/chat-completion-client v0.0.0-20240531200700-af8e7ecf037
173173
github.com/gptscript-ai/chat-completion-client v0.0.0-20240531200700-af8e7ecf0379/go.mod h1:7P/o6/IWa1KqsntVf68hSnLKuu3+xuqm6lYhch1w4jo=
174174
github.com/gptscript-ai/go-gptscript v0.0.0-20240613214812-8111c2b02d71 h1:WehkkausLuXI91ePpIVrzZ6eBmfFIU/HfNsSA1CHiwo=
175175
github.com/gptscript-ai/go-gptscript v0.0.0-20240613214812-8111c2b02d71/go.mod h1:Dh6vYRAiVcyC3ElZIGzTvNF1FxtYwA07BHfSiFKQY7s=
176-
github.com/gptscript-ai/tui v0.0.0-20240614062633-985091711b0a h1:LFsEDiIAx0Rq0V6aOMlRjXMMIfkA3uEhqqqjoggLlDQ=
177-
github.com/gptscript-ai/tui v0.0.0-20240614062633-985091711b0a/go.mod h1:ZlyM+BRiD6mV04w+Xw2mXP1VKGEUbn8BvwrosWlplUo=
176+
github.com/gptscript-ai/tui v0.0.0-20240618230843-2b5961f3341b h1:OJfmpDQ/6ffz5P4UdJJEd5xeqo2dfWnsg1YZLDqJWYo=
177+
github.com/gptscript-ai/tui v0.0.0-20240618230843-2b5961f3341b/go.mod h1:ZlyM+BRiD6mV04w+Xw2mXP1VKGEUbn8BvwrosWlplUo=
178178
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
179179
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
180180
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=

pkg/builtin/builtin.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -593,14 +593,6 @@ func SysGetenv(_ context.Context, env []string, input string) (string, error) {
593593
return value, nil
594594
}
595595

596-
type ErrChatFinish struct {
597-
Message string
598-
}
599-
600-
func (e *ErrChatFinish) Error() string {
601-
return fmt.Sprintf("CHAT FINISH: %s", e.Message)
602-
}
603-
604596
func invalidArgument(input string, err error) string {
605597
return fmt.Sprintf("Failed to parse arguments %s: %v", input, err)
606598
}
@@ -640,11 +632,11 @@ func SysChatFinish(_ context.Context, _ []string, input string) (string, error)
640632
Message string `json:"return,omitempty"`
641633
}
642634
if err := json.Unmarshal([]byte(input), &params); err != nil {
643-
return "", &ErrChatFinish{
635+
return "", &engine.ErrChatFinish{
644636
Message: input,
645637
}
646638
}
647-
return "", &ErrChatFinish{
639+
return "", &engine.ErrChatFinish{
648640
Message: params.Message,
649641
}
650642
}

pkg/engine/cmd.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ var requiredFileExtensions = map[string]string{
2525
"powershell": "*.ps1",
2626
}
2727

28+
type outputWriter struct {
29+
id string
30+
progress chan<- types.CompletionStatus
31+
buf bytes.Buffer
32+
}
33+
34+
func (o *outputWriter) Write(p []byte) (n int, err error) {
35+
o.buf.Write(p)
36+
o.progress <- types.CompletionStatus{
37+
CompletionID: o.id,
38+
PartialResponse: &types.CompletionMessage{
39+
Role: types.CompletionMessageRoleTypeAssistant,
40+
Content: types.Text(o.buf.String()),
41+
},
42+
}
43+
return len(p), nil
44+
}
45+
2846
func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCategory ToolCategory) (cmdOut string, cmdErr error) {
2947
id := counter.Next()
3048

@@ -74,7 +92,10 @@ func (e *Engine) runCommand(ctx Context, tool types.Tool, input string, toolCate
7492
output := &bytes.Buffer{}
7593
all := &bytes.Buffer{}
7694
cmd.Stderr = io.MultiWriter(all, os.Stderr)
77-
cmd.Stdout = io.MultiWriter(all, output)
95+
cmd.Stdout = io.MultiWriter(all, output, &outputWriter{
96+
id: id,
97+
progress: e.Progress,
98+
})
7899

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

88-
return output.String(), nil
109+
return output.String(), IsChatFinishMessage(output.String())
89110
}
90111

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

164-
env = appendEnv(env, "GPTSCRIPT_INPUT", input)
185+
env = appendEnv(env, "GPTSCRIPT_INPUT_CONTENT", input)
165186
return env
166187
}
167188

pkg/engine/engine.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Model interface {
2121

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

@@ -105,6 +106,21 @@ type InputContext struct {
105106
Content string `json:"content,omitempty"`
106107
}
107108

109+
type ErrChatFinish struct {
110+
Message string
111+
}
112+
113+
func (e *ErrChatFinish) Error() string {
114+
return fmt.Sprintf("CHAT FINISH: %s", e.Message)
115+
}
116+
117+
func IsChatFinishMessage(msg string) error {
118+
if msg, ok := strings.CutPrefix(msg, "CHAT FINISH: "); ok {
119+
return &ErrChatFinish{Message: msg}
120+
}
121+
return nil
122+
}
123+
108124
func (c *Context) ParentID() string {
109125
if c.Parent == nil {
110126
return ""

pkg/input/input.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"fmt"
55
"io"
66
"os"
7+
"path/filepath"
78
"strings"
89

910
"github.com/gptscript-ai/gptscript/pkg/loader"
11+
"github.com/gptscript-ai/gptscript/pkg/types"
1012
)
1113

1214
func FromArgs(args []string) string {
@@ -31,6 +33,14 @@ func FromFile(file string) (string, error) {
3133
}
3234
return string(data), nil
3335
} else if file != "" {
36+
if s, err := os.Stat(file); err == nil && s.IsDir() {
37+
for _, ext := range types.DefaultFiles {
38+
if _, err := os.Stat(filepath.Join(file, ext)); err == nil {
39+
file = filepath.Join(file, ext)
40+
break
41+
}
42+
}
43+
}
3444
log.Debugf("reading file %s", file)
3545
data, err := os.ReadFile(file)
3646
if err != nil {

pkg/monitor/display.go

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,22 @@ import (
2020
)
2121

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

2827
func Complete(opts ...Options) (result Options) {
2928
for _, opt := range opts {
3029
result.DumpState = types.FirstSet(opt.DumpState, result.DumpState)
31-
result.DisplayProgress = types.FirstSet(opt.DisplayProgress, result.DisplayProgress)
3230
result.DebugMessages = types.FirstSet(opt.DebugMessages, result.DebugMessages)
3331
}
3432
return
3533
}
3634

3735
type Console struct {
38-
dumpState string
39-
displayProgress bool
40-
printMessages bool
41-
callLock sync.Mutex
36+
dumpState string
37+
printMessages bool
38+
callLock sync.Mutex
4239
}
4340

4441
var (
@@ -47,7 +44,7 @@ var (
4744

4845
func (c *Console) Start(_ context.Context, prg *types.Program, _ []string, input string) (runner.Monitor, error) {
4946
id := counter.Next()
50-
mon := newDisplay(c.dumpState, c.displayProgress, c.printMessages)
47+
mon := newDisplay(c.dumpState, c.printMessages)
5148
mon.callLock = &c.callLock
5249
mon.dump.ID = fmt.Sprint(id)
5350
mon.dump.Program = prg
@@ -315,23 +312,20 @@ func (d *display) Stop(output string, err error) {
315312
func NewConsole(opts ...Options) *Console {
316313
opt := Complete(opts...)
317314
return &Console{
318-
dumpState: opt.DumpState,
319-
displayProgress: opt.DisplayProgress,
320-
printMessages: opt.DebugMessages,
315+
dumpState: opt.DumpState,
316+
printMessages: opt.DebugMessages,
321317
}
322318
}
323319

324-
func newDisplay(dumpState string, progress, printMessages bool) *display {
320+
func newDisplay(dumpState string, printMessages bool) *display {
325321
display := &display{
326322
dumpState: dumpState,
327323
callIDMap: make(map[string]string),
328324
printMessages: printMessages,
329325
}
330-
if progress {
331-
display.livePrinter = &livePrinter{
332-
lastContent: map[string]string{},
333-
callIDMap: display.callIDMap,
334-
}
326+
display.livePrinter = &livePrinter{
327+
lastContent: map[string]string{},
328+
callIDMap: display.callIDMap,
335329
}
336330
return display
337331
}

pkg/mvl/log.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mvl
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"io"
@@ -156,6 +157,25 @@ func (l *Logger) Fields(kv ...any) *Logger {
156157
}
157158
}
158159

160+
type InfoLogger interface {
161+
Infof(msg string, args ...any)
162+
}
163+
164+
type infoKey struct{}
165+
166+
func WithInfo(ctx context.Context, logger InfoLogger) context.Context {
167+
return context.WithValue(ctx, infoKey{}, logger)
168+
}
169+
170+
func (l *Logger) InfofCtx(ctx context.Context, msg string, args ...any) {
171+
il, ok := ctx.Value(infoKey{}).(InfoLogger)
172+
if ok {
173+
il.Infof(msg, args...)
174+
return
175+
}
176+
l.log.WithFields(l.fields).Infof(msg, args...)
177+
}
178+
159179
func (l *Logger) Infof(msg string, args ...any) {
160180
l.log.WithFields(l.fields).Infof(msg, args...)
161181
}

pkg/openai/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ func appendMessage(msg types.CompletionMessage, response openai.ChatCompletionSt
437437
if tc.ToolCall.Function.Name != tool.Function.Name {
438438
tc.ToolCall.Function.Name += tool.Function.Name
439439
}
440+
// OpenAI like to sometimes add this prefix for no good reason
441+
tc.ToolCall.Function.Name = strings.TrimPrefix(tc.ToolCall.Function.Name, "namespace.")
440442
tc.ToolCall.Function.Arguments += tool.Function.Arguments
441443

442444
msg.Content[idx] = tc

pkg/repos/get.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"path/filepath"
1111
"strings"
12+
"sync"
1213

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

4546
type Manager struct {
46-
storageDir string
47-
gitDir string
48-
runtimeDir string
49-
credHelperDirs credentials.CredentialHelperDirs
50-
runtimes []Runtime
47+
storageDir string
48+
gitDir string
49+
runtimeDir string
50+
credHelperDirs credentials.CredentialHelperDirs
51+
runtimes []Runtime
52+
credHelperConfig *credHelperConfig
53+
}
54+
55+
type credHelperConfig struct {
56+
lock sync.Mutex
57+
initialized bool
58+
cliCfg *config.CLIConfig
59+
env []string
5160
}
5261

5362
func New(cacheDir string, runtimes ...Runtime) *Manager {
@@ -61,7 +70,32 @@ func New(cacheDir string, runtimes ...Runtime) *Manager {
6170
}
6271
}
6372

64-
func (m *Manager) SetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig, env []string) error {
73+
func (m *Manager) EnsureCredentialHelpers(ctx context.Context) error {
74+
if m.credHelperConfig == nil {
75+
return nil
76+
}
77+
m.credHelperConfig.lock.Lock()
78+
defer m.credHelperConfig.lock.Unlock()
79+
80+
if !m.credHelperConfig.initialized {
81+
if err := m.deferredSetUpCredentialHelpers(ctx, m.credHelperConfig.cliCfg, m.credHelperConfig.env); err != nil {
82+
return err
83+
}
84+
m.credHelperConfig.initialized = true
85+
}
86+
87+
return nil
88+
}
89+
90+
func (m *Manager) SetUpCredentialHelpers(_ context.Context, cliCfg *config.CLIConfig, env []string) error {
91+
m.credHelperConfig = &credHelperConfig{
92+
cliCfg: cliCfg,
93+
env: env,
94+
}
95+
return nil
96+
}
97+
98+
func (m *Manager) deferredSetUpCredentialHelpers(ctx context.Context, cliCfg *config.CLIConfig, env []string) error {
6599
var (
66100
helperName = cliCfg.CredentialsStore
67101
suffix string

pkg/repos/git/git.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func Checkout(ctx context.Context, base, repo, commit, toDir string) error {
3333
return err
3434
}
3535

36-
log.Infof("Checking out %s to %s", commit, toDir)
36+
log.InfofCtx(ctx, "Checking out %s to %s", commit, toDir)
3737
return gitWorktreeAdd(ctx, gitDir(base, repo), toDir, commit)
3838
}
3939

@@ -46,11 +46,11 @@ func Fetch(ctx context.Context, base, repo, commit string) error {
4646
if found, err := exists(gitDir); err != nil {
4747
return err
4848
} else if !found {
49-
log.Infof("Cloning %s", repo)
49+
log.InfofCtx(ctx, "Cloning %s", repo)
5050
if err := cloneBare(ctx, repo, gitDir); err != nil {
5151
return err
5252
}
5353
}
54-
log.Infof("Fetching %s at %s", commit, repo)
54+
log.InfofCtx(ctx, "Fetching %s at %s", commit, repo)
5555
return fetchCommit(ctx, gitDir, commit)
5656
}

pkg/repos/runtimes/golang/golang.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func (r *Runtime) BuildCredentialHelper(ctx context.Context, helperName string,
6868
}
6969
newEnv := runtimeEnv.AppendPath(env, binPath)
7070

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

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

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

0 commit comments

Comments
 (0)