codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.3

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.
Files changed (191) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +27 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +40 -1
  4. package/contracts/v1/fixtures/backend-compatibility.json +21 -0
  5. package/contracts/v1/fixtures/backend-version.json +6 -1
  6. package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
  7. package/contracts/v1/fixtures/operation-action.json +13 -0
  8. package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
  9. package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
  10. package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
  11. package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
  12. package/contracts/v1/fixtures/operation-blocker.json +10 -0
  13. package/contracts/v1/fixtures/operation-collect-request.json +17 -0
  14. package/contracts/v1/fixtures/operation-collect-result.json +42 -0
  15. package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
  16. package/contracts/v1/fixtures/operation-control-request.json +18 -0
  17. package/contracts/v1/fixtures/operation-control-result.json +31 -0
  18. package/contracts/v1/fixtures/operation-event.json +18 -0
  19. package/contracts/v1/fixtures/operation-handle.json +10 -0
  20. package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
  21. package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
  22. package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
  23. package/contracts/v1/fixtures/operation-receipt.json +32 -0
  24. package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
  25. package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
  26. package/contracts/v1/fixtures/operation-request.json +33 -0
  27. package/contracts/v1/fixtures/operation-state.json +144 -0
  28. package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
  29. package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
  30. package/contracts/v1/fixtures/operation-submit-result.json +16 -0
  31. package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
  32. package/contracts/v1/manifest.json +155 -1
  33. package/contracts/v1/parity-suite.json +96 -1
  34. package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
  35. package/contracts/v1/schemas/backend-request.schema.json +8 -2
  36. package/contracts/v1/schemas/capabilities.schema.json +81 -1
  37. package/contracts/v1/schemas/manifest.schema.json +57 -0
  38. package/contracts/v1/schemas/operation-action.schema.json +155 -0
  39. package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
  40. package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
  41. package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
  42. package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
  43. package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
  44. package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
  45. package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
  46. package/contracts/v1/schemas/operation-event.schema.json +756 -0
  47. package/contracts/v1/schemas/operation-handle.schema.json +55 -0
  48. package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
  49. package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
  50. package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
  51. package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
  52. package/contracts/v1/schemas/operation-request.schema.json +158 -0
  53. package/contracts/v1/schemas/operation-state.schema.json +691 -0
  54. package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
  55. package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
  56. package/contracts/v1/surface-drift-policy.json +50 -0
  57. package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
  58. package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
  59. package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
  60. package/dist/src/backend/client.d.ts +103 -2
  61. package/dist/src/backend/client.js +1436 -97
  62. package/dist/src/backend/compatibility.d.ts +12 -0
  63. package/dist/src/backend/compatibility.js +208 -0
  64. package/dist/src/backend/protocol.d.ts +84 -1
  65. package/dist/src/backend/protocol.js +31 -5
  66. package/dist/src/backend/runtime-identity.d.ts +10 -0
  67. package/dist/src/backend/runtime-identity.js +141 -0
  68. package/dist/src/backend/session.d.ts +10 -2
  69. package/dist/src/backend/session.js +478 -7
  70. package/dist/src/backend/stdio-server.d.ts +4 -1
  71. package/dist/src/backend/stdio-server.js +240 -28
  72. package/dist/src/browser/active-composer-file-input.d.ts +9 -0
  73. package/dist/src/browser/active-composer-file-input.js +33 -0
  74. package/dist/src/browser/attach.d.ts +4 -1
  75. package/dist/src/browser/attach.js +136 -28
  76. package/dist/src/browser/downloads.d.ts +2 -0
  77. package/dist/src/client.d.ts +38 -0
  78. package/dist/src/client.js +1222 -59
  79. package/dist/src/commands/configuration.js +13 -7
  80. package/dist/src/commands/doctor.d.ts +1 -1
  81. package/dist/src/commands/doctor.js +26 -3
  82. package/dist/src/commands/experience.js +35 -0
  83. package/dist/src/commands/files.js +4 -27
  84. package/dist/src/commands/modes.js +49 -17
  85. package/dist/src/commands/power-discovery.d.ts +130 -0
  86. package/dist/src/commands/power-discovery.js +627 -0
  87. package/dist/src/commands/project-sources.js +325 -31
  88. package/dist/src/commands/sequence.js +59 -32
  89. package/dist/src/commands/session.js +7 -2
  90. package/dist/src/commands/work.js +31 -0
  91. package/dist/src/errors.d.ts +9 -0
  92. package/dist/src/errors.js +22 -0
  93. package/dist/src/index.d.ts +6 -0
  94. package/dist/src/index.js +9 -0
  95. package/dist/src/operations/artifact-output.d.ts +69 -0
  96. package/dist/src/operations/artifact-output.js +1748 -0
  97. package/dist/src/operations/artifact-stream.d.ts +19 -0
  98. package/dist/src/operations/artifact-stream.js +35 -0
  99. package/dist/src/operations/artifact-transfer.d.ts +149 -0
  100. package/dist/src/operations/artifact-transfer.js +1312 -0
  101. package/dist/src/operations/browser-adapter.d.ts +161 -0
  102. package/dist/src/operations/browser-adapter.js +2023 -0
  103. package/dist/src/operations/browser-observation.d.ts +110 -0
  104. package/dist/src/operations/browser-observation.js +1208 -0
  105. package/dist/src/operations/browser-target.d.ts +103 -0
  106. package/dist/src/operations/browser-target.js +489 -0
  107. package/dist/src/operations/canonical.d.ts +4 -0
  108. package/dist/src/operations/canonical.js +412 -0
  109. package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
  110. package/dist/src/operations/chatgpt-runtime.js +1150 -0
  111. package/dist/src/operations/client.d.ts +138 -0
  112. package/dist/src/operations/client.js +1143 -0
  113. package/dist/src/operations/collector.d.ts +195 -0
  114. package/dist/src/operations/collector.js +1039 -0
  115. package/dist/src/operations/control.d.ts +372 -0
  116. package/dist/src/operations/control.js +1498 -0
  117. package/dist/src/operations/file-identity.d.ts +33 -0
  118. package/dist/src/operations/file-identity.js +153 -0
  119. package/dist/src/operations/handle.d.ts +19 -0
  120. package/dist/src/operations/handle.js +727 -0
  121. package/dist/src/operations/index.d.ts +22 -0
  122. package/dist/src/operations/index.js +22 -0
  123. package/dist/src/operations/journal.d.ts +142 -0
  124. package/dist/src/operations/journal.js +1931 -0
  125. package/dist/src/operations/production-attachments.d.ts +116 -0
  126. package/dist/src/operations/production-attachments.js +1018 -0
  127. package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
  128. package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
  129. package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
  130. package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
  131. package/dist/src/operations/production-configuration.d.ts +42 -0
  132. package/dist/src/operations/production-configuration.js +1363 -0
  133. package/dist/src/operations/production-primitives.d.ts +53 -0
  134. package/dist/src/operations/production-primitives.js +1073 -0
  135. package/dist/src/operations/production-work-steer.d.ts +203 -0
  136. package/dist/src/operations/production-work-steer.js +1218 -0
  137. package/dist/src/operations/recovery.d.ts +80 -0
  138. package/dist/src/operations/recovery.js +174 -0
  139. package/dist/src/operations/runtime-adapter.d.ts +118 -0
  140. package/dist/src/operations/runtime-adapter.js +786 -0
  141. package/dist/src/operations/send-once.d.ts +224 -0
  142. package/dist/src/operations/send-once.js +1081 -0
  143. package/dist/src/operations/service.d.ts +281 -0
  144. package/dist/src/operations/service.js +2810 -0
  145. package/dist/src/operations/staging.d.ts +136 -0
  146. package/dist/src/operations/staging.js +630 -0
  147. package/dist/src/operations/state-machine.d.ts +29 -0
  148. package/dist/src/operations/state-machine.js +2047 -0
  149. package/dist/src/operations/submission.d.ts +423 -0
  150. package/dist/src/operations/submission.js +1676 -0
  151. package/dist/src/operations/turn-ownership.d.ts +202 -0
  152. package/dist/src/operations/turn-ownership.js +700 -0
  153. package/dist/src/operations/types.d.ts +454 -0
  154. package/dist/src/operations/types.js +20 -0
  155. package/dist/src/operations/wire-requests.d.ts +21 -0
  156. package/dist/src/operations/wire-requests.js +396 -0
  157. package/dist/src/operations/wire-results.d.ts +97 -0
  158. package/dist/src/operations/wire-results.js +818 -0
  159. package/dist/src/runner/responses.d.ts +3 -1
  160. package/dist/src/runner/responses.js +19 -1
  161. package/dist/src/runner/result.js +227 -55
  162. package/dist/src/runner/types.d.ts +12 -0
  163. package/dist/src/runtime/command-routing.d.ts +149 -0
  164. package/dist/src/runtime/command-routing.js +431 -0
  165. package/dist/src/runtime/coordinated-browser.d.ts +24 -0
  166. package/dist/src/runtime/coordinated-browser.js +315 -0
  167. package/dist/src/runtime/coordinated-page.d.ts +39 -0
  168. package/dist/src/runtime/coordinated-page.js +634 -0
  169. package/dist/src/runtime/operation-context.d.ts +142 -0
  170. package/dist/src/runtime/operation-context.js +410 -0
  171. package/dist/src/runtime/runtime-session.d.ts +95 -0
  172. package/dist/src/runtime/runtime-session.js +314 -0
  173. package/dist/src/runtime/tab-coordinator.d.ts +200 -0
  174. package/dist/src/runtime/tab-coordinator.js +1200 -0
  175. package/dist/src/runtime/value-boundaries.d.ts +18 -0
  176. package/dist/src/runtime/value-boundaries.js +49 -0
  177. package/dist/src/safety/untrusted-output.js +2 -1
  178. package/dist/src/scripts/backend-server.js +4 -1
  179. package/dist/src/scripts/live-smoke/harness.js +79 -7
  180. package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
  181. package/dist/src/scripts/live-smoke/scenarios.js +110 -18
  182. package/dist/src/scripts/live-smoke/types.d.ts +3 -0
  183. package/dist/src/scripts/release-canary-module.js +17 -4
  184. package/dist/src/types.d.ts +38 -1
  185. package/package.json +1 -1
  186. package/references/2026-08-16-transactional-operations.md +459 -0
  187. package/references/agents-runner.md +34 -0
  188. package/references/backend-protocol.md +73 -0
  189. package/references/python-parity.md +72 -0
  190. package/references/responses-adapter.md +19 -0
  191. package/references/streaming.md +6 -0
