|
1 | 1 | package gptscript
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "bufio" |
4 | 5 | "context"
|
5 | 6 | "encoding/json"
|
6 | 7 | "fmt"
|
7 | 8 | "io"
|
8 | 9 | "log/slog"
|
9 |
| - "net" |
10 |
| - "net/http" |
11 | 10 | "os"
|
12 | 11 | "os/exec"
|
13 | 12 | "path/filepath"
|
14 | 13 | "strings"
|
15 | 14 | "sync"
|
16 |
| - "time" |
17 | 15 | )
|
18 | 16 |
|
19 | 17 | var (
|
@@ -42,70 +40,69 @@ func NewGPTScript(opts GlobalOptions) (*GPTScript, error) {
|
42 | 40 | }
|
43 | 41 |
|
44 | 42 | if serverProcessCancel == nil && !disableServer {
|
45 |
| - if serverURL == "" { |
46 |
| - l, err := net.Listen("tcp", "127.0.0.1:0") |
47 |
| - if err != nil { |
48 |
| - slog.Debug("failed to start gptscript listener", "err", err) |
49 |
| - return nil, fmt.Errorf("failed to start gptscript: %w", err) |
50 |
| - } |
51 |
| - |
52 |
| - serverURL = l.Addr().String() |
53 |
| - if err = l.Close(); err != nil { |
54 |
| - slog.Debug("failed to close gptscript listener", "err", err) |
55 |
| - return nil, fmt.Errorf("failed to start gptscript: %w", err) |
56 |
| - } |
57 |
| - } |
58 |
| - |
59 | 43 | ctx, cancel := context.WithCancel(context.Background())
|
60 |
| - |
61 | 44 | in, _ := io.Pipe()
|
| 45 | + |
62 | 46 | serverProcess = exec.CommandContext(ctx, getCommand(), "sys.sdkserver", "--listen-address", serverURL)
|
63 | 47 | if opts.Env == nil {
|
64 | 48 | opts.Env = os.Environ()
|
65 | 49 | }
|
| 50 | + |
66 | 51 | serverProcess.Env = append(opts.Env[:], opts.toEnv()...)
|
| 52 | + |
67 | 53 | serverProcess.Stdin = in
|
| 54 | + stdErr, err := serverProcess.StderrPipe() |
| 55 | + if err != nil { |
| 56 | + cancel() |
| 57 | + return nil, fmt.Errorf("failed to get stderr pipe: %w", err) |
| 58 | + } |
68 | 59 |
|
69 | 60 | serverProcessCancel = func() {
|
70 | 61 | cancel()
|
71 | 62 | _ = in.Close()
|
| 63 | + _ = serverProcess.Wait() |
72 | 64 | }
|
73 | 65 |
|
74 |
| - if err := serverProcess.Start(); err != nil { |
| 66 | + if err = serverProcess.Start(); err != nil { |
75 | 67 | serverProcessCancel()
|
76 | 68 | return nil, fmt.Errorf("failed to start server: %w", err)
|
77 | 69 | }
|
78 | 70 |
|
79 |
| - timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second) |
80 |
| - defer cancel() |
81 |
| - if err := waitForServerReady(timeoutCtx, serverURL); err != nil { |
| 71 | + serverURL, err = readAddress(stdErr) |
| 72 | + if err != nil { |
82 | 73 | serverProcessCancel()
|
83 |
| - _ = serverProcess.Wait() |
84 |
| - return nil, fmt.Errorf("failed to wait for gptscript to be ready: %w", err) |
| 74 | + return nil, fmt.Errorf("failed to read server URL: %w", err) |
85 | 75 | }
|
| 76 | + |
| 77 | + go func() { |
| 78 | + for { |
| 79 | + // Ensure that stdErr is drained as logs come in |
| 80 | + _, _, _ = bufio.NewReader(stdErr).ReadLine() |
| 81 | + } |
| 82 | + }() |
| 83 | + |
| 84 | + if _, url, found := strings.Cut(serverURL, "addr="); found { |
| 85 | + // Ensure backwards compatibility with older versions of the SDK server |
| 86 | + serverURL = url |
| 87 | + } |
| 88 | + |
| 89 | + serverURL = strings.TrimSpace(serverURL) |
86 | 90 | }
|
87 | 91 | return &GPTScript{url: "http://" + serverURL}, nil
|
88 | 92 | }
|
89 | 93 |
|
90 |
| -func waitForServerReady(ctx context.Context, serverURL string) error { |
91 |
| - for { |
92 |
| - resp, err := http.Get("http://" + serverURL + "/healthz") |
93 |
| - if err != nil { |
94 |
| - slog.DebugContext(ctx, "waiting for server to become ready") |
95 |
| - } else { |
96 |
| - _ = resp.Body.Close() |
97 |
| - |
98 |
| - if resp.StatusCode == http.StatusOK { |
99 |
| - return nil |
100 |
| - } |
101 |
| - } |
| 94 | +func readAddress(stdErr io.Reader) (string, error) { |
| 95 | + addr, err := bufio.NewReader(stdErr).ReadString('\n') |
| 96 | + if err != nil { |
| 97 | + return "", fmt.Errorf("failed to read server address: %w", err) |
| 98 | + } |
102 | 99 |
|
103 |
| - select { |
104 |
| - case <-ctx.Done(): |
105 |
| - return ctx.Err() |
106 |
| - case <-time.After(time.Second): |
107 |
| - } |
| 100 | + if _, url, found := strings.Cut(addr, "addr="); found { |
| 101 | + // For backward compatibility: older versions of the SDK server print the address in a slightly different way. |
| 102 | + addr = url |
108 | 103 | }
|
| 104 | + |
| 105 | + return addr, nil |
109 | 106 | }
|
110 | 107 |
|
111 | 108 | func (g *GPTScript) Close() {
|
|
0 commit comments