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.
- package/CHANGELOG.md +15 -0
- package/README.md +27 -0
- package/contracts/v1/fixtures/backend-capabilities.json +40 -1
- package/contracts/v1/fixtures/backend-compatibility.json +21 -0
- package/contracts/v1/fixtures/backend-version.json +6 -1
- package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
- package/contracts/v1/fixtures/operation-action.json +13 -0
- package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
- package/contracts/v1/fixtures/operation-blocker.json +10 -0
- package/contracts/v1/fixtures/operation-collect-request.json +17 -0
- package/contracts/v1/fixtures/operation-collect-result.json +42 -0
- package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
- package/contracts/v1/fixtures/operation-control-request.json +18 -0
- package/contracts/v1/fixtures/operation-control-result.json +31 -0
- package/contracts/v1/fixtures/operation-event.json +18 -0
- package/contracts/v1/fixtures/operation-handle.json +10 -0
- package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
- package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
- package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
- package/contracts/v1/fixtures/operation-receipt.json +32 -0
- package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
- package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
- package/contracts/v1/fixtures/operation-request.json +33 -0
- package/contracts/v1/fixtures/operation-state.json +144 -0
- package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
- package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
- package/contracts/v1/fixtures/operation-submit-result.json +16 -0
- package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
- package/contracts/v1/manifest.json +155 -1
- package/contracts/v1/parity-suite.json +96 -1
- package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
- package/contracts/v1/schemas/backend-request.schema.json +8 -2
- package/contracts/v1/schemas/capabilities.schema.json +81 -1
- package/contracts/v1/schemas/manifest.schema.json +57 -0
- package/contracts/v1/schemas/operation-action.schema.json +155 -0
- package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
- package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
- package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
- package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
- package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
- package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
- package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
- package/contracts/v1/schemas/operation-event.schema.json +756 -0
- package/contracts/v1/schemas/operation-handle.schema.json +55 -0
- package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
- package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
- package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
- package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
- package/contracts/v1/schemas/operation-request.schema.json +158 -0
- package/contracts/v1/schemas/operation-state.schema.json +691 -0
- package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
- package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
- package/contracts/v1/surface-drift-policy.json +50 -0
- package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
- package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
- package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
- package/dist/src/backend/client.d.ts +103 -2
- package/dist/src/backend/client.js +1436 -97
- package/dist/src/backend/compatibility.d.ts +12 -0
- package/dist/src/backend/compatibility.js +208 -0
- package/dist/src/backend/protocol.d.ts +84 -1
- package/dist/src/backend/protocol.js +31 -5
- package/dist/src/backend/runtime-identity.d.ts +10 -0
- package/dist/src/backend/runtime-identity.js +141 -0
- package/dist/src/backend/session.d.ts +10 -2
- package/dist/src/backend/session.js +478 -7
- package/dist/src/backend/stdio-server.d.ts +4 -1
- package/dist/src/backend/stdio-server.js +240 -28
- package/dist/src/browser/active-composer-file-input.d.ts +9 -0
- package/dist/src/browser/active-composer-file-input.js +33 -0
- package/dist/src/browser/attach.d.ts +4 -1
- package/dist/src/browser/attach.js +136 -28
- package/dist/src/browser/downloads.d.ts +2 -0
- package/dist/src/client.d.ts +38 -0
- package/dist/src/client.js +1222 -59
- package/dist/src/commands/configuration.js +13 -7
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +26 -3
- package/dist/src/commands/experience.js +35 -0
- package/dist/src/commands/files.js +4 -27
- package/dist/src/commands/modes.js +49 -17
- package/dist/src/commands/power-discovery.d.ts +130 -0
- package/dist/src/commands/power-discovery.js +627 -0
- package/dist/src/commands/project-sources.js +325 -31
- package/dist/src/commands/sequence.js +59 -32
- package/dist/src/commands/session.js +7 -2
- package/dist/src/commands/work.js +31 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.js +22 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +9 -0
- package/dist/src/operations/artifact-output.d.ts +69 -0
- package/dist/src/operations/artifact-output.js +1748 -0
- package/dist/src/operations/artifact-stream.d.ts +19 -0
- package/dist/src/operations/artifact-stream.js +35 -0
- package/dist/src/operations/artifact-transfer.d.ts +149 -0
- package/dist/src/operations/artifact-transfer.js +1312 -0
- package/dist/src/operations/browser-adapter.d.ts +161 -0
- package/dist/src/operations/browser-adapter.js +2023 -0
- package/dist/src/operations/browser-observation.d.ts +110 -0
- package/dist/src/operations/browser-observation.js +1208 -0
- package/dist/src/operations/browser-target.d.ts +103 -0
- package/dist/src/operations/browser-target.js +489 -0
- package/dist/src/operations/canonical.d.ts +4 -0
- package/dist/src/operations/canonical.js +412 -0
- package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
- package/dist/src/operations/chatgpt-runtime.js +1150 -0
- package/dist/src/operations/client.d.ts +138 -0
- package/dist/src/operations/client.js +1143 -0
- package/dist/src/operations/collector.d.ts +195 -0
- package/dist/src/operations/collector.js +1039 -0
- package/dist/src/operations/control.d.ts +372 -0
- package/dist/src/operations/control.js +1498 -0
- package/dist/src/operations/file-identity.d.ts +33 -0
- package/dist/src/operations/file-identity.js +153 -0
- package/dist/src/operations/handle.d.ts +19 -0
- package/dist/src/operations/handle.js +727 -0
- package/dist/src/operations/index.d.ts +22 -0
- package/dist/src/operations/index.js +22 -0
- package/dist/src/operations/journal.d.ts +142 -0
- package/dist/src/operations/journal.js +1931 -0
- package/dist/src/operations/production-attachments.d.ts +116 -0
- package/dist/src/operations/production-attachments.js +1018 -0
- package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
- package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
- package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
- package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
- package/dist/src/operations/production-configuration.d.ts +42 -0
- package/dist/src/operations/production-configuration.js +1363 -0
- package/dist/src/operations/production-primitives.d.ts +53 -0
- package/dist/src/operations/production-primitives.js +1073 -0
- package/dist/src/operations/production-work-steer.d.ts +203 -0
- package/dist/src/operations/production-work-steer.js +1218 -0
- package/dist/src/operations/recovery.d.ts +80 -0
- package/dist/src/operations/recovery.js +174 -0
- package/dist/src/operations/runtime-adapter.d.ts +118 -0
- package/dist/src/operations/runtime-adapter.js +786 -0
- package/dist/src/operations/send-once.d.ts +224 -0
- package/dist/src/operations/send-once.js +1081 -0
- package/dist/src/operations/service.d.ts +281 -0
- package/dist/src/operations/service.js +2810 -0
- package/dist/src/operations/staging.d.ts +136 -0
- package/dist/src/operations/staging.js +630 -0
- package/dist/src/operations/state-machine.d.ts +29 -0
- package/dist/src/operations/state-machine.js +2047 -0
- package/dist/src/operations/submission.d.ts +423 -0
- package/dist/src/operations/submission.js +1676 -0
- package/dist/src/operations/turn-ownership.d.ts +202 -0
- package/dist/src/operations/turn-ownership.js +700 -0
- package/dist/src/operations/types.d.ts +454 -0
- package/dist/src/operations/types.js +20 -0
- package/dist/src/operations/wire-requests.d.ts +21 -0
- package/dist/src/operations/wire-requests.js +396 -0
- package/dist/src/operations/wire-results.d.ts +97 -0
- package/dist/src/operations/wire-results.js +818 -0
- package/dist/src/runner/responses.d.ts +3 -1
- package/dist/src/runner/responses.js +19 -1
- package/dist/src/runner/result.js +227 -55
- package/dist/src/runner/types.d.ts +12 -0
- package/dist/src/runtime/command-routing.d.ts +149 -0
- package/dist/src/runtime/command-routing.js +431 -0
- package/dist/src/runtime/coordinated-browser.d.ts +24 -0
- package/dist/src/runtime/coordinated-browser.js +315 -0
- package/dist/src/runtime/coordinated-page.d.ts +39 -0
- package/dist/src/runtime/coordinated-page.js +634 -0
- package/dist/src/runtime/operation-context.d.ts +142 -0
- package/dist/src/runtime/operation-context.js +410 -0
- package/dist/src/runtime/runtime-session.d.ts +95 -0
- package/dist/src/runtime/runtime-session.js +314 -0
- package/dist/src/runtime/tab-coordinator.d.ts +200 -0
- package/dist/src/runtime/tab-coordinator.js +1200 -0
- package/dist/src/runtime/value-boundaries.d.ts +18 -0
- package/dist/src/runtime/value-boundaries.js +49 -0
- package/dist/src/safety/untrusted-output.js +2 -1
- package/dist/src/scripts/backend-server.js +4 -1
- package/dist/src/scripts/live-smoke/harness.js +79 -7
- package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
- package/dist/src/scripts/live-smoke/scenarios.js +110 -18
- package/dist/src/scripts/live-smoke/types.d.ts +3 -0
- package/dist/src/scripts/release-canary-module.js +17 -4
- package/dist/src/types.d.ts +38 -1
- package/package.json +1 -1
- package/references/2026-08-16-transactional-operations.md +459 -0
- package/references/agents-runner.md +34 -0
- package/references/backend-protocol.md +73 -0
- package/references/python-parity.md +72 -0
- package/references/responses-adapter.md +19 -0
- package/references/streaming.md +6 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
import { assertDurableCapturePolicyShape, assertOperationStateShape } from "./state-machine.js";
|
|
2
|
+
import { validateBackendCompatibilityReport } from "../backend/compatibility.js";
|
|
3
|
+
/** Versioned result envelopes used at backend/SDK boundaries. */
|
|
4
|
+
export const OPERATION_SUBMIT_RESULT_SCHEMA_VERSION = "chatgpt.browser_control.operation_submit_result.v1";
|
|
5
|
+
export const OPERATION_COLLECT_RESULT_SCHEMA_VERSION = "chatgpt.browser_control.operation_collect_result.v1";
|
|
6
|
+
export const OPERATION_INSPECT_RESULT_SCHEMA_VERSION = "chatgpt.browser_control.operation_inspect_result.v1";
|
|
7
|
+
export const OPERATION_CONTROL_RESULT_SCHEMA_VERSION = "chatgpt.browser_control.operation_control_result.v1";
|
|
8
|
+
export const OPERATION_LIVE_RESPONSE_SCHEMA_VERSION = "chatgpt.browser_control.operation_live_response.v1";
|
|
9
|
+
export const MAX_WIRE_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
10
|
+
export const MAX_WIRE_RESPONSE_CHARS = 8 * 1024 * 1024;
|
|
11
|
+
export const MAX_WIRE_BLOCKER_MESSAGE_LENGTH = 512;
|
|
12
|
+
export const MAX_WIRE_ARTIFACTS = 32;
|
|
13
|
+
export class OperationWireResultError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
constructor(code, message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.name = "OperationWireResultError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Convert the internal submission result into a strict, versioned envelope. */
|
|
22
|
+
export function toOperationSubmitWireResult(result, receipt) {
|
|
23
|
+
assertSubmissionIdentity(result.submission, result.handle);
|
|
24
|
+
const base = {
|
|
25
|
+
operationId: result.handle.operationId,
|
|
26
|
+
requestDigest: result.handle.requestDigest,
|
|
27
|
+
handle: result.handle
|
|
28
|
+
};
|
|
29
|
+
switch (result.submission.kind) {
|
|
30
|
+
case "submitted":
|
|
31
|
+
case "already_submitted":
|
|
32
|
+
return validateOperationSubmitWireResult({
|
|
33
|
+
schemaVersion: OPERATION_SUBMIT_RESULT_SCHEMA_VERSION,
|
|
34
|
+
status: "accepted",
|
|
35
|
+
...base
|
|
36
|
+
});
|
|
37
|
+
case "completed_receipt":
|
|
38
|
+
if (receipt === undefined) {
|
|
39
|
+
throw new OperationWireResultError("completed_receipt_missing", "A completed submit result requires its durable receipt.");
|
|
40
|
+
}
|
|
41
|
+
return validateOperationSubmitWireResult({
|
|
42
|
+
schemaVersion: OPERATION_SUBMIT_RESULT_SCHEMA_VERSION,
|
|
43
|
+
status: "completed",
|
|
44
|
+
...base,
|
|
45
|
+
receipt
|
|
46
|
+
});
|
|
47
|
+
case "cancelled":
|
|
48
|
+
return validateOperationSubmitWireResult({
|
|
49
|
+
schemaVersion: OPERATION_SUBMIT_RESULT_SCHEMA_VERSION,
|
|
50
|
+
status: result.submission.blocker.mutationBoundary === "none" ? "blocked" : "uncertain",
|
|
51
|
+
...base,
|
|
52
|
+
blocker: blockerFromInternal(result.submission.blocker.code, result.handle, result.submission.blocker.mutationBoundary, result.submission.blocker.observationRequired, result.submission.blocker.evidenceDigest)
|
|
53
|
+
});
|
|
54
|
+
case "blocked":
|
|
55
|
+
case "uncertain":
|
|
56
|
+
return validateOperationSubmitWireResult({
|
|
57
|
+
schemaVersion: OPERATION_SUBMIT_RESULT_SCHEMA_VERSION,
|
|
58
|
+
status: result.submission.kind,
|
|
59
|
+
...base,
|
|
60
|
+
blocker: blockerFromInternal(result.submission.blocker.code, result.handle, result.submission.blocker.mutationBoundary, result.submission.blocker.observationRequired, result.submission.blocker.evidenceDigest)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Convert the collector result using the freshly reloaded handle and receipt. */
|
|
65
|
+
export function toOperationCollectWireResult(
|
|
66
|
+
/** The handle freshly reloaded after the collector attempt (including pending). */
|
|
67
|
+
handle, result, receipt) {
|
|
68
|
+
assertCollectorIdentity(result, handle);
|
|
69
|
+
const base = {
|
|
70
|
+
operationId: handle.operationId,
|
|
71
|
+
requestDigest: handle.requestDigest,
|
|
72
|
+
handle
|
|
73
|
+
};
|
|
74
|
+
switch (result.kind) {
|
|
75
|
+
case "completed": {
|
|
76
|
+
if (receipt === undefined) {
|
|
77
|
+
throw new OperationWireResultError("completed_receipt_missing", "A completed collect result requires its durable receipt.");
|
|
78
|
+
}
|
|
79
|
+
const liveResponse = result.response.rawText === undefined
|
|
80
|
+
? undefined
|
|
81
|
+
: liveResponseFromText(result.response.rawText, result.response.responseFormat);
|
|
82
|
+
return validateOperationCollectWireResult({
|
|
83
|
+
schemaVersion: OPERATION_COLLECT_RESULT_SCHEMA_VERSION,
|
|
84
|
+
status: "completed",
|
|
85
|
+
...base,
|
|
86
|
+
receipt,
|
|
87
|
+
...(liveResponse === undefined ? {} : { liveResponse })
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
case "pending":
|
|
91
|
+
return validateOperationCollectWireResult({
|
|
92
|
+
schemaVersion: OPERATION_COLLECT_RESULT_SCHEMA_VERSION,
|
|
93
|
+
status: "pending",
|
|
94
|
+
...base
|
|
95
|
+
});
|
|
96
|
+
case "blocked":
|
|
97
|
+
return validateOperationCollectWireResult({
|
|
98
|
+
schemaVersion: OPERATION_COLLECT_RESULT_SCHEMA_VERSION,
|
|
99
|
+
status: "blocked",
|
|
100
|
+
...base,
|
|
101
|
+
blocker: blockerFromCollector(result.blocker, handle)
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Inspect is a durable read, so its status is derived from the fresh state. */
|
|
106
|
+
export function toOperationInspectWireResult(result) {
|
|
107
|
+
const status = result.state.phase === "completed"
|
|
108
|
+
? "completed"
|
|
109
|
+
: result.state.phase === "uncertain"
|
|
110
|
+
? "uncertain"
|
|
111
|
+
: "pending";
|
|
112
|
+
return validateOperationInspectWireResult({
|
|
113
|
+
schemaVersion: OPERATION_INSPECT_RESULT_SCHEMA_VERSION,
|
|
114
|
+
status,
|
|
115
|
+
operationId: result.handle.operationId,
|
|
116
|
+
requestDigest: result.handle.requestDigest,
|
|
117
|
+
handle: result.handle,
|
|
118
|
+
state: result.state
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/** Convert a control result with the current parent handle reloaded by the service. */
|
|
122
|
+
export function toOperationControlWireResult(result, handle) {
|
|
123
|
+
const wire = {
|
|
124
|
+
schemaVersion: OPERATION_CONTROL_RESULT_SCHEMA_VERSION,
|
|
125
|
+
status: result.kind,
|
|
126
|
+
operationId: result.parentOperationId,
|
|
127
|
+
requestDigest: result.requestDigest,
|
|
128
|
+
handle,
|
|
129
|
+
parentRequestDigest: result.parentRequestDigest,
|
|
130
|
+
parentTargetBindingDigest: result.parentTargetBindingDigest,
|
|
131
|
+
controlActionId: result.controlActionId,
|
|
132
|
+
action: result.action,
|
|
133
|
+
expectedAssistantTurnId: result.expectedAssistantTurnId,
|
|
134
|
+
...(result.receipt === undefined ? {} : { receipt: result.receipt }),
|
|
135
|
+
...(result.kind === "completed" ? {} : {
|
|
136
|
+
blocker: blockerFromInternal(result.blocker.code, handle, result.blocker.mutationBoundary, result.blocker.observationRequired, result.blocker.evidenceDigest, result.parentRequestDigest)
|
|
137
|
+
})
|
|
138
|
+
};
|
|
139
|
+
return validateOperationControlWireResult(wire);
|
|
140
|
+
}
|
|
141
|
+
export function liveResponseFromText(content, responseFormat) {
|
|
142
|
+
if (typeof content !== "string") {
|
|
143
|
+
throw new OperationWireResultError("invalid_live_response", "Live response content must be a string.");
|
|
144
|
+
}
|
|
145
|
+
assertUnicodeScalarString(content);
|
|
146
|
+
const response = {
|
|
147
|
+
schemaVersion: OPERATION_LIVE_RESPONSE_SCHEMA_VERSION,
|
|
148
|
+
durability: "ephemeral",
|
|
149
|
+
durable: false,
|
|
150
|
+
content,
|
|
151
|
+
...(responseFormat === undefined ? {} : { responseFormat }),
|
|
152
|
+
bytes: Buffer.byteLength(content, "utf8"),
|
|
153
|
+
chars: content.length
|
|
154
|
+
};
|
|
155
|
+
return validateLiveResponse(response);
|
|
156
|
+
}
|
|
157
|
+
export function validateOperationSubmitWireResult(value) {
|
|
158
|
+
const result = validateEnvelope(value, OPERATION_SUBMIT_RESULT_SCHEMA_VERSION);
|
|
159
|
+
const status = result.status;
|
|
160
|
+
if (!SUBMIT_STATUSES.has(status)) {
|
|
161
|
+
throw new OperationWireResultError("invalid_submit_status", "Submit results must be accepted, completed, blocked, or uncertain.");
|
|
162
|
+
}
|
|
163
|
+
if (status === "completed") {
|
|
164
|
+
assertExactKeys(result, ["schemaVersion", "status", "operationId", "requestDigest", "handle", "receipt"]);
|
|
165
|
+
validateReceipt(result.receipt, result.operationId, result.requestDigest);
|
|
166
|
+
if (result.receipt.targetBindingDigest !== result.handle.targetBindingDigest)
|
|
167
|
+
throw new OperationWireResultError("receipt_target_mismatch", "Submit receipt target does not match the fresh handle.");
|
|
168
|
+
}
|
|
169
|
+
else if (status === "blocked" || status === "uncertain") {
|
|
170
|
+
assertExactKeys(result, ["schemaVersion", "status", "operationId", "requestDigest", "handle", "blocker"]);
|
|
171
|
+
validateBlocker(result.blocker, result.operationId, result.requestDigest);
|
|
172
|
+
validateBlockerHandleCoherence(result.blocker, result.handle);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
assertExactKeys(result, ["schemaVersion", "status", "operationId", "requestDigest", "handle"]);
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
export function validateOperationCollectWireResult(value) {
|
|
180
|
+
const result = validateEnvelope(value, OPERATION_COLLECT_RESULT_SCHEMA_VERSION);
|
|
181
|
+
const status = result.status;
|
|
182
|
+
if (!COLLECT_STATUSES.has(status)) {
|
|
183
|
+
throw new OperationWireResultError("invalid_collect_status", "Collect results must be completed, pending, blocked, or uncertain.");
|
|
184
|
+
}
|
|
185
|
+
if (status === "completed") {
|
|
186
|
+
assertExactKeys(result, ["schemaVersion", "status", "operationId", "requestDigest", "handle", "receipt", "liveResponse"]);
|
|
187
|
+
validateReceipt(result.receipt, result.operationId, result.requestDigest);
|
|
188
|
+
if (result.receipt.targetBindingDigest !== result.handle.targetBindingDigest)
|
|
189
|
+
throw new OperationWireResultError("receipt_target_mismatch", "Collect receipt target does not match the fresh handle.");
|
|
190
|
+
if (result.liveResponse !== undefined) {
|
|
191
|
+
validateLiveResponse(result.liveResponse);
|
|
192
|
+
validateLiveResponseReceiptCoherence(result.liveResponse, result.receipt);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else if (status === "blocked" || status === "uncertain") {
|
|
196
|
+
assertExactKeys(result, ["schemaVersion", "status", "operationId", "requestDigest", "handle", "blocker"]);
|
|
197
|
+
validateBlocker(result.blocker, result.operationId, result.requestDigest);
|
|
198
|
+
validateBlockerHandleCoherence(result.blocker, result.handle);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
assertExactKeys(result, ["schemaVersion", "status", "operationId", "requestDigest", "handle"]);
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
export function validateOperationInspectWireResult(value) {
|
|
206
|
+
const result = validateEnvelope(value, OPERATION_INSPECT_RESULT_SCHEMA_VERSION);
|
|
207
|
+
if (result.status !== "completed" && result.status !== "pending" && result.status !== "uncertain") {
|
|
208
|
+
throw new OperationWireResultError("invalid_inspect_status", "Inspect results must be completed, pending, or uncertain.");
|
|
209
|
+
}
|
|
210
|
+
assertExactKeys(result, ["schemaVersion", "status", "operationId", "requestDigest", "handle", "state", "compatibility"]);
|
|
211
|
+
validateState(result.state, result.operationId, result.requestDigest);
|
|
212
|
+
if (result.compatibility !== undefined)
|
|
213
|
+
validateCompatibility(result.compatibility);
|
|
214
|
+
const expectedStatus = result.state.phase === "completed"
|
|
215
|
+
? "completed"
|
|
216
|
+
: result.state.phase === "uncertain"
|
|
217
|
+
? "uncertain"
|
|
218
|
+
: "pending";
|
|
219
|
+
if (result.status !== expectedStatus) {
|
|
220
|
+
throw new OperationWireResultError("inspect_status_mismatch", "Inspect status does not match the durable state phase.");
|
|
221
|
+
}
|
|
222
|
+
if (result.handle.surface !== result.state.surface
|
|
223
|
+
|| result.handle.revision !== result.state.revision
|
|
224
|
+
|| result.handle.phase !== result.state.phase
|
|
225
|
+
|| result.handle.mutationBoundary !== result.state.mutationBoundary
|
|
226
|
+
|| (result.handle.targetBindingDigest === undefined) !== (result.state.target === undefined)) {
|
|
227
|
+
throw new OperationWireResultError("inspect_handle_mismatch", "Inspect handle does not match the durable state snapshot.");
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
function validateCompatibility(value) {
|
|
232
|
+
try {
|
|
233
|
+
validateBackendCompatibilityReport(value);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
throw new OperationWireResultError("invalid_compatibility", "Inspect compatibility diagnostics are malformed.");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export function validateOperationControlWireResult(value) {
|
|
240
|
+
const expectedParentDigest = isRecord(value) && typeof value.parentRequestDigest === "string"
|
|
241
|
+
? value.parentRequestDigest
|
|
242
|
+
: undefined;
|
|
243
|
+
const result = validateEnvelope(value, OPERATION_CONTROL_RESULT_SCHEMA_VERSION, expectedParentDigest);
|
|
244
|
+
if (!CONTROL_STATUSES.has(result.status)) {
|
|
245
|
+
throw new OperationWireResultError("invalid_control_status", "Control results must be completed, blocked, or uncertain.");
|
|
246
|
+
}
|
|
247
|
+
if (typeof result.parentRequestDigest !== "string" || !DIGEST_PATTERN.test(result.parentRequestDigest)) {
|
|
248
|
+
throw new OperationWireResultError("invalid_parent_request_digest", "Control parentRequestDigest must be canonical.");
|
|
249
|
+
}
|
|
250
|
+
if (typeof result.parentTargetBindingDigest !== "string" || !DIGEST_PATTERN.test(result.parentTargetBindingDigest)) {
|
|
251
|
+
throw new OperationWireResultError("invalid_parent_target_digest", "Control parentTargetBindingDigest must be canonical.");
|
|
252
|
+
}
|
|
253
|
+
if (typeof result.controlActionId !== "string" || !UUID_PATTERN.test(result.controlActionId)) {
|
|
254
|
+
throw new OperationWireResultError("invalid_control_action_id", "Control action ID must be a UUID.");
|
|
255
|
+
}
|
|
256
|
+
if (result.action !== "stop" && result.action !== "steer") {
|
|
257
|
+
throw new OperationWireResultError("invalid_control_action", "Control action must be stop or steer.");
|
|
258
|
+
}
|
|
259
|
+
assertOpaqueId(result.expectedAssistantTurnId, "expectedAssistantTurnId");
|
|
260
|
+
const keys = [
|
|
261
|
+
"schemaVersion", "status", "operationId", "requestDigest", "handle", "parentRequestDigest",
|
|
262
|
+
"parentTargetBindingDigest", "controlActionId", "action", "expectedAssistantTurnId", "receipt", "blocker"
|
|
263
|
+
];
|
|
264
|
+
assertExactKeys(result, keys);
|
|
265
|
+
if (result.handle.requestDigest !== result.parentRequestDigest) {
|
|
266
|
+
throw new OperationWireResultError("control_parent_mismatch", "Control handle does not match parent request identity.");
|
|
267
|
+
}
|
|
268
|
+
if (result.handle.targetBindingDigest !== result.parentTargetBindingDigest) {
|
|
269
|
+
throw new OperationWireResultError("control_parent_mismatch", "Control handle does not match parent target identity.");
|
|
270
|
+
}
|
|
271
|
+
if (result.status === "completed") {
|
|
272
|
+
if (result.receipt === undefined || result.blocker !== undefined) {
|
|
273
|
+
throw new OperationWireResultError("invalid_control_result", "A completed control result requires a receipt and no blocker.");
|
|
274
|
+
}
|
|
275
|
+
validateControlReceipt(result.receipt, result);
|
|
276
|
+
if (result.receipt.outcome !== "satisfied") {
|
|
277
|
+
throw new OperationWireResultError("invalid_control_result", "A completed control result requires a satisfied receipt.");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
if (result.blocker === undefined) {
|
|
282
|
+
throw new OperationWireResultError("invalid_control_result", "A blocked or uncertain control result requires a blocker.");
|
|
283
|
+
}
|
|
284
|
+
validateBlocker(result.blocker, result.operationId, result.parentRequestDigest);
|
|
285
|
+
validateBlockerHandleCoherence(result.blocker, result.handle);
|
|
286
|
+
if (result.receipt !== undefined) {
|
|
287
|
+
validateControlReceipt(result.receipt, result);
|
|
288
|
+
const expectedOutcome = result.status === "blocked" ? "not_satisfied" : "uncertain";
|
|
289
|
+
if (result.receipt.outcome !== expectedOutcome) {
|
|
290
|
+
throw new OperationWireResultError("invalid_control_result", "Control receipt outcome does not match result status.");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
function validateEnvelope(value, schemaVersion, expectedHandleRequestDigest) {
|
|
297
|
+
assertJsonSafe(value);
|
|
298
|
+
if (!isRecord(value))
|
|
299
|
+
throw new OperationWireResultError("invalid_wire_result", "Operation result must be an object.");
|
|
300
|
+
if (value.schemaVersion !== schemaVersion)
|
|
301
|
+
throw new OperationWireResultError("unsupported_wire_result", "Operation result schemaVersion is unsupported.");
|
|
302
|
+
const status = value.status;
|
|
303
|
+
if (!WIRE_STATUSES.has(status))
|
|
304
|
+
throw new OperationWireResultError("invalid_wire_status", "Operation result status is invalid.");
|
|
305
|
+
if (typeof value.operationId !== "string" || !UUID_PATTERN.test(value.operationId)) {
|
|
306
|
+
throw new OperationWireResultError("invalid_operation_id", "Operation result operationId must be a UUID.");
|
|
307
|
+
}
|
|
308
|
+
if (typeof value.requestDigest !== "string" || !DIGEST_PATTERN.test(value.requestDigest)) {
|
|
309
|
+
throw new OperationWireResultError("invalid_request_digest", "Operation result requestDigest must be canonical.");
|
|
310
|
+
}
|
|
311
|
+
validateHandle(value.handle, value.operationId, expectedHandleRequestDigest ?? value.requestDigest);
|
|
312
|
+
return value;
|
|
313
|
+
}
|
|
314
|
+
function validateHandle(value, operationId, requestDigest) {
|
|
315
|
+
if (!isRecord(value))
|
|
316
|
+
throw new OperationWireResultError("invalid_handle", "Operation result must carry a handle.");
|
|
317
|
+
assertExactKeys(value, ["schemaVersion", "operationId", "requestDigest", "surface", "revision", "phase", "mutationBoundary", "targetBindingDigest"]);
|
|
318
|
+
if (value.schemaVersion !== "chatgpt.browser_control.operation_handle.v1")
|
|
319
|
+
throw new OperationWireResultError("unsupported_handle", "Operation result handle schemaVersion is unsupported.");
|
|
320
|
+
if (value.operationId !== operationId || value.requestDigest !== requestDigest)
|
|
321
|
+
throw new OperationWireResultError("operation_identity_mismatch", "Operation result handle identity does not match the envelope.");
|
|
322
|
+
if (value.surface !== "chat" && value.surface !== "work")
|
|
323
|
+
throw new OperationWireResultError("invalid_handle", "Operation result handle surface is invalid.");
|
|
324
|
+
if (!isSafeInteger(value.revision) || value.revision < 1)
|
|
325
|
+
throw new OperationWireResultError("invalid_handle", "Operation result handle revision is invalid.");
|
|
326
|
+
if (!PHASES.has(value.phase))
|
|
327
|
+
throw new OperationWireResultError("invalid_handle", "Operation result handle phase is invalid.");
|
|
328
|
+
if (!BOUNDARIES.has(value.mutationBoundary))
|
|
329
|
+
throw new OperationWireResultError("invalid_handle", "Operation result handle mutation boundary is invalid.");
|
|
330
|
+
if (value.targetBindingDigest !== undefined && !isDigest(value.targetBindingDigest))
|
|
331
|
+
throw new OperationWireResultError("invalid_handle", "Operation result target binding digest is invalid.");
|
|
332
|
+
}
|
|
333
|
+
function validateReceipt(value, operationId, requestDigest) {
|
|
334
|
+
if (!isRecord(value))
|
|
335
|
+
throw new OperationWireResultError("invalid_receipt", "Operation result receipt must be an object.");
|
|
336
|
+
assertExactKeys(value, [
|
|
337
|
+
"schemaVersion", "operationId", "requestDigest", "targetBindingDigest", "userTurnId", "userTurnEvidenceDigest",
|
|
338
|
+
"assistantTurnId", "ownershipEvidenceDigest", "responseDigest", "responseBytes", "responseFormat", "finishReason", "contentAvailable", "artifacts", "completedAt"
|
|
339
|
+
]);
|
|
340
|
+
if (value.schemaVersion !== "chatgpt.browser_control.operation_receipt.v1")
|
|
341
|
+
throw new OperationWireResultError("unsupported_receipt", "Operation result receipt schemaVersion is unsupported.");
|
|
342
|
+
if (value.operationId !== operationId || value.requestDigest !== requestDigest)
|
|
343
|
+
throw new OperationWireResultError("receipt_identity_mismatch", "Receipt identity does not match the result.");
|
|
344
|
+
assertDigest(value.targetBindingDigest, "targetBindingDigest");
|
|
345
|
+
assertOpaqueId(value.userTurnId, "userTurnId");
|
|
346
|
+
assertDigest(value.userTurnEvidenceDigest, "userTurnEvidenceDigest");
|
|
347
|
+
assertOpaqueId(value.assistantTurnId, "assistantTurnId");
|
|
348
|
+
assertDigest(value.ownershipEvidenceDigest, "ownershipEvidenceDigest");
|
|
349
|
+
if (value.responseFormat !== undefined && value.responseFormat !== "markdown" && value.responseFormat !== "text")
|
|
350
|
+
throw new OperationWireResultError("invalid_receipt", "Receipt responseFormat is invalid.");
|
|
351
|
+
if (value.responseDigest !== undefined)
|
|
352
|
+
assertDigest(value.responseDigest, "responseDigest");
|
|
353
|
+
if (value.responseBytes !== undefined && (!isSafeInteger(value.responseBytes) || value.responseBytes < 0 || value.responseBytes > MAX_WIRE_RESPONSE_BYTES))
|
|
354
|
+
throw new OperationWireResultError("invalid_receipt", "Receipt responseBytes exceeds the bounded wire limit.");
|
|
355
|
+
if ((value.responseDigest === undefined) !== (value.responseBytes === undefined))
|
|
356
|
+
throw new OperationWireResultError("invalid_receipt", "Receipt response digest and byte count must be paired.");
|
|
357
|
+
if (value.contentAvailable !== true && value.contentAvailable !== false)
|
|
358
|
+
throw new OperationWireResultError("invalid_receipt", "Receipt contentAvailable must be boolean.");
|
|
359
|
+
if (value.contentAvailable && (value.responseDigest === undefined || value.responseBytes === undefined))
|
|
360
|
+
throw new OperationWireResultError("invalid_receipt", "Available receipt content requires a digest and byte count.");
|
|
361
|
+
if (typeof value.finishReason !== "string" || !FINISH_REASON_PATTERN.test(value.finishReason))
|
|
362
|
+
throw new OperationWireResultError("invalid_receipt", "Receipt finishReason is invalid.");
|
|
363
|
+
if (!Array.isArray(value.artifacts) || value.artifacts.length > MAX_WIRE_ARTIFACTS)
|
|
364
|
+
throw new OperationWireResultError("invalid_receipt", "Receipt artifacts exceed the bounded wire limit.");
|
|
365
|
+
const artifactKeys = new Set();
|
|
366
|
+
const artifactOrdinals = new Set();
|
|
367
|
+
for (const artifact of value.artifacts) {
|
|
368
|
+
validateArtifact(artifact, operationId, value.assistantTurnId);
|
|
369
|
+
if (artifactKeys.has(artifact.artifactKey) || artifactOrdinals.has(artifact.ordinal))
|
|
370
|
+
throw new OperationWireResultError("invalid_receipt", "Receipt artifact keys and ordinals must be unique.");
|
|
371
|
+
artifactKeys.add(artifact.artifactKey);
|
|
372
|
+
artifactOrdinals.add(artifact.ordinal);
|
|
373
|
+
}
|
|
374
|
+
assertInstant(value.completedAt, "completedAt");
|
|
375
|
+
}
|
|
376
|
+
function validateArtifact(value, operationId, assistantTurnId) {
|
|
377
|
+
if (!isRecord(value))
|
|
378
|
+
throw new OperationWireResultError("invalid_artifact", "Receipt artifact must be an object.");
|
|
379
|
+
assertExactKeys(value, ["schemaVersion", "operationId", "artifactKey", "assistantTurnId", "sourceIdentityDigest", "kind", "ordinal", "outputKey", "mimeType", "bytes", "sha256", "status", "blockerCode"]);
|
|
380
|
+
if (value.schemaVersion !== "chatgpt.browser_control.operation_artifact_receipt.v1" || value.operationId !== operationId || value.assistantTurnId !== assistantTurnId)
|
|
381
|
+
throw new OperationWireResultError("artifact_identity_mismatch", "Artifact receipt identity does not match the terminal receipt.");
|
|
382
|
+
assertOpaqueKey(value.artifactKey, "artifactKey");
|
|
383
|
+
assertDigest(value.sourceIdentityDigest, "sourceIdentityDigest");
|
|
384
|
+
if (value.kind !== "file" && value.kind !== "image" && value.kind !== "other")
|
|
385
|
+
throw new OperationWireResultError("invalid_artifact", "Artifact kind is invalid.");
|
|
386
|
+
if (!isSafeInteger(value.ordinal) || value.ordinal < 0 || value.ordinal >= MAX_WIRE_ARTIFACTS)
|
|
387
|
+
throw new OperationWireResultError("invalid_artifact", "Artifact ordinal is invalid.");
|
|
388
|
+
if (value.outputKey !== undefined)
|
|
389
|
+
assertOutputKey(value.outputKey);
|
|
390
|
+
if (value.mimeType !== undefined && (typeof value.mimeType !== "string" || !MIME_PATTERN.test(value.mimeType)))
|
|
391
|
+
throw new OperationWireResultError("invalid_artifact", "Artifact MIME type is invalid.");
|
|
392
|
+
if (value.bytes !== undefined && (!isSafeInteger(value.bytes) || value.bytes < 0 || value.bytes > MAX_WIRE_RESPONSE_BYTES))
|
|
393
|
+
throw new OperationWireResultError("invalid_artifact", "Artifact size exceeds the bounded wire limit.");
|
|
394
|
+
if (value.sha256 !== undefined && (typeof value.sha256 !== "string" || !SHA256_PATTERN.test(value.sha256)))
|
|
395
|
+
throw new OperationWireResultError("invalid_artifact", "Artifact SHA-256 is invalid.");
|
|
396
|
+
if (value.status !== "available" && value.status !== "transferred" && value.status !== "partial" && value.status !== "blocked")
|
|
397
|
+
throw new OperationWireResultError("invalid_artifact", "Artifact status is invalid.");
|
|
398
|
+
if (value.status === "transferred" && (value.outputKey === undefined || value.bytes === undefined || value.sha256 === undefined))
|
|
399
|
+
throw new OperationWireResultError("invalid_artifact", "Transferred artifact requires output identity, size, and SHA-256.");
|
|
400
|
+
if ((value.status === "partial" || value.status === "blocked") && (typeof value.blockerCode !== "string" || !CODE_PATTERN.test(value.blockerCode)))
|
|
401
|
+
throw new OperationWireResultError("invalid_artifact", "Blocked artifact requires a blocker code.");
|
|
402
|
+
if ((value.status === "available" || value.status === "transferred") && value.blockerCode !== undefined)
|
|
403
|
+
throw new OperationWireResultError("invalid_artifact", "Available artifact must not carry a blocker code.");
|
|
404
|
+
}
|
|
405
|
+
function validateBlocker(value, operationId, requestDigest) {
|
|
406
|
+
if (!isRecord(value))
|
|
407
|
+
throw new OperationWireResultError("invalid_blocker", "Operation result blocker must be an object.");
|
|
408
|
+
assertExactKeys(value, ["schemaVersion", "code", "recoverable", "operationId", "requestDigest", "phase", "mutationBoundary", "message"]);
|
|
409
|
+
if (value.schemaVersion !== "chatgpt.browser_control.operation_blocker.v1")
|
|
410
|
+
throw new OperationWireResultError("unsupported_blocker", "Operation result blocker schemaVersion is unsupported.");
|
|
411
|
+
if (value.operationId !== operationId || value.requestDigest !== requestDigest)
|
|
412
|
+
throw new OperationWireResultError("blocker_identity_mismatch", "Blocker identity does not match the result.");
|
|
413
|
+
if (typeof value.code !== "string" || !WIRE_BLOCKER_CODES.has(value.code))
|
|
414
|
+
throw new OperationWireResultError("invalid_blocker", "Blocker code is not in the versioned taxonomy.");
|
|
415
|
+
if (typeof value.recoverable !== "boolean")
|
|
416
|
+
throw new OperationWireResultError("invalid_blocker", "Blocker recoverable must be boolean.");
|
|
417
|
+
if (!PHASES.has(value.phase) || !BOUNDARIES.has(value.mutationBoundary))
|
|
418
|
+
throw new OperationWireResultError("invalid_blocker", "Blocker phase or mutation boundary is invalid.");
|
|
419
|
+
if (typeof value.message !== "string" || !SAFE_MESSAGE_PATTERN.test(value.message) || value.message.length > MAX_WIRE_BLOCKER_MESSAGE_LENGTH)
|
|
420
|
+
throw new OperationWireResultError("invalid_blocker", "Blocker message is not a safe bounded diagnostic.");
|
|
421
|
+
}
|
|
422
|
+
function validateControlReceipt(value, result) {
|
|
423
|
+
if (!isRecord(value))
|
|
424
|
+
throw new OperationWireResultError("invalid_control_receipt", "Control result receipt must be an object.");
|
|
425
|
+
assertExactKeys(value, ["schemaVersion", "controlActionId", "parentOperationId", "parentRequestDigest", "parentTargetBindingDigest", "expectedAssistantTurnId", "requestDigest", "action", "outcome", "evidenceDigest", "blockerCode", "observedAt"]);
|
|
426
|
+
if (value.schemaVersion !== "chatgpt.browser_control.operation_control_receipt.v1")
|
|
427
|
+
throw new OperationWireResultError("unsupported_control_receipt", "Control receipt schemaVersion is unsupported.");
|
|
428
|
+
if (value.controlActionId !== result.controlActionId || value.parentOperationId !== result.operationId || value.parentRequestDigest !== result.parentRequestDigest || value.parentTargetBindingDigest !== result.parentTargetBindingDigest || value.requestDigest !== result.requestDigest || value.action !== result.action || value.expectedAssistantTurnId !== result.expectedAssistantTurnId)
|
|
429
|
+
throw new OperationWireResultError("control_receipt_identity_mismatch", "Control receipt identity does not match the result.");
|
|
430
|
+
if (value.outcome !== "satisfied" && value.outcome !== "not_satisfied" && value.outcome !== "uncertain")
|
|
431
|
+
throw new OperationWireResultError("invalid_control_receipt", "Control receipt outcome is invalid.");
|
|
432
|
+
if (value.evidenceDigest !== undefined)
|
|
433
|
+
assertDigest(value.evidenceDigest, "evidenceDigest");
|
|
434
|
+
if (value.outcome === "satisfied" && value.evidenceDigest === undefined)
|
|
435
|
+
throw new OperationWireResultError("invalid_control_receipt", "Satisfied control receipt requires evidence.");
|
|
436
|
+
if (value.blockerCode !== undefined && (typeof value.blockerCode !== "string" || !CODE_PATTERN.test(value.blockerCode)))
|
|
437
|
+
throw new OperationWireResultError("invalid_control_receipt", "Control receipt blocker code is invalid.");
|
|
438
|
+
if (value.outcome === "satisfied" && value.blockerCode !== undefined)
|
|
439
|
+
throw new OperationWireResultError("invalid_control_receipt", "Satisfied control receipt must not carry a blocker code.");
|
|
440
|
+
if ((value.outcome === "not_satisfied" || value.outcome === "uncertain") && value.blockerCode === undefined)
|
|
441
|
+
throw new OperationWireResultError("invalid_control_receipt", "Non-satisfied control receipt requires a blocker code.");
|
|
442
|
+
assertInstant(value.observedAt, "observedAt");
|
|
443
|
+
}
|
|
444
|
+
function validateLiveResponse(value) {
|
|
445
|
+
if (!isRecord(value))
|
|
446
|
+
throw new OperationWireResultError("invalid_live_response", "Live response must be an object.");
|
|
447
|
+
assertExactKeys(value, ["schemaVersion", "durability", "durable", "content", "responseFormat", "bytes", "chars"]);
|
|
448
|
+
if (value.schemaVersion !== OPERATION_LIVE_RESPONSE_SCHEMA_VERSION || value.durability !== "ephemeral" || value.durable !== false)
|
|
449
|
+
throw new OperationWireResultError("invalid_live_response", "Live response must be explicitly ephemeral and non-durable.");
|
|
450
|
+
if (value.responseFormat !== undefined && value.responseFormat !== "markdown" && value.responseFormat !== "text")
|
|
451
|
+
throw new OperationWireResultError("invalid_live_response", "Live response responseFormat is invalid.");
|
|
452
|
+
if (typeof value.content !== "string")
|
|
453
|
+
throw new OperationWireResultError("invalid_live_response", "Live response content must be a string.");
|
|
454
|
+
assertUnicodeScalarString(value.content);
|
|
455
|
+
if (Buffer.byteLength(value.content, "utf8") !== value.bytes || value.content.length !== value.chars || !isSafeInteger(value.bytes) || value.bytes < 0 || value.bytes > MAX_WIRE_RESPONSE_BYTES || !isSafeInteger(value.chars) || value.chars < 0 || value.chars > MAX_WIRE_RESPONSE_CHARS)
|
|
456
|
+
throw new OperationWireResultError("invalid_live_response", "Live response content size metadata is invalid.");
|
|
457
|
+
return value;
|
|
458
|
+
}
|
|
459
|
+
function validateLiveResponseReceiptCoherence(liveResponse, receipt) {
|
|
460
|
+
if (receipt.contentAvailable !== true
|
|
461
|
+
|| receipt.responseBytes !== liveResponse.bytes
|
|
462
|
+
|| receipt.responseFormat !== liveResponse.responseFormat) {
|
|
463
|
+
throw new OperationWireResultError("live_response_receipt_mismatch", "Live response metadata does not match the durable receipt.");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function validateState(value, operationId, requestDigest) {
|
|
467
|
+
if (!isRecord(value))
|
|
468
|
+
throw new OperationWireResultError("invalid_state", "Inspect result state must be an object.");
|
|
469
|
+
assertExactKeys(value, ["schemaVersion", "operationId", "requestDigest", "surface", "phase", "mutationBoundary", "revision", "createdAt", "updatedAt", "capturePolicy", "responseFormat", "target", "actions", "ownershipBaseline", "ownershipBaselines", "artifactTransfers", "submissionWitnesses", "submissionWitness", "lastBlocker", "receipt"]);
|
|
470
|
+
if (value.schemaVersion !== "chatgpt.browser_control.operation.v1")
|
|
471
|
+
throw new OperationWireResultError("unsupported_state", "Inspect state schemaVersion is unsupported.");
|
|
472
|
+
if (value.operationId !== operationId || value.requestDigest !== requestDigest)
|
|
473
|
+
throw new OperationWireResultError("state_identity_mismatch", "Inspect state identity does not match the result.");
|
|
474
|
+
assertDigest(value.requestDigest, "state.requestDigest");
|
|
475
|
+
if (value.surface !== "chat" && value.surface !== "work")
|
|
476
|
+
throw new OperationWireResultError("invalid_state", "Inspect state surface is invalid.");
|
|
477
|
+
if (!PHASES.has(value.phase) || !BOUNDARIES.has(value.mutationBoundary))
|
|
478
|
+
throw new OperationWireResultError("invalid_state", "Inspect state phase or mutation boundary is invalid.");
|
|
479
|
+
if (!isSafeInteger(value.revision) || value.revision < 1)
|
|
480
|
+
throw new OperationWireResultError("invalid_state", "Inspect state revision is invalid.");
|
|
481
|
+
assertInstant(value.createdAt, "state.createdAt");
|
|
482
|
+
assertInstant(value.updatedAt, "state.updatedAt");
|
|
483
|
+
if (value.responseFormat !== undefined && value.responseFormat !== "markdown" && value.responseFormat !== "text")
|
|
484
|
+
throw new OperationWireResultError("invalid_state", "Inspect state responseFormat is invalid.");
|
|
485
|
+
if (value.capturePolicy !== undefined) {
|
|
486
|
+
try {
|
|
487
|
+
assertDurableCapturePolicyShape(value.capturePolicy);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
throw new OperationWireResultError("invalid_state", "Inspect state capture policy is invalid.");
|
|
491
|
+
}
|
|
492
|
+
if (value.responseFormat !== undefined && value.responseFormat !== value.capturePolicy.responseFormat) {
|
|
493
|
+
throw new OperationWireResultError("invalid_state", "Inspect state capture policy format is inconsistent.");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (!isRecord(value.actions))
|
|
497
|
+
throw new OperationWireResultError("invalid_state", "Inspect state actions must be an object.");
|
|
498
|
+
for (const [actionId, action] of Object.entries(value.actions))
|
|
499
|
+
validateStateAction(actionId, action, operationId, requestDigest);
|
|
500
|
+
if (value.target !== undefined)
|
|
501
|
+
validateStateTarget(value.target);
|
|
502
|
+
if (value.lastBlocker !== undefined)
|
|
503
|
+
validateStateBlocker(value.lastBlocker);
|
|
504
|
+
if (value.receipt !== undefined)
|
|
505
|
+
validateReceipt(value.receipt, operationId, requestDigest);
|
|
506
|
+
if (value.phase === "completed" && value.receipt === undefined)
|
|
507
|
+
throw new OperationWireResultError("invalid_state", "Completed inspect state requires a receipt.");
|
|
508
|
+
if (value.phase !== "completed" && value.receipt !== undefined)
|
|
509
|
+
throw new OperationWireResultError("invalid_state", "Only completed inspect state may contain a receipt.");
|
|
510
|
+
try {
|
|
511
|
+
assertOperationStateShape(value);
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
// The durable validator may mention persisted identifiers while explaining
|
|
515
|
+
// a failed invariant. Keep wire diagnostics fixed and privacy-safe.
|
|
516
|
+
throw new OperationWireResultError("invalid_state", "Inspect state failed durable invariant validation.");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function validateStateTarget(value) {
|
|
520
|
+
if (!isRecord(value))
|
|
521
|
+
throw new OperationWireResultError("invalid_state_target", "Inspect target binding must be an object.");
|
|
522
|
+
assertExactKeys(value, ["providerId", "browserId", "tabId", "coordinationScope", "tabClaimEvidenceDigest", "canonicalThreadUrl", "conversationId", "userTurnBaselineDigest", "assistantTurnBaselineDigest", "configurationReceiptDigest", "evidenceProfile", "targetLifecycle", "newTargetAnchorDigest", "blankTaskEvidenceDigest", "targetEstablishment"]);
|
|
523
|
+
for (const key of ["providerId", "browserId", "tabId"])
|
|
524
|
+
assertOpaqueId(value[key], `target.${key}`);
|
|
525
|
+
if (value.coordinationScope !== "process" && value.coordinationScope !== "provider")
|
|
526
|
+
throw new OperationWireResultError("invalid_state_target", "Target coordination scope is invalid.");
|
|
527
|
+
if (value.coordinationScope === "provider" && value.tabClaimEvidenceDigest === undefined)
|
|
528
|
+
throw new OperationWireResultError("invalid_state_target", "Provider-coordinated target requires a tab claim digest.");
|
|
529
|
+
for (const key of ["tabClaimEvidenceDigest", "userTurnBaselineDigest", "assistantTurnBaselineDigest", "configurationReceiptDigest"])
|
|
530
|
+
if (value[key] !== undefined)
|
|
531
|
+
assertDigest(value[key], `target.${key}`);
|
|
532
|
+
if (value.canonicalThreadUrl !== undefined && (typeof value.canonicalThreadUrl !== "string" || !/^https:\/\/[^?#]+$/u.test(value.canonicalThreadUrl)))
|
|
533
|
+
throw new OperationWireResultError("invalid_state_target", "Target URL is not a sanitized HTTPS URL.");
|
|
534
|
+
if (value.conversationId !== undefined)
|
|
535
|
+
assertOpaqueId(value.conversationId, "target.conversationId");
|
|
536
|
+
if (!isRecord(value.evidenceProfile))
|
|
537
|
+
throw new OperationWireResultError("invalid_state_target", "Target evidence profile is invalid.");
|
|
538
|
+
assertExactKeys(value.evidenceProfile, ["providerIdentity", "stableTabId", "stableConversationId", "stableUserTurnId", "authoritativeTabClaim", "replacementTabRecovery"]);
|
|
539
|
+
for (const key of ["providerIdentity", "stableTabId", "stableConversationId", "stableUserTurnId", "authoritativeTabClaim"])
|
|
540
|
+
if (value.evidenceProfile[key] !== "required" && value.evidenceProfile[key] !== "unavailable")
|
|
541
|
+
throw new OperationWireResultError("invalid_state_target", "Target evidence profile value is invalid.");
|
|
542
|
+
if (typeof value.evidenceProfile.replacementTabRecovery !== "boolean")
|
|
543
|
+
throw new OperationWireResultError("invalid_state_target", "Target evidence recovery flag is invalid.");
|
|
544
|
+
const lifecycle = value.targetLifecycle ?? "fixed";
|
|
545
|
+
if (lifecycle !== "fixed" && lifecycle !== "new_pending" && lifecycle !== "new_established")
|
|
546
|
+
throw new OperationWireResultError("invalid_state_target", "Target lifecycle is invalid.");
|
|
547
|
+
for (const key of ["newTargetAnchorDigest", "blankTaskEvidenceDigest"])
|
|
548
|
+
if (value[key] !== undefined)
|
|
549
|
+
assertDigest(value[key], `target.${key}`);
|
|
550
|
+
if (lifecycle === "fixed" && (value.newTargetAnchorDigest !== undefined || value.blankTaskEvidenceDigest !== undefined || value.targetEstablishment !== undefined)) {
|
|
551
|
+
throw new OperationWireResultError("invalid_state_target", "Fixed target contains new-target identity fields.");
|
|
552
|
+
}
|
|
553
|
+
if (lifecycle === "new_pending" && (value.newTargetAnchorDigest === undefined
|
|
554
|
+
|| value.blankTaskEvidenceDigest === undefined
|
|
555
|
+
|| value.conversationId !== undefined
|
|
556
|
+
|| value.canonicalThreadUrl !== undefined
|
|
557
|
+
|| value.targetEstablishment !== undefined
|
|
558
|
+
|| value.evidenceProfile.stableConversationId !== "unavailable"
|
|
559
|
+
|| value.evidenceProfile.stableUserTurnId !== "unavailable"))
|
|
560
|
+
throw new OperationWireResultError("invalid_state_target", "Pending new target identity is invalid.");
|
|
561
|
+
if (value.targetEstablishment !== undefined) {
|
|
562
|
+
try {
|
|
563
|
+
// The submission adapter's establishment observation is stricter than
|
|
564
|
+
// the read-compatible target projection: old authenticated state may
|
|
565
|
+
// legitimately predate post-Send delta capture. Keep the state wire
|
|
566
|
+
// contract aligned with `targetEstablishmentRead` in the JSON schema and
|
|
567
|
+
// the Python `OperationTargetEstablishmentRead` model while still
|
|
568
|
+
// validating every identity/evidence field that is present.
|
|
569
|
+
validateStateTargetEstablishment(value.targetEstablishment);
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
throw new OperationWireResultError("invalid_state_target", "Target establishment evidence is invalid.");
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
function validateStateTargetEstablishment(value) {
|
|
577
|
+
if (!isRecord(value))
|
|
578
|
+
throw new OperationWireResultError("invalid_state_target", "Target establishment evidence must be an object.");
|
|
579
|
+
assertExactKeys(value, [
|
|
580
|
+
"targetBindingDigest",
|
|
581
|
+
"anchorDigest",
|
|
582
|
+
"causalSendActionId",
|
|
583
|
+
"conversationId",
|
|
584
|
+
"canonicalThreadUrl",
|
|
585
|
+
"userTurnId",
|
|
586
|
+
"userTurnEvidenceDigest",
|
|
587
|
+
"postSendDeltaDigest",
|
|
588
|
+
"evidenceDigest",
|
|
589
|
+
"observedAt"
|
|
590
|
+
]);
|
|
591
|
+
assertDigest(value.targetBindingDigest, "targetEstablishment.targetBindingDigest");
|
|
592
|
+
assertDigest(value.anchorDigest, "targetEstablishment.anchorDigest");
|
|
593
|
+
if (typeof value.causalSendActionId !== "string" || !UUID_PATTERN.test(value.causalSendActionId)) {
|
|
594
|
+
throw new OperationWireResultError("invalid_state_target", "Target establishment causal Send action is invalid.");
|
|
595
|
+
}
|
|
596
|
+
assertOpaqueId(value.conversationId, "targetEstablishment.conversationId");
|
|
597
|
+
if (typeof value.canonicalThreadUrl !== "string" || !/^https:\/\/[^?#]+$/u.test(value.canonicalThreadUrl)) {
|
|
598
|
+
throw new OperationWireResultError("invalid_state_target", "Target establishment URL is invalid.");
|
|
599
|
+
}
|
|
600
|
+
assertOpaqueId(value.userTurnId, "targetEstablishment.userTurnId");
|
|
601
|
+
assertDigest(value.userTurnEvidenceDigest, "targetEstablishment.userTurnEvidenceDigest");
|
|
602
|
+
if (value.postSendDeltaDigest !== undefined)
|
|
603
|
+
assertDigest(value.postSendDeltaDigest, "targetEstablishment.postSendDeltaDigest");
|
|
604
|
+
assertDigest(value.evidenceDigest, "targetEstablishment.evidenceDigest");
|
|
605
|
+
assertInstant(value.observedAt, "targetEstablishment.observedAt");
|
|
606
|
+
}
|
|
607
|
+
function validateStateAction(actionId, value, operationId, requestDigest) {
|
|
608
|
+
if (!UUID_PATTERN.test(actionId) || !isRecord(value))
|
|
609
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect state action identity is invalid.");
|
|
610
|
+
assertExactKeys(value, ["actionId", "kind", "repeatPolicy", "requestDigest", "parentActionId", "targetDigest", "intentRevision", "intentAt", "outcome", "receiptRevision", "receiptAt", "evidenceDigest", "blockerCode"]);
|
|
611
|
+
if (value.actionId !== actionId)
|
|
612
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect action map key does not match actionId.");
|
|
613
|
+
if (typeof value.kind !== "string" || !ACTION_KINDS.has(value.kind))
|
|
614
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect action kind is invalid.");
|
|
615
|
+
if (typeof value.repeatPolicy !== "string" || !ACTION_POLICIES.has(value.repeatPolicy) || value.repeatPolicy !== requiredActionPolicy(value.kind))
|
|
616
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect action repeat policy is invalid.");
|
|
617
|
+
assertDigest(value.requestDigest, "action.requestDigest");
|
|
618
|
+
if (value.parentActionId !== undefined && !UUID_PATTERN.test(String(value.parentActionId)))
|
|
619
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect action parent ID is invalid.");
|
|
620
|
+
if (value.targetDigest !== undefined)
|
|
621
|
+
assertDigest(value.targetDigest, "action.targetDigest");
|
|
622
|
+
if (!isSafeInteger(value.intentRevision) || value.intentRevision < 1)
|
|
623
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect action intent revision is invalid.");
|
|
624
|
+
assertInstant(value.intentAt, "action.intentAt");
|
|
625
|
+
if (value.outcome !== undefined && value.outcome !== "satisfied" && value.outcome !== "not_satisfied" && value.outcome !== "uncertain")
|
|
626
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect action outcome is invalid.");
|
|
627
|
+
if (value.outcome === undefined && (value.receiptRevision !== undefined || value.receiptAt !== undefined || value.evidenceDigest !== undefined || value.blockerCode !== undefined))
|
|
628
|
+
throw new OperationWireResultError("invalid_state_action", "Unsettled inspect action contains receipt fields.");
|
|
629
|
+
if (value.outcome !== undefined && (value.receiptRevision === undefined || value.receiptAt === undefined))
|
|
630
|
+
throw new OperationWireResultError("invalid_state_action", "Settled inspect action requires a receipt revision and time.");
|
|
631
|
+
if (value.receiptRevision !== undefined && (!isSafeInteger(value.receiptRevision) || value.receiptRevision < 1))
|
|
632
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect action receipt revision is invalid.");
|
|
633
|
+
if (value.receiptAt !== undefined)
|
|
634
|
+
assertInstant(value.receiptAt, "action.receiptAt");
|
|
635
|
+
if (value.evidenceDigest !== undefined)
|
|
636
|
+
assertDigest(value.evidenceDigest, "action.evidenceDigest");
|
|
637
|
+
if (value.blockerCode !== undefined && (typeof value.blockerCode !== "string" || !CODE_PATTERN.test(value.blockerCode)))
|
|
638
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect action blocker code is invalid.");
|
|
639
|
+
if (value.blockerCode !== undefined && value.outcome !== "not_satisfied" && value.outcome !== "uncertain") {
|
|
640
|
+
throw new OperationWireResultError("invalid_state_action", "Inspect action blocker code requires a non-satisfied or uncertain outcome.");
|
|
641
|
+
}
|
|
642
|
+
if (value.kind !== "stop" && value.kind !== "work_steer" && value.requestDigest !== requestDigest) {
|
|
643
|
+
throw new OperationWireResultError("state_identity_mismatch", "Inspect action request identity does not match the operation.");
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function validateBlockerHandleCoherence(blocker, handle) {
|
|
647
|
+
if (blocker.phase !== handle.phase || blocker.mutationBoundary !== handle.mutationBoundary) {
|
|
648
|
+
throw new OperationWireResultError("blocker_handle_mismatch", "Operation blocker does not match the fresh handle.");
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function validateStateBlocker(value) {
|
|
652
|
+
if (!isRecord(value))
|
|
653
|
+
throw new OperationWireResultError("invalid_state_blocker", "Inspect state blocker must be an object.");
|
|
654
|
+
assertExactKeys(value, ["code", "messageDigest", "recoverable", "observedAt"]);
|
|
655
|
+
if (typeof value.code !== "string" || !CODE_PATTERN.test(value.code))
|
|
656
|
+
throw new OperationWireResultError("invalid_state_blocker", "Inspect state blocker code is invalid.");
|
|
657
|
+
assertDigest(value.messageDigest, "lastBlocker.messageDigest");
|
|
658
|
+
if (typeof value.recoverable !== "boolean")
|
|
659
|
+
throw new OperationWireResultError("invalid_state_blocker", "Inspect state blocker recoverable must be boolean.");
|
|
660
|
+
assertInstant(value.observedAt, "lastBlocker.observedAt");
|
|
661
|
+
}
|
|
662
|
+
function blockerFromCollector(blocker, handle) {
|
|
663
|
+
return blockerFromInternal(blocker.code, handle, blocker.mutationBoundary, false, blocker.evidenceDigest);
|
|
664
|
+
}
|
|
665
|
+
function assertSubmissionIdentity(result, handle) {
|
|
666
|
+
if (result.operationId !== handle.operationId || result.requestDigest !== handle.requestDigest) {
|
|
667
|
+
throw new OperationWireResultError("operation_identity_mismatch", "Submission identity does not match the fresh handle.");
|
|
668
|
+
}
|
|
669
|
+
if (result.targetBindingDigest !== handle.targetBindingDigest) {
|
|
670
|
+
throw new OperationWireResultError("target_binding_mismatch", "Submission target identity does not match the fresh handle.");
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function assertCollectorIdentity(result, handle) {
|
|
674
|
+
if (result.operationId !== handle.operationId || result.requestDigest !== handle.requestDigest) {
|
|
675
|
+
throw new OperationWireResultError("operation_identity_mismatch", "Collector identity does not match the fresh handle.");
|
|
676
|
+
}
|
|
677
|
+
if (result.targetBindingDigest !== undefined && result.targetBindingDigest !== handle.targetBindingDigest) {
|
|
678
|
+
throw new OperationWireResultError("target_binding_mismatch", "Collector target identity does not match the fresh handle.");
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function blockerFromInternal(code, handle, mutationBoundary, recoverable, evidenceDigest, requestDigest = handle.requestDigest) {
|
|
682
|
+
const normalized = normalizeBlockerCode(code);
|
|
683
|
+
return {
|
|
684
|
+
schemaVersion: "chatgpt.browser_control.operation_blocker.v1",
|
|
685
|
+
code: normalized,
|
|
686
|
+
recoverable,
|
|
687
|
+
operationId: handle.operationId,
|
|
688
|
+
requestDigest,
|
|
689
|
+
phase: handle.phase,
|
|
690
|
+
mutationBoundary,
|
|
691
|
+
message: blockerMessage(normalized, evidenceDigest)
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function normalizeBlockerCode(code) {
|
|
695
|
+
if (WIRE_BLOCKER_CODES.has(code))
|
|
696
|
+
return code;
|
|
697
|
+
if (code === "operation_not_collectable" || code === "operation_receipt_expired")
|
|
698
|
+
return "operation_state_corrupt";
|
|
699
|
+
if (code === "operation_receipt_persistence_failed" || code === "operation_receipt_indeterminate" || code === "operation_progress_persistence_failed" || code === "journal_unavailable" || code === "port_protocol_violation")
|
|
700
|
+
return "backend_unavailable";
|
|
701
|
+
if (code === "composer_drift")
|
|
702
|
+
return "configuration_drift";
|
|
703
|
+
if (code === "already_completed")
|
|
704
|
+
return "operation_state_corrupt";
|
|
705
|
+
return "backend_unavailable";
|
|
706
|
+
}
|
|
707
|
+
function blockerMessage(code, evidenceDigest) {
|
|
708
|
+
// Deliberately fixed text: adapter/error messages may contain prompt, path,
|
|
709
|
+
// URL, account, or provider-private content and never cross this boundary.
|
|
710
|
+
return evidenceDigest === undefined ? `Operation blocked: ${code.replaceAll("_", " ")}.` : `Operation blocked: ${code.replaceAll("_", " ")} (evidence recorded).`;
|
|
711
|
+
}
|
|
712
|
+
function assertJsonSafe(value, depth = 0, nodes = { value: 0 }) {
|
|
713
|
+
if (++nodes.value > 10_000 || depth > 16)
|
|
714
|
+
throw new OperationWireResultError("wire_result_too_large", "Operation result exceeds the bounded JSON envelope.");
|
|
715
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
716
|
+
return;
|
|
717
|
+
if (typeof value === "number") {
|
|
718
|
+
if (!Number.isFinite(value))
|
|
719
|
+
throw new OperationWireResultError("invalid_json_value", "Operation result contains a non-finite number.");
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (typeof value !== "object")
|
|
723
|
+
throw new OperationWireResultError("invalid_json_value", "Operation result contains a non-JSON value.");
|
|
724
|
+
if (Array.isArray(value)) {
|
|
725
|
+
for (const child of value)
|
|
726
|
+
assertJsonSafe(child, depth + 1, nodes);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (!isRecord(value))
|
|
730
|
+
throw new OperationWireResultError("invalid_json_value", "Operation result must contain plain objects only.");
|
|
731
|
+
for (const child of Object.values(value))
|
|
732
|
+
assertJsonSafe(child, depth + 1, nodes);
|
|
733
|
+
}
|
|
734
|
+
function assertExactKeys(value, keys) {
|
|
735
|
+
const expected = new Set(keys);
|
|
736
|
+
for (const key of Object.keys(value))
|
|
737
|
+
if (!expected.has(key))
|
|
738
|
+
throw new OperationWireResultError("unexpected_wire_field", "Unexpected wire field.");
|
|
739
|
+
}
|
|
740
|
+
function assertDigest(value, label) {
|
|
741
|
+
if (typeof value !== "string" || !DIGEST_PATTERN.test(value))
|
|
742
|
+
throw new OperationWireResultError("invalid_digest", `${label} must be a canonical digest.`);
|
|
743
|
+
}
|
|
744
|
+
function assertOpaqueId(value, label) {
|
|
745
|
+
if (typeof value !== "string" || !OPAQUE_ID_PATTERN.test(value) || value.trim().length === 0)
|
|
746
|
+
throw new OperationWireResultError("invalid_id", `${label} must be a bounded opaque identifier.`);
|
|
747
|
+
}
|
|
748
|
+
function assertOpaqueKey(value, label) {
|
|
749
|
+
if (typeof value !== "string" || !OPAQUE_KEY_PATTERN.test(value))
|
|
750
|
+
throw new OperationWireResultError("invalid_key", `${label} must be a bounded opaque key.`);
|
|
751
|
+
}
|
|
752
|
+
function assertOutputKey(value) {
|
|
753
|
+
if (typeof value !== "string" || !OUTPUT_KEY_PATTERN.test(value))
|
|
754
|
+
throw new OperationWireResultError("invalid_output_key", "Artifact outputKey is invalid.");
|
|
755
|
+
}
|
|
756
|
+
function assertInstant(value, label) {
|
|
757
|
+
if (typeof value !== "string" || !INSTANT_PATTERN.test(value))
|
|
758
|
+
throw new OperationWireResultError("invalid_instant", `${label} must be a canonical UTC instant.`);
|
|
759
|
+
const timestamp = Date.parse(value);
|
|
760
|
+
if (!Number.isFinite(timestamp) || new Date(timestamp).toISOString() !== value) {
|
|
761
|
+
throw new OperationWireResultError("invalid_instant", `${label} must be a real canonical UTC instant.`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function assertUnicodeScalarString(value) {
|
|
765
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
766
|
+
const code = value.charCodeAt(index);
|
|
767
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
768
|
+
const next = value.charCodeAt(index + 1);
|
|
769
|
+
if (!(next >= 0xdc00 && next <= 0xdfff)) {
|
|
770
|
+
throw new OperationWireResultError("invalid_live_response", "Live response content contains invalid Unicode.");
|
|
771
|
+
}
|
|
772
|
+
index += 1;
|
|
773
|
+
}
|
|
774
|
+
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
775
|
+
throw new OperationWireResultError("invalid_live_response", "Live response content contains invalid Unicode.");
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
function isRecord(value) {
|
|
780
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
781
|
+
}
|
|
782
|
+
function isSafeInteger(value) {
|
|
783
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
784
|
+
}
|
|
785
|
+
function isDigest(value) {
|
|
786
|
+
return typeof value === "string" && DIGEST_PATTERN.test(value);
|
|
787
|
+
}
|
|
788
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
789
|
+
const DIGEST_PATTERN = /^hmac-sha256:[0-9a-f]{64}$/;
|
|
790
|
+
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
791
|
+
const OPAQUE_ID_PATTERN = /^(?=.*\S)[^\u0000-\u001f\u007f]{1,512}$/u;
|
|
792
|
+
const OPAQUE_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
793
|
+
const OUTPUT_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
794
|
+
const INSTANT_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
795
|
+
const FINISH_REASON_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
796
|
+
const MIME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.+/-]{0,126}$/;
|
|
797
|
+
const CODE_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
|
|
798
|
+
const SAFE_MESSAGE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 .,:;_'()\-]{0,511}$/;
|
|
799
|
+
const PHASES = new Set(["prepared", "handoff_pending", "ready", "send_pending", "submitted", "generating", "capturing", "completed", "uncertain"]);
|
|
800
|
+
const BOUNDARIES = new Set(["none", "handoff_may_have_occurred", "send_may_have_occurred", "control_may_have_occurred"]);
|
|
801
|
+
const WIRE_STATUSES = new Set(["accepted", "completed", "pending", "blocked", "uncertain"]);
|
|
802
|
+
const SUBMIT_STATUSES = new Set(["accepted", "completed", "blocked", "uncertain"]);
|
|
803
|
+
const COLLECT_STATUSES = new Set(["completed", "pending", "blocked", "uncertain"]);
|
|
804
|
+
const CONTROL_STATUSES = new Set(["completed", "blocked", "uncertain"]);
|
|
805
|
+
const WIRE_BLOCKER_CODES = new Set([
|
|
806
|
+
"operation_not_found", "operation_request_mismatch", "operation_state_corrupt", "operation_receipt_expired", "operation_quota_exceeded", "operation_cancelled", "operation_timeout", "ambiguous_file_handoff", "ambiguous_submit", "attachment_manifest_mismatch", "input_file_changed", "target_binding_mismatch", "target_evidence_unavailable", "turn_ownership_ambiguous", "concurrent_user_turn", "configuration_drift", "tab_ownership_conflict", "provider_concurrency_unsupported", "runtime_incompatible", "backend_unavailable", "browser_bridge_unavailable", "login_required", "captcha", "rate_limited", "permission_required", "needs_confirmation", "selector_drift", "send_control_unavailable", "capture_ownership_lost", "artifact_unavailable", "artifact_transfer_partial", "output_collision", "output_commit_indeterminate", "clipboard_restore_failed"
|
|
807
|
+
]);
|
|
808
|
+
const ACTION_KINDS = new Set(["status_read", "configuration_set", "tool_set", "composer_set", "power_discovery", "power_select", "file_handoff", "send", "work_steer", "stop", "download", "local_output_commit", "clipboard_capture_restore"]);
|
|
809
|
+
const ACTION_POLICIES = new Set(["read_only", "reconcile_set_to_value", "reconcile_local_effect", "observe_only_after_intent"]);
|
|
810
|
+
function requiredActionPolicy(kind) {
|
|
811
|
+
if (kind === "status_read" || kind === "power_discovery")
|
|
812
|
+
return "read_only";
|
|
813
|
+
if (kind === "configuration_set" || kind === "tool_set" || kind === "composer_set" || kind === "power_select")
|
|
814
|
+
return "reconcile_set_to_value";
|
|
815
|
+
if (kind === "file_handoff" || kind === "send" || kind === "work_steer" || kind === "stop")
|
|
816
|
+
return "observe_only_after_intent";
|
|
817
|
+
return "reconcile_local_effect";
|
|
818
|
+
}
|