8
8
"fmt"
9
9
"io"
10
10
"log/slog"
11
+ "maps"
11
12
"net/http"
12
13
"os/exec"
13
14
"strconv"
@@ -26,10 +27,14 @@ type Run struct {
26
27
wait func ()
27
28
basicCommand bool
28
29
29
- rawOutput map [string ]any
30
- output , errput string
31
- events chan Frame
32
- lock sync.Mutex
30
+ program * Program
31
+ callsLock sync.RWMutex
32
+ calls map [string ]CallFrame
33
+ parentCallFrameID string
34
+ rawOutput map [string ]any
35
+ output , errput string
36
+ events chan Frame
37
+ lock sync.Mutex
33
38
}
34
39
35
40
// Text returns the text output of the gptscript. It blocks until the output is ready.
@@ -59,6 +64,49 @@ func (r *Run) Err() error {
59
64
return r .err
60
65
}
61
66
67
+ // Program returns the gptscript program for the run.
68
+ func (r * Run ) Program () * Program {
69
+ r .lock .Lock ()
70
+ defer r .lock .Unlock ()
71
+ return r .program
72
+ }
73
+
74
+ // RespondingTool returns the name of the tool that produced the output.
75
+ func (r * Run ) RespondingTool () Tool {
76
+ r .lock .Lock ()
77
+ defer r .lock .Unlock ()
78
+
79
+ if r .program == nil {
80
+ return Tool {}
81
+ }
82
+
83
+ s , ok := r .rawOutput ["toolID" ].(string )
84
+ if ! ok {
85
+ return Tool {}
86
+ }
87
+
88
+ return r .program .ToolSet [s ]
89
+ }
90
+
91
+ // Calls will return a flattened array of the calls for this run.
92
+ func (r * Run ) Calls () map [string ]CallFrame {
93
+ r .callsLock .RLock ()
94
+ defer r .callsLock .RUnlock ()
95
+ return maps .Clone (r .calls )
96
+ }
97
+
98
+ // ParentCallFrame returns the CallFrame for the top-level or "parent" call. The boolean indicates whether there is a parent CallFrame.
99
+ func (r * Run ) ParentCallFrame () (CallFrame , bool ) {
100
+ r .callsLock .RLock ()
101
+ defer r .callsLock .RUnlock ()
102
+
103
+ if r .parentCallFrameID == "" {
104
+ return CallFrame {}, false
105
+ }
106
+
107
+ return r .calls [r .parentCallFrameID ], true
108
+ }
109
+
62
110
// ErrorOutput returns the stderr output of the gptscript.
63
111
// Should only be called after Bytes or Text has returned an error.
64
112
func (r * Run ) ErrorOutput () string {
@@ -143,6 +191,10 @@ func (r *Run) NextChat(ctx context.Context, input string) (*Run, error) {
143
191
}
144
192
145
193
func (r * Run ) request (ctx context.Context , payload any ) (err error ) {
194
+ if r .state .IsTerminal () {
195
+ return fmt .Errorf ("run is in terminal state and cannot be run again: state %q" , r .state )
196
+ }
197
+
146
198
var (
147
199
req * http.Request
148
200
url = fmt .Sprintf ("%s/%s" , r .url , r .requestPath )
@@ -205,6 +257,10 @@ func (r *Run) request(ctx context.Context, payload any) (err error) {
205
257
r .lock .Unlock ()
206
258
}()
207
259
260
+ r .callsLock .Lock ()
261
+ r .calls = make (map [string ]CallFrame )
262
+ r .callsLock .Unlock ()
263
+
208
264
for n := 0 ; n != 0 || err == nil ; n , err = resp .Body .Read (buf ) {
209
265
for _ , line := range bytes .Split (bytes .TrimSpace (append (frag , buf [:n ]... )), []byte ("\n \n " )) {
210
266
line = bytes .TrimSpace (bytes .TrimPrefix (line , []byte ("data: " )))
@@ -287,6 +343,19 @@ func (r *Run) request(ctx context.Context, payload any) (err error) {
287
343
return
288
344
}
289
345
346
+ if event .Call != nil {
347
+ r .callsLock .Lock ()
348
+ r .calls [event .Call .ID ] = * event .Call
349
+ if r .parentCallFrameID == "" && event .Call .ParentID == "" {
350
+ r .parentCallFrameID = event .Call .ID
351
+ }
352
+ r .callsLock .Unlock ()
353
+ } else if event .Run != nil && event .Run .Type == EventTypeRunStart {
354
+ r .callsLock .Lock ()
355
+ r .program = & event .Run .Program
356
+ r .callsLock .Unlock ()
357
+ }
358
+
290
359
if r .opts .IncludeEvents {
291
360
r .events <- event
292
361
}
0 commit comments