@@ -1,14 +1,25 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { createChatGPT } from "../client.js";
3
+ import { toOperationCollectWireResult, toOperationControlWireResult, toOperationInspectWireResult, toOperationSubmitWireResult } from "../operations/wire-results.js";
4
+ import { OperationWireRequestError, validateOperationCollectRequest as validateWireCollectRequest, validateOperationControlRequest as validateWireControlRequest, validateOperationInspectRequest as validateWireInspectRequest, validateOperationSubmitRequest as validateWireSubmitRequest } from "../operations/wire-requests.js";
5
+ import { assertOperationAwareDispatchAllowed, classifyCommandRouting, commandRoutingDisposition, CommandRoutingError } from "../runtime/command-routing.js";
2
6
  import { BACKEND_REQUEST_SCHEMA_VERSION, backendCommands, backendResponseError, backendEvent, backendEventCompleted, backendResponseOk, ProtocolError } from "./protocol.js";
7
+ // A stdio backend process owns one identity. BackendSession objects created by
8
+ // that process share it unless an embedding explicitly injects a diagnostic
9
+ // identity (for example, a test or a separately fenced runtime).
10
+ const PROCESS_BACKEND_SESSION_ID = randomUUID();
11
+ const MAX_IDENTITY_FIELD_LENGTH = 512;
3
12
  export class BackendSession {
4
13
  options;
5
14
  clientInstance;
15
+ identity;
6
16
  constructor(options = {}) {
7
17
  this.options = options;
18
+ this.identity = backendRuntimeIdentity(options.backendIdentity);
8
19
  }
9
20
  async dispatch(request) {
10
21
  try {
11
- const result = await dispatchBackendCommand(this.client(), request);
22
+ const result = await dispatchBackendCommand(request, payload => this.hello(payload), this.identity, () => this.client());
12
23
  return backendResponseOk(request.requestId, result);
13
24
  }
14
25
  catch (error) {
@@ -17,6 +28,8 @@ export class BackendSession {
17
28
  }
18
29
  async *stream(request) {
19
30
  try {
31
+ assertCommandRoutingClassified(request.command);
32
+ assertBackendOperationRoutingAllowed(request.command, request.payload);
20
33
  if (request.command !== "runner.stream") {
21
34
  const response = await this.dispatch(request);
22
35
  if (response.ok) {
@@ -40,9 +53,11 @@ export class BackendSession {
40
53
  yield backendEventCompleted(request.requestId, await stream.completed);
41
54
  }
42
55
  catch (error) {
56
+ // Runner/provider errors may contain prompt or page content. Only an
57
+ // explicit ProtocolError carries a reviewed wire-safe message.
43
58
  const protocolError = error instanceof ProtocolError
44
59
  ? error
45
- : new ProtocolError("invalid_request", error instanceof Error ? error.message : String(error), false);
60
+ : new ProtocolError("invalid_request", "Backend stream failed safely.", false);
46
61
  yield backendEvent(request.requestId, {
47
62
  type: "error",
48
63
  error: {
@@ -53,19 +68,41 @@ export class BackendSession {
53
68
  });
54
69
  }
55
70
  }
71
+ hello(payload) {
72
+ const requestedProtocol = payload.protocolVersion;
73
+ const hasRequestedCapabilities = Object.hasOwn(payload, "capabilities");
74
+ const baseCapabilities = backendCapabilities(this.identity);
75
+ const requestedCapabilities = isRecord(payload.capabilities) ? payload.capabilities : undefined;
76
+ const capabilities = intersectCapabilities(baseCapabilities, requestedCapabilities);
77
+ const accepted = (requestedProtocol === undefined || requestedProtocol === this.identity.protocolVersion)
78
+ && (!hasRequestedCapabilities
79
+ || (requestedCapabilities !== undefined
80
+ && requestedCapabilitiesSatisfied(baseCapabilities, capabilities, requestedCapabilities)));
81
+ return {
82
+ ...this.identity,
83
+ accepted,
84
+ capabilities
85
+ };
86
+ }
56
87
  client() {
57
88
  this.clientInstance ??= createChatGPT(this.options);
58
89
  return this.clientInstance;
59
90
  }
60
91
  }
61
- async function dispatchBackendCommand(client, request) {
92
+ async function dispatchBackendCommand(request, hello, identity, clientFactory) {
93
+ assertCommandRoutingClassified(request.command);
94
+ assertBackendOperationRoutingAllowed(request.command, request.payload);
62
95
  const payload = request.payload;
96
+ const client = new Proxy({}, {
97
+ get: (_target, property, receiver) => Reflect.get(clientFactory(), property, receiver)
98
+ });
63
99
  switch (request.command) {
100
+ case "backend.hello":
101
+ return hello(payload);
64
102
  case "backend.version":
65
103
  return {
66
104
  name: "codex-chatgpt-control-backend",
67
- runtime: "node",
68
- protocolVersion: BACKEND_REQUEST_SCHEMA_VERSION
105
+ ...identity
69
106
  };
70
107
  case "backend.health":
71
108
  return {
@@ -74,7 +111,7 @@ async function dispatchBackendCommand(client, request) {
74
111
  timestamp: new Date().toISOString()
75
112
  };
76
113
  case "backend.capabilities":
77
- return backendCapabilities();
114
+ return backendCapabilities(identity);
78
115
  case "runner.run": {
79
116
  const agent = client.agent(agentConfig(payload));
80
117
  return client.runner.run(agent, runInput(payload));
@@ -189,19 +226,453 @@ async function dispatchBackendCommand(client, request) {
189
226
  return client.tools.select(payload);
190
227
  case "response.copy":
191
228
  return client.response.copy(emptyToUndefined(payload));
229
+ case "operations.submit":
230
+ return dispatchOperationSubmit(client, payload);
231
+ case "operations.collect":
232
+ return dispatchOperationCollect(client, payload);
233
+ case "operations.inspect":
234
+ return dispatchOperationInspect(client, payload);
235
+ case "operations.control":
236
+ return dispatchOperationControl(client, payload);
237
+ }
238
+ }
239
+ function assertCommandRoutingClassified(command) {
240
+ if (classifyCommandRouting(command) === undefined) {
241
+ throw new ProtocolError("invalid_request", "Backend command routing classification is unavailable.", false);
242
+ }
243
+ if (commandRoutingDisposition(command) === undefined) {
244
+ // A known legacy browser command may only be admitted once its bounded
245
+ // coordinator seam is present. Keep this static: command payloads can
246
+ // contain prompts, local paths, or operation handles.
247
+ throw new ProtocolError("invalid_request", "Backend command has no bounded coordinator route.", false);
248
+ }
249
+ }
250
+ function assertBackendOperationRoutingAllowed(command, payload) {
251
+ try {
252
+ assertOperationAwareDispatchAllowed(command, payload);
253
+ }
254
+ catch (error) {
255
+ if (error instanceof CommandRoutingError) {
256
+ // Keep the protocol error static. Operation payloads can contain raw
257
+ // prompts, instructions, file paths, or handles and must never be
258
+ // echoed as part of a routing failure.
259
+ throw new ProtocolError("invalid_request", error.message, false);
260
+ }
261
+ throw error;
192
262
  }
193
263
  }
194
- function backendCapabilities() {
264
+ function backendCapabilities(identity) {
195
265
  return {
266
+ ...identity,
196
267
  protocolVersion: BACKEND_REQUEST_SCHEMA_VERSION,
197
268
  commands: [...backendCommands],
198
269
  transports: ["stdio"],
199
270
  streaming: {
200
271
  modes: ["ndjson"],
201
272
  tokenDeltas: false
273
+ },
274
+ supportedProtocolVersions: [BACKEND_REQUEST_SCHEMA_VERSION],
275
+ requestIds: {
276
+ required: true,
277
+ scope: "connection"
278
+ },
279
+ multiplexing: {
280
+ unary: true,
281
+ streams: true
282
+ },
283
+ cancellation: {
284
+ supported: false,
285
+ requests: false,
286
+ streams: false
287
+ },
288
+ tabs: {
289
+ stableProviderIdentity: false,
290
+ stableBrowserIdentity: false,
291
+ stableTabIdentity: false,
292
+ coordinationScope: "none",
293
+ authoritativeClaim: false,
294
+ fencing: false,
295
+ concurrentTabs: false,
296
+ stableIdentity: false,
297
+ coordination: false,
298
+ concurrent: false
299
+ }
300
+ };
301
+ }
302
+ async function dispatchOperationSubmit(client, payload) {
303
+ const operations = requireOperations(client);
304
+ const request = operationSubmitRequest(payload);
305
+ const result = await runOperationSafely(() => operations.submit(request));
306
+ const inspected = await runOperationSafely(() => operations.inspect(result.handle));
307
+ return operationWireSafely(() => toOperationSubmitWireResult(result, inspected.state.receipt));
308
+ }
309
+ async function dispatchOperationCollect(client, payload) {
310
+ const operations = requireOperations(client);
311
+ const request = operationCollectRequest(payload);
312
+ const result = await runOperationSafely(() => operations.collect(request.handle, {
313
+ ...(request.wait === undefined ? {} : { wait: request.wait }),
314
+ ...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
315
+ ...(request.pollIntervalMs === undefined ? {} : { pollIntervalMs: request.pollIntervalMs }),
316
+ ...(request.responseContent === undefined ? {} : { responseContent: request.responseContent })
317
+ }));
318
+ // The caller's locator is never returned as authoritative state. Reloading
319
+ // after the attempt obtains a fresh, authenticated handle for the wire
320
+ // envelope and keeps collection's browser-free terminal path intact.
321
+ const inspected = await runOperationSafely(() => operations.inspect(request.handle));
322
+ return operationWireSafely(() => toOperationCollectWireResult(inspected.handle, result, inspected.state.receipt));
323
+ }
324
+ async function dispatchOperationInspect(client, payload) {
325
+ const operations = requireOperations(client);
326
+ const request = operationInspectRequest(payload);
327
+ // OperationClient.inspect is explicitly browser-free. Keep this command on
328
+ // that path; no adapter factory or browser bridge is consulted here.
329
+ const result = await runOperationSafely(() => operations.inspect(request.handle));
330
+ return operationWireSafely(() => toOperationInspectWireResult(result));
331
+ }
332
+ async function dispatchOperationControl(client, payload) {
333
+ const operations = requireOperations(client);
334
+ const request = operationControlRequest(payload);
335
+ const result = await runOperationSafely(() => operations.control(request));
336
+ const inspected = await runOperationSafely(() => operations.inspect(request.parent));
337
+ return operationWireSafely(() => toOperationControlWireResult(result, inspected.handle));
338
+ }
339
+ function requireOperations(client) {
340
+ const operations = client.operations;
341
+ if (operations === undefined
342
+ || typeof operations !== "object"
343
+ || typeof operations.submit !== "function"
344
+ || typeof operations.collect !== "function"
345
+ || typeof operations.inspect !== "function"
346
+ || typeof operations.control !== "function") {
347
+ throw new ProtocolError("invalid_request", "Transactional browser operations are unavailable in this backend.", false);
348
+ }
349
+ return operations;
350
+ }
351
+ function operationSubmitRequest(payload) {
352
+ return validateOperationPayload(payload, validateWireSubmitRequest);
353
+ }
354
+ function operationCollectRequest(payload) {
355
+ return validateOperationPayload(payload, validateWireCollectRequest);
356
+ }
357
+ function operationInspectRequest(payload) {
358
+ return validateOperationPayload(payload, validateWireInspectRequest);
359
+ }
360
+ function operationControlRequest(payload) {
361
+ return validateOperationPayload(payload, validateWireControlRequest);
362
+ }
363
+ function validateOperationPayload(payload, validator) {
364
+ try {
365
+ validator(payload);
366
+ return payload;
367
+ }
368
+ catch (error) {
369
+ if (error instanceof OperationWireRequestError)
370
+ throw invalidOperationPayload();
371
+ throw error;
372
+ }
373
+ }
374
+ function invalidOperationPayload() {
375
+ // Do not include the offending value: it may contain a prompt or local path.
376
+ return new ProtocolError("invalid_request", "Transactional operation payload is invalid.", false);
377
+ }
378
+ async function runOperationSafely(call) {
379
+ try {
380
+ return await call();
381
+ }
382
+ catch (error) {
383
+ if (error instanceof ProtocolError)
384
+ throw error;
385
+ // Operation adapters and journal errors can contain raw prompts, paths,
386
+ // URLs, or provider-private diagnostics. None may cross the backend wire.
387
+ throw new ProtocolError("invalid_request", "Transactional browser operation could not complete safely.", false);
388
+ }
389
+ }
390
+ function operationWireSafely(call) {
391
+ try {
392
+ return call();
393
+ }
394
+ catch (error) {
395
+ if (error instanceof ProtocolError)
396
+ throw error;
397
+ throw new ProtocolError("invalid_request", "Transactional browser operation could not complete safely.", false);
398
+ }
399
+ }
400
+ function intersectCapabilities(base, requested) {
401
+ if (requested === undefined)
402
+ return base;
403
+ const requestedStreaming = isRecord(requested.streaming) ? requested.streaming : undefined;
404
+ const requestedRequestIds = isRecord(requested.requestIds) ? requested.requestIds : undefined;
405
+ const requestedMultiplexing = isRecord(requested.multiplexing) ? requested.multiplexing : undefined;
406
+ const requestedCancellation = isRecord(requested.cancellation) ? requested.cancellation : undefined;
407
+ const requestedTabs = isRecord(requested.tabs) ? requested.tabs : undefined;
408
+ const intersectedCommands = Array.isArray(requested.commands)
409
+ ? base.commands.filter(command => includesString(requested.commands, command))
410
+ : base.commands;
411
+ const commands = intersectedCommands.length > 0 ? intersectedCommands : base.commands;
412
+ const intersectedTransports = Array.isArray(requested.transports)
413
+ ? base.transports.filter(transport => includesString(requested.transports, transport))
414
+ : base.transports;
415
+ const transports = intersectedTransports.length > 0 ? intersectedTransports : base.transports;
416
+ const intersectedModes = requestedStreaming !== undefined && Array.isArray(requestedStreaming.modes)
417
+ ? base.streaming.modes.filter(mode => includesString(requestedStreaming.modes, mode))
418
+ : base.streaming.modes;
419
+ const modes = intersectedModes.length > 0 ? intersectedModes : base.streaming.modes;
420
+ const intersectedProtocolVersions = Array.isArray(requested.supportedProtocolVersions)
421
+ ? base.supportedProtocolVersions.filter(version => includesString(requested.supportedProtocolVersions, version))
422
+ : base.supportedProtocolVersions;
423
+ const requestIdScope = requestedRequestIds?.scope === undefined || requestedRequestIds.scope === base.requestIds.scope
424
+ ? base.requestIds.scope
425
+ : "none";
426
+ const coordinationScope = requestedTabs?.coordinationScope === undefined
427
+ || requestedTabs.coordinationScope === base.tabs.coordinationScope
428
+ ? base.tabs.coordinationScope
429
+ : "none";
430
+ const stableProviderIdentity = base.tabs.stableProviderIdentity && requestedTabs?.stableProviderIdentity !== false;
431
+ const stableBrowserIdentity = base.tabs.stableBrowserIdentity && requestedTabs?.stableBrowserIdentity !== false;
432
+ const stableTabIdentity = base.tabs.stableTabIdentity && requestedTabs?.stableTabIdentity !== false;
433
+ const authoritativeClaim = base.tabs.authoritativeClaim && requestedTabs?.authoritativeClaim !== false;
434
+ const fencing = base.tabs.fencing && requestedTabs?.fencing !== false;
435
+ const concurrentTabs = base.tabs.concurrentTabs && requestedTabs?.concurrentTabs !== false;
436
+ return {
437
+ ...base,
438
+ supportedProtocolVersions: intersectedProtocolVersions.length > 0
439
+ ? intersectedProtocolVersions
440
+ : base.supportedProtocolVersions,
441
+ commands,
442
+ transports,
443
+ streaming: {
444
+ modes,
445
+ tokenDeltas: base.streaming.tokenDeltas && requestedStreaming?.tokenDeltas !== false
446
+ },
447
+ requestIds: {
448
+ required: base.requestIds.required && requestedRequestIds?.required !== false,
449
+ scope: requestIdScope
450
+ },
451
+ multiplexing: {
452
+ unary: base.multiplexing.unary && requestedMultiplexing?.unary !== false,
453
+ streams: base.multiplexing.streams && requestedMultiplexing?.streams !== false
454
+ },
455
+ cancellation: {
456
+ supported: base.cancellation.supported && requestedCancellation?.supported === true,
457
+ requests: base.cancellation.requests && requestedCancellation?.requests === true,
458
+ streams: base.cancellation.streams && requestedCancellation?.streams === true
459
+ },
460
+ tabs: {
461
+ ...base.tabs,
462
+ stableProviderIdentity,
463
+ stableBrowserIdentity,
464
+ stableTabIdentity,
465
+ coordinationScope,
466
+ authoritativeClaim,
467
+ fencing,
468
+ concurrentTabs,
469
+ stableIdentity: stableProviderIdentity && stableBrowserIdentity && stableTabIdentity,
470
+ coordination: coordinationScope !== "none",
471
+ concurrent: concurrentTabs
202
472
  }
203
473
  };
204
474
  }
475
+ function requestedCapabilitiesSatisfied(base, intersection, requested) {
476
+ if (!requestedCapabilitiesShapeValid(requested))
477
+ return false;
478
+ if ((Array.isArray(requested.commands) && requested.commands.length === 0)
479
+ || (Array.isArray(requested.transports) && requested.transports.length === 0)
480
+ || (Array.isArray(requested.supportedProtocolVersions) && requested.supportedProtocolVersions.length === 0))
481
+ return false;
482
+ if (Array.isArray(requested.commands)
483
+ && requested.commands.some(command => typeof command !== "string" || !base.commands.includes(command)))
484
+ return false;
485
+ if (Array.isArray(requested.transports)
486
+ && requested.transports.some(transport => typeof transport !== "string" || !base.transports.includes(transport)))
487
+ return false;
488
+ const requestedStreaming = isRecord(requested.streaming) ? requested.streaming : undefined;
489
+ if (requested.streaming !== undefined && requestedStreaming === undefined)
490
+ return false;
491
+ if (requestedStreaming
492
+ && Array.isArray(requestedStreaming.modes)) {
493
+ if (requestedStreaming.modes.length === 0
494
+ || requestedStreaming.modes.some(mode => typeof mode !== "string" || !base.streaming.modes.includes(mode)))
495
+ return false;
496
+ }
497
+ if (requestedStreaming?.tokenDeltas === true && !intersection.streaming.tokenDeltas)
498
+ return false;
499
+ if (Array.isArray(requested.supportedProtocolVersions)
500
+ && requested.supportedProtocolVersions.some(version => typeof version !== "string" || !base.supportedProtocolVersions.includes(version)))
501
+ return false;
502
+ const requestedRequestIds = isRecord(requested.requestIds) ? requested.requestIds : undefined;
503
+ if (requested.requestIds !== undefined && requestedRequestIds === undefined)
504
+ return false;
505
+ if (requestedRequestIds
506
+ && ((requestedRequestIds.required === true && !intersection.requestIds.required)
507
+ || (requestedRequestIds.scope !== undefined && requestedRequestIds.scope !== intersection.requestIds.scope)))
508
+ return false;
509
+ const requestedMultiplexing = isRecord(requested.multiplexing) ? requested.multiplexing : undefined;
510
+ if (requested.multiplexing !== undefined && requestedMultiplexing === undefined)
511
+ return false;
512
+ if (requestedMultiplexing
513
+ && ((requestedMultiplexing.unary === true && !intersection.multiplexing.unary)
514
+ || (requestedMultiplexing.streams === true && !intersection.multiplexing.streams)))
515
+ return false;
516
+ const requestedCancellation = isRecord(requested.cancellation) ? requested.cancellation : undefined;
517
+ if (requested.cancellation !== undefined && requestedCancellation === undefined)
518
+ return false;
519
+ if (requestedCancellation
520
+ && ((requestedCancellation.supported === true && !intersection.cancellation.supported)
521
+ || (requestedCancellation.requests === true && !intersection.cancellation.requests)
522
+ || (requestedCancellation.streams === true && !intersection.cancellation.streams)))
523
+ return false;
524
+ const requestedTabs = isRecord(requested.tabs) ? requested.tabs : undefined;
525
+ if (requested.tabs !== undefined && requestedTabs === undefined)
526
+ return false;
527
+ if (requestedTabs
528
+ && ((requestedTabs.stableProviderIdentity === true && !intersection.tabs.stableProviderIdentity)
529
+ || (requestedTabs.stableBrowserIdentity === true && !intersection.tabs.stableBrowserIdentity)
530
+ || (requestedTabs.stableTabIdentity === true && !intersection.tabs.stableTabIdentity)
531
+ || (requestedTabs.authoritativeClaim === true && !intersection.tabs.authoritativeClaim)
532
+ || (requestedTabs.fencing === true && !intersection.tabs.fencing)
533
+ || (requestedTabs.concurrentTabs === true && !intersection.tabs.concurrentTabs)
534
+ || (requestedTabs.stableIdentity === true && !intersection.tabs.stableIdentity)
535
+ || (requestedTabs.coordination === true && !intersection.tabs.coordination)
536
+ || (requestedTabs.concurrent === true && !intersection.tabs.concurrent)
537
+ || (requestedTabs.coordinationScope !== undefined && requestedTabs.coordinationScope !== intersection.tabs.coordinationScope)
538
+ || !requestedDeprecatedTabAliasesConsistent(requestedTabs)))
539
+ return false;
540
+ return true;
541
+ }
542
+ function requestedCapabilitiesShapeValid(requested) {
543
+ if (!optionalStringArray(requested.commands)
544
+ || !optionalStringArray(requested.transports)
545
+ || !optionalStringArray(requested.supportedProtocolVersions))
546
+ return false;
547
+ for (const field of ["backendSessionId", "packageName", "packageVersion", "runtimeVersion", "buildDigest"]) {
548
+ if (Object.hasOwn(requested, field) && !optionalIdentityString(requested[field]))
549
+ return false;
550
+ }
551
+ if (Object.hasOwn(requested, "runtime") && requested.runtime !== "node")
552
+ return false;
553
+ if (Object.hasOwn(requested, "protocolVersion")
554
+ && requested.protocolVersion !== BACKEND_REQUEST_SCHEMA_VERSION)
555
+ return false;
556
+ if (Object.hasOwn(requested, "streaming")) {
557
+ if (!isRecord(requested.streaming)
558
+ || !optionalStringArray(requested.streaming.modes)
559
+ || !optionalBoolean(requested.streaming.tokenDeltas))
560
+ return false;
561
+ }
562
+ if (Object.hasOwn(requested, "requestIds")) {
563
+ if (!isRecord(requested.requestIds)
564
+ || !optionalBoolean(requested.requestIds.required)
565
+ || (requested.requestIds.scope !== undefined
566
+ && requested.requestIds.scope !== "connection"
567
+ && requested.requestIds.scope !== "process"
568
+ && requested.requestIds.scope !== "none"))
569
+ return false;
570
+ }
571
+ if (Object.hasOwn(requested, "multiplexing")) {
572
+ if (!isRecord(requested.multiplexing)
573
+ || !optionalBoolean(requested.multiplexing.unary)
574
+ || !optionalBoolean(requested.multiplexing.streams))
575
+ return false;
576
+ }
577
+ if (Object.hasOwn(requested, "cancellation")) {
578
+ if (!isRecord(requested.cancellation)
579
+ || !optionalBoolean(requested.cancellation.supported)
580
+ || !optionalBoolean(requested.cancellation.requests)
581
+ || !optionalBoolean(requested.cancellation.streams))
582
+ return false;
583
+ }
584
+ if (Object.hasOwn(requested, "tabs")) {
585
+ if (!isRecord(requested.tabs)
586
+ || !optionalBoolean(requested.tabs.stableProviderIdentity)
587
+ || !optionalBoolean(requested.tabs.stableBrowserIdentity)
588
+ || !optionalBoolean(requested.tabs.stableTabIdentity)
589
+ || (requested.tabs.coordinationScope !== undefined
590
+ && requested.tabs.coordinationScope !== "none"
591
+ && requested.tabs.coordinationScope !== "process"
592
+ && requested.tabs.coordinationScope !== "provider")
593
+ || !optionalBoolean(requested.tabs.authoritativeClaim)
594
+ || !optionalBoolean(requested.tabs.fencing)
595
+ || !optionalBoolean(requested.tabs.concurrentTabs)
596
+ || !optionalBoolean(requested.tabs.stableIdentity)
597
+ || !optionalBoolean(requested.tabs.coordination)
598
+ || !optionalBoolean(requested.tabs.concurrent))
599
+ return false;
600
+ }
601
+ return true;
602
+ }
603
+ function optionalStringArray(value) {
604
+ return value === undefined || (Array.isArray(value) && value.every(item => typeof item === "string"));
605
+ }
606
+ function optionalBoolean(value) {
607
+ return value === undefined || typeof value === "boolean";
608
+ }
609
+ function optionalIdentityString(value) {
610
+ return value === undefined
611
+ || (typeof value === "string"
612
+ && value.length > 0
613
+ && value.length <= MAX_IDENTITY_FIELD_LENGTH
614
+ && value.trim() === value
615
+ && !/[\u0000-\u001f\u007f]/u.test(value));
616
+ }
617
+ function requestedDeprecatedTabAliasesConsistent(tabs) {
618
+ const hasStableFields = ["stableProviderIdentity", "stableBrowserIdentity", "stableTabIdentity"]
619
+ .every(field => Object.hasOwn(tabs, field));
620
+ const expectedStableIdentity = hasStableFields
621
+ && tabs.stableProviderIdentity === true
622
+ && tabs.stableBrowserIdentity === true
623
+ && tabs.stableTabIdentity === true;
624
+ const expectedCoordination = tabs.coordinationScope !== undefined && tabs.coordinationScope !== "none";
625
+ const expectedConcurrent = tabs.concurrentTabs === true;
626
+ return (!Object.hasOwn(tabs, "stableIdentity")
627
+ || !hasStableFields
628
+ || tabs.stableIdentity === expectedStableIdentity)
629
+ && (!Object.hasOwn(tabs, "coordination")
630
+ || tabs.coordination === expectedCoordination)
631
+ && (!Object.hasOwn(tabs, "concurrent")
632
+ || tabs.concurrent === expectedConcurrent);
633
+ }
634
+ function includesString(value, expected) {
635
+ return Array.isArray(value) && value.includes(expected);
636
+ }
637
+ function backendRuntimeIdentity(rawOverrides = {}) {
638
+ const overrides = validateIdentityOverrides(rawOverrides);
639
+ return {
640
+ backendSessionId: identityString(overrides.backendSessionId ?? PROCESS_BACKEND_SESSION_ID, "backendSessionId"),
641
+ packageName: identityString(overrides.packageName ?? process.env.CHATGPT_BROWSER_CONTROL_PACKAGE_NAME ?? "codex-chatgpt-control", "packageName"),
642
+ packageVersion: identityString(overrides.packageVersion ?? process.env.CHATGPT_BROWSER_CONTROL_PACKAGE_VERSION ?? "unknown", "packageVersion"),
643
+ runtime: "node",
644
+ runtimeVersion: identityString(overrides.runtimeVersion ?? process.env.CHATGPT_BROWSER_CONTROL_RUNTIME_VERSION ?? process.version, "runtimeVersion"),
645
+ buildDigest: identityString(overrides.buildDigest
646
+ ?? process.env.CHATGPT_BROWSER_CONTROL_BUILD_DIGEST
647
+ ?? process.env.CHATGPT_BROWSER_CONTROL_BUILD_SHA
648
+ ?? "unknown", "buildDigest"),
649
+ protocolVersion: BACKEND_REQUEST_SCHEMA_VERSION
650
+ };
651
+ }
652
+ function validateIdentityOverrides(value) {
653
+ if (value === undefined)
654
+ return {};
655
+ if (!isRecord(value))
656
+ throw new TypeError("backendIdentity must be an object when provided.");
657
+ const allowed = new Set(["backendSessionId", "packageName", "packageVersion", "runtimeVersion", "buildDigest"]);
658
+ if (Object.keys(value).some(key => !allowed.has(key))) {
659
+ throw new TypeError("backendIdentity contains unsupported fields.");
660
+ }
661
+ for (const [key, field] of Object.entries(value)) {
662
+ identityString(field, key);
663
+ }
664
+ return value;
665
+ }
666
+ function identityString(value, label) {
667
+ if (typeof value !== "string"
668
+ || value.length === 0
669
+ || value.length > MAX_IDENTITY_FIELD_LENGTH
670
+ || value.trim() !== value
671
+ || /[\u0000-\u001f\u007f]/u.test(value)) {
672
+ throw new TypeError(`backend identity ${label} must be a bounded, non-empty string.`);
673
+ }
674
+ return value;
675
+ }
205
676
  function agentConfig(payload) {
206
677
  return requiredRecord(payload, "agent");
207
678
  }
@@ -1,9 +1,12 @@
1
1
  import type { Readable, Writable } from "node:stream";
2
- import { BackendSession } from "./session.js";
2
+ import { BackendSession, type BackendSessionOptions } from "./session.js";
3
3
  export type BackendStdioServerOptions = {
4
4
  input: Readable;
5
5
  output: Writable;
6
6
  error?: Writable;
7
7
  session?: BackendSession;
8
+ backendIdentity?: BackendSessionOptions["backendIdentity"];
9
+ maxInFlight?: number;
10
+ frameLimitBytes?: number;
8
11
  };
9
12
  export declare function runBackendStdioServer(options: BackendStdioServerOptions): Promise<void>;