@opencode/browse 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1410 +0,0 @@
1
- import {
2
- unpackedExtensionId,
3
- extensionReconnectAlarmPeriodMs,
4
- extensionProtocolAdvice,
5
- parseJsonObject
6
- } from "./main-3gthmkzw.mjs";
7
- import {
8
- relayAuthorization
9
- } from "./main-yv64m9we.mjs";
10
- import {
11
- opencodeBrowserBuildId2
12
- } from "./main-ry14nc9q.mjs";
13
- import {
14
- compiledBinary,
15
- scriptEntry,
16
- cliCommand,
17
- installCommand,
18
- npmRuntime
19
- } from "./main-3cza6vx1.mjs";
20
-
21
- // src/relay/helpers.ts
22
- import { Option, Predicate, Schema as Schema3 } from "effect";
23
-
24
- // src/relay/http.ts
25
- import { Effect, Schema as Schema2 } from "effect";
26
-
27
- // src/relay/schema.ts
28
- import { Schema } from "effect";
29
- var SessionSummary = Schema.Struct({
30
- id: Schema.String,
31
- createdAt: Schema.String,
32
- updatedAt: Schema.String,
33
- connected: Schema.Boolean,
34
- pageUrl: Schema.NullOr(Schema.String),
35
- stateKeys: Schema.Array(Schema.String),
36
- readOnly: Schema.optionalKey(Schema.Boolean),
37
- allowedOrigins: Schema.optionalKey(Schema.Array(Schema.String)),
38
- browser: Schema.optionalKey(Schema.String)
39
- });
40
- var ExecuteSessionSummary = SessionSummary.pipe(Schema.fieldsAssign({
41
- created: Schema.optionalKey(Schema.Boolean)
42
- }));
43
- var SessionContainer = Schema.Struct({
44
- session: SessionSummary
45
- });
46
- var SessionsContainer = Schema.Struct({
47
- sessions: Schema.Array(SessionSummary)
48
- });
49
- var SessionStopReport = Schema.Struct({
50
- execute: Schema.Boolean,
51
- handoff: Schema.Boolean,
52
- forced: Schema.Boolean
53
- });
54
- var SessionDeleted = Schema.Struct({
55
- deleted: Schema.Boolean,
56
- id: Schema.String,
57
- stopped: Schema.optionalKey(SessionStopReport)
58
- });
59
- var SessionResetResponse = Schema.Struct({
60
- session: SessionSummary,
61
- stopped: Schema.optionalKey(SessionStopReport)
62
- });
63
- var TargetSelection = Schema.Struct({
64
- urlIncludes: Schema.optionalKey(Schema.NonEmptyString),
65
- index: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))
66
- }).check(Schema.makeFilter((selection) => {
67
- const hasUrl = selection.urlIncludes !== undefined;
68
- const hasIndex = selection.index !== undefined;
69
- return hasUrl !== hasIndex ? undefined : "targetSelection must contain exactly one of urlIncludes or index";
70
- }));
71
- var ExecuteRequest = Schema.Struct({
72
- sessionId: Schema.optionalKey(Schema.String),
73
- code: Schema.String,
74
- createIfMissing: Schema.Boolean,
75
- browser: Schema.optionalKey(Schema.String),
76
- removeAfterExecute: Schema.optionalKey(Schema.Boolean),
77
- timeoutMs: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
78
- targetSelection: Schema.optionalKey(TargetSelection)
79
- });
80
- var SessionAdoptRequest = Schema.Struct({
81
- sessionId: Schema.optionalKey(Schema.String),
82
- createIfMissing: Schema.Boolean,
83
- targetSelection: Schema.optionalKey(TargetSelection),
84
- tabId: Schema.optionalKey(Schema.Int)
85
- });
86
- var SessionNewRequest = Schema.Struct({
87
- id: Schema.optionalKey(Schema.String),
88
- readOnly: Schema.optionalKey(Schema.Boolean),
89
- allowedOrigins: Schema.optionalKey(Schema.Array(Schema.String)),
90
- browser: Schema.optionalKey(Schema.String)
91
- });
92
- var SessionEnsureRequest = Schema.Struct({
93
- id: Schema.NonEmptyString,
94
- readOnly: Schema.optionalKey(Schema.Boolean)
95
- });
96
- var SessionIdRequest = Schema.Struct({
97
- id: Schema.String
98
- });
99
- var SessionAllowlistRequest = Schema.Struct({
100
- id: Schema.String,
101
- allowedOrigins: Schema.Array(Schema.String)
102
- });
103
- var SessionAdoptResponse = Schema.Struct({
104
- session: ExecuteSessionSummary,
105
- adoptedUrl: Schema.String,
106
- adoptedTargetId: Schema.String
107
- });
108
- var ExecuteLogLocation = Schema.Struct({
109
- url: Schema.String,
110
- lineNumber: Schema.Number,
111
- columnNumber: Schema.Number
112
- });
113
- var ExecuteLogEntry = Schema.Struct({
114
- source: Schema.Literals(["script", "page"]),
115
- type: Schema.String,
116
- text: Schema.String,
117
- location: Schema.optionalKey(ExecuteLogLocation),
118
- repeatCount: Schema.optionalKey(Schema.Number)
119
- });
120
- var ExecuteLogSummary = Schema.Struct({
121
- totalCount: Schema.Number,
122
- returnedCount: Schema.Number,
123
- repeatedCount: Schema.Number,
124
- omittedCount: Schema.Number
125
- });
126
- var ExecuteAftermath = Schema.Struct({
127
- startUrl: Schema.NullOr(Schema.String),
128
- endUrl: Schema.NullOr(Schema.String),
129
- navigations: Schema.Array(Schema.String),
130
- consoleErrorCount: Schema.Number,
131
- pageErrorCount: Schema.Number,
132
- handoffs: Schema.Number,
133
- challenge: Schema.optionalKey(Schema.Struct({
134
- vendor: Schema.String,
135
- kind: Schema.Literals(["non-interactive", "interactive", "block"]),
136
- status: Schema.Literals(["cleared", "blocked"]),
137
- signal: Schema.String,
138
- url: Schema.String,
139
- waitedMs: Schema.Number,
140
- recommendation: Schema.Literals(["continue", "handoff", "stop"])
141
- }))
142
- });
143
- var ExecuteMedia = Schema.Struct({
144
- type: Schema.Literal("image"),
145
- mimeType: Schema.String,
146
- data: Schema.String,
147
- size: Schema.Number
148
- });
149
- var ExecuteResponse = Schema.Struct({
150
- text: Schema.String,
151
- value: Schema.optionalKey(Schema.Unknown),
152
- media: Schema.optionalKey(Schema.Array(ExecuteMedia)),
153
- isError: Schema.Boolean,
154
- logs: Schema.Array(ExecuteLogEntry),
155
- logSummary: Schema.optionalKey(ExecuteLogSummary),
156
- warnings: Schema.optionalKey(Schema.Array(Schema.String)),
157
- diagnostic: Schema.optionalKey(Schema.String),
158
- aftermath: Schema.optionalKey(ExecuteAftermath),
159
- session: ExecuteSessionSummary
160
- });
161
- var AuthenticatedJsonMethod = Schema.Literals(["GET", "POST", "PUT", "PATCH", "DELETE"]);
162
- var AuthenticatedJsonRequest = Schema.Struct({
163
- sessionId: Schema.NonEmptyString,
164
- origin: Schema.NonEmptyString,
165
- startUrl: Schema.optionalKey(Schema.NonEmptyString),
166
- method: AuthenticatedJsonMethod,
167
- path: Schema.NonEmptyString,
168
- headers: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
169
- body: Schema.optionalKey(Schema.Json),
170
- sensitive: Schema.optionalKey(Schema.Boolean),
171
- handoffOnAuthFailure: Schema.optionalKey(Schema.Boolean),
172
- handoffMessage: Schema.optionalKey(Schema.String),
173
- timeoutMs: Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 120000 }))),
174
- maxResponseBytes: Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 1e7 })))
175
- });
176
- var AuthenticatedJsonOutcome = Schema.TaggedUnion({
177
- Success: {
178
- status: Schema.Int,
179
- value: Schema.Json
180
- },
181
- OriginMismatch: {
182
- expectedOrigin: Schema.String,
183
- actualOrigin: Schema.String
184
- },
185
- RequestFailed: {
186
- outcome: Schema.Literals(["not-sent", "unknown"])
187
- },
188
- HttpError: {
189
- status: Schema.Int
190
- },
191
- ResponseTooLarge: {
192
- status: Schema.Int,
193
- maxResponseBytes: Schema.Int
194
- },
195
- InvalidJson: {
196
- status: Schema.Int
197
- },
198
- SensitiveCaptureActive: {}
199
- });
200
- var TargetSummary = Schema.Struct({
201
- id: Schema.String,
202
- type: Schema.String,
203
- title: Schema.String,
204
- url: Schema.String,
205
- tabId: Schema.optionalKey(Schema.Number),
206
- sessionId: Schema.optionalKey(Schema.String),
207
- opencodeBrowserSessionId: Schema.optionalKey(Schema.String),
208
- owner: Schema.optionalKey(Schema.Literals(["relay", "user"])),
209
- crashed: Schema.optionalKey(Schema.Boolean),
210
- protectedUi: Schema.optionalKey(Schema.Boolean)
211
- });
212
- var TargetSummaries = Schema.Array(TargetSummary);
213
- var ConnectedBrowser = Schema.Struct({
214
- id: Schema.String,
215
- name: Schema.String,
216
- default: Schema.Boolean,
217
- connected: Schema.Boolean,
218
- version: Schema.NullOr(Schema.String),
219
- tabs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
220
- });
221
- var ExtensionStatus = Schema.Struct({
222
- connected: Schema.Boolean,
223
- pairingState: Schema.optionalKey(Schema.Literals(["connected", "incompatible", "refused", "unpaired", "paired-offline", "not-seen"])),
224
- nextStep: Schema.optionalKey(Schema.String),
225
- version: Schema.NullOr(Schema.String),
226
- build: Schema.optionalKey(Schema.NullOr(Schema.String)),
227
- origin: Schema.optionalKey(Schema.NullOr(Schema.String)),
228
- protocolVersion: Schema.optionalKey(Schema.NullOr(Schema.Number)),
229
- protocolCompatible: Schema.optionalKey(Schema.NullOr(Schema.Boolean)),
230
- protocolLegacy: Schema.optionalKey(Schema.NullOr(Schema.Boolean)),
231
- browsers: Schema.optionalKey(Schema.Array(ConnectedBrowser)),
232
- rejectedConnections: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
233
- pairingFailures: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
234
- processFaults: Schema.optionalKey(Schema.Struct({
235
- suppressed: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
236
- lastAt: Schema.optionalKey(Schema.String),
237
- lastMessage: Schema.optionalKey(Schema.String)
238
- })),
239
- activeTargets: Schema.Number,
240
- childTargets: Schema.optionalKey(Schema.Number),
241
- cdpClients: Schema.optionalKey(Schema.Number),
242
- sessions: Schema.optionalKey(Schema.Array(SessionSummary)),
243
- targets: Schema.optionalKey(TargetSummaries)
244
- });
245
- var RelayVersion = Schema.Struct({
246
- version: Schema.String,
247
- buildId: Schema.optionalKey(Schema.String),
248
- instanceId: Schema.optionalKey(Schema.String),
249
- startedAt: Schema.optionalKey(Schema.String),
250
- pid: Schema.optionalKey(Schema.Number),
251
- managed: Schema.optionalKey(Schema.Boolean),
252
- shutdownProtocol: Schema.optionalKey(Schema.Literal(2))
253
- });
254
- var RelayIdentity = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}(?![\s\S])/));
255
- var RelayBuildId = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._:+-]{0,255}(?![\s\S])/));
256
- var RelayShutdownRequest = Schema.Struct({
257
- instanceId: RelayIdentity,
258
- requestId: RelayIdentity,
259
- reason: Schema.Literal("explicit-restart"),
260
- client: Schema.Struct({
261
- kind: Schema.Literals(["cli", "mcp", "sdk"]),
262
- instanceId: RelayIdentity,
263
- buildId: RelayBuildId
264
- }),
265
- force: Schema.optionalKey(Schema.Boolean)
266
- });
267
- var RelayShutdownResponse = Schema.Struct({
268
- stopping: Schema.Literal(true)
269
- });
270
- var NetworkContentMode = Schema.Literals(["omit", "embed"]);
271
- var NetworkStartRequest = Schema.Struct({
272
- sessionId: Schema.NonEmptyString,
273
- urlFilter: Schema.optionalKey(Schema.String),
274
- resourceTypes: Schema.optionalKey(Schema.Array(Schema.NonEmptyString).check(Schema.isMaxLength(50))),
275
- content: Schema.optionalKey(NetworkContentMode),
276
- maxBodyBytes: Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 1e7 }))),
277
- maxTotalBodyBytes: Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 1e8 }))),
278
- maxEntries: Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 1e4 }))),
279
- maxDurationMs: Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 86400000 })))
280
- });
281
- var NetworkSessionRequest = Schema.Struct({ sessionId: Schema.NonEmptyString });
282
- var NetworkStopRequest = NetworkSessionRequest.pipe(Schema.fieldsAssign({
283
- outputPath: Schema.optionalKey(Schema.String),
284
- secrets: Schema.optionalKey(Schema.NonEmptyString)
285
- }));
286
- var NetworkStatusResponse = Schema.Struct({
287
- active: Schema.Boolean,
288
- startedAt: Schema.optionalKey(Schema.String),
289
- entryCount: Schema.Number,
290
- responseCount: Schema.Number,
291
- failureCount: Schema.Number,
292
- capturedBodyBytes: Schema.Number,
293
- truncatedBodyCount: Schema.Number,
294
- droppedEntryCount: Schema.Number,
295
- urlFilter: Schema.optionalKey(Schema.String),
296
- resourceTypes: Schema.optionalKey(Schema.Array(Schema.String)),
297
- content: Schema.optionalKey(NetworkContentMode),
298
- secrets: Schema.optionalKey(Schema.String),
299
- endsAt: Schema.optionalKey(Schema.String),
300
- ended: Schema.optionalKey(Schema.String)
301
- });
302
- var AuthProfileSlotSummary = Schema.Struct({
303
- ref: Schema.String,
304
- sources: Schema.Array(Schema.String),
305
- expiresAt: Schema.optionalKey(Schema.String),
306
- expired: Schema.Boolean
307
- });
308
- var AuthProfileSummary = Schema.Struct({
309
- name: Schema.String,
310
- createdAt: Schema.String,
311
- updatedAt: Schema.String,
312
- slotCount: Schema.Number,
313
- slots: Schema.Array(AuthProfileSlotSummary)
314
- });
315
- var NetworkEndpointDigest = Schema.Struct({
316
- method: Schema.String,
317
- url: Schema.String,
318
- status: Schema.optionalKey(Schema.Number),
319
- count: Schema.Number,
320
- requestHeaders: Schema.optionalKey(Schema.Array(Schema.String)),
321
- requestKeys: Schema.optionalKey(Schema.Array(Schema.String)),
322
- responseKeys: Schema.optionalKey(Schema.Array(Schema.String))
323
- });
324
- var NetworkStopResponse = NetworkStatusResponse.pipe(Schema.fieldsAssign({
325
- active: Schema.Literal(false),
326
- stoppedAt: Schema.String,
327
- outputPath: Schema.optionalKey(Schema.String),
328
- authProfile: Schema.optionalKey(AuthProfileSummary),
329
- updatedSecretRefs: Schema.Array(Schema.String),
330
- observedSecretRefs: Schema.Array(Schema.String),
331
- endpoints: Schema.optionalKey(Schema.Array(NetworkEndpointDigest))
332
- }));
333
- var NetworkCancelResponse = Schema.Struct({ cancelled: Schema.Boolean });
334
- var AuthProfileRequest = Schema.Struct({ name: Schema.NonEmptyString });
335
- var AuthRefreshRequest = Schema.Struct({
336
- sessionId: Schema.NonEmptyString,
337
- name: Schema.NonEmptyString,
338
- urlFilter: Schema.optionalKey(Schema.String),
339
- timeoutMs: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0)))
340
- });
341
- var AuthRunRequest = Schema.Struct({
342
- name: Schema.NonEmptyString,
343
- command: Schema.NonEmptyString,
344
- args: Schema.optionalKey(Schema.Array(Schema.String)),
345
- cwd: Schema.optionalKey(Schema.String),
346
- timeoutMs: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0)))
347
- });
348
- var AuthRunResponse = Schema.Struct({
349
- exitCode: Schema.Number,
350
- signal: Schema.NullOr(Schema.String),
351
- stdout: Schema.String,
352
- stderr: Schema.String,
353
- stdoutTruncated: Schema.Boolean,
354
- stderrTruncated: Schema.Boolean,
355
- durationMs: Schema.Number
356
- });
357
- var RecordingMode = Schema.Literals(["tab-capture", "cdp"]);
358
- var RecordingRequestedMode = Schema.Literals(["auto", "tab-capture", "cdp"]);
359
- var RecordingFrameRate = Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 60 }));
360
- var RecordingQuality = Schema.Struct({
361
- width: Schema.Number,
362
- height: Schema.Number,
363
- frameRate: Schema.Number,
364
- sourceFrameCount: Schema.Number,
365
- encodedSourceFrameCount: Schema.Number,
366
- coalescedFrameCount: Schema.Number,
367
- droppedFrameCount: Schema.Number,
368
- achievedSourceFrameRate: Schema.Number,
369
- achievedEncodedSourceFrameRate: Schema.Number,
370
- screenshotFallback: Schema.Boolean,
371
- sourceWidth: Schema.optionalKey(Schema.Number),
372
- sourceHeight: Schema.optionalKey(Schema.Number)
373
- });
374
- var RecordingTargetRequest = Schema.Struct({
375
- sessionId: Schema.optionalKey(Schema.String),
376
- tabId: Schema.optionalKey(Schema.Number)
377
- });
378
- var RecordingStartRequest = RecordingTargetRequest.pipe(Schema.fieldsAssign({
379
- outputPath: Schema.String,
380
- mode: Schema.optionalKey(RecordingRequestedMode),
381
- audio: Schema.optionalKey(Schema.Boolean),
382
- frameRate: Schema.optionalKey(RecordingFrameRate),
383
- videoBitsPerSecond: Schema.optionalKey(Schema.Number),
384
- audioBitsPerSecond: Schema.optionalKey(Schema.Number),
385
- maxDurationMs: Schema.optionalKey(Schema.Number),
386
- cursor: Schema.optionalKey(Schema.Boolean)
387
- }));
388
- var RecordingArtifactType = Schema.Literals(["webm", "mp4"]);
389
- var RecordingStartResponse = Schema.Struct({
390
- success: Schema.Boolean,
391
- tabId: Schema.optionalKey(Schema.Number),
392
- startedAt: Schema.optionalKey(Schema.Number),
393
- path: Schema.optionalKey(Schema.String),
394
- mimeType: Schema.optionalKey(Schema.String),
395
- mode: Schema.optionalKey(RecordingMode),
396
- artifactType: Schema.optionalKey(RecordingArtifactType),
397
- frameRate: Schema.optionalKey(Schema.Number),
398
- error: Schema.optionalKey(Schema.String)
399
- });
400
- var RecordingStopResponse = Schema.Struct({
401
- success: Schema.Boolean,
402
- tabId: Schema.optionalKey(Schema.Number),
403
- duration: Schema.optionalKey(Schema.Number),
404
- path: Schema.optionalKey(Schema.String),
405
- size: Schema.optionalKey(Schema.Number),
406
- mode: Schema.optionalKey(RecordingMode),
407
- artifactType: Schema.optionalKey(RecordingArtifactType),
408
- frameCount: Schema.optionalKey(Schema.Number),
409
- quality: Schema.optionalKey(RecordingQuality),
410
- error: Schema.optionalKey(Schema.String)
411
- });
412
- var RecordingStatusResponse = Schema.Struct({
413
- isRecording: Schema.Boolean,
414
- tabId: Schema.optionalKey(Schema.Number),
415
- startedAt: Schema.optionalKey(Schema.Number),
416
- path: Schema.optionalKey(Schema.String),
417
- size: Schema.optionalKey(Schema.Number),
418
- mode: Schema.optionalKey(RecordingMode),
419
- artifactType: Schema.optionalKey(RecordingArtifactType),
420
- frameCount: Schema.optionalKey(Schema.Number),
421
- quality: Schema.optionalKey(RecordingQuality)
422
- });
423
- var TabsCleanupResponse = Schema.Struct({
424
- closed: Schema.Array(Schema.Struct({ tabId: Schema.Number, title: Schema.String, idleMinutes: Schema.Number }))
425
- });
426
- var SettingValue = Schema.Union([Schema.String, Schema.Number]);
427
- var SettingReport = Schema.Struct({
428
- value: SettingValue,
429
- effective: SettingValue,
430
- allowed: Schema.Array(SettingValue),
431
- description: Schema.String,
432
- overriddenBy: Schema.optionalKey(Schema.String)
433
- });
434
- var SettingsGetResponse = Schema.Struct({
435
- settings: Schema.Record(Schema.String, SettingReport),
436
- note: Schema.String
437
- });
438
- var SettingsSetResponse = Schema.Struct({
439
- changed: Schema.Array(Schema.Struct({ key: Schema.String, from: SettingValue, to: SettingValue })),
440
- unchanged: Schema.Array(Schema.String),
441
- overridden: Schema.Array(Schema.Struct({ key: Schema.String, requested: SettingValue, variable: Schema.String, effective: SettingValue })),
442
- settings: Schema.Record(Schema.String, SettingReport),
443
- message: Schema.String
444
- });
445
- var RecordingCancelResponse = Schema.Struct({
446
- success: Schema.Boolean,
447
- error: Schema.optionalKey(Schema.String)
448
- });
449
- var FlightRecorderStartRequest = RecordingTargetRequest.pipe(Schema.fieldsAssign({
450
- retentionMs: Schema.optionalKey(Schema.Number.check(Schema.isInt(), Schema.isBetween({ minimum: 1000, maximum: 120000 }))),
451
- frameRate: Schema.optionalKey(RecordingFrameRate),
452
- cursor: Schema.optionalKey(Schema.Boolean)
453
- }));
454
- var FlightRecorderStatusResponse = Schema.Struct({
455
- active: Schema.Boolean,
456
- tabId: Schema.optionalKey(Schema.Number),
457
- sessionId: Schema.optionalKey(Schema.String),
458
- startedAt: Schema.optionalKey(Schema.Number),
459
- retentionMs: Schema.optionalKey(Schema.Number),
460
- retainedDurationMs: Schema.optionalKey(Schema.Number),
461
- frameRate: Schema.optionalKey(Schema.Number),
462
- bufferedFrames: Schema.optionalKey(Schema.Number),
463
- bufferedBytes: Schema.optionalKey(Schema.Number),
464
- sourceFrameCount: Schema.optionalKey(Schema.Number),
465
- droppedFrameCount: Schema.optionalKey(Schema.Number),
466
- saving: Schema.optionalKey(Schema.Boolean)
467
- });
468
- var FlightRecorderSaveRequest = RecordingTargetRequest.pipe(Schema.fieldsAssign({
469
- outputPath: Schema.String,
470
- durationMs: Schema.optionalKey(Schema.Number.check(Schema.isInt(), Schema.isGreaterThan(0)))
471
- }));
472
- var FlightRecorderSaveResponse = Schema.Struct({
473
- path: Schema.String,
474
- durationMs: Schema.Number,
475
- frameCount: Schema.Number,
476
- sourceFrameCount: Schema.Number,
477
- droppedFrameCount: Schema.Number
478
- });
479
- var FlightRecorderCancelResponse = Schema.Struct({ cancelled: Schema.Boolean });
480
- var RelayErrorCode = Schema.Literals([
481
- "browser-unavailable",
482
- "invalid-request",
483
- "unauthorized",
484
- "relay-starting",
485
- "relay-busy",
486
- "auth-profile-not-found",
487
- "capture-conflict",
488
- "session-already-exists",
489
- "session-inactive",
490
- "session-not-found",
491
- "session-timeout",
492
- "setup-failed",
493
- "target-ambiguous",
494
- "target-changed",
495
- "target-not-found",
496
- "target-owned",
497
- "internal"
498
- ]);
499
- var ErrorEnvelope = Schema.Struct({
500
- error: Schema.String,
501
- code: Schema.optionalKey(RelayErrorCode)
502
- });
503
-
504
- // src/relay/http.ts
505
- var maxCliBodyBytes = 1e6;
506
-
507
- class HttpRouteError2 extends Schema2.TaggedError()("HttpApi.HttpRouteError", { message: Schema2.String, status: Schema2.Number, code: RelayErrorCode }) {
508
- }
509
- function formatHostForUrl2(host) {
510
- if (host.includes(":") && !host.startsWith("[")) {
511
- return `[${host}]`;
512
- }
513
- return host;
514
- }
515
- function listenHttpServer2(options) {
516
- return Effect.callback((resume) => {
517
- const onError = (error) => {
518
- resume(Effect.fail(error));
519
- };
520
- options.server.once("error", onError);
521
- options.server.listen(options.port, options.host, () => {
522
- options.server.off("error", onError);
523
- resume(Effect.void);
524
- });
525
- return Effect.sync(() => {
526
- options.server.off("error", onError);
527
- });
528
- });
529
- }
530
- function closeHttpServer2(server) {
531
- if (!server.listening) {
532
- return Effect.void;
533
- }
534
- return Effect.callback((resume) => {
535
- server.close((error) => {
536
- if (error) {
537
- resume(Effect.fail(new Error("close http server", { cause: error })));
538
- return;
539
- }
540
- resume(Effect.void);
541
- });
542
- return Effect.void;
543
- });
544
- }
545
- function closeWebSocketServer2(server) {
546
- return Effect.callback((resume) => {
547
- server.close((error) => {
548
- const nodeError = error;
549
- if (nodeError?.code === "ERR_SERVER_NOT_RUNNING") {
550
- resume(Effect.void);
551
- return;
552
- }
553
- if (error) {
554
- resume(Effect.fail(new Error("close websocket server", { cause: error })));
555
- return;
556
- }
557
- resume(Effect.void);
558
- });
559
- return Effect.void;
560
- });
561
- }
562
- function logCloseError2(message) {
563
- return (effect) => {
564
- return Effect.catch(effect, (error) => {
565
- return Effect.sync(() => {
566
- console.error(message, error);
567
- });
568
- });
569
- };
570
- }
571
- function sendJson2(response, value, status = 200) {
572
- response.writeHead(status, { "content-type": "application/json" });
573
- response.end(JSON.stringify(value));
574
- }
575
- function readJsonBody2(request) {
576
- const contentType = request.headers["content-type"];
577
- const contentTypeValue = Array.isArray(contentType) ? contentType[0] : contentType;
578
- if (!contentTypeValue?.toLowerCase().includes("application/json")) {
579
- return Effect.fail(new HttpRouteError2({ message: "Content-Type must be application/json", status: 415, code: "invalid-request" }));
580
- }
581
- return Effect.callback((resume) => {
582
- const chunks = [];
583
- let totalBytes = 0;
584
- let completed = false;
585
- const onData = (chunk) => {
586
- if (completed) {
587
- return;
588
- }
589
- totalBytes += chunk.byteLength;
590
- if (totalBytes > maxCliBodyBytes) {
591
- completed = true;
592
- request.destroy(new Error(`Request body exceeds ${maxCliBodyBytes} bytes`));
593
- resume(Effect.fail(new HttpRouteError2({ message: `Request body exceeds ${maxCliBodyBytes} bytes`, status: 413, code: "invalid-request" })));
594
- return;
595
- }
596
- chunks.push(chunk);
597
- };
598
- const onError = (error) => {
599
- if (completed) {
600
- return;
601
- }
602
- completed = true;
603
- resume(Effect.fail(new Error("read request body", { cause: error })));
604
- };
605
- const onAbort = () => {
606
- if (completed) {
607
- return;
608
- }
609
- completed = true;
610
- resume(Effect.fail(new Error("request body aborted")));
611
- };
612
- const onClose = () => {
613
- if (completed || request.complete) {
614
- return;
615
- }
616
- completed = true;
617
- resume(Effect.fail(new Error("request closed before body completed")));
618
- };
619
- const onEnd = () => {
620
- if (completed) {
621
- return;
622
- }
623
- completed = true;
624
- const text = Buffer.concat(chunks).toString("utf8");
625
- if (!text.trim()) {
626
- resume(Effect.succeed({}));
627
- return;
628
- }
629
- try {
630
- resume(Effect.succeed(parseJsonObject(text)));
631
- } catch (error) {
632
- resume(Effect.fail(new HttpRouteError2({ message: "Invalid JSON body", status: 400, code: "invalid-request" })));
633
- }
634
- };
635
- request.on("data", onData);
636
- request.on("error", onError);
637
- request.on("aborted", onAbort);
638
- request.on("close", onClose);
639
- request.on("end", onEnd);
640
- return Effect.sync(() => {
641
- request.off("data", onData);
642
- request.off("error", onError);
643
- request.off("aborted", onAbort);
644
- request.off("close", onClose);
645
- request.off("end", onEnd);
646
- });
647
- });
648
- }
649
-
650
- // src/relay/origins.ts
651
- import crypto from "node:crypto";
652
- var stableUnpackedExtensionOrigin2 = `chrome-extension://${unpackedExtensionId}`;
653
- function chromeExtensionOriginForPath2(extensionPath, platform = process.platform) {
654
- const normalizedPath = platform === "win32" && /^[a-z]:/.test(extensionPath) ? extensionPath.charAt(0).toUpperCase() + extensionPath.slice(1) : extensionPath;
655
- const pathBytes = platform === "win32" ? Buffer.from(normalizedPath, "utf16le") : normalizedPath;
656
- const digest = crypto.createHash("sha256").update(pathBytes).digest();
657
- let extensionId = "";
658
- for (const byte of digest.subarray(0, 16)) {
659
- extensionId += String.fromCharCode(97 + (byte >> 4), 97 + (byte & 15));
660
- }
661
- return `chrome-extension://${extensionId}`;
662
- }
663
- var chromeExtensionOriginPattern = /^chrome-extension:\/\/[a-p]+$/;
664
- function parseAdditionalExtensionOrigins2(raw) {
665
- if (!raw) {
666
- return [];
667
- }
668
- const seen = new Set;
669
- const origins = [];
670
- for (const token of raw.split(/[\s,]+/)) {
671
- const value = token.trim();
672
- if (!value || !chromeExtensionOriginPattern.test(value) || seen.has(value)) {
673
- continue;
674
- }
675
- seen.add(value);
676
- origins.push(value);
677
- }
678
- return origins;
679
- }
680
- function validateHostHeader2(options) {
681
- const parsed = parseHostHeader(options.hostHeader);
682
- if (!parsed) {
683
- return "Invalid Host header";
684
- }
685
- if (parsed.port !== undefined && parsed.port !== options.port) {
686
- return "Invalid Host header port";
687
- }
688
- const allowedHosts = new Set(["localhost", "127.0.0.1", "::1", normalizeHostname(options.host)]);
689
- if (!allowedHosts.has(parsed.hostname)) {
690
- return "Invalid Host header";
691
- }
692
- return;
693
- }
694
- function validateBrowserFetchSite2(request) {
695
- const secFetchSite = request.headers["sec-fetch-site"];
696
- const value = Array.isArray(secFetchSite) ? secFetchSite[0] : secFetchSite;
697
- if (!value || value === "same-origin" || value === "none") {
698
- return;
699
- }
700
- return "Cross-origin browser requests are not allowed";
701
- }
702
- function validateWebSocketOrigin2(options) {
703
- if (!options.origin) {
704
- return options.requireChromeExtension ? "Extension WebSocket origin is required" : undefined;
705
- }
706
- if (options.origin === stableUnpackedExtensionOrigin2 || options.additionalChromeExtensionOrigins?.has(options.origin)) {
707
- return;
708
- }
709
- if (options.requireChromeExtension) {
710
- return "Extension WebSocket origin is not allowed";
711
- }
712
- return "WebSocket origin is not allowed";
713
- }
714
- function normalizeHostname(host) {
715
- const value = host.trim().toLowerCase();
716
- if (value.startsWith("[") && value.endsWith("]")) {
717
- return value.slice(1, -1);
718
- }
719
- return value;
720
- }
721
- function parseHostHeader(hostHeader) {
722
- const value = hostHeader?.trim().toLowerCase();
723
- if (!value) {
724
- return;
725
- }
726
- if (value.startsWith("[")) {
727
- const closingBracket = value.indexOf("]");
728
- if (closingBracket === -1) {
729
- return;
730
- }
731
- const hostname = value.slice(1, closingBracket);
732
- const rest = value.slice(closingBracket + 1);
733
- if (!hostname) {
734
- return;
735
- }
736
- if (!rest) {
737
- return { hostname };
738
- }
739
- if (!rest.startsWith(":")) {
740
- return;
741
- }
742
- const port = parsePort(rest.slice(1));
743
- return port === undefined ? undefined : { hostname, port };
744
- }
745
- if (value === "::1") {
746
- return { hostname: "::1" };
747
- }
748
- const colonCount = value.split(":").length - 1;
749
- if (colonCount > 1) {
750
- return;
751
- }
752
- if (colonCount === 0) {
753
- return { hostname: value };
754
- }
755
- const [hostname, portText] = value.split(":");
756
- if (!hostname || portText === undefined) {
757
- return;
758
- }
759
- const port = parsePort(portText);
760
- return port === undefined ? undefined : { hostname, port };
761
- }
762
- function parsePort(value) {
763
- if (!/^\d+$/.test(value)) {
764
- return;
765
- }
766
- const port = Number(value);
767
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
768
- return;
769
- }
770
- return port;
771
- }
772
-
773
- // src/relay/helpers.ts
774
- var defaultHost2 = "127.0.0.1";
775
- var defaultPort2 = 19988;
776
- var RawTargetSelection = Schema3.Struct({
777
- urlIncludes: Schema3.optionalKey(Schema3.String),
778
- index: Schema3.optionalKey(Schema3.Int.check(Schema3.isGreaterThanOrEqualTo(0)))
779
- });
780
- var decodeRawTargetSelection = Schema3.decodeUnknownOption(RawTargetSelection);
781
- var RawTargetInfo = Schema3.Struct({
782
- targetId: Schema3.String,
783
- type: Schema3.Literals(["page", "iframe", "worker"]),
784
- title: Schema3.optionalKey(Schema3.String),
785
- url: Schema3.String,
786
- canAccessOpener: Schema3.optionalKey(Schema3.Boolean),
787
- browserContextId: Schema3.optionalKey(Schema3.String),
788
- openerId: Schema3.optionalKey(Schema3.String),
789
- parentFrameId: Schema3.optionalKey(Schema3.String)
790
- });
791
- var decodeRawTargetInfo = Schema3.decodeUnknownOption(RawTargetInfo);
792
- function optionalSessionId2(value) {
793
- if (!Predicate.isString(value) || !value.trim()) {
794
- return;
795
- }
796
- const id = value.trim();
797
- if (!isValidSessionId2(id)) {
798
- throw new HttpRouteError2({
799
- message: "Session ids must use lowercase letters, numbers, and dashes, and be at most 63 characters",
800
- status: 400,
801
- code: "invalid-request"
802
- });
803
- }
804
- return id;
805
- }
806
- function isValidSessionId2(id) {
807
- if (id.length < 1 || id.length > 63 || id === "." || id === "..")
808
- return false;
809
- if (/[A-Z/\\:\0-\x1f\x7f]/.test(id))
810
- return false;
811
- return /^(?:[\p{Extended_Pictographic}\u200d\ufe0f]+\s*)?[a-z0-9][a-z0-9\p{Extended_Pictographic}\u200d\ufe0f\s_-]*$/u.test(id);
812
- }
813
- function requiredSessionId2(value) {
814
- const id = optionalSessionId2(value);
815
- if (!id) {
816
- throw new HttpRouteError2({ message: "sessionId is required", status: 400, code: "invalid-request" });
817
- }
818
- return id;
819
- }
820
- function parseTargetSelection2(value) {
821
- if (value === undefined) {
822
- return;
823
- }
824
- if (!getObject2(value)) {
825
- throw new HttpRouteError2({ message: "targetSelection must be an object", status: 400, code: "invalid-request" });
826
- }
827
- const decoded = decodeRawTargetSelection(value);
828
- if (Option.isNone(decoded)) {
829
- throw new HttpRouteError2({ message: "targetSelection.index must be a non-negative integer", status: 400, code: "invalid-request" });
830
- }
831
- const urlIncludes = decoded.value.urlIncludes ? decoded.value.urlIncludes : undefined;
832
- const index = decoded.value.index;
833
- if (urlIncludes && index !== undefined) {
834
- throw new HttpRouteError2({ message: "Use only one target selector", status: 400, code: "invalid-request" });
835
- }
836
- return {
837
- ...urlIncludes ? { urlIncludes } : {},
838
- ...index !== undefined ? { index } : {}
839
- };
840
- }
841
- function generateSessionId2(existing) {
842
- const adjectives = ["amber", "brisk", "calm", "clever", "cosmic", "gentle", "lucky", "quiet", "rapid", "tidy"];
843
- const nouns = ["badger", "comet", "falcon", "otter", "panda", "raven", "sparrow", "tiger", "walrus", "wombat"];
844
- for (let attempt = 0;attempt < 100; attempt++) {
845
- const adjective = adjectives[Math.floor(Math.random() * adjectives.length)] ?? "calm";
846
- const noun = nouns[Math.floor(Math.random() * nouns.length)] ?? "otter";
847
- const suffix = String(Math.floor(Math.random() * 1000)).padStart(3, "0");
848
- const id = `${adjective}-${noun}-${suffix}`;
849
- if (!existing.has(id)) {
850
- return id;
851
- }
852
- }
853
- return `session-${Date.now().toString(36)}`;
854
- }
855
- function sendCdpResponse2(socket, response) {
856
- socket.send(JSON.stringify(response));
857
- }
858
- function sendCdpEvent2(socket, event) {
859
- socket.send(JSON.stringify(event));
860
- }
861
- function getObject2(value) {
862
- return Predicate.isObject(value) ? value : undefined;
863
- }
864
- function getString2(object, key) {
865
- const value = object?.[key];
866
- return Predicate.isString(value) ? value : undefined;
867
- }
868
- function getNumber2(object, key) {
869
- const value = object?.[key];
870
- return Predicate.isNumber(value) ? value : undefined;
871
- }
872
- function getIdText2(object, key) {
873
- const value = object?.[key];
874
- return Predicate.isNumber(value) || Predicate.isString(value) ? String(value) : undefined;
875
- }
876
- function headerValue2(value) {
877
- if (!Predicate.isString(value) || !value)
878
- return;
879
- try {
880
- return decodeURIComponent(value);
881
- } catch {
882
- return value;
883
- }
884
- }
885
- function getTargetInfo2(value) {
886
- return Option.match(decodeRawTargetInfo(value), {
887
- onNone: () => {
888
- return;
889
- },
890
- onSome: (info) => ({
891
- targetId: info.targetId,
892
- type: info.type,
893
- title: info.title ?? info.url,
894
- url: info.url,
895
- attached: true,
896
- canAccessOpener: info.canAccessOpener ?? false,
897
- ...info.browserContextId === undefined ? {} : { browserContextId: info.browserContextId },
898
- ...info.openerId === undefined ? {} : { openerId: info.openerId },
899
- ...info.parentFrameId === undefined ? {} : { parentFrameId: info.parentFrameId }
900
- })
901
- });
902
- }
903
- function isRestrictedTarget2(targetInfo) {
904
- if (targetInfo.type !== "page" && targetInfo.type !== "iframe" && targetInfo.type !== "worker") {
905
- return true;
906
- }
907
- return isRestrictedUrl2(targetInfo.url);
908
- }
909
- var restrictedUrlPrefixes = ["chrome://", "chrome-extension://", "chrome-untrusted://", "devtools://", "edge://", "brave://"];
910
- function isRestrictedUrl2(url) {
911
- if (!url) {
912
- return false;
913
- }
914
- return restrictedUrlPrefixes.some((prefix) => url.startsWith(prefix));
915
- }
916
- async function dataUrlDocument2(url) {
917
- const response = await fetch(url);
918
- const type = (response.headers.get("content-type") ?? "text/plain").split(";")[0]?.trim().toLowerCase() ?? "text/plain";
919
- if (type === "text/html" || type === "application/xhtml+xml")
920
- return await response.text();
921
- if (type.startsWith("text/") || type === "application/json" || type.endsWith("+json") || type === "application/javascript") {
922
- const text = (await response.text()).replace(/[&<>]/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[char] ?? char);
923
- return `<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">${text}</pre></body></html>`;
924
- }
925
- if (type.startsWith("image/")) {
926
- return `<html><body style="margin: 0; display: grid; place-items: center; min-height: 100vh;"><img src="${url.replace(/"/g, "%22")}"></body></html>`;
927
- }
928
- return;
929
- }
930
-
931
- // src/relay/lifecycle.ts
932
- import { Config as Config2, Effect as Effect3, Result, Schedule, Schema as Schema5 } from "effect";
933
- import { spawn } from "node:child_process";
934
- import crypto2 from "node:crypto";
935
- import path from "node:path";
936
- import process2 from "node:process";
937
- import { fileURLToPath } from "node:url";
938
-
939
- // src/relay/client.ts
940
- import { NodeHttpClient } from "@effect/platform-node";
941
- import { Config, Context, Effect as Effect2, Layer, Option as Option2, Schema as Schema4 } from "effect";
942
- import { HttpClient, HttpClientRequest } from "effect/http";
943
- var portConfig = Config.Int("OPENCODE_BROWSER_PORT").pipe(Config.withDefault(defaultPort2));
944
- var extensionOriginsConfig = Config.String("OPENCODE_BROWSER_EXTENSION_ORIGINS").pipe(Config.withDefault(""));
945
- var endpointForPort = (port) => `http://127.0.0.1:${port}`;
946
-
947
- class RelayUnreachable extends Schema4.TaggedError()("RelayClient.RelayUnreachable", {
948
- message: Schema4.String,
949
- endpoint: Schema4.String,
950
- path: Schema4.String,
951
- cause: Schema4.Defect()
952
- }) {
953
- }
954
-
955
- class RelayRejected extends Schema4.TaggedError()("RelayClient.RelayRejected", {
956
- message: Schema4.String,
957
- status: Schema4.Number,
958
- path: Schema4.String,
959
- code: Schema4.optionalKey(RelayErrorCode)
960
- }) {
961
- }
962
-
963
- class RelayDecodeFailed extends Schema4.TaggedError()("RelayClient.RelayDecodeFailed", {
964
- message: Schema4.String,
965
- path: Schema4.String,
966
- cause: Schema4.Defect()
967
- }) {
968
- }
969
-
970
- class RelayEncodeFailed extends Schema4.TaggedError()("RelayClient.RelayEncodeFailed", {
971
- message: Schema4.String,
972
- path: Schema4.String,
973
- cause: Schema4.Defect()
974
- }) {
975
- }
976
-
977
- class RelayConfigInvalid extends Schema4.TaggedError()("RelayClient.RelayConfigInvalid", {
978
- message: Schema4.String,
979
- cause: Schema4.Defect()
980
- }) {
981
- }
982
-
983
- class Service extends Context.Service()("opencode-browser-cli/RelayClient") {
984
- }
985
- var decodeErrorEnvelope = Schema4.decodeUnknownOption(ErrorEnvelope);
986
- var decodeErrorMessage = Schema4.decodeUnknownOption(Schema4.Struct({ error: Schema4.String }));
987
- var make = Effect2.fn("RelayClient.make")(function* (options) {
988
- const port = yield* portConfig.pipe(Effect2.mapError((cause) => new RelayConfigInvalid({
989
- message: `Invalid OPENCODE_BROWSER_PORT configuration: ${cause.message}`,
990
- cause
991
- })));
992
- const endpoint = options?.endpoint ?? endpointForPort(port);
993
- const httpClient = yield* HttpClient.HttpClient;
994
- const readBody = (response, path) => response.json.pipe(Effect2.mapError((cause) => new RelayDecodeFailed({
995
- message: `Relay returned an unreadable response for ${path}`,
996
- path,
997
- cause
998
- })));
999
- const handleResponse = (response, path, schema) => readBody(response, path).pipe(Effect2.flatMap((body) => {
1000
- if (response.status < 200 || response.status >= 300) {
1001
- const envelope = decodeErrorEnvelope(body);
1002
- const message = Option2.isSome(envelope) ? envelope.value.error : Option2.getOrElse(Option2.map(decodeErrorMessage(body), (value) => value.error), () => `Relay responded with HTTP ${response.status} for ${path}`);
1003
- return Effect2.fail(new RelayRejected({
1004
- message,
1005
- status: response.status,
1006
- path,
1007
- ...Option2.isSome(envelope) && envelope.value.code ? { code: envelope.value.code } : {}
1008
- }));
1009
- }
1010
- return Schema4.decodeUnknownEffect(schema)(body).pipe(Effect2.mapError((cause) => new RelayDecodeFailed({
1011
- message: `Relay response for ${path} did not match the expected shape: ${cause.message}`,
1012
- path,
1013
- cause
1014
- })));
1015
- }));
1016
- const transportError = (path) => (cause) => new RelayUnreachable({
1017
- message: `browse relay is not reachable at ${endpoint}. Relay-backed CLI commands start it automatically; use \`${cliCommand("serve")}\` only for foreground debugging.`,
1018
- endpoint,
1019
- path,
1020
- cause
1021
- });
1022
- let authorization;
1023
- const authorize = (request) => {
1024
- try {
1025
- authorization ??= relayAuthorization();
1026
- } catch {
1027
- return request;
1028
- }
1029
- const authorized = HttpClientRequest.setHeader(request, "authorization", authorization);
1030
- return options?.clientPid === undefined ? authorized : HttpClientRequest.setHeader(authorized, "opencode-browser-client-pid", String(options.clientPid));
1031
- };
1032
- const getJson = (path, schema) => Effect2.suspend(() => httpClient.execute(authorize(HttpClientRequest.get(new URL(path, endpoint))))).pipe(Effect2.mapError(transportError(path)), Effect2.flatMap((response) => handleResponse(response, path, schema)));
1033
- const postJson = (path, body, schema) => HttpClientRequest.post(new URL(path, endpoint)).pipe(HttpClientRequest.bodyJson(body), Effect2.mapError((cause) => new RelayEncodeFailed({
1034
- message: `Could not encode relay request for ${path}`,
1035
- path,
1036
- cause
1037
- })), Effect2.flatMap((request) => httpClient.execute(authorize(request)).pipe(Effect2.mapError(transportError(path)), Effect2.flatMap((response) => handleResponse(response, path, schema)))));
1038
- const recordingTargetBody = (target) => ({
1039
- ...target.sessionId ? { sessionId: target.sessionId } : {},
1040
- ...target.tabId === undefined ? {} : { tabId: target.tabId }
1041
- });
1042
- const recordingTargetQuery = (target) => {
1043
- const searchParams = new URLSearchParams;
1044
- if (target.sessionId) {
1045
- searchParams.set("sessionId", target.sessionId);
1046
- }
1047
- if (target.tabId !== undefined) {
1048
- searchParams.set("tabId", String(target.tabId));
1049
- }
1050
- const text = searchParams.toString();
1051
- return text ? `?${text}` : "";
1052
- };
1053
- return Service.of({
1054
- endpoint,
1055
- version: getJson("/version", RelayVersion),
1056
- shutdown: (request) => postJson("/shutdown", { ...request }, RelayShutdownResponse),
1057
- extensionStatus: getJson("/extension/status", ExtensionStatus),
1058
- targets: getJson("/json/list", TargetSummaries),
1059
- sessions: getJson("/cli/sessions", SessionsContainer).pipe(Effect2.map((container) => container.sessions)),
1060
- sessionNew: (id, options) => postJson("/cli/session/new", {
1061
- ...id ? { id } : {},
1062
- ...options?.readOnly ? { readOnly: true } : {},
1063
- ...options?.allowedOrigins && options.allowedOrigins.length > 0 ? { allowedOrigins: [...options.allowedOrigins] } : {},
1064
- ...options?.browser ? { browser: options.browser } : {}
1065
- }, SessionContainer).pipe(Effect2.map((container) => container.session)),
1066
- sessionAllowlist: (id, allowedOrigins) => postJson("/cli/session/allowlist", { id, allowedOrigins: [...allowedOrigins] }, SessionContainer).pipe(Effect2.map((container) => container.session)),
1067
- sessionEnsure: (id, options) => postJson("/v1/sessions/ensure", {
1068
- id,
1069
- ...options?.readOnly === undefined ? {} : { readOnly: options.readOnly }
1070
- }, SessionContainer).pipe(Effect2.map((container) => container.session)),
1071
- sessionReset: (id) => postJson("/cli/session/reset", { id }, SessionResetResponse),
1072
- sessionAdopt: ({ sessionId, ...request }) => postJson("/cli/session/adopt", {
1073
- ...request,
1074
- ...sessionId ? { sessionId } : {}
1075
- }, SessionAdoptResponse),
1076
- sessionDelete: (id) => postJson("/cli/session/delete", { id }, SessionDeleted),
1077
- execute: ({ sessionId, ...request }) => postJson("/cli/execute", {
1078
- ...request,
1079
- ...sessionId ? { sessionId } : {}
1080
- }, ExecuteResponse),
1081
- authenticatedJson: (request) => postJson("/v1/authenticated-origin/json", { ...request }, AuthenticatedJsonOutcome),
1082
- networkStart: (request) => postJson("/network/start", { ...request }, NetworkStatusResponse),
1083
- networkStatus: (request) => postJson("/network/status", { ...request }, NetworkStatusResponse),
1084
- networkStop: (request) => postJson("/network/stop", { ...request }, NetworkStopResponse),
1085
- networkCancel: (request) => postJson("/network/cancel", { ...request }, NetworkCancelResponse),
1086
- authStatus: (request) => postJson("/auth/status", { ...request }, AuthProfileSummary),
1087
- authRefresh: (request) => postJson("/auth/refresh", { ...request }, NetworkStopResponse),
1088
- authRun: (request) => postJson("/auth/run", { ...request }, AuthRunResponse),
1089
- recordingStart: ({ sessionId, tabId, ...request }) => postJson("/recording/start", { ...recordingTargetBody({ sessionId, tabId }), ...request }, RecordingStartResponse),
1090
- recordingStop: (target) => postJson("/recording/stop", recordingTargetBody(target), RecordingStopResponse),
1091
- recordingStatus: (target) => getJson(`/recording/status${recordingTargetQuery(target)}`, RecordingStatusResponse),
1092
- recordingCancel: (target) => postJson("/recording/cancel", recordingTargetBody(target), RecordingCancelResponse),
1093
- tabsCleanup: (idleMinutes) => postJson("/tabs/cleanup", idleMinutes === undefined ? {} : { idleMinutes }, TabsCleanupResponse),
1094
- settingsGet: getJson("/settings", SettingsGetResponse),
1095
- settingsSet: (changes) => postJson("/settings", { set: changes }, SettingsSetResponse),
1096
- flightRecorderStart: ({ sessionId, tabId, ...request }) => postJson("/flight-recorder/start", { ...recordingTargetBody({ sessionId, tabId }), ...request }, FlightRecorderStatusResponse),
1097
- flightRecorderStatus: (target) => getJson(`/flight-recorder/status${recordingTargetQuery(target)}`, FlightRecorderStatusResponse),
1098
- flightRecorderSaveLast: ({ sessionId, tabId, ...request }) => postJson("/flight-recorder/save-last", { ...recordingTargetBody({ sessionId, tabId }), ...request }, FlightRecorderSaveResponse),
1099
- flightRecorderCancel: (target) => postJson("/flight-recorder/cancel", recordingTargetBody(target), FlightRecorderCancelResponse)
1100
- });
1101
- });
1102
- var layer = Layer.effect(Service, make());
1103
- var layerFetch = layer.pipe(Layer.provide(NodeHttpClient.layerNodeHttp));
1104
- function mcpClientPid() {
1105
- const shim = Number(process.env.OPENCODE_BROWSER_CLIENT_PID);
1106
- return Number.isSafeInteger(shim) && shim > 1 && shim === process.ppid ? shim : process.pid;
1107
- }
1108
- var layerFetchMcp = Layer.effect(Service, make({ clientPid: mcpClientPid() })).pipe(Layer.provide(NodeHttpClient.layerNodeHttp));
1109
-
1110
- // src/relay/lifecycle.ts
1111
- var loadedCliEntrypoint = compiledBinary() ? process2.execPath : fileURLToPath(new URL(import.meta.url.endsWith(".ts") ? "../main.ts" : "./cli.mjs", import.meta.url));
1112
- var clientInstanceId = crypto2.randomUUID();
1113
- function shouldWaitForExtensionReconnect(readiness) {
1114
- return readiness.started || readiness.waitForReconnect === true;
1115
- }
1116
-
1117
- class RelayStillRunning extends Error {
1118
- }
1119
-
1120
- class RelayStartFailed extends Schema5.TaggedError()("RelayLifecycle.RelayStartFailed", {
1121
- message: Schema5.String,
1122
- endpoint: Schema5.String,
1123
- cause: Schema5.Defect()
1124
- }) {
1125
- }
1126
-
1127
- class ExtensionDisconnected extends Schema5.TaggedError()("RelayLifecycle.ExtensionDisconnected", { message: Schema5.String, needsPairing: Schema5.optionalKey(Schema5.Boolean) }) {
1128
- }
1129
-
1130
- class ExtensionProtocolIncompatible extends Schema5.TaggedError()("RelayLifecycle.ExtensionProtocolIncompatible", {
1131
- message: Schema5.String,
1132
- protocolVersion: Schema5.NullOr(Schema5.Number)
1133
- }) {
1134
- }
1135
- function supersededRuntime() {
1136
- try {
1137
- const runtime = npmRuntime();
1138
- return runtime !== undefined && runtime.current.version !== runtime.version;
1139
- } catch {
1140
- return false;
1141
- }
1142
- }
1143
- function relayBuildProblem(version, buildId = opencodeBrowserBuildId2) {
1144
- if (!version.buildId) {
1145
- return `Running relay does not report a build id; use \`${cliCommand("relay restart")}\` for upgrade guidance (${buildId}).`;
1146
- }
1147
- if (version.buildId !== buildId) {
1148
- if (isNewerBuild(version.buildId, buildId)) {
1149
- return `Running relay build ${version.buildId} is newer than CLI build ${buildId}; refresh or restart this CLI or MCP client. Only use \`${cliCommand("relay restart")}\` from the current installation.`;
1150
- }
1151
- return `Running relay build ${version.buildId} does not match CLI build ${buildId}; run \`${cliCommand("relay restart")}\` when no agent is using the browser. This leaves browser tabs open but resets in-memory JavaScript state.`;
1152
- }
1153
- return;
1154
- }
1155
- var ensureRelay = Effect3.fn("RelayLifecycle.ensureRelay")(function* (options) {
1156
- const buildId = options.buildId ?? opencodeBrowserBuildId2;
1157
- const probe = options.relay.version;
1158
- const initial = yield* Effect3.result(probe);
1159
- if (Result.isSuccess(initial)) {
1160
- const buildProblem = relayBuildProblem(initial.success, buildId);
1161
- return {
1162
- version: initial.success,
1163
- started: false,
1164
- ...buildProblem ? { buildProblem } : {}
1165
- };
1166
- }
1167
- const relayWasAbsent = isRelayUnreachable(initial.failure);
1168
- if (!relayWasAbsent && !isRelayStarting(initial.failure)) {
1169
- return yield* initial.failure;
1170
- }
1171
- if (relayWasAbsent) {
1172
- const autoStart = yield* Config2.Boolean("OPENCODE_BROWSER_AUTOSTART").pipe(Config2.withDefault(true), Effect3.mapError((cause) => new Error("Invalid OPENCODE_BROWSER_AUTOSTART configuration", { cause })));
1173
- if (!autoStart) {
1174
- return yield* Effect3.fail(new Error(`Automatic relay startup is disabled; no relay is available at ${options.relay.endpoint}`));
1175
- }
1176
- if (!supersededRuntime())
1177
- yield* options.start ?? startManagedRelay();
1178
- }
1179
- const version = yield* waitForRelayReady(options);
1180
- const buildProblem = relayBuildProblem(version, buildId);
1181
- return { version, started: relayWasAbsent, ...buildProblem ? { buildProblem } : {} };
1182
- });
1183
- function waitForRelayReady(options) {
1184
- return options.relay.version.pipe(Effect3.retry({
1185
- times: options.retryTimes ?? 200,
1186
- schedule: Schedule.spaced(options.retryDelayMs ?? 50),
1187
- while: isRelayStartingOrUnreachable
1188
- }), Effect3.mapError((error) => isRelayStartingOrUnreachable(error) ? new RelayStartFailed({
1189
- message: `browse relay did not start at ${options.relay.endpoint}`,
1190
- endpoint: options.relay.endpoint,
1191
- cause: error
1192
- }) : error));
1193
- }
1194
- var restartRelay = Effect3.fn("RelayLifecycle.restartRelay")(function* (options) {
1195
- const buildId = options.buildId ?? opencodeBrowserBuildId2;
1196
- const initial = yield* Effect3.result(options.relay.version);
1197
- let original;
1198
- let replacement;
1199
- let restartRequestId;
1200
- if (Result.isSuccess(initial)) {
1201
- original = initial.success;
1202
- } else if (isRelayStarting(initial.failure)) {
1203
- original = yield* waitForRelayReady(options);
1204
- } else if (!isRelayUnreachable(initial.failure)) {
1205
- return yield* initial.failure;
1206
- }
1207
- if (original) {
1208
- if (original.managed !== true || !original.instanceId) {
1209
- return yield* Effect3.fail(new Error(`Cannot restart a foreground or unidentified relay. Stop it manually, then run \`${cliCommand("relay restart")}\`.`));
1210
- }
1211
- if (original.shutdownProtocol !== 2) {
1212
- return yield* Effect3.fail(new Error(`This legacy relay does not support safe shutdown protocol 2. Stop it manually once, then run \`${cliCommand("relay restart")}\` to upgrade; no shutdown request was sent.`));
1213
- }
1214
- if (!Number.isFinite(Date.parse(buildId)) || !original.buildId || !Number.isFinite(Date.parse(original.buildId))) {
1215
- return yield* Effect3.fail(new Error("Cannot explicitly replace a source or unorderable relay build. Stop it manually, then launch the intended build."));
1216
- }
1217
- if (!options.otherBuild && isNewerBuild(original.buildId, buildId)) {
1218
- return yield* Effect3.fail(new Error(`Refusing to downgrade relay build ${original.buildId} to ${buildId}; refresh or restart this CLI or MCP client.`));
1219
- }
1220
- const confirmed = yield* Effect3.result(options.relay.version);
1221
- if (Result.isFailure(confirmed)) {
1222
- if (!isRelayUnreachable(confirmed.failure))
1223
- return yield* confirmed.failure;
1224
- } else if (!isSameRelayInstance(original, confirmed.success)) {
1225
- replacement = confirmed.success;
1226
- } else {
1227
- if (confirmed.success.managed !== true || confirmed.success.shutdownProtocol !== 2 || confirmed.success.buildId !== original.buildId) {
1228
- return yield* Effect3.fail(new Error("Relay identity metadata changed during restart; no shutdown request was sent."));
1229
- }
1230
- const request = yield* RelayShutdownRequest.makeEffect({
1231
- instanceId: original.instanceId,
1232
- requestId: crypto2.randomUUID(),
1233
- reason: "explicit-restart",
1234
- client: { kind: options.clientKind ?? "sdk", instanceId: clientInstanceId, buildId },
1235
- ...options.force ? { force: true } : {}
1236
- });
1237
- const shutdown = yield* Effect3.result(options.relay.shutdown(request));
1238
- if (Result.isSuccess(shutdown)) {
1239
- restartRequestId = request.requestId;
1240
- if (options.onShutdownAccepted)
1241
- yield* options.onShutdownAccepted;
1242
- } else if (!isRelayUnreachable(shutdown.failure) && !isRelayInstanceChanged(shutdown.failure)) {
1243
- return yield* shutdown.failure;
1244
- }
1245
- replacement = yield* waitForRelayExitOrReplacement({ ...options, version: original });
1246
- }
1247
- }
1248
- const started = replacement === undefined;
1249
- if (started) {
1250
- yield* options.start ?? startManagedRelay(undefined, undefined, undefined, { ...restartRequestId ? { restartRequestId } : {} });
1251
- replacement = yield* waitForRelayReady(options);
1252
- }
1253
- if (!replacement || !replacement.instanceId || replacement.managed !== true || original && isSameRelayInstance(original, replacement)) {
1254
- return yield* Effect3.fail(new Error("Relay restart did not produce a new managed instance; no further shutdown was attempted."));
1255
- }
1256
- const buildProblem = options.otherBuild ? undefined : relayBuildProblem(replacement, buildId);
1257
- if (buildProblem)
1258
- return yield* Effect3.fail(new Error(`${buildProblem} A competing relay was left untouched.`));
1259
- return { version: replacement, started, waitForReconnect: true };
1260
- });
1261
- function waitForRelayExitOrReplacement(options) {
1262
- return options.relay.version.pipe(Effect3.flatMap((version) => isSameRelayInstance(options.version, version) ? Effect3.fail(new RelayStillRunning("browse relay is still draining; no replacement was started")) : Effect3.succeed(version)), Effect3.catch((error) => isRelayUnreachable(error) ? Effect3.succeed(undefined) : Effect3.fail(error)), Effect3.retry({
1263
- times: options.retryTimes ?? 200,
1264
- schedule: Schedule.spaced(options.retryDelayMs ?? 50),
1265
- while: (error) => error instanceof RelayStillRunning || isRelayStarting(error)
1266
- }));
1267
- }
1268
- function isSameRelayInstance(left, right) {
1269
- return left.instanceId !== undefined && left.instanceId === right.instanceId;
1270
- }
1271
- function isNewerBuild(current, running) {
1272
- if (!running)
1273
- return false;
1274
- const currentTime = Date.parse(current);
1275
- const runningTime = Date.parse(running);
1276
- return Number.isFinite(currentTime) && Number.isFinite(runningTime) && currentTime > runningTime;
1277
- }
1278
- function isRelayInstanceChanged(error) {
1279
- return error instanceof RelayRejected && error.status === 409 && error.code === "invalid-request";
1280
- }
1281
- var extensionReconnectWaitMs = extensionReconnectAlarmPeriodMs + 5000;
1282
- var extensionReconnectProbeMs = 200;
1283
- var ensureExtensionConnected = Effect3.fn("RelayLifecycle.ensureExtensionConnected")(function* (options) {
1284
- const check = options.relay.extensionStatus.pipe(Effect3.flatMap((status) => {
1285
- if (options.sessionId && status.sessions?.some((session) => session.id === options.sessionId && session.browser)) {
1286
- return Effect3.succeed(status);
1287
- }
1288
- if (status.protocolCompatible === false) {
1289
- return Effect3.fail(new ExtensionProtocolIncompatible({
1290
- message: status.nextStep ?? extensionProtocolAdvice(status.protocolVersion, { install: installCommand() }),
1291
- protocolVersion: status.protocolVersion ?? null
1292
- }));
1293
- }
1294
- return status.connected ? Effect3.succeed(status) : Effect3.fail(new ExtensionDisconnected({
1295
- message: status.nextStep ?? `browse extension is not connected. Open the browser with browse installed (\`${installCommand()}\`); it reconnects automatically after relay or browser startup.`,
1296
- needsPairing: status.pairingState === "refused" || status.pairingState === "unpaired"
1297
- }));
1298
- }));
1299
- if (!options.waitForReconnect) {
1300
- return yield* check;
1301
- }
1302
- let waitAnnounced = false;
1303
- return yield* check.pipe(Effect3.tapError((error) => {
1304
- if (waitAnnounced || !options.onWait || !(error instanceof ExtensionDisconnected))
1305
- return Effect3.void;
1306
- waitAnnounced = true;
1307
- return options.onWait;
1308
- }), Effect3.retry({
1309
- times: options.retryTimes ?? Math.ceil(extensionReconnectWaitMs / extensionReconnectProbeMs),
1310
- schedule: Schedule.spaced(options.retryDelayMs ?? extensionReconnectProbeMs),
1311
- while: (error) => error instanceof ExtensionDisconnected && !error.needsPairing || isRelayStartingOrUnreachable(error)
1312
- }));
1313
- });
1314
- function stoppedRelayStatus(endpoint) {
1315
- return { endpoint, relay: { running: false }, extension: null, sessions: [], targets: [] };
1316
- }
1317
- function statusCollections(status) {
1318
- return status.sessions && status.targets ? { sessions: status.sessions, targets: status.targets } : undefined;
1319
- }
1320
- function startManagedRelay(entrypoint = loadedCliEntrypoint, executable = process2.execPath, execArgv = process2.execArgv, options = {}) {
1321
- return Effect3.try({
1322
- try: () => {
1323
- if (!entrypoint) {
1324
- throw new Error("Cannot locate the opencode-browser CLI entrypoint");
1325
- }
1326
- const launch = managedRelayLaunch(entrypoint, executable, execArgv);
1327
- const { OPENCODE_BROWSER_RESTART_REQUEST_ID: _inheritedRestartRequestId, ...env } = relayEnvironment(process2.env);
1328
- const child = spawn(launch.executable, launch.args, {
1329
- detached: true,
1330
- stdio: "ignore",
1331
- env: { ...env, OPENCODE_BROWSER_MANAGED_RELAY: "1", ...options.restartRequestId ? { OPENCODE_BROWSER_RESTART_REQUEST_ID: options.restartRequestId } : {} }
1332
- });
1333
- child.unref();
1334
- },
1335
- catch: (cause) => cause instanceof Error ? cause : new Error("Failed to start browse relay", { cause })
1336
- });
1337
- }
1338
- var relayEnvironmentNames = new Set([
1339
- "PATH",
1340
- "HOME",
1341
- "USER",
1342
- "LOGNAME",
1343
- "SHELL",
1344
- "TMPDIR",
1345
- "TEMP",
1346
- "TMP",
1347
- "LANG",
1348
- "LANGUAGE",
1349
- "TZ",
1350
- "XDG_DATA_HOME",
1351
- "XDG_CONFIG_HOME",
1352
- "XDG_RUNTIME_DIR",
1353
- "DISPLAY",
1354
- "WAYLAND_DISPLAY",
1355
- "HTTP_PROXY",
1356
- "HTTPS_PROXY",
1357
- "NO_PROXY",
1358
- "ALL_PROXY",
1359
- "SSL_CERT_FILE",
1360
- "SSL_CERT_DIR",
1361
- "NODE_EXTRA_CA_CERTS",
1362
- "SYSTEMROOT",
1363
- "SYSTEMDRIVE",
1364
- "WINDIR",
1365
- "COMSPEC",
1366
- "PATHEXT",
1367
- "USERPROFILE",
1368
- "APPDATA",
1369
- "LOCALAPPDATA",
1370
- "PROGRAMDATA",
1371
- "PROGRAMFILES",
1372
- "PROGRAMFILES(X86)",
1373
- "HOMEDRIVE",
1374
- "HOMEPATH",
1375
- "USERNAME"
1376
- ]);
1377
- var relayEnvironmentPrefixes = ["OPENCODE_BROWSER_", "LC_"];
1378
- function relayEnvironment(env) {
1379
- return Object.fromEntries(Object.entries(env).filter(([name, value]) => {
1380
- if (value === undefined)
1381
- return false;
1382
- const upper = name.toUpperCase();
1383
- return relayEnvironmentNames.has(upper) || relayEnvironmentPrefixes.some((prefix) => upper.startsWith(prefix));
1384
- }));
1385
- }
1386
- function managedRelayLaunch(entrypoint = loadedCliEntrypoint, executable = process2.execPath, execArgv = process2.execArgv) {
1387
- if (!scriptEntry(entrypoint))
1388
- return { executable: entrypoint, args: ["serve"] };
1389
- return {
1390
- executable,
1391
- args: [...execArgv, managedRelayEntrypoint(entrypoint), "serve"]
1392
- };
1393
- }
1394
- function managedRelayEntrypoint(entrypoint) {
1395
- const name = path.basename(entrypoint);
1396
- if (name === "index.ts" || name === "opencode-browser-client.ts" || name === "cli.ts")
1397
- return path.join(path.dirname(entrypoint), "main.ts");
1398
- return entrypoint;
1399
- }
1400
- function isRelayUnreachable(error) {
1401
- return error instanceof RelayUnreachable;
1402
- }
1403
- function isRelayStarting(error) {
1404
- return error instanceof RelayRejected && error.code === "relay-starting";
1405
- }
1406
- function isRelayStartingOrUnreachable(error) {
1407
- return isRelayStarting(error) || isRelayUnreachable(error);
1408
- }
1409
-
1410
- export { ExecuteRequest, SessionAdoptRequest, SessionNewRequest, SessionEnsureRequest, SessionIdRequest, SessionAllowlistRequest, AuthenticatedJsonRequest, AuthenticatedJsonOutcome, RelayShutdownRequest, NetworkStartRequest, NetworkSessionRequest, NetworkStopRequest, AuthProfileRequest, AuthRefreshRequest, AuthRunRequest, RecordingTargetRequest, RecordingStartRequest, FlightRecorderStartRequest, FlightRecorderSaveRequest, HttpRouteError2, formatHostForUrl2, listenHttpServer2, closeHttpServer2, closeWebSocketServer2, logCloseError2, sendJson2, readJsonBody2, stableUnpackedExtensionOrigin2, chromeExtensionOriginForPath2, parseAdditionalExtensionOrigins2, validateHostHeader2, validateBrowserFetchSite2, validateWebSocketOrigin2, defaultHost2, defaultPort2, optionalSessionId2, isValidSessionId2, requiredSessionId2, parseTargetSelection2, generateSessionId2, sendCdpResponse2, sendCdpEvent2, getObject2, getString2, getNumber2, getIdText2, headerValue2, getTargetInfo2, isRestrictedTarget2, isRestrictedUrl2, dataUrlDocument2, portConfig, extensionOriginsConfig, endpointForPort, RelayUnreachable, RelayRejected, Service, layerFetch, layerFetchMcp, shouldWaitForExtensionReconnect, relayBuildProblem, ensureRelay, restartRelay, extensionReconnectWaitMs, ensureExtensionConnected, stoppedRelayStatus, statusCollections, startManagedRelay };