返回 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 Error string `json:"error,omitempty"`
73 }
74
75 func toWireTab(t Tab) wireTab {
76 return wireTab(t)
77 }
78
79 func (t wireTab) tab() Tab {
80 return Tab(t)
81 }
82
83 type wireTabs struct {
84 Tabs []wireTab `json:"tabs"`
85 }
86
87 type wireOpenRequest struct {
88 OperationID string `json:"operationId"`
89 URL string `json:"url"`
90 Temporary bool `json:"temporary,omitempty"`
91 }
92
93 type wireNavigateRequest struct {
94 OperationID string `json:"operationId"`
95 TabID string `json:"tabId"`
96 URL string `json:"url,omitempty"`
97 Action string `json:"action"`
98 }
99
100 type wireSnapshotRequest struct {
101 TabID string `json:"tabId"`
102 Selector string `json:"selector,omitempty"`
103 }
104
105 type wireSnapshot struct {
106 DocumentToken string `json:"documentToken"`
107 URL string `json:"url"`
108 Title string `json:"title,omitempty"`
109 Tree string `json:"tree"`
110 Refs int `json:"refs"`
111 Observation *Observation `json:"observation,omitempty"`
112 }
113
114 type wireScreenshotRequest struct {
115 TabID string `json:"tabId"`
116 Ref string `json:"ref,omitempty"`
117 FullPage bool `json:"fullPage,omitempty"`
118 }
119
120 type wireScreenshot struct {
121 Observation *Observation `json:"observation,omitempty"`
122 Path string `json:"path"`
123 MIME string `json:"mime,omitempty"`
124 Width int `json:"width,omitempty"`
125 Height int `json:"height,omitempty"`
126 ObservationToken string `json:"observationToken,omitempty"`
127 CSSWidth int `json:"cssWidth,omitempty"`
128 CSSHeight int `json:"cssHeight,omitempty"`
129 }
130
131 type wireActRequest struct {
132 OperationID string `json:"operationId"`
133 TabID string `json:"tabId"`
134 DocumentToken string `json:"documentToken,omitempty"`
135 Action string `json:"action"`
136 Ref string `json:"ref,omitempty"`
137 Text string `json:"text,omitempty"`
138 Keys string `json:"keys,omitempty"`
139 Options []string `json:"options,omitempty"`
140 Files []string `json:"files,omitempty"`
141 Submit bool `json:"submit,omitempty"`
142 DeltaX int `json:"deltaX,omitempty"`
143 DeltaY int `json:"deltaY,omitempty"`
144 }
145
146 func toWireAct(req ActRequest) wireActRequest {
147 return wireActRequest(req)
148 }
149
150 func (w wireActRequest) request() ActRequest {
151 return ActRequest(w)
152 }
153
154 type wireActResult struct {
155 Executed bool `json:"executed"`
156 Reason string `json:"reason,omitempty"`
157 DocumentToken string `json:"documentToken,omitempty"`
158 Outcome string `json:"outcome,omitempty"`
159 }
160
161 func (w *wireActResult) UnmarshalJSON(data []byte) error {
162 type receipt wireActResult
163 var fields map[string]json.RawMessage
164 if err := json.Unmarshal(data, &fields); err != nil {
165 return err
166 }
167 var executed *bool
168 if err := json.Unmarshal(fields["executed"], &executed); err != nil || executed == nil {
169 return fmt.Errorf("browser receipt is missing a boolean executed field")
170 }
171 var decoded receipt
172 if err := json.Unmarshal(data, &decoded); err != nil {
173 return err
174 }
175 *w = wireActResult(decoded)
176 return nil
177 }
178
179 type wireDownloadsRequest struct {
180 TabID string `json:"tabId"`
181 WaitForMs int64 `json:"waitForMs,omitempty"`
182 }
183
184 func (w wireDownloadsRequest) request() DownloadsRequest {
185 return DownloadsRequest{TabID: w.TabID, WaitFor: time.Duration(w.WaitForMs) * time.Millisecond}
186 }
187
188 type wireDownload struct {
189 ID string `json:"id"`
190 URL string `json:"url,omitempty"`
191 Path string `json:"path,omitempty"`
192 State string `json:"state,omitempty"`
193 Bytes int64 `json:"bytes,omitempty"`
194 }
195
196 type wireDownloads struct {
197 Downloads []wireDownload `json:"downloads"`
198 }
199
200 type wireCloseRequest struct {
201 OperationID string `json:"operationId"`
202 TabID string `json:"tabId"`
203 }
204
204 lines GO