Skip to content

Commit 6ee6685

Browse files
committed
fix: address issues with processing large events
Signed-off-by: Donnie Adams <[email protected]>
1 parent 76dd1f1 commit 6ee6685

File tree

4 files changed

+155
-52
lines changed

4 files changed

+155
-52
lines changed

client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func (c *Client) runBasicCommand(ctx context.Context, command, requestPath, tool
171171
if run.url != "" {
172172
var m any
173173
if content != "" || toolPath != "" {
174-
m = map[string]any{"input": content, "file": toolPath}
174+
m = map[string]any{"content": content, "file": toolPath}
175175
}
176176
err = run.request(ctx, m)
177177
} else {

client_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,57 @@ func TestFileChat(t *testing.T) {
567567
}
568568
}
569569

570+
func TestToolWithGlobalTools(t *testing.T) {
571+
var runStartSeen, callStartSeen, callFinishSeen, callProgressSeen, runFinishSeen bool
572+
wd, err := os.Getwd()
573+
if err != nil {
574+
t.Fatalf("Error getting current working directory: %v", err)
575+
}
576+
577+
var eventContent string
578+
579+
run, err := client.Run(context.Background(), wd+"/test/global-tools.gpt", Opts{DisableCache: true, IncludeEvents: true})
580+
if err != nil {
581+
t.Errorf("Error executing tool: %v", err)
582+
}
583+
584+
for e := range run.Events() {
585+
if e.Type == EventTypeRunStart {
586+
runStartSeen = true
587+
} else if e.Type == EventTypeCallStart {
588+
callStartSeen = true
589+
} else if e.Type == EventTypeCallFinish {
590+
callFinishSeen = true
591+
} else if e.Type == EventTypeRunFinish {
592+
runFinishSeen = true
593+
} else if e.Type == EventTypeCallProgress {
594+
callProgressSeen = true
595+
}
596+
eventContent += e.Content
597+
}
598+
599+
out, err := run.Text()
600+
if err != nil {
601+
t.Errorf("Error reading output: %v", err)
602+
}
603+
604+
if !strings.Contains(eventContent, "Hello") {
605+
t.Errorf("Unexpected event output: %s", eventContent)
606+
}
607+
608+
if !strings.Contains(out, "Hello!") {
609+
t.Errorf("Unexpected output: %s", out)
610+
}
611+
612+
if len(run.ErrorOutput()) == 0 {
613+
t.Error("No stderr output")
614+
}
615+
616+
if !runStartSeen || !callStartSeen || !callFinishSeen || !runFinishSeen || !callProgressSeen {
617+
t.Errorf("Missing events: %t %t %t %t %t", runStartSeen, callStartSeen, callFinishSeen, runFinishSeen, callProgressSeen)
618+
}
619+
}
620+
570621
func TestGetCommand(t *testing.T) {
571622
currentEnvVar := os.Getenv("GPTSCRIPT_BIN")
572623
t.Cleanup(func() {

run.go

+84-51
Original file line numberDiff line numberDiff line change
@@ -215,34 +215,48 @@ func (r *Run) exec(ctx context.Context, extraArgs ...string) error {
215215
func (r *Run) readEvents(ctx context.Context, events io.Reader) {
216216
defer close(r.events)
217217

218-
scan := bufio.NewScanner(events)
219-
for scan.Scan() {
220-
if !r.opts.IncludeEvents {
221-
continue
222-
}
218+
var (
219+
n int
220+
err error
221+
frag []byte
223222

224-
line := scan.Bytes()
225-
if len(line) == 0 {
226-
continue
223+
b = make([]byte, 64*1024)
224+
)
225+
for ; ; n, err = events.Read(b) {
226+
if n == 0 && err != nil {
227+
break
227228
}
228229

229-
var event Event
230-
if err := json.Unmarshal(line, &event); err != nil {
231-
slog.Debug("failed to unmarshal event", "error", err, "event", string(line))
230+
if !r.opts.IncludeEvents {
232231
continue
233232
}
234233

235-
select {
236-
case <-ctx.Done():
237-
go func() {
238-
for scan.Scan() {
239-
// Drain any remaining events
240-
}
241-
}()
242-
return
243-
case r.events <- event:
234+
for _, line := range bytes.Split(append(frag, b[:n]...), []byte("\n\n")) {
235+
if line = bytes.TrimSpace(line); len(line) == 0 {
236+
frag = frag[:0]
237+
continue
238+
}
239+
240+
var event Event
241+
if err := json.Unmarshal(line, &event); err != nil {
242+
slog.Debug("failed to unmarshal event", "error", err, "event", string(b))
243+
frag = line[:]
244+
continue
245+
}
246+
247+
select {
248+
case <-ctx.Done():
249+
return
250+
case r.events <- event:
251+
frag = frag[:0]
252+
}
244253
}
245254
}
255+
256+
if err != nil && !errors.Is(err, io.EOF) {
257+
slog.Debug("failed to read events", "error", err)
258+
r.err = fmt.Errorf("failed to read events: %w", err)
259+
}
246260
}
247261

248262
func (r *Run) readAllOutput() error {
@@ -367,16 +381,14 @@ func (r *Run) request(ctx context.Context, payload any) (err error) {
367381
go r.readEvents(cancelCtx, eventsRead)
368382

369383
go func() {
384+
var (
385+
n int
386+
frag []byte
387+
buf = make([]byte, 64*1024)
388+
)
370389
bufferedStdout := bufio.NewWriter(stdoutWriter)
371390
bufferedStderr := bufio.NewWriter(stderrWriter)
372-
scan := bufio.NewScanner(resp.Body)
373391
defer func() {
374-
go func() {
375-
for scan.Scan() {
376-
// Drain any remaining events
377-
}
378-
}()
379-
380392
eventsWrite.Close()
381393

382394
bufferedStderr.Flush()
@@ -390,38 +402,59 @@ func (r *Run) request(ctx context.Context, payload any) (err error) {
390402
resp.Body.Close()
391403
}()
392404

393-
for scan.Scan() {
394-
line := bytes.TrimSpace(bytes.TrimPrefix(scan.Bytes(), []byte("data: ")))
395-
if len(line) == 0 {
396-
continue
397-
}
398-
if bytes.Equal(line, []byte("[DONE]")) {
399-
return
405+
for ; ; n, err = resp.Body.Read(buf) {
406+
if n == 0 && err != nil {
407+
break
400408
}
401409

402-
if bytes.HasPrefix(line, []byte(`{"stdout":`)) {
403-
_, err = bufferedStdout.Write(bytes.TrimSuffix(bytes.TrimPrefix(line, []byte(`{"stdout":`)), []byte("}")))
404-
if err != nil {
405-
r.state = Error
406-
r.err = fmt.Errorf("failed to write stdout: %w", err)
407-
return
410+
for _, line := range bytes.Split(bytes.TrimSpace(append(frag, buf[:n]...)), []byte("\n\n")) {
411+
line = bytes.TrimSpace(bytes.TrimPrefix(line, []byte("data: ")))
412+
if len(line) == 0 {
413+
frag = frag[:0]
414+
continue
408415
}
409-
} else if bytes.HasPrefix(line, []byte(`{"stderr":`)) {
410-
_, err = bufferedStderr.Write(bytes.TrimSuffix(bytes.TrimPrefix(line, []byte(`{"stderr":`)), []byte("}")))
411-
if err != nil {
412-
r.state = Error
413-
r.err = fmt.Errorf("failed to write stderr: %w", err)
416+
if bytes.Equal(line, []byte("[DONE]")) {
414417
return
415418
}
416-
} else {
417-
_, err = eventsWrite.Write(append(line, '\n'))
418-
if err != nil {
419-
r.state = Error
420-
r.err = fmt.Errorf("failed to write events: %w", err)
421-
return
419+
420+
// Is this a JSON object?
421+
if err := json.Unmarshal(line, &[]map[string]any{make(map[string]any)}[0]); err != nil {
422+
// If not, then wait until we get the rest of the output.
423+
frag = line[:]
424+
continue
425+
}
426+
427+
frag = frag[:0]
428+
429+
if bytes.HasPrefix(line, []byte(`{"stdout":`)) {
430+
_, err = bufferedStdout.Write(bytes.TrimSuffix(bytes.TrimPrefix(line, []byte(`{"stdout":`)), []byte("}")))
431+
if err != nil {
432+
r.state = Error
433+
r.err = fmt.Errorf("failed to write stdout: %w", err)
434+
return
435+
}
436+
} else if bytes.HasPrefix(line, []byte(`{"stderr":`)) {
437+
_, err = bufferedStderr.Write(bytes.TrimSuffix(bytes.TrimPrefix(line, []byte(`{"stderr":`)), []byte("}")))
438+
if err != nil {
439+
r.state = Error
440+
r.err = fmt.Errorf("failed to write stderr: %w", err)
441+
return
442+
}
443+
} else {
444+
_, err = eventsWrite.Write(append(line, '\n', '\n'))
445+
if err != nil {
446+
r.state = Error
447+
r.err = fmt.Errorf("failed to write events: %w", err)
448+
return
449+
}
422450
}
423451
}
424452
}
453+
454+
if err != nil && !errors.Is(err, io.EOF) {
455+
slog.Debug("failed to read events from response", "error", err)
456+
r.err = fmt.Errorf("failed to read events: %w", err)
457+
}
425458
}()
426459

427460
r.wait = func() error {

test/global-tools.gpt

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
!title
2+
3+
Runbook 3
4+
5+
---
6+
Name: tool_1
7+
Global Tools: sys.workspace.ls, sys.workspace.read, sys.workspace.write, github.com/gptscript-ai/knowledge, github.com/drpebcak/duckdb, github.com/gptscript-ai/browser, github.com/gptscript-ai/browser-search/google, github.com/gptscript-ai/browser-search/google-question-answerer
8+
9+
Hi
10+
11+
---
12+
Name: tool_2
13+
14+
What time is it?
15+
16+
---
17+
Name: tool_3
18+
19+
Give me a paragraph of lorem ipsum

0 commit comments

Comments
 (0)