返回 DeepSeek-Reasonix
httpwire.go
根目录 / internal / browser / httpwire.go
1 package browser
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "time"
8 )
9
10 // HTTP contract shared by the client in httpexec.go and the handler in
11 // httpserve.go: POST <endpoint>/v1/browser/<method> with a JSON body that
12 // mirrors the request struct, a JSON reply that mirrors the result struct,
13 // and a 409 carrying {"error": code, "message": text} for every sentinel.
14 const (
15 httpRoutePrefix = "/v1/browser/"
16 httpHealthRoute = httpRoutePrefix + "health"
17
18 // SessionHeader carries the calling session's ID so a broker serving
19 // several sessions over one token can route each call to its own task.
20 SessionHeader = "X-Reasonix-Browser-Session"
21
22 httpMaxRequestBytes = 1 << 20
23 httpMaxResponseBytes = 32 << 20
24 )
25
26 // Wire error codes carried in a 409 body; each maps onto one sentinel.
27 const (
28 wireStaleReference = "stale_reference"
29 wireTakenOver = "taken_over"
30 wireNoGrant = "no_grant"
31 wireUnknownOutcome = "unknown_outcome"
32 )
33
34 var wireErrorCodes = map[string]error{
35 wireStaleReference: ErrStaleReference,
36 wireTakenOver: ErrTakenOver,
37 wireNoGrant: ErrNoGrant,
38 wireUnknownOutcome: ErrUnknownOutcome,
39 }
40
41 type sessionKey struct{}
42
43 // WithSession scopes ctx to one session ID; the HTTP client sends it as
44 // SessionHeader and the HTTP handler restores it for the served Executor.
45 func WithSession(ctx context.Context, id string) context.Context {
46 if id == "" {
47 return ctx
48 }
49 return context.WithValue(ctx, sessionKey{}, id)
50 }
51
52 // SessionFromContext returns the session ID set by WithSession, or "".
53 func SessionFromContext(ctx context.Context) string {
54 if ctx == nil {
55 return ""
56 }
57 id, _ := ctx.Value(sessionKey{}).(string)
58 return id
59 }
60
61 type wireError struct {
62 Error string `json:"error"`
63 Message string `json:"message,omitempty"`
64 }
65
66 type wireTab struct {
67 ID string `json:"id"`
68 URL string `json:"url"`
69 Title string `json:"title,omitempty"`
70 Loading bool `json:"loading,omitempty"`
71 Temporary bool `json:"temporary,omitempty"`
72 }
73
74 func toWireTab(t Tab) wireTab {
75 return wireTab(t)
76 }
77
78 func (t wireTab) tab() Tab {
79 return Tab(t)
80 }
81
82 type wireTabs struct {
83 Tabs []wireTab `json:"tabs"`
84 }
85
86 type wireOpenRequest struct {
87 OperationID string `json:"operationId"`
88 URL string `json:"url"`
89 Temporary bool `json:"temporary,omitempty"`
90 }
91
92 type wireNavigateRequest struct {
93 OperationID string `json:"operationId"`
94 TabID string `json:"tabId"`
95 URL string `json:"url,omitempty"`
96 Action string `json:"action"`
97 }
98
99 type wireSnapshotRequest struct {
100 TabID string `json:"tabId"`
101 Selector string `json:"selector,omitempty"`
102 }
103
104 type wireSnapshot struct {
105 DocumentToken string `json:"documentToken"`
106 URL string `json:"url"`
107 Title string `json:"title,omitempty"`
108 Tree string `json:"tree"`
109 Refs int `json:"refs"`
110 }
111
112 type wireScreenshotRequest struct {
113 TabID string `json:"tabId"`
114 Ref string `json:"ref,omitempty"`
115 FullPage bool `json:"fullPage,omitempty"`
116 }
117
118 type wireScreenshot struct {
119 Path string `json:"path"`
120 MIME string `json:"mime,omitempty"`
121 Width int `json:"width,omitempty"`
122 Height int `json:"height,omitempty"`
123 }
124
125 type wireActRequest struct {
126 OperationID string `json:"operationId"`
127 TabID string `json:"tabId"`
128 DocumentToken string `json:"documentToken,omitempty"`
129 Action string `json:"action"`
130 Ref string `json:"ref,omitempty"`
131 Text string `json:"text,omitempty"`
132 Keys string `json:"keys,omitempty"`
133 Options []string `json:"options,omitempty"`
134 Files []string `json:"files,omitempty"`
135 Submit bool `json:"submit,omitempty"`
136 DeltaX int `json:"deltaX,omitempty"`
137 DeltaY int `json:"deltaY,omitempty"`
138 }
139
140 func toWireAct(req ActRequest) wireActRequest {
141 return wireActRequest(req)
142 }
143
144 func (w wireActRequest) request() ActRequest {
145 return ActRequest(w)
146 }
147
148 type wireActResult struct {
149 Executed bool `json:"executed"`
150 Reason string `json:"reason,omitempty"`
151 DocumentToken string `json:"documentToken,omitempty"`
152 Outcome string `json:"outcome,omitempty"`
153 }
154
155 func (w *wireActResult) UnmarshalJSON(data []byte) error {
156 type receipt wireActResult
157 var fields map[string]json.RawMessage
158 if err := json.Unmarshal(data, &fields); err != nil {
159 return err
160 }
161 var executed *bool
162 if err := json.Unmarshal(fields["executed"], &executed); err != nil || executed == nil {
163 return fmt.Errorf("browser receipt is missing a boolean executed field")
164 }
165 var decoded receipt
166 if err := json.Unmarshal(data, &decoded); err != nil {
167 return err
168 }
169 *w = wireActResult(decoded)
170 return nil
171 }
172
173 type wireDownloadsRequest struct {
174 TabID string `json:"tabId"`
175 WaitForMs int64 `json:"waitForMs,omitempty"`
176 }
177
178 func (w wireDownloadsRequest) request() DownloadsRequest {
179 return DownloadsRequest{TabID: w.TabID, WaitFor: time.Duration(w.WaitForMs) * time.Millisecond}
180 }
181
182 type wireDownload struct {
183 ID string `json:"id"`
184 URL string `json:"url,omitempty"`
185 Path string `json:"path,omitempty"`
186 State string `json:"state,omitempty"`
187 Bytes int64 `json:"bytes,omitempty"`
188 }
189
190 type wireDownloads struct {
191 Downloads []wireDownload `json:"downloads"`
192 }
193
194 type wireCloseRequest struct {
195 OperationID string `json:"operationId"`
196 TabID string `json:"tabId"`
197 }
198
198 lines GO