Skip to content

Commit 8452bda

Browse files
authored
Merge pull request #22 from thedadams/make-prompt-explicit
fix: make prompt explicit
2 parents 57eeae8 + b8dd6b3 commit 8452bda

File tree

7 files changed

+126
-14
lines changed

7 files changed

+126
-14
lines changed

README.md

+56-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ None of the options is required, and the defaults will reduce the number of call
3333
- `inlcudeEvents`: Whether to include the streaming of events. Default (false). Note that if this is true, you must stream the events. See below for details.
3434
- `chatState`: The chat state to continue, or null to start a new chat and return the state
3535
- `confirm`: Prompt before running potentially dangerous commands
36+
- `prompt`: Allow prompting of the user
3637

3738
## Functions
3839

@@ -298,7 +299,7 @@ func runFileWithConfirm(ctx context.Context) (string, error) {
298299
}
299300

300301
for event := range run.Events() {
301-
if event.Type == gptscript.EventTypeCallConfirm {
302+
if event.Call != nil && event.Call.Type == gptscript.EventTypeCallConfirm {
302303
// event.Tool has the information on the command being run.
303304
// and event.Input will have the input to the command being run.
304305

@@ -319,6 +320,60 @@ func runFileWithConfirm(ctx context.Context) (string, error) {
319320
}
320321
```
321322

323+
### Prompt
324+
325+
Using the `Prompt: true` option allows a script to prompt a user for input. In order to do this, a caller should look for the `Prompt` event. This also means that `IncludeEvent` should be `true`. Note that if a `Prompt` event occurs when it has not explicitly been allowed, then the run will error.
326+
327+
```go
328+
package main
329+
330+
import (
331+
"context"
332+
333+
"github.com/gptscript-ai/go-gptscript"
334+
)
335+
336+
func runFileWithPrompt(ctx context.Context) (string, error) {
337+
opts := gptscript.Options{
338+
DisableCache: &[]bool{true}[0],
339+
Input: "--input hello",
340+
Prompt: true,
341+
IncludeEvents: true,
342+
}
343+
344+
client, err := gptscript.NewClient()
345+
if err != nil {
346+
return "", err
347+
}
348+
defer client.Close()
349+
350+
run, err := client.Run(ctx, "./hello.gpt", opts)
351+
if err != nil {
352+
return "", err
353+
}
354+
355+
for event := range run.Events() {
356+
if event.Prompt != nil {
357+
// event.Prompt has the information to prompt the user.
358+
359+
err = client.PromptResponse(ctx, gptscript.PromptResponse{
360+
ID: event.Prompt.ID,
361+
// Responses is a map[string]string of Fields to values
362+
Responses: map[string]string{
363+
event.Prompt.Fields[0]: "Some Value",
364+
},
365+
})
366+
if err != nil {
367+
// Handle error
368+
}
369+
}
370+
371+
// Process event...
372+
}
373+
374+
return run.Text()
375+
}
376+
```
322377

323378
## Types
324379

client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ func (c *client) Confirm(ctx context.Context, resp AuthResponse) error {
210210
}
211211

212212
func (c *client) PromptResponse(ctx context.Context, resp PromptResponse) error {
213-
_, err := c.runBasicCommand(ctx, "prompt-response/"+resp.ID, resp.Response)
213+
_, err := c.runBasicCommand(ctx, "prompt-response/"+resp.ID, resp.Responses)
214214
return err
215215
}
216216

client_test.go

+41-3
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ func TestPrompt(t *testing.T) {
817817
},
818818
}
819819

820-
run, err := c.Evaluate(context.Background(), Options{IncludeEvents: true}, tools...)
820+
run, err := c.Evaluate(context.Background(), Options{IncludeEvents: true, Prompt: true}, tools...)
821821
if err != nil {
822822
t.Errorf("Error executing tool: %v", err)
823823
}
@@ -859,8 +859,8 @@ func TestPrompt(t *testing.T) {
859859
}
860860

861861
if err = c.PromptResponse(context.Background(), PromptResponse{
862-
ID: promptFrame.ID,
863-
Response: map[string]string{promptFrame.Fields[0]: "Clicky"},
862+
ID: promptFrame.ID,
863+
Responses: map[string]string{promptFrame.Fields[0]: "Clicky"},
864864
}); err != nil {
865865
t.Errorf("Error responding: %v", err)
866866
}
@@ -892,6 +892,44 @@ func TestPrompt(t *testing.T) {
892892
}
893893
}
894894

895+
func TestPromptWithoutPromptAllowed(t *testing.T) {
896+
tools := []fmt.Stringer{
897+
&ToolDef{
898+
Instructions: "Use the sys.prompt user to ask the user for 'first name' which is not sensitive. After you get their first name, say hello.",
899+
Tools: []string{"sys.prompt"},
900+
},
901+
}
902+
903+
run, err := c.Evaluate(context.Background(), Options{IncludeEvents: true}, tools...)
904+
if err != nil {
905+
t.Errorf("Error executing tool: %v", err)
906+
}
907+
908+
// Wait for the prompt event
909+
var promptFrame *PromptFrame
910+
for e := range run.Events() {
911+
if e.Prompt != nil {
912+
if e.Prompt.Type == EventTypePrompt {
913+
promptFrame = e.Prompt
914+
break
915+
}
916+
}
917+
}
918+
919+
if promptFrame != nil {
920+
t.Errorf("Prompt call event shouldn't happen")
921+
}
922+
923+
_, err = run.Text()
924+
if err == nil || !strings.Contains(err.Error(), "prompt event occurred") {
925+
t.Errorf("Error reading output: %v", err)
926+
}
927+
928+
if run.State() != Error {
929+
t.Errorf("Unexpected state: %v", run.State())
930+
}
931+
}
932+
895933
func TestGetCommand(t *testing.T) {
896934
currentEnvVar := os.Getenv("GPTSCRIPT_BIN")
897935
t.Cleanup(func() {

frame.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package gptscript
22

3-
import "time"
3+
import (
4+
"fmt"
5+
"time"
6+
)
47

58
type ToolCategory string
69

@@ -103,3 +106,9 @@ type PromptFrame struct {
103106
Fields []string `json:"fields,omitempty"`
104107
Sensitive bool `json:"sensitive,omitempty"`
105108
}
109+
110+
func (p *PromptFrame) String() string {
111+
return fmt.Sprintf(`Message: %s
112+
Fields: %v
113+
Sensitive: %v`, p.Message, p.Fields, p.Sensitive)
114+
}

opts.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ type Options struct {
1010
Workspace string `json:"workspace"`
1111
ChatState string `json:"chatState"`
1212
IncludeEvents bool `json:"includeEvents"`
13+
Prompt bool `json:"prompt"`
1314
}

prompt.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package gptscript
22

33
type PromptResponse struct {
4-
ID string `json:"id,omitempty"`
5-
Response map[string]string `json:"response,omitempty"`
4+
ID string `json:"id,omitempty"`
5+
Responses map[string]string `json:"response,omitempty"`
66
}

run.go

+15-6
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,21 @@ func (r *Run) request(ctx context.Context, payload any) (err error) {
273273
r.err = fmt.Errorf("failed to process stderr, invalid type: %T", out)
274274
}
275275
} else {
276-
if r.opts.IncludeEvents {
277-
var event Frame
278-
if err := json.Unmarshal(line, &event); err != nil {
279-
slog.Debug("failed to unmarshal event", "error", err, "event", string(line))
280-
}
276+
var event Frame
277+
if err := json.Unmarshal(line, &event); err != nil {
278+
slog.Debug("failed to unmarshal event", "error", err, "event", string(line))
279+
}
280+
281+
if event.Prompt != nil && !r.opts.Prompt {
282+
r.state = Error
283+
r.err = fmt.Errorf("prompt event occurred when prompt was not allowed: %s", event.Prompt)
284+
// Ignore the error because it is the same as the above error.
285+
_ = r.Close()
281286

287+
return
288+
}
289+
290+
if r.opts.IncludeEvents {
282291
r.events <- event
283292
}
284293
}
@@ -304,7 +313,7 @@ func (r *Run) request(ctx context.Context, payload any) (err error) {
304313
if err := context.Cause(cancelCtx); !errors.Is(err, context.Canceled) && r.err == nil {
305314
r.state = Error
306315
r.err = err
307-
} else if r.state != Continue {
316+
} else if r.state != Continue && r.state != Error {
308317
r.state = Finished
309318
}
310319
}

0 commit comments

Comments
 (0)