Skip to content

Commit 0fd1a4d

Browse files
chore: allow nested gptscript to run sys.chat.finish
1 parent 7181c20 commit 0fd1a4d

34 files changed

+368
-108
lines changed

go.mod

+1-1
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-20240618175050-a1d627a00cff
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

+2-2
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-20240618175050-a1d627a00cff h1:mjcUKZ4hHVpT8EkyIsxGpU608BTNko0EovpsY0fGfvU=
177-
github.com/gptscript-ai/tui v0.0.0-20240618175050-a1d627a00cff/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

+2-10
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

+24-3
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

+15
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,21 @@ type InputContext struct {
106106
Content string `json:"content,omitempty"`
107107
}
108108

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+
109124
func (c *Context) ParentID() string {
110125
if c.Parent == nil {
111126
return ""

pkg/input/input.go

+10
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/runner/runner.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@ type ChatState interface{}
131131
func (r *Runner) Chat(ctx context.Context, prevState ChatState, prg types.Program, env []string, input string) (resp ChatResponse, err error) {
132132
var state *State
133133

134+
defer func() {
135+
if finish := (*engine.ErrChatFinish)(nil); errors.As(err, &finish) {
136+
resp = ChatResponse{
137+
Done: true,
138+
Content: err.Error(),
139+
}
140+
err = nil
141+
}
142+
}()
143+
134144
if prevState != nil {
135145
switch v := prevState.(type) {
136146
case *State:
@@ -568,7 +578,7 @@ func (r *Runner) resume(callCtx engine.Context, monitor Monitor, env []string, s
568578
)
569579

570580
state, callResults, err = r.subCalls(callCtx, monitor, env, state, callCtx.ToolCategory)
571-
if errMessage := (*builtin.ErrChatFinish)(nil); errors.As(err, &errMessage) && callCtx.Tool.Chat {
581+
if errMessage := (*engine.ErrChatFinish)(nil); errors.As(err, &errMessage) && callCtx.Tool.Chat {
572582
return &State{
573583
Result: &errMessage.Message,
574584
}, nil

pkg/system/prompt.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,30 @@ You don't move to the next step until you have a result.
2424

2525
// DefaultPromptParameter is used as the key in a json map to indication that we really wanted
2626
// to just send pure text but the interface required JSON (as that is the fundamental interface of tools in OpenAI)
27-
var DefaultPromptParameter = "defaultPromptParameter"
27+
var DefaultPromptParameter = "prompt"
2828

2929
var DefaultToolSchema = openapi3.Schema{
3030
Type: &openapi3.Types{"object"},
3131
Properties: openapi3.Schemas{
3232
DefaultPromptParameter: &openapi3.SchemaRef{
3333
Value: &openapi3.Schema{
34-
Description: "Prompt to send to the tool or assistant. This may be instructions or question.",
34+
Description: "Prompt to send to the tool. This may be an instruction or question.",
35+
Type: &openapi3.Types{"string"},
36+
},
37+
},
38+
},
39+
}
40+
41+
var DefaultChatSchema = openapi3.Schema{
42+
Type: &openapi3.Types{"object"},
43+
Properties: openapi3.Schemas{
44+
DefaultPromptParameter: &openapi3.SchemaRef{
45+
Value: &openapi3.Schema{
46+
Description: "Prompt to send to the assistant. This may be an instruction or question.",
3547
Type: &openapi3.Types{"string"},
3648
},
3749
},
3850
},
39-
Required: []string{DefaultPromptParameter},
4051
}
4152

4253
func init() {

pkg/tests/runner_test.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,15 @@ func TestSubChat(t *testing.T) {
319319
"function": {
320320
"toolID": "testdata/TestSubChat/test.gpt:chatbot",
321321
"name": "chatbot",
322-
"parameters": null
322+
"parameters": {
323+
"properties": {
324+
"prompt": {
325+
"description": "Prompt to send to the assistant. This may be an instruction or question.",
326+
"type": "string"
327+
}
328+
},
329+
"type": "object"
330+
}
323331
}
324332
}
325333
],
@@ -435,7 +443,15 @@ func TestSubChat(t *testing.T) {
435443
"function": {
436444
"toolID": "testdata/TestSubChat/test.gpt:chatbot",
437445
"name": "chatbot",
438-
"parameters": null
446+
"parameters": {
447+
"properties": {
448+
"prompt": {
449+
"description": "Prompt to send to the assistant. This may be an instruction or question.",
450+
"type": "string"
451+
}
452+
},
453+
"type": "object"
454+
}
439455
}
440456
}
441457
],

pkg/tests/testdata/TestAgents/call1.golden

+18-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,30 @@
66
"function": {
77
"toolID": "testdata/TestAgents/test.gpt:agent1",
88
"name": "agent1",
9-
"parameters": null
9+
"parameters": {
10+
"properties": {
11+
"prompt": {
12+
"description": "Prompt to send to the assistant. This may be an instruction or question.",
13+
"type": "string"
14+
}
15+
},
16+
"type": "object"
17+
}
1018
}
1119
},
1220
{
1321
"function": {
1422
"toolID": "testdata/TestAgents/test.gpt:agent2",
1523
"name": "agent2",
16-
"parameters": null
24+
"parameters": {
25+
"properties": {
26+
"prompt": {
27+
"description": "Prompt to send to the assistant. This may be an instruction or question.",
28+
"type": "string"
29+
}
30+
},
31+
"type": "object"
32+
}
1733
}
1834
}
1935
],

pkg/tests/testdata/TestAgents/call2.golden

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@
66
"function": {
77
"toolID": "testdata/TestAgents/test.gpt:agent2",
88
"name": "agent2",
9-
"parameters": null
9+
"parameters": {
10+
"properties": {
11+
"prompt": {
12+
"description": "Prompt to send to the assistant. This may be an instruction or question.",
13+
"type": "string"
14+
}
15+
},
16+
"type": "object"
17+
}
1018
}
1119
}
1220
],

pkg/tests/testdata/TestAgents/call3.golden

+18-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,30 @@
66
"function": {
77
"toolID": "testdata/TestAgents/test.gpt:agent1",
88
"name": "agent1",
9-
"parameters": null
9+
"parameters": {
10+
"properties": {
11+
"prompt": {
12+
"description": "Prompt to send to the assistant. This may be an instruction or question.",
13+
"type": "string"
14+
}
15+
},
16+
"type": "object"
17+
}
1018
}
1119
},
1220
{
1321
"function": {
1422
"toolID": "testdata/TestAgents/test.gpt:agent3",
1523
"name": "agent3",
16-
"parameters": null
24+
"parameters": {
25+
"properties": {
26+
"prompt": {
27+
"description": "Prompt to send to the assistant. This may be an instruction or question.",
28+
"type": "string"
29+
}
30+
},
31+
"type": "object"
32+
}
1733
}
1834
}
1935
],

0 commit comments

Comments
 (0)