@opengeni/contracts 0.40.0 → 0.50.0
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/dist/atlassian.d.ts +278 -0
- package/dist/atlassian.js +57 -0
- package/dist/atlassian.js.map +1 -0
- package/dist/canonical-human-identities.d.ts +157 -0
- package/dist/canonical-human-identities.js +23 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/{chunk-7JZMI22I.js → chunk-5UDNP7AO.js} +34 -2
- package/dist/chunk-5UDNP7AO.js.map +1 -0
- package/dist/chunk-BVGIYW7D.js +11 -0
- package/dist/chunk-BVGIYW7D.js.map +1 -0
- package/dist/chunk-CADSX6UN.js +1145 -0
- package/dist/chunk-CADSX6UN.js.map +1 -0
- package/dist/chunk-CM6BMECR.js +24 -0
- package/dist/chunk-CM6BMECR.js.map +1 -0
- package/dist/chunk-D2HKF2EF.js +1460 -0
- package/dist/chunk-D2HKF2EF.js.map +1 -0
- package/dist/chunk-JBCIY7QD.js +194 -0
- package/dist/chunk-JBCIY7QD.js.map +1 -0
- package/dist/chunk-JUFK3T4Q.js +80 -0
- package/dist/chunk-JUFK3T4Q.js.map +1 -0
- package/dist/chunk-LSB7GRLG.js +779 -0
- package/dist/chunk-LSB7GRLG.js.map +1 -0
- package/dist/chunk-LW7OH6CS.js +382 -0
- package/dist/chunk-LW7OH6CS.js.map +1 -0
- package/dist/chunk-M5ZGRVIQ.js +719 -0
- package/dist/chunk-M5ZGRVIQ.js.map +1 -0
- package/dist/chunk-MYBZRDTY.js +340 -0
- package/dist/chunk-MYBZRDTY.js.map +1 -0
- package/dist/chunk-Q6A7XT2N.js +16734 -0
- package/dist/chunk-Q6A7XT2N.js.map +1 -0
- package/dist/chunk-RIY7GYQS.js +1204 -0
- package/dist/chunk-RIY7GYQS.js.map +1 -0
- package/dist/chunk-TZ6YEYN4.js +386 -0
- package/dist/chunk-TZ6YEYN4.js.map +1 -0
- package/dist/chunk-Y3MLSLB4.js +392 -0
- package/dist/chunk-Y3MLSLB4.js.map +1 -0
- package/dist/chunk-YRKRZ6OF.js +134 -0
- package/dist/chunk-YRKRZ6OF.js.map +1 -0
- package/dist/codex-provider-account-authority.d.ts +23 -0
- package/dist/codex-provider-account-authority.js +23 -0
- package/dist/codex-provider-account-authority.js.map +1 -0
- package/dist/company-profile.d.ts +629 -0
- package/dist/document-artifact-commands.d.ts +282 -0
- package/dist/document-artifact-commands.js +54 -0
- package/dist/document-artifact-commands.js.map +1 -0
- package/dist/document-artifact-query.d.ts +2 -0
- package/dist/document-artifact-query.js +54 -0
- package/dist/document-artifact-query.js.map +1 -0
- package/dist/editable-artifact-binary.d.ts +56 -0
- package/dist/editable-artifact-causal-frontier.d.ts +12 -0
- package/dist/editable-artifact-causal-frontier.js +18 -0
- package/dist/editable-artifact-causal-frontier.js.map +1 -0
- package/dist/editable-artifact-codec-registry.d.ts +48 -0
- package/dist/editable-artifact-codec-registry.js +20 -0
- package/dist/editable-artifact-codec-registry.js.map +1 -0
- package/dist/editable-artifact-committed-transaction.d.ts +35 -0
- package/dist/editable-artifact-committed-transaction.js +19 -0
- package/dist/editable-artifact-committed-transaction.js.map +1 -0
- package/dist/editable-artifact-live.d.ts +276 -0
- package/dist/editable-artifact-live.js +907 -0
- package/dist/editable-artifact-live.js.map +1 -0
- package/dist/editable-artifact-serialized-commit.d.ts +53 -0
- package/dist/editable-artifact-serialized-commit.js +26 -0
- package/dist/editable-artifact-serialized-commit.js.map +1 -0
- package/dist/editable-artifact-versions.d.ts +6 -0
- package/dist/editable-artifact-versions.js +11 -0
- package/dist/editable-artifact-versions.js.map +1 -0
- package/dist/editable-artifacts.d.ts +46 -0
- package/dist/editable-artifacts.js +236 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/event-preview.d.ts +1 -1
- package/dist/google-drive.d.ts +114 -2
- package/dist/google-drive.js +72 -14
- package/dist/google-drive.js.map +1 -1
- package/dist/image-generation.d.ts +70 -0
- package/dist/index.d.ts +5661 -166
- package/dist/index.js +1438 -21
- package/dist/interaction.d.ts +8290 -0
- package/dist/knowledge.d.ts +543 -0
- package/dist/memory-slack-delivery.d.ts +397 -0
- package/dist/presentation-artifact-commands.d.ts +349 -0
- package/dist/presentation-artifact-commands.js +66 -0
- package/dist/presentation-artifact-commands.js.map +1 -0
- package/dist/presentation-artifact-query.d.ts +2 -0
- package/dist/presentation-artifact-query.js +66 -0
- package/dist/presentation-artifact-query.js.map +1 -0
- package/dist/retained-output.d.ts +24 -0
- package/dist/sandbox-snapshots.d.ts +9 -0
- package/dist/scoped-knowledge.d.ts +14 -13
- package/dist/slack-bot-scopes.d.ts +4 -0
- package/dist/slack-bot-scopes.js +3 -1
- package/dist/slack-task-policy.d.ts +294 -0
- package/dist/spreadsheet-artifact-commands.d.ts +101 -0
- package/dist/spreadsheet-artifact-commands.js +27 -0
- package/dist/spreadsheet-artifact-commands.js.map +1 -0
- package/dist/spreadsheet-artifact-date.d.ts +4 -0
- package/dist/spreadsheet-artifact-query.d.ts +120 -0
- package/dist/spreadsheet-artifact-query.js +48 -0
- package/dist/spreadsheet-artifact-query.js.map +1 -0
- package/dist/task-notes.d.ts +128 -0
- package/dist/tool-catalog.d.ts +576 -0
- package/dist/video-generation.d.ts +580 -0
- package/dist/video-generation.js +59 -0
- package/dist/video-generation.js.map +1 -0
- package/dist/workspace-learning-policy.d.ts +208 -0
- package/dist/xai-provider-account-authority.d.ts +23 -0
- package/dist/xai-provider-account-authority.js +9 -0
- package/dist/xai-provider-account-authority.js.map +1 -0
- package/package.json +73 -1
- package/src/artifacts.ts +1 -1
- package/src/atlassian.ts +160 -0
- package/src/canonical-human-identities.ts +93 -0
- package/src/codex-provider-account-authority.ts +37 -0
- package/src/company-profile.ts +306 -0
- package/src/document-artifact-commands.ts +1709 -0
- package/src/document-artifact-query.ts +2 -0
- package/src/editable-artifact-binary.ts +396 -0
- package/src/editable-artifact-causal-frontier.ts +154 -0
- package/src/editable-artifact-codec-registry.ts +261 -0
- package/src/editable-artifact-committed-transaction.ts +478 -0
- package/src/editable-artifact-live.ts +1379 -0
- package/src/editable-artifact-serialized-commit.ts +494 -0
- package/src/editable-artifact-versions.ts +8 -0
- package/src/editable-artifacts.ts +473 -0
- package/src/google-drive.ts +68 -15
- package/src/image-generation.ts +106 -0
- package/src/index.ts +2920 -326
- package/src/interaction.ts +3173 -0
- package/src/knowledge.ts +207 -0
- package/src/memory-slack-delivery.ts +183 -0
- package/src/presentation-artifact-commands.ts +2035 -0
- package/src/presentation-artifact-query.ts +2 -0
- package/src/retained-output.ts +149 -2
- package/src/sandbox-snapshots.ts +28 -1
- package/src/scoped-knowledge.ts +18 -4
- package/src/slack-bot-scopes.ts +34 -1
- package/src/slack-task-policy.ts +215 -0
- package/src/spreadsheet-artifact-commands.ts +999 -0
- package/src/spreadsheet-artifact-date.ts +21 -0
- package/src/spreadsheet-artifact-query.ts +1411 -0
- package/src/task-notes.ts +92 -0
- package/src/tool-catalog.ts +334 -0
- package/src/video-generation.ts +431 -0
- package/src/workspace-learning-policy.ts +233 -0
- package/src/xai-provider-account-authority.ts +37 -0
- package/dist/chunk-5HR2GPSC.js +0 -11031
- package/dist/chunk-5HR2GPSC.js.map +0 -1
- package/dist/chunk-7JZMI22I.js.map +0 -1
|
@@ -0,0 +1,3173 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { RetainedArtifactReferenceSchema } from "./retained-output";
|
|
3
|
+
|
|
4
|
+
export const INTERACTION_PROTOCOL_VERSION = 1 as const;
|
|
5
|
+
export const BROWSER_CONTROL_PROTOCOL_VERSION = INTERACTION_PROTOCOL_VERSION;
|
|
6
|
+
export const BROWSER_CONTROL_WEBSOCKET_PROTOCOL = "opengeni.browser.v1" as const;
|
|
7
|
+
export const COMPUTER_CONTROL_WEBSOCKET_PROTOCOL = "opengeni.computer.v1" as const;
|
|
8
|
+
export const COMPUTER_RFB_WEBSOCKET_PROTOCOL = "opengeni.computer.rfb.v1" as const;
|
|
9
|
+
export const BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX = "opengeni.auth." as const;
|
|
10
|
+
export const BROWSER_CONTROL_MAX_JSON_BYTES = 40 * 1024 * 1024;
|
|
11
|
+
export const BROWSER_CONTROL_MAX_FRAME_HEADER_BYTES = 64 * 1024;
|
|
12
|
+
export const BROWSER_PROFILE_ARTIFACT_FORMAT =
|
|
13
|
+
"opengeni.chromium-profile.v1+gzip+aes-256-gcm" as const;
|
|
14
|
+
export const BROWSER_STATE_ARTIFACT_CONTENT_TYPE =
|
|
15
|
+
"application/vnd.opengeni.browser-profile+octet-stream" as const;
|
|
16
|
+
export const INTERACTION_MAX_SEMANTIC_NODES = 10_000;
|
|
17
|
+
export const INTERACTION_MAX_CHANGED_NODES = 2_000;
|
|
18
|
+
export const INTERACTION_MAX_DIAGNOSTIC_ENTRIES = 1_000;
|
|
19
|
+
export const INTERACTION_MAX_ACTIONS_PER_BATCH = 32;
|
|
20
|
+
export const INTERACTION_MAX_WORKSPACE_FILES_PER_COMMAND = 100;
|
|
21
|
+
export const INTERACTION_MAX_CLIPBOARD_BYTES = 1024 * 1024;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Latest-wins workspace invalidation for Browser/Computer resources. This is
|
|
25
|
+
* deliberately a cursor snapshot rather than a durable event-log entry:
|
|
26
|
+
* consumers refetch authoritative resource lists whenever `revision` advances.
|
|
27
|
+
*/
|
|
28
|
+
export const WorkspaceInteractionRevisionEvent = z
|
|
29
|
+
.object({
|
|
30
|
+
workspaceId: z.string().uuid(),
|
|
31
|
+
sequence: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
32
|
+
revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
33
|
+
type: z.literal("workspace.interaction.changed"),
|
|
34
|
+
occurredAt: z.string().datetime({ offset: true }),
|
|
35
|
+
})
|
|
36
|
+
.strict()
|
|
37
|
+
.refine((event) => event.sequence === event.revision, {
|
|
38
|
+
message: "interaction event sequence must equal revision",
|
|
39
|
+
path: ["sequence"],
|
|
40
|
+
});
|
|
41
|
+
export type WorkspaceInteractionRevisionEvent = z.infer<typeof WorkspaceInteractionRevisionEvent>;
|
|
42
|
+
|
|
43
|
+
const opaqueGeneration = z
|
|
44
|
+
.string()
|
|
45
|
+
.min(1)
|
|
46
|
+
.max(256)
|
|
47
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u);
|
|
48
|
+
const boundedOpaqueId = z
|
|
49
|
+
.string()
|
|
50
|
+
.min(1)
|
|
51
|
+
.max(512)
|
|
52
|
+
.regex(/^[^\u0000-\u001f\u007f]+$/u);
|
|
53
|
+
const boundedUrl = z
|
|
54
|
+
.string()
|
|
55
|
+
.min(1)
|
|
56
|
+
.max(16_384)
|
|
57
|
+
.refine(
|
|
58
|
+
(value) => {
|
|
59
|
+
try {
|
|
60
|
+
return new URL(value).href.length > 0;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{ message: "URL must be absolute" },
|
|
66
|
+
);
|
|
67
|
+
const boundedHttpUrl = boundedUrl.refine(
|
|
68
|
+
(value) => {
|
|
69
|
+
const url = new URL(value);
|
|
70
|
+
return (
|
|
71
|
+
(url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password
|
|
72
|
+
);
|
|
73
|
+
},
|
|
74
|
+
{ message: "URL must use HTTP(S) and cannot contain credentials" },
|
|
75
|
+
);
|
|
76
|
+
const canonicalWebOrigin = z
|
|
77
|
+
.string()
|
|
78
|
+
.min(1)
|
|
79
|
+
.max(2_048)
|
|
80
|
+
.refine(
|
|
81
|
+
(value) => {
|
|
82
|
+
try {
|
|
83
|
+
const url = new URL(value);
|
|
84
|
+
return (
|
|
85
|
+
(url.protocol === "http:" || url.protocol === "https:") &&
|
|
86
|
+
!url.username &&
|
|
87
|
+
!url.password &&
|
|
88
|
+
value === url.origin
|
|
89
|
+
);
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{ message: "origin must be a canonical HTTP(S) origin" },
|
|
95
|
+
);
|
|
96
|
+
const sha256Hex = z.string().regex(/^[0-9a-f]{64}$/u);
|
|
97
|
+
|
|
98
|
+
export const InteractionPlacement = z.discriminatedUnion("kind", [
|
|
99
|
+
z
|
|
100
|
+
.object({
|
|
101
|
+
kind: z.literal("sandbox_group"),
|
|
102
|
+
sandboxGroupId: z.string().uuid(),
|
|
103
|
+
})
|
|
104
|
+
.strict(),
|
|
105
|
+
z
|
|
106
|
+
.object({
|
|
107
|
+
kind: z.literal("connected_machine"),
|
|
108
|
+
sandboxId: z.string().uuid(),
|
|
109
|
+
})
|
|
110
|
+
.strict(),
|
|
111
|
+
z
|
|
112
|
+
.object({
|
|
113
|
+
kind: z.literal("attached_device"),
|
|
114
|
+
deviceId: z.string().uuid(),
|
|
115
|
+
})
|
|
116
|
+
.strict(),
|
|
117
|
+
z
|
|
118
|
+
.object({
|
|
119
|
+
kind: z.literal("external_provider"),
|
|
120
|
+
providerId: boundedOpaqueId,
|
|
121
|
+
placementId: boundedOpaqueId,
|
|
122
|
+
})
|
|
123
|
+
.strict(),
|
|
124
|
+
]);
|
|
125
|
+
export type InteractionPlacement = z.infer<typeof InteractionPlacement>;
|
|
126
|
+
|
|
127
|
+
export const InteractionControllerBinding = z
|
|
128
|
+
.object({
|
|
129
|
+
controllerId: boundedOpaqueId,
|
|
130
|
+
controllerGeneration: opaqueGeneration,
|
|
131
|
+
placementInstanceId: boundedOpaqueId,
|
|
132
|
+
})
|
|
133
|
+
.strict();
|
|
134
|
+
export type InteractionControllerBinding = z.infer<typeof InteractionControllerBinding>;
|
|
135
|
+
|
|
136
|
+
export const InteractionAssociation = z
|
|
137
|
+
.object({
|
|
138
|
+
sessionId: z.string().uuid(),
|
|
139
|
+
turnId: z.string().uuid().nullable(),
|
|
140
|
+
attemptId: z.string().uuid().nullable(),
|
|
141
|
+
relationship: z.enum(["created", "using", "observing", "related"]),
|
|
142
|
+
actorSubjectId: z.string().min(1).max(1_024),
|
|
143
|
+
lastUsedAt: z.string().datetime({ offset: true }),
|
|
144
|
+
})
|
|
145
|
+
.strict();
|
|
146
|
+
export type InteractionAssociation = z.infer<typeof InteractionAssociation>;
|
|
147
|
+
|
|
148
|
+
export const InteractionActor = z
|
|
149
|
+
.object({
|
|
150
|
+
kind: z.enum(["agent", "human", "system"]),
|
|
151
|
+
subjectId: z.string().min(1).max(1_024),
|
|
152
|
+
sessionId: z.string().uuid().optional(),
|
|
153
|
+
turnId: z.string().uuid().optional(),
|
|
154
|
+
attemptId: z.string().uuid().optional(),
|
|
155
|
+
executionGeneration: z.number().int().positive().optional(),
|
|
156
|
+
})
|
|
157
|
+
.strict()
|
|
158
|
+
.superRefine((actor, context) => {
|
|
159
|
+
const attemptFields = [actor.turnId, actor.attemptId, actor.executionGeneration];
|
|
160
|
+
const attemptFieldCount = attemptFields.filter((value) => value !== undefined).length;
|
|
161
|
+
if (attemptFieldCount !== 0 && (attemptFieldCount !== 3 || actor.sessionId === undefined)) {
|
|
162
|
+
context.addIssue({
|
|
163
|
+
code: "custom",
|
|
164
|
+
message: "turnId, attemptId, executionGeneration, and sessionId must be supplied together",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
export type InteractionActor = z.infer<typeof InteractionActor>;
|
|
169
|
+
|
|
170
|
+
export const InteractionLifecycle = z.enum([
|
|
171
|
+
"starting",
|
|
172
|
+
"active",
|
|
173
|
+
"suspending",
|
|
174
|
+
"suspended",
|
|
175
|
+
"restoring",
|
|
176
|
+
"repair_required",
|
|
177
|
+
"lost",
|
|
178
|
+
"ending",
|
|
179
|
+
"ended",
|
|
180
|
+
"failed",
|
|
181
|
+
]);
|
|
182
|
+
export type InteractionLifecycle = z.infer<typeof InteractionLifecycle>;
|
|
183
|
+
|
|
184
|
+
export const BrowserSessionCapabilities = z
|
|
185
|
+
.object({
|
|
186
|
+
semanticObservation: z.boolean(),
|
|
187
|
+
screenshots: z.boolean(),
|
|
188
|
+
liveFrames: z.boolean(),
|
|
189
|
+
humanInput: z.boolean(),
|
|
190
|
+
tabs: z.boolean(),
|
|
191
|
+
downloads: z.boolean(),
|
|
192
|
+
uploads: z.boolean(),
|
|
193
|
+
clipboard: z.boolean(),
|
|
194
|
+
permissions: z.boolean(),
|
|
195
|
+
diagnostics: z.boolean(),
|
|
196
|
+
rawCdp: z.boolean(),
|
|
197
|
+
linkedComputer: z.boolean(),
|
|
198
|
+
privateCheckpoint: z.boolean(),
|
|
199
|
+
identityPublication: z.boolean(),
|
|
200
|
+
parallelTargets: z.boolean(),
|
|
201
|
+
})
|
|
202
|
+
.strict();
|
|
203
|
+
export type BrowserSessionCapabilities = z.infer<typeof BrowserSessionCapabilities>;
|
|
204
|
+
|
|
205
|
+
export const BrowserSession = z
|
|
206
|
+
.object({
|
|
207
|
+
id: z.string().uuid(),
|
|
208
|
+
accountId: z.string().uuid(),
|
|
209
|
+
workspaceId: z.string().uuid(),
|
|
210
|
+
name: z.string().trim().min(1).max(200),
|
|
211
|
+
lifecycle: InteractionLifecycle,
|
|
212
|
+
placement: InteractionPlacement,
|
|
213
|
+
controller: InteractionControllerBinding.nullable(),
|
|
214
|
+
driverId: boundedOpaqueId,
|
|
215
|
+
engine: z.enum(["chromium", "chrome", "firefox", "webkit", "lightpanda", "external"]),
|
|
216
|
+
engineVersion: z.string().min(1).max(256).nullable(),
|
|
217
|
+
headless: z.boolean(),
|
|
218
|
+
identityId: z.string().uuid().nullable(),
|
|
219
|
+
baseRevisionId: z.string().uuid().nullable(),
|
|
220
|
+
networkRouteId: z.string().uuid().nullable(),
|
|
221
|
+
linkedComputerSessionId: z.string().uuid().nullable(),
|
|
222
|
+
capabilities: BrowserSessionCapabilities,
|
|
223
|
+
associations: z.array(InteractionAssociation).max(1_000),
|
|
224
|
+
createdBySubjectId: z.string().min(1).max(1_024),
|
|
225
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
226
|
+
lastUsedAt: z.string().datetime({ offset: true }),
|
|
227
|
+
failureCode: boundedOpaqueId.nullable(),
|
|
228
|
+
})
|
|
229
|
+
.strict();
|
|
230
|
+
export type BrowserSession = z.infer<typeof BrowserSession>;
|
|
231
|
+
|
|
232
|
+
/** Controller-private, BrowserSession-scoped clipboard. This is deliberately
|
|
233
|
+
* distinct from a ComputerSession/host OS clipboard so concurrent browsers do
|
|
234
|
+
* not exchange ambient machine state. */
|
|
235
|
+
export const BrowserClipboard = z
|
|
236
|
+
.object({
|
|
237
|
+
browserSessionId: z.string().uuid(),
|
|
238
|
+
controllerGeneration: opaqueGeneration,
|
|
239
|
+
revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
240
|
+
text: z
|
|
241
|
+
.string()
|
|
242
|
+
.max(INTERACTION_MAX_CLIPBOARD_BYTES)
|
|
243
|
+
.refine(
|
|
244
|
+
(value) => new TextEncoder().encode(value).byteLength <= INTERACTION_MAX_CLIPBOARD_BYTES,
|
|
245
|
+
{ message: "browser clipboard text exceeds its UTF-8 byte envelope" },
|
|
246
|
+
),
|
|
247
|
+
source: z.enum(["empty", "write", "clear", "copy", "paste"]),
|
|
248
|
+
sourceTargetId: boundedOpaqueId.nullable(),
|
|
249
|
+
updatedAt: z.string().datetime({ offset: true }).nullable(),
|
|
250
|
+
})
|
|
251
|
+
.strict()
|
|
252
|
+
.superRefine((clipboard, context) => {
|
|
253
|
+
if (clipboard.revision === 0) {
|
|
254
|
+
if (
|
|
255
|
+
clipboard.text !== "" ||
|
|
256
|
+
clipboard.source !== "empty" ||
|
|
257
|
+
clipboard.sourceTargetId !== null ||
|
|
258
|
+
clipboard.updatedAt !== null
|
|
259
|
+
) {
|
|
260
|
+
context.addIssue({
|
|
261
|
+
code: "custom",
|
|
262
|
+
message: "initial browser clipboard state must be empty",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
} else if (clipboard.source === "empty" || clipboard.updatedAt === null) {
|
|
266
|
+
context.addIssue({
|
|
267
|
+
code: "custom",
|
|
268
|
+
message: "updated browser clipboard state requires a source and timestamp",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
export type BrowserClipboard = z.infer<typeof BrowserClipboard>;
|
|
273
|
+
|
|
274
|
+
/** One live Chrome-profile bridge installed on an enrolled machine. This is a
|
|
275
|
+
* transport endpoint, not saved browser/login state: BrowserIdentity remains
|
|
276
|
+
* the immutable, reusable state abstraction. */
|
|
277
|
+
export const AttachedBrowserDeviceCapabilities = z
|
|
278
|
+
.object({
|
|
279
|
+
tabInventory: z.boolean(),
|
|
280
|
+
debuggerAttachment: z.boolean(),
|
|
281
|
+
semanticObservation: z.boolean(),
|
|
282
|
+
screenshots: z.boolean(),
|
|
283
|
+
liveFrames: z.boolean(),
|
|
284
|
+
humanInput: z.boolean(),
|
|
285
|
+
diagnostics: z.boolean(),
|
|
286
|
+
rawCdp: z.boolean(),
|
|
287
|
+
linkedComputer: z.boolean(),
|
|
288
|
+
})
|
|
289
|
+
.strict();
|
|
290
|
+
export type AttachedBrowserDeviceCapabilities = z.infer<typeof AttachedBrowserDeviceCapabilities>;
|
|
291
|
+
|
|
292
|
+
export const AttachedBrowserDeviceAnnouncement = z
|
|
293
|
+
.object({
|
|
294
|
+
id: z.string().uuid(),
|
|
295
|
+
name: z.string().trim().min(1).max(200),
|
|
296
|
+
profileLabel: z.string().trim().min(1).max(200).nullable(),
|
|
297
|
+
browserName: z.string().trim().min(1).max(100),
|
|
298
|
+
browserVersion: z.string().trim().min(1).max(256),
|
|
299
|
+
extensionVersion: z.string().trim().min(1).max(256),
|
|
300
|
+
platform: z.enum(["linux", "macos", "windows"]),
|
|
301
|
+
architecture: z.enum(["x64", "arm64"]),
|
|
302
|
+
connectionGeneration: opaqueGeneration,
|
|
303
|
+
inventoryRevision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
304
|
+
tabCount: z.number().int().nonnegative().max(100_000),
|
|
305
|
+
capabilities: AttachedBrowserDeviceCapabilities,
|
|
306
|
+
})
|
|
307
|
+
.strict();
|
|
308
|
+
export type AttachedBrowserDeviceAnnouncement = z.infer<typeof AttachedBrowserDeviceAnnouncement>;
|
|
309
|
+
|
|
310
|
+
export const AttachedBrowserInventorySnapshot = z
|
|
311
|
+
.object({
|
|
312
|
+
bridgeGeneration: opaqueGeneration,
|
|
313
|
+
revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
314
|
+
devices: z.array(AttachedBrowserDeviceAnnouncement).max(10_000),
|
|
315
|
+
})
|
|
316
|
+
.strict()
|
|
317
|
+
.superRefine((snapshot, context) => {
|
|
318
|
+
const ids = new Set<string>();
|
|
319
|
+
for (const [index, device] of snapshot.devices.entries()) {
|
|
320
|
+
if (ids.has(device.id)) {
|
|
321
|
+
context.addIssue({
|
|
322
|
+
code: "custom",
|
|
323
|
+
path: ["devices", index, "id"],
|
|
324
|
+
message: "device ids must be unique within one inventory snapshot",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
ids.add(device.id);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
export type AttachedBrowserInventorySnapshot = z.infer<typeof AttachedBrowserInventorySnapshot>;
|
|
331
|
+
|
|
332
|
+
export const AttachedBrowserDevice = z
|
|
333
|
+
.object({
|
|
334
|
+
id: z.string().uuid(),
|
|
335
|
+
accountId: z.string().uuid(),
|
|
336
|
+
workspaceId: z.string().uuid(),
|
|
337
|
+
enrollmentId: z.string().uuid(),
|
|
338
|
+
name: z.string().trim().min(1).max(200),
|
|
339
|
+
profileLabel: z.string().trim().min(1).max(200).nullable(),
|
|
340
|
+
browserName: z.string().trim().min(1).max(100),
|
|
341
|
+
browserVersion: z.string().trim().min(1).max(256),
|
|
342
|
+
extensionVersion: z.string().trim().min(1).max(256),
|
|
343
|
+
platform: z.enum(["linux", "macos", "windows"]),
|
|
344
|
+
architecture: z.enum(["x64", "arm64"]),
|
|
345
|
+
state: z.enum(["connected", "disconnected"]),
|
|
346
|
+
connectionGeneration: opaqueGeneration,
|
|
347
|
+
inventoryRevision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
348
|
+
tabCount: z.number().int().nonnegative().max(100_000),
|
|
349
|
+
capabilities: AttachedBrowserDeviceCapabilities,
|
|
350
|
+
lastSeenAt: z.string().datetime({ offset: true }),
|
|
351
|
+
disconnectedAt: z.string().datetime({ offset: true }).nullable(),
|
|
352
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
353
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
354
|
+
})
|
|
355
|
+
.strict()
|
|
356
|
+
.superRefine((device, context) => {
|
|
357
|
+
if ((device.state === "connected") !== (device.disconnectedAt === null)) {
|
|
358
|
+
context.addIssue({
|
|
359
|
+
code: "custom",
|
|
360
|
+
path: ["disconnectedAt"],
|
|
361
|
+
message: "connected state and disconnectedAt must agree",
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
export type AttachedBrowserDevice = z.infer<typeof AttachedBrowserDevice>;
|
|
366
|
+
|
|
367
|
+
export const AttachedBrowserDeviceListResponse = z
|
|
368
|
+
.object({
|
|
369
|
+
revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
370
|
+
devices: z.array(AttachedBrowserDevice).max(10_000),
|
|
371
|
+
})
|
|
372
|
+
.strict();
|
|
373
|
+
export type AttachedBrowserDeviceListResponse = z.infer<typeof AttachedBrowserDeviceListResponse>;
|
|
374
|
+
|
|
375
|
+
export const AttachedBrowserTab = z
|
|
376
|
+
.object({
|
|
377
|
+
id: boundedOpaqueId,
|
|
378
|
+
windowId: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
379
|
+
index: z.number().int().nonnegative().max(100_000),
|
|
380
|
+
title: z.string().max(8_192),
|
|
381
|
+
url: boundedUrl.nullable(),
|
|
382
|
+
active: z.boolean(),
|
|
383
|
+
pinned: z.boolean(),
|
|
384
|
+
incognito: z.boolean(),
|
|
385
|
+
audible: z.boolean(),
|
|
386
|
+
discarded: z.boolean(),
|
|
387
|
+
controllable: z.boolean(),
|
|
388
|
+
unavailableReason: z.string().trim().min(1).max(2_048).nullable(),
|
|
389
|
+
})
|
|
390
|
+
.strict()
|
|
391
|
+
.superRefine((tab, context) => {
|
|
392
|
+
if (tab.controllable === (tab.unavailableReason !== null)) {
|
|
393
|
+
context.addIssue({
|
|
394
|
+
code: "custom",
|
|
395
|
+
path: ["unavailableReason"],
|
|
396
|
+
message: "unavailableReason is required exactly when a tab is not controllable",
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
export type AttachedBrowserTab = z.infer<typeof AttachedBrowserTab>;
|
|
401
|
+
|
|
402
|
+
export const AttachedBrowserTabListResponse = z
|
|
403
|
+
.object({
|
|
404
|
+
deviceId: z.string().uuid(),
|
|
405
|
+
connectionGeneration: opaqueGeneration,
|
|
406
|
+
inventoryRevision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
407
|
+
tabs: z.array(AttachedBrowserTab).max(100_000),
|
|
408
|
+
})
|
|
409
|
+
.strict();
|
|
410
|
+
export type AttachedBrowserTabListResponse = z.infer<typeof AttachedBrowserTabListResponse>;
|
|
411
|
+
|
|
412
|
+
export const BrowserIdentityStatus = z.enum(["active", "archived"]);
|
|
413
|
+
export type BrowserIdentityStatus = z.infer<typeof BrowserIdentityStatus>;
|
|
414
|
+
|
|
415
|
+
export const BrowserRevisionComponentKind = z.enum([
|
|
416
|
+
"chromium_profile",
|
|
417
|
+
"normalized_web_state",
|
|
418
|
+
"provider_snapshot",
|
|
419
|
+
]);
|
|
420
|
+
export type BrowserRevisionComponentKind = z.infer<typeof BrowserRevisionComponentKind>;
|
|
421
|
+
|
|
422
|
+
export const BrowserRevisionPortability = z.enum(["portable", "provider_bound", "placement_bound"]);
|
|
423
|
+
export type BrowserRevisionPortability = z.infer<typeof BrowserRevisionPortability>;
|
|
424
|
+
|
|
425
|
+
/** Public, non-secret compatibility declaration for one immutable state
|
|
426
|
+
* component. Object keys, provider snapshot handles, and encryption material
|
|
427
|
+
* never enter this contract. */
|
|
428
|
+
export const BrowserRevisionMaterialization = z
|
|
429
|
+
.object({
|
|
430
|
+
portability: BrowserRevisionPortability,
|
|
431
|
+
reason: z.string().trim().min(1).max(2_048).nullable(),
|
|
432
|
+
platform: z.enum(["linux", "macos", "windows"]).nullable(),
|
|
433
|
+
architecture: z.enum(["x64", "arm64"]).nullable(),
|
|
434
|
+
engine: z.enum(["chromium", "chrome", "firefox", "webkit", "lightpanda", "external"]),
|
|
435
|
+
engineVersion: z.string().min(1).max(256).nullable(),
|
|
436
|
+
driverId: boundedOpaqueId,
|
|
437
|
+
driverSchemaVersion: z.number().int().positive().max(1_000_000),
|
|
438
|
+
profileCrypto: z.enum(["chromium_basic", "chromium_mock_keychain", "platform_bound"]),
|
|
439
|
+
providerId: boundedOpaqueId.nullable(),
|
|
440
|
+
placement: InteractionPlacement.nullable(),
|
|
441
|
+
})
|
|
442
|
+
.strict()
|
|
443
|
+
.superRefine((value, context) => {
|
|
444
|
+
if (value.portability === "portable" && (value.providerId || value.placement)) {
|
|
445
|
+
context.addIssue({
|
|
446
|
+
code: "custom",
|
|
447
|
+
message: "portable state cannot carry a binding",
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (
|
|
451
|
+
value.portability === "provider_bound" &&
|
|
452
|
+
(!value.providerId || value.placement || !value.reason)
|
|
453
|
+
) {
|
|
454
|
+
context.addIssue({
|
|
455
|
+
code: "custom",
|
|
456
|
+
message: "provider-bound state requires one provider and reason",
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (
|
|
460
|
+
value.portability === "placement_bound" &&
|
|
461
|
+
(!value.placement || value.providerId || !value.reason)
|
|
462
|
+
) {
|
|
463
|
+
context.addIssue({
|
|
464
|
+
code: "custom",
|
|
465
|
+
message: "placement-bound state requires one placement and reason",
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (value.profileCrypto === "platform_bound" && value.portability !== "placement_bound") {
|
|
469
|
+
context.addIssue({
|
|
470
|
+
code: "custom",
|
|
471
|
+
message: "platform-bound profile encryption requires placement-bound state",
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
export type BrowserRevisionMaterialization = z.infer<typeof BrowserRevisionMaterialization>;
|
|
476
|
+
|
|
477
|
+
export const BrowserRevisionComponent = z
|
|
478
|
+
.object({
|
|
479
|
+
id: z.string().uuid(),
|
|
480
|
+
kind: BrowserRevisionComponentKind,
|
|
481
|
+
format: boundedOpaqueId,
|
|
482
|
+
artifactDigest: sha256Hex,
|
|
483
|
+
sizeBytes: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
484
|
+
materialization: BrowserRevisionMaterialization,
|
|
485
|
+
})
|
|
486
|
+
.strict();
|
|
487
|
+
export type BrowserRevisionComponent = z.infer<typeof BrowserRevisionComponent>;
|
|
488
|
+
|
|
489
|
+
export const BrowserRevision = z
|
|
490
|
+
.object({
|
|
491
|
+
id: z.string().uuid(),
|
|
492
|
+
accountId: z.string().uuid(),
|
|
493
|
+
workspaceId: z.string().uuid(),
|
|
494
|
+
identityId: z.string().uuid(),
|
|
495
|
+
parentRevisionId: z.string().uuid().nullable(),
|
|
496
|
+
ordinal: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
497
|
+
sourceBrowserSessionId: z.string().uuid(),
|
|
498
|
+
manifestDigest: sha256Hex,
|
|
499
|
+
components: z.array(BrowserRevisionComponent).min(1).max(16),
|
|
500
|
+
createdBySubjectId: z.string().min(1).max(1_024),
|
|
501
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
502
|
+
})
|
|
503
|
+
.strict();
|
|
504
|
+
export type BrowserRevision = z.infer<typeof BrowserRevision>;
|
|
505
|
+
|
|
506
|
+
export const BrowserIdentity = z
|
|
507
|
+
.object({
|
|
508
|
+
id: z.string().uuid(),
|
|
509
|
+
accountId: z.string().uuid(),
|
|
510
|
+
workspaceId: z.string().uuid(),
|
|
511
|
+
name: z.string().trim().min(1).max(200),
|
|
512
|
+
status: BrowserIdentityStatus,
|
|
513
|
+
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
514
|
+
defaultRevisionId: z.string().uuid().nullable(),
|
|
515
|
+
headGeneration: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
516
|
+
revisionCount: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
517
|
+
createdBySubjectId: z.string().min(1).max(1_024),
|
|
518
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
519
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
520
|
+
})
|
|
521
|
+
.strict();
|
|
522
|
+
export type BrowserIdentity = z.infer<typeof BrowserIdentity>;
|
|
523
|
+
|
|
524
|
+
/** A non-secret reference to credential authority held by OpenGeni Connections.
|
|
525
|
+
* The subject and provider bindings are copied when the browser-auth resource is
|
|
526
|
+
* configured so a later agent cannot swap the UUID to another credential. */
|
|
527
|
+
export const InteractionCredentialAuthorityRef = z
|
|
528
|
+
.object({
|
|
529
|
+
connectionId: z.string().uuid(),
|
|
530
|
+
connectionSubjectId: z.string().min(1).max(1_024).nullable(),
|
|
531
|
+
providerDomain: z.string().trim().min(1).max(512),
|
|
532
|
+
})
|
|
533
|
+
.strict();
|
|
534
|
+
export type InteractionCredentialAuthorityRef = z.infer<typeof InteractionCredentialAuthorityRef>;
|
|
535
|
+
|
|
536
|
+
export const NetworkRouteStatus = z.enum(["active", "archived"]);
|
|
537
|
+
export type NetworkRouteStatus = z.infer<typeof NetworkRouteStatus>;
|
|
538
|
+
|
|
539
|
+
export const NetworkRouteConfiguration = z.discriminatedUnion("kind", [
|
|
540
|
+
z.object({ kind: z.literal("direct") }).strict(),
|
|
541
|
+
z
|
|
542
|
+
.object({
|
|
543
|
+
kind: z.literal("proxy"),
|
|
544
|
+
protocol: z.enum(["http", "https", "socks5"]),
|
|
545
|
+
host: z.string().trim().min(1).max(253),
|
|
546
|
+
port: z.number().int().min(1).max(65_535),
|
|
547
|
+
credential: InteractionCredentialAuthorityRef.nullable(),
|
|
548
|
+
})
|
|
549
|
+
.strict(),
|
|
550
|
+
z
|
|
551
|
+
.object({
|
|
552
|
+
kind: z.literal("managed"),
|
|
553
|
+
providerId: boundedOpaqueId,
|
|
554
|
+
routeId: boundedOpaqueId,
|
|
555
|
+
egressClass: z.enum(["datacenter", "residential", "isp"]),
|
|
556
|
+
region: z.string().trim().min(1).max(128).nullable(),
|
|
557
|
+
credential: InteractionCredentialAuthorityRef.nullable(),
|
|
558
|
+
})
|
|
559
|
+
.strict(),
|
|
560
|
+
z
|
|
561
|
+
.object({
|
|
562
|
+
kind: z.literal("tunnel"),
|
|
563
|
+
placement: InteractionPlacement,
|
|
564
|
+
tunnelId: boundedOpaqueId,
|
|
565
|
+
})
|
|
566
|
+
.strict(),
|
|
567
|
+
]);
|
|
568
|
+
export type NetworkRouteConfiguration = z.infer<typeof NetworkRouteConfiguration>;
|
|
569
|
+
|
|
570
|
+
export const NetworkRouteConsistency = z
|
|
571
|
+
.object({
|
|
572
|
+
dns: z.enum(["placement", "proxy", "provider"]),
|
|
573
|
+
expectedPublicIp: z.string().trim().min(1).max(128).nullable(),
|
|
574
|
+
expectedRegion: z.string().trim().min(1).max(128).nullable(),
|
|
575
|
+
locale: z.string().trim().min(1).max(64).nullable(),
|
|
576
|
+
timezone: z.string().trim().min(1).max(128).nullable(),
|
|
577
|
+
geolocation: z
|
|
578
|
+
.object({
|
|
579
|
+
latitude: z.number().finite().min(-90).max(90),
|
|
580
|
+
longitude: z.number().finite().min(-180).max(180),
|
|
581
|
+
accuracyMeters: z.number().finite().positive().max(1_000_000),
|
|
582
|
+
})
|
|
583
|
+
.strict()
|
|
584
|
+
.nullable(),
|
|
585
|
+
webRtc: z.enum(["default", "disable_non_proxied_udp", "proxy_only"]),
|
|
586
|
+
stability: z.enum(["session", "sticky", "persistent"]),
|
|
587
|
+
})
|
|
588
|
+
.strict();
|
|
589
|
+
export type NetworkRouteConsistency = z.infer<typeof NetworkRouteConsistency>;
|
|
590
|
+
|
|
591
|
+
export function interactionPlacementsEqual(
|
|
592
|
+
left: InteractionPlacement,
|
|
593
|
+
right: InteractionPlacement,
|
|
594
|
+
): boolean {
|
|
595
|
+
if (left.kind !== right.kind) return false;
|
|
596
|
+
switch (left.kind) {
|
|
597
|
+
case "sandbox_group":
|
|
598
|
+
return right.kind === "sandbox_group" && left.sandboxGroupId === right.sandboxGroupId;
|
|
599
|
+
case "connected_machine":
|
|
600
|
+
return right.kind === "connected_machine" && left.sandboxId === right.sandboxId;
|
|
601
|
+
case "attached_device":
|
|
602
|
+
return right.kind === "attached_device" && left.deviceId === right.deviceId;
|
|
603
|
+
case "external_provider":
|
|
604
|
+
return (
|
|
605
|
+
right.kind === "external_provider" &&
|
|
606
|
+
left.providerId === right.providerId &&
|
|
607
|
+
left.placementId === right.placementId
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** One secret-free compatibility rule shared by configuration, persistence,
|
|
613
|
+
* and launch. `null` placement means the ordinary managed sandbox default. */
|
|
614
|
+
export function networkRoutePlacementCompatibilityIssue(
|
|
615
|
+
configuration: NetworkRouteConfiguration,
|
|
616
|
+
consistency: NetworkRouteConsistency,
|
|
617
|
+
placement: InteractionPlacement | null,
|
|
618
|
+
): string | null {
|
|
619
|
+
if (configuration.kind === "tunnel") {
|
|
620
|
+
if (!placement || !interactionPlacementsEqual(configuration.placement, placement)) {
|
|
621
|
+
return "Tunnel route is bound to another placement";
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (configuration.kind === "managed") {
|
|
625
|
+
if (placement?.kind !== "external_provider") {
|
|
626
|
+
return "Managed NetworkRoutes require an external browser provider placement";
|
|
627
|
+
}
|
|
628
|
+
if (configuration.providerId !== placement.providerId) {
|
|
629
|
+
return "Managed NetworkRoute belongs to another external browser provider";
|
|
630
|
+
}
|
|
631
|
+
if (configuration.credential !== null) {
|
|
632
|
+
return "Managed provider routes cannot use a separate proxy credential";
|
|
633
|
+
}
|
|
634
|
+
if (consistency.dns !== "provider") {
|
|
635
|
+
return "Managed provider routes require provider DNS";
|
|
636
|
+
}
|
|
637
|
+
if (configuration.providerId === "browserbase") {
|
|
638
|
+
if (configuration.routeId !== "default" || configuration.egressClass !== "residential") {
|
|
639
|
+
return "Browserbase supports only its default managed residential route";
|
|
640
|
+
}
|
|
641
|
+
if (configuration.region !== null && !/^[A-Za-z]{2}$/u.test(configuration.region)) {
|
|
642
|
+
return "Browserbase managed route region must be a two-letter country code";
|
|
643
|
+
}
|
|
644
|
+
if (consistency.stability !== "session") {
|
|
645
|
+
return "Browserbase managed routing cannot promise a stable IP across sessions";
|
|
646
|
+
}
|
|
647
|
+
} else if (configuration.providerId !== "kernel") {
|
|
648
|
+
return `Managed NetworkRoute provider ${configuration.providerId} is unsupported`;
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
if (placement?.kind === "external_provider") {
|
|
653
|
+
return "External browser providers require a provider-managed NetworkRoute";
|
|
654
|
+
}
|
|
655
|
+
const expectedDns = configuration.kind === "proxy" ? "proxy" : "placement";
|
|
656
|
+
if (consistency.dns !== expectedDns) {
|
|
657
|
+
return `Network route ${configuration.kind} cannot provide ${consistency.dns} DNS`;
|
|
658
|
+
}
|
|
659
|
+
if (consistency.webRtc === "proxy_only" && configuration.kind !== "proxy") {
|
|
660
|
+
return "WebRTC proxy-only routing requires a proxy network route";
|
|
661
|
+
}
|
|
662
|
+
if (placement?.kind === "attached_device") {
|
|
663
|
+
if (configuration.kind === "proxy") {
|
|
664
|
+
return "Attached Chrome cannot change its process-scoped proxy configuration";
|
|
665
|
+
}
|
|
666
|
+
if (
|
|
667
|
+
consistency.locale !== null ||
|
|
668
|
+
consistency.timezone !== null ||
|
|
669
|
+
consistency.geolocation !== null ||
|
|
670
|
+
consistency.webRtc !== "default"
|
|
671
|
+
) {
|
|
672
|
+
return "Attached Chrome cannot change process-scoped route emulation";
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
export const NetworkRoute = z
|
|
679
|
+
.object({
|
|
680
|
+
id: z.string().uuid(),
|
|
681
|
+
accountId: z.string().uuid(),
|
|
682
|
+
workspaceId: z.string().uuid(),
|
|
683
|
+
name: z.string().trim().min(1).max(200),
|
|
684
|
+
status: NetworkRouteStatus,
|
|
685
|
+
configuration: NetworkRouteConfiguration,
|
|
686
|
+
consistency: NetworkRouteConsistency,
|
|
687
|
+
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
688
|
+
createdBySubjectId: z.string().min(1).max(1_024),
|
|
689
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
690
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
691
|
+
})
|
|
692
|
+
.strict();
|
|
693
|
+
export type NetworkRoute = z.infer<typeof NetworkRoute>;
|
|
694
|
+
|
|
695
|
+
export const NetworkRouteListResponse = z
|
|
696
|
+
.object({
|
|
697
|
+
revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
698
|
+
routes: z.array(NetworkRoute).max(10_000),
|
|
699
|
+
})
|
|
700
|
+
.strict();
|
|
701
|
+
export type NetworkRouteListResponse = z.infer<typeof NetworkRouteListResponse>;
|
|
702
|
+
|
|
703
|
+
export const CreateNetworkRouteRequest = z
|
|
704
|
+
.object({
|
|
705
|
+
operationId: z.string().uuid(),
|
|
706
|
+
name: z.string().trim().min(1).max(200),
|
|
707
|
+
configuration: NetworkRouteConfiguration,
|
|
708
|
+
consistency: NetworkRouteConsistency,
|
|
709
|
+
})
|
|
710
|
+
.strict();
|
|
711
|
+
export type CreateNetworkRouteRequest = z.infer<typeof CreateNetworkRouteRequest>;
|
|
712
|
+
|
|
713
|
+
export const UpdateNetworkRouteRequest = z
|
|
714
|
+
.object({
|
|
715
|
+
operationId: z.string().uuid(),
|
|
716
|
+
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
717
|
+
name: z.string().trim().min(1).max(200).optional(),
|
|
718
|
+
status: NetworkRouteStatus.optional(),
|
|
719
|
+
configuration: NetworkRouteConfiguration.optional(),
|
|
720
|
+
consistency: NetworkRouteConsistency.optional(),
|
|
721
|
+
})
|
|
722
|
+
.strict()
|
|
723
|
+
.refine(
|
|
724
|
+
(value) =>
|
|
725
|
+
value.name !== undefined ||
|
|
726
|
+
value.status !== undefined ||
|
|
727
|
+
value.configuration !== undefined ||
|
|
728
|
+
value.consistency !== undefined,
|
|
729
|
+
{ message: "network route update is empty" },
|
|
730
|
+
);
|
|
731
|
+
export type UpdateNetworkRouteRequest = z.infer<typeof UpdateNetworkRouteRequest>;
|
|
732
|
+
|
|
733
|
+
export const NetworkRouteMutationResponse = z
|
|
734
|
+
.object({
|
|
735
|
+
route: NetworkRoute,
|
|
736
|
+
operationId: z.string().uuid(),
|
|
737
|
+
replayed: z.boolean(),
|
|
738
|
+
})
|
|
739
|
+
.strict();
|
|
740
|
+
export type NetworkRouteMutationResponse = z.infer<typeof NetworkRouteMutationResponse>;
|
|
741
|
+
|
|
742
|
+
export const SiteAuthFieldPurpose = z.enum(["identifier", "password", "secret", "totp"]);
|
|
743
|
+
export type SiteAuthFieldPurpose = z.infer<typeof SiteAuthFieldPurpose>;
|
|
744
|
+
|
|
745
|
+
export const SiteAuthAuthority = z.discriminatedUnion("kind", [
|
|
746
|
+
z
|
|
747
|
+
.object({
|
|
748
|
+
id: boundedOpaqueId,
|
|
749
|
+
kind: z.literal("connection_fields"),
|
|
750
|
+
label: z.string().trim().min(1).max(200),
|
|
751
|
+
credential: InteractionCredentialAuthorityRef,
|
|
752
|
+
fields: z
|
|
753
|
+
.array(
|
|
754
|
+
z
|
|
755
|
+
.object({
|
|
756
|
+
id: boundedOpaqueId,
|
|
757
|
+
purpose: SiteAuthFieldPurpose,
|
|
758
|
+
credentialKey: z.string().trim().min(1).max(256),
|
|
759
|
+
digits: z.number().int().min(6).max(10).optional(),
|
|
760
|
+
periodSeconds: z.number().int().min(15).max(300).optional(),
|
|
761
|
+
algorithm: z.enum(["sha1", "sha256", "sha512"]).optional(),
|
|
762
|
+
})
|
|
763
|
+
.strict()
|
|
764
|
+
.superRefine((field, context) => {
|
|
765
|
+
const totp = field.purpose === "totp";
|
|
766
|
+
if (!totp && (field.digits || field.periodSeconds || field.algorithm)) {
|
|
767
|
+
context.addIssue({
|
|
768
|
+
code: "custom",
|
|
769
|
+
message: "TOTP parameters require purpose=totp",
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}),
|
|
773
|
+
)
|
|
774
|
+
.min(1)
|
|
775
|
+
.max(32),
|
|
776
|
+
})
|
|
777
|
+
.strict(),
|
|
778
|
+
z
|
|
779
|
+
.object({
|
|
780
|
+
id: boundedOpaqueId,
|
|
781
|
+
kind: z.literal("human"),
|
|
782
|
+
label: z.string().trim().min(1).max(200),
|
|
783
|
+
fields: z
|
|
784
|
+
.array(z.object({ id: boundedOpaqueId, purpose: SiteAuthFieldPurpose }).strict())
|
|
785
|
+
.max(32),
|
|
786
|
+
})
|
|
787
|
+
.strict(),
|
|
788
|
+
z
|
|
789
|
+
.object({
|
|
790
|
+
id: boundedOpaqueId,
|
|
791
|
+
kind: z.literal("external_provider"),
|
|
792
|
+
label: z.string().trim().min(1).max(200),
|
|
793
|
+
adapterId: boundedOpaqueId,
|
|
794
|
+
/** Opaque provider-owned auth-connection reference. It is meaningful
|
|
795
|
+
* only to the named adapter and never treated as credential material. */
|
|
796
|
+
connectionId: boundedOpaqueId,
|
|
797
|
+
credential: InteractionCredentialAuthorityRef.nullable(),
|
|
798
|
+
})
|
|
799
|
+
.strict(),
|
|
800
|
+
]);
|
|
801
|
+
export type SiteAuthAuthority = z.infer<typeof SiteAuthAuthority>;
|
|
802
|
+
|
|
803
|
+
export const SiteAuthMethod = z
|
|
804
|
+
.object({
|
|
805
|
+
id: boundedOpaqueId,
|
|
806
|
+
kind: z.enum(["password", "sso", "magic_link", "passkey", "external"]),
|
|
807
|
+
label: z.string().trim().min(1).max(200),
|
|
808
|
+
authorityIds: z.array(boundedOpaqueId).min(1).max(32),
|
|
809
|
+
})
|
|
810
|
+
.strict();
|
|
811
|
+
export type SiteAuthMethod = z.infer<typeof SiteAuthMethod>;
|
|
812
|
+
|
|
813
|
+
export const SiteAuthHealthPolicy = z
|
|
814
|
+
.object({
|
|
815
|
+
mode: z.enum(["on_use", "maintained"]),
|
|
816
|
+
intervalSeconds: z.number().int().min(60).max(31_536_000).nullable(),
|
|
817
|
+
automaticRepair: z.boolean(),
|
|
818
|
+
})
|
|
819
|
+
.strict()
|
|
820
|
+
.superRefine((policy, context) => {
|
|
821
|
+
if (policy.mode === "maintained" && policy.intervalSeconds === null) {
|
|
822
|
+
context.addIssue({ code: "custom", message: "maintained auth requires an interval" });
|
|
823
|
+
}
|
|
824
|
+
if (policy.mode === "on_use" && policy.intervalSeconds !== null) {
|
|
825
|
+
context.addIssue({ code: "custom", message: "on-use auth cannot have an interval" });
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
export type SiteAuthHealthPolicy = z.infer<typeof SiteAuthHealthPolicy>;
|
|
829
|
+
|
|
830
|
+
export const SiteAuthVerificationState = z.enum(["unknown", "verified", "needs_repair", "failed"]);
|
|
831
|
+
export type SiteAuthVerificationState = z.infer<typeof SiteAuthVerificationState>;
|
|
832
|
+
|
|
833
|
+
export const SiteAuthMaintenance = z
|
|
834
|
+
.object({
|
|
835
|
+
action: z.enum(["health_check", "repair"]),
|
|
836
|
+
/** Hidden until the durable session start has been confirmed. */
|
|
837
|
+
sessionId: z.string().uuid().nullable(),
|
|
838
|
+
dueAt: z.string().datetime({ offset: true }),
|
|
839
|
+
startedAt: z.string().datetime({ offset: true }).nullable(),
|
|
840
|
+
})
|
|
841
|
+
.strict();
|
|
842
|
+
export type SiteAuthMaintenance = z.infer<typeof SiteAuthMaintenance>;
|
|
843
|
+
|
|
844
|
+
/** Trusted service provenance for scheduler-authored maintenance sessions. */
|
|
845
|
+
export const SITE_AUTH_MAINTENANCE_OPERATION_CONTEXT_KEY =
|
|
846
|
+
"opengeniSiteAuthMaintenanceOperationId" as const;
|
|
847
|
+
export const SITE_AUTH_MAINTENANCE_CONNECTION_CONTEXT_KEY = "opengeniSiteAuthConnectionId" as const;
|
|
848
|
+
|
|
849
|
+
const SiteAuthConnectionConfiguration = z
|
|
850
|
+
.object({
|
|
851
|
+
name: z.string().trim().min(1).max(200),
|
|
852
|
+
accountLabel: z.string().trim().min(1).max(200),
|
|
853
|
+
origins: z.array(canonicalWebOrigin).min(1).max(64),
|
|
854
|
+
loginUrl: boundedHttpUrl.nullable(),
|
|
855
|
+
verificationUrlPrefixes: z.array(boundedHttpUrl).max(32),
|
|
856
|
+
authorities: z.array(SiteAuthAuthority).min(1).max(32),
|
|
857
|
+
methods: z.array(SiteAuthMethod).min(1).max(32),
|
|
858
|
+
preferredIdentityId: z.string().uuid().nullable(),
|
|
859
|
+
preferredPlacement: InteractionPlacement.nullable(),
|
|
860
|
+
preferredNetworkRouteId: z.string().uuid().nullable(),
|
|
861
|
+
healthPolicy: SiteAuthHealthPolicy,
|
|
862
|
+
})
|
|
863
|
+
.strict();
|
|
864
|
+
|
|
865
|
+
function validateSiteAuthConfiguration(
|
|
866
|
+
connection: z.infer<typeof SiteAuthConnectionConfiguration>,
|
|
867
|
+
context: z.RefinementCtx,
|
|
868
|
+
): void {
|
|
869
|
+
if (
|
|
870
|
+
connection.preferredIdentityId &&
|
|
871
|
+
(connection.preferredPlacement?.kind === "attached_device" ||
|
|
872
|
+
connection.preferredPlacement?.kind === "external_provider")
|
|
873
|
+
) {
|
|
874
|
+
context.addIssue({
|
|
875
|
+
code: "custom",
|
|
876
|
+
path: ["preferredIdentityId"],
|
|
877
|
+
message: "the preferred placement owns its live profile identity",
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
if (new Set(connection.origins).size !== connection.origins.length) {
|
|
881
|
+
context.addIssue({ code: "custom", path: ["origins"], message: "origins repeat" });
|
|
882
|
+
}
|
|
883
|
+
if (
|
|
884
|
+
new Set(connection.verificationUrlPrefixes).size !== connection.verificationUrlPrefixes.length
|
|
885
|
+
) {
|
|
886
|
+
context.addIssue({
|
|
887
|
+
code: "custom",
|
|
888
|
+
path: ["verificationUrlPrefixes"],
|
|
889
|
+
message: "verification URL prefixes repeat",
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
const allowedOrigins = new Set(connection.origins);
|
|
893
|
+
const checkedUrls = [
|
|
894
|
+
...(connection.loginUrl ? [{ path: ["loginUrl"], value: connection.loginUrl }] : []),
|
|
895
|
+
...connection.verificationUrlPrefixes.map((value, index) => ({
|
|
896
|
+
path: ["verificationUrlPrefixes", index],
|
|
897
|
+
value,
|
|
898
|
+
})),
|
|
899
|
+
];
|
|
900
|
+
for (const checked of checkedUrls) {
|
|
901
|
+
if (!allowedOrigins.has(new URL(checked.value).origin)) {
|
|
902
|
+
context.addIssue({
|
|
903
|
+
code: "custom",
|
|
904
|
+
path: checked.path,
|
|
905
|
+
message: "URL origin must be explicitly allowed",
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
const authorityIds = new Set(connection.authorities.map((authority) => authority.id));
|
|
910
|
+
if (authorityIds.size !== connection.authorities.length) {
|
|
911
|
+
context.addIssue({ code: "custom", path: ["authorities"], message: "authority ids repeat" });
|
|
912
|
+
}
|
|
913
|
+
const methodIds = new Set(connection.methods.map((method) => method.id));
|
|
914
|
+
if (methodIds.size !== connection.methods.length) {
|
|
915
|
+
context.addIssue({ code: "custom", path: ["methods"], message: "method ids repeat" });
|
|
916
|
+
}
|
|
917
|
+
for (const [index, method] of connection.methods.entries()) {
|
|
918
|
+
if (new Set(method.authorityIds).size !== method.authorityIds.length) {
|
|
919
|
+
context.addIssue({
|
|
920
|
+
code: "custom",
|
|
921
|
+
path: ["methods", index, "authorityIds"],
|
|
922
|
+
message: "method authority ids repeat",
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
if (method.authorityIds.some((id) => !authorityIds.has(id))) {
|
|
926
|
+
context.addIssue({
|
|
927
|
+
code: "custom",
|
|
928
|
+
path: ["methods", index, "authorityIds"],
|
|
929
|
+
message: "method references an unknown authority",
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
for (const [index, authority] of connection.authorities.entries()) {
|
|
934
|
+
if (authority.kind === "external_provider") continue;
|
|
935
|
+
const fieldIds = authority.fields.map((field) => field.id);
|
|
936
|
+
if (new Set(fieldIds).size !== fieldIds.length) {
|
|
937
|
+
context.addIssue({
|
|
938
|
+
code: "custom",
|
|
939
|
+
path: ["authorities", index, "fields"],
|
|
940
|
+
message: "authority field ids repeat",
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
export const SiteAuthConnection = SiteAuthConnectionConfiguration.extend({
|
|
947
|
+
id: z.string().uuid(),
|
|
948
|
+
accountId: z.string().uuid(),
|
|
949
|
+
workspaceId: z.string().uuid(),
|
|
950
|
+
status: z.enum(["active", "archived"]),
|
|
951
|
+
verificationState: SiteAuthVerificationState,
|
|
952
|
+
lastVerifiedAt: z.string().datetime({ offset: true }).nullable(),
|
|
953
|
+
lastVerifiedUrl: boundedUrl.nullable(),
|
|
954
|
+
lastCheckedAt: z.string().datetime({ offset: true }).nullable(),
|
|
955
|
+
nextCheckAt: z.string().datetime({ offset: true }).nullable(),
|
|
956
|
+
maintenance: SiteAuthMaintenance.nullable(),
|
|
957
|
+
repairCode: boundedOpaqueId.nullable(),
|
|
958
|
+
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
959
|
+
createdBySubjectId: z.string().min(1).max(1_024),
|
|
960
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
961
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
962
|
+
}).superRefine(validateSiteAuthConfiguration);
|
|
963
|
+
export type SiteAuthConnection = z.infer<typeof SiteAuthConnection>;
|
|
964
|
+
|
|
965
|
+
export const SiteAuthConnectionListResponse = z
|
|
966
|
+
.object({
|
|
967
|
+
revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
968
|
+
connections: z.array(SiteAuthConnection).max(10_000),
|
|
969
|
+
})
|
|
970
|
+
.strict();
|
|
971
|
+
export type SiteAuthConnectionListResponse = z.infer<typeof SiteAuthConnectionListResponse>;
|
|
972
|
+
|
|
973
|
+
export const CreateSiteAuthConnectionRequest = SiteAuthConnectionConfiguration.extend({
|
|
974
|
+
operationId: z.string().uuid(),
|
|
975
|
+
}).superRefine(validateSiteAuthConfiguration);
|
|
976
|
+
export type CreateSiteAuthConnectionRequest = z.infer<typeof CreateSiteAuthConnectionRequest>;
|
|
977
|
+
|
|
978
|
+
export const UpdateSiteAuthConnectionRequest = SiteAuthConnectionConfiguration.partial()
|
|
979
|
+
.extend({
|
|
980
|
+
operationId: z.string().uuid(),
|
|
981
|
+
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
982
|
+
status: z.enum(["active", "archived"]).optional(),
|
|
983
|
+
})
|
|
984
|
+
.strict()
|
|
985
|
+
.refine(
|
|
986
|
+
(value) => Object.keys(value).some((key) => !["operationId", "expectedVersion"].includes(key)),
|
|
987
|
+
{
|
|
988
|
+
message: "site auth connection update is empty",
|
|
989
|
+
},
|
|
990
|
+
);
|
|
991
|
+
export type UpdateSiteAuthConnectionRequest = z.infer<typeof UpdateSiteAuthConnectionRequest>;
|
|
992
|
+
|
|
993
|
+
export const SiteAuthConnectionMutationResponse = z
|
|
994
|
+
.object({
|
|
995
|
+
connection: SiteAuthConnection,
|
|
996
|
+
operationId: z.string().uuid(),
|
|
997
|
+
replayed: z.boolean(),
|
|
998
|
+
})
|
|
999
|
+
.strict();
|
|
1000
|
+
export type SiteAuthConnectionMutationResponse = z.infer<typeof SiteAuthConnectionMutationResponse>;
|
|
1001
|
+
|
|
1002
|
+
export const AuthRunState = z.enum([
|
|
1003
|
+
"discovering",
|
|
1004
|
+
"awaiting_choice",
|
|
1005
|
+
"awaiting_secret",
|
|
1006
|
+
"awaiting_external_action",
|
|
1007
|
+
"working",
|
|
1008
|
+
"verified",
|
|
1009
|
+
"failed",
|
|
1010
|
+
"cancelled",
|
|
1011
|
+
]);
|
|
1012
|
+
export type AuthRunState = z.infer<typeof AuthRunState>;
|
|
1013
|
+
|
|
1014
|
+
export const AuthRunChoice = z
|
|
1015
|
+
.object({
|
|
1016
|
+
id: boundedOpaqueId,
|
|
1017
|
+
label: z.string().trim().min(1).max(200),
|
|
1018
|
+
methodId: boundedOpaqueId,
|
|
1019
|
+
})
|
|
1020
|
+
.strict();
|
|
1021
|
+
export type AuthRunChoice = z.infer<typeof AuthRunChoice>;
|
|
1022
|
+
|
|
1023
|
+
export const AuthRunPendingField = z
|
|
1024
|
+
.object({
|
|
1025
|
+
id: boundedOpaqueId,
|
|
1026
|
+
label: z.string().trim().min(1).max(200),
|
|
1027
|
+
purpose: SiteAuthFieldPurpose,
|
|
1028
|
+
})
|
|
1029
|
+
.strict();
|
|
1030
|
+
export type AuthRunPendingField = z.infer<typeof AuthRunPendingField>;
|
|
1031
|
+
|
|
1032
|
+
export const AuthRunExternalAction = z
|
|
1033
|
+
.object({
|
|
1034
|
+
kind: z.enum(["push", "security_key", "passkey", "device", "human", "other"]),
|
|
1035
|
+
label: z.string().trim().min(1).max(500),
|
|
1036
|
+
expiresAt: z.string().datetime({ offset: true }).nullable(),
|
|
1037
|
+
})
|
|
1038
|
+
.strict();
|
|
1039
|
+
export type AuthRunExternalAction = z.infer<typeof AuthRunExternalAction>;
|
|
1040
|
+
|
|
1041
|
+
export const AuthRun = z
|
|
1042
|
+
.object({
|
|
1043
|
+
id: z.string().uuid(),
|
|
1044
|
+
accountId: z.string().uuid(),
|
|
1045
|
+
workspaceId: z.string().uuid(),
|
|
1046
|
+
siteAuthConnectionId: z.string().uuid(),
|
|
1047
|
+
browserSessionId: z.string().uuid(),
|
|
1048
|
+
targetId: boundedOpaqueId,
|
|
1049
|
+
controllerGeneration: opaqueGeneration,
|
|
1050
|
+
targetGeneration: opaqueGeneration,
|
|
1051
|
+
documentGeneration: opaqueGeneration.nullable(),
|
|
1052
|
+
purpose: z.enum(["authenticate", "health_check", "repair"]),
|
|
1053
|
+
methodId: boundedOpaqueId.nullable(),
|
|
1054
|
+
authorityId: boundedOpaqueId.nullable(),
|
|
1055
|
+
state: AuthRunState,
|
|
1056
|
+
choices: z.array(AuthRunChoice).max(64),
|
|
1057
|
+
pendingFields: z.array(AuthRunPendingField).max(64),
|
|
1058
|
+
externalAction: AuthRunExternalAction.nullable(),
|
|
1059
|
+
interventionId: z.string().uuid().nullable(),
|
|
1060
|
+
verifiedUrl: boundedUrl.nullable(),
|
|
1061
|
+
failureCode: boundedOpaqueId.nullable(),
|
|
1062
|
+
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1063
|
+
operationId: z.string().uuid(),
|
|
1064
|
+
createdBySubjectId: z.string().min(1).max(1_024),
|
|
1065
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
1066
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
1067
|
+
settledAt: z.string().datetime({ offset: true }).nullable(),
|
|
1068
|
+
})
|
|
1069
|
+
.strict()
|
|
1070
|
+
.superRefine((run, context) => {
|
|
1071
|
+
const settled = run.state === "verified" || run.state === "failed" || run.state === "cancelled";
|
|
1072
|
+
if (settled !== (run.settledAt !== null)) {
|
|
1073
|
+
context.addIssue({
|
|
1074
|
+
code: "custom",
|
|
1075
|
+
path: ["settledAt"],
|
|
1076
|
+
message: "settled state and timestamp must agree",
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
if ((run.state === "verified") !== (run.verifiedUrl !== null)) {
|
|
1080
|
+
context.addIssue({
|
|
1081
|
+
code: "custom",
|
|
1082
|
+
path: ["verifiedUrl"],
|
|
1083
|
+
message: "verified URL requires verified state",
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
if ((run.state === "failed") !== (run.failureCode !== null)) {
|
|
1087
|
+
context.addIssue({
|
|
1088
|
+
code: "custom",
|
|
1089
|
+
path: ["failureCode"],
|
|
1090
|
+
message: "failure code requires failed state",
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
if ((run.state === "awaiting_choice") !== run.choices.length > 0) {
|
|
1094
|
+
context.addIssue({
|
|
1095
|
+
code: "custom",
|
|
1096
|
+
path: ["choices"],
|
|
1097
|
+
message: "choices require awaiting-choice state",
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
if ((run.state === "awaiting_secret") !== run.pendingFields.length > 0) {
|
|
1101
|
+
context.addIssue({
|
|
1102
|
+
code: "custom",
|
|
1103
|
+
path: ["pendingFields"],
|
|
1104
|
+
message: "pending fields require awaiting-secret state",
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
if ((run.state === "awaiting_external_action") !== (run.externalAction !== null)) {
|
|
1108
|
+
context.addIssue({
|
|
1109
|
+
code: "custom",
|
|
1110
|
+
path: ["externalAction"],
|
|
1111
|
+
message: "external action requires its waiting state",
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
export type AuthRun = z.infer<typeof AuthRun>;
|
|
1116
|
+
|
|
1117
|
+
export const AuthRunListResponse = z.object({ runs: z.array(AuthRun).max(10_000) }).strict();
|
|
1118
|
+
export type AuthRunListResponse = z.infer<typeof AuthRunListResponse>;
|
|
1119
|
+
|
|
1120
|
+
export const AuthRunMutationResponse = z
|
|
1121
|
+
.object({
|
|
1122
|
+
run: AuthRun,
|
|
1123
|
+
operationId: z.string().uuid(),
|
|
1124
|
+
replayed: z.boolean(),
|
|
1125
|
+
})
|
|
1126
|
+
.strict();
|
|
1127
|
+
export type AuthRunMutationResponse = z.infer<typeof AuthRunMutationResponse>;
|
|
1128
|
+
|
|
1129
|
+
export const StartAuthRunRequest = z
|
|
1130
|
+
.object({
|
|
1131
|
+
operationId: z.string().uuid(),
|
|
1132
|
+
siteAuthConnectionId: z.string().uuid(),
|
|
1133
|
+
targetId: boundedOpaqueId,
|
|
1134
|
+
expectedTargetGeneration: opaqueGeneration,
|
|
1135
|
+
expectedDocumentGeneration: opaqueGeneration.nullable(),
|
|
1136
|
+
purpose: z.enum(["authenticate", "health_check", "repair"]).optional(),
|
|
1137
|
+
methodId: boundedOpaqueId.optional(),
|
|
1138
|
+
authorityId: boundedOpaqueId.optional(),
|
|
1139
|
+
})
|
|
1140
|
+
.strict();
|
|
1141
|
+
export type StartAuthRunRequest = z.infer<typeof StartAuthRunRequest>;
|
|
1142
|
+
|
|
1143
|
+
export const ReportAuthRunPayload = z
|
|
1144
|
+
.object({
|
|
1145
|
+
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1146
|
+
methodId: boundedOpaqueId.optional(),
|
|
1147
|
+
authorityId: boundedOpaqueId.optional(),
|
|
1148
|
+
state: z.enum([
|
|
1149
|
+
"awaiting_choice",
|
|
1150
|
+
"awaiting_secret",
|
|
1151
|
+
"awaiting_external_action",
|
|
1152
|
+
"working",
|
|
1153
|
+
"failed",
|
|
1154
|
+
"cancelled",
|
|
1155
|
+
]),
|
|
1156
|
+
choices: z.array(AuthRunChoice).max(64).optional(),
|
|
1157
|
+
pendingFields: z.array(AuthRunPendingField).max(64).optional(),
|
|
1158
|
+
externalAction: AuthRunExternalAction.nullable().optional(),
|
|
1159
|
+
failureCode: boundedOpaqueId.nullable().optional(),
|
|
1160
|
+
})
|
|
1161
|
+
.strict()
|
|
1162
|
+
.superRefine((report, context) => {
|
|
1163
|
+
if ((report.state === "awaiting_choice") !== Boolean(report.choices?.length)) {
|
|
1164
|
+
context.addIssue({
|
|
1165
|
+
code: "custom",
|
|
1166
|
+
path: ["choices"],
|
|
1167
|
+
message: "choices require awaiting-choice state",
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
if ((report.state === "awaiting_secret") !== Boolean(report.pendingFields?.length)) {
|
|
1171
|
+
context.addIssue({
|
|
1172
|
+
code: "custom",
|
|
1173
|
+
path: ["pendingFields"],
|
|
1174
|
+
message: "pending fields require awaiting-secret state",
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
if ((report.state === "awaiting_external_action") !== Boolean(report.externalAction)) {
|
|
1178
|
+
context.addIssue({
|
|
1179
|
+
code: "custom",
|
|
1180
|
+
path: ["externalAction"],
|
|
1181
|
+
message: "external action requires its waiting state",
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
if ((report.state === "failed") !== Boolean(report.failureCode)) {
|
|
1185
|
+
context.addIssue({
|
|
1186
|
+
code: "custom",
|
|
1187
|
+
path: ["failureCode"],
|
|
1188
|
+
message: "failure code requires failed state",
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
export type ReportAuthRunPayload = z.infer<typeof ReportAuthRunPayload>;
|
|
1193
|
+
|
|
1194
|
+
export const ReportAuthRunRequest = ReportAuthRunPayload.safeExtend({
|
|
1195
|
+
operationId: z.string().uuid(),
|
|
1196
|
+
});
|
|
1197
|
+
export type ReportAuthRunRequest = z.infer<typeof ReportAuthRunRequest>;
|
|
1198
|
+
|
|
1199
|
+
export const ProtectedAuthField = z
|
|
1200
|
+
.object({ fieldId: boundedOpaqueId, locator: z.lazy(() => BrowserLocator) })
|
|
1201
|
+
.strict();
|
|
1202
|
+
export type ProtectedAuthField = z.infer<typeof ProtectedAuthField>;
|
|
1203
|
+
|
|
1204
|
+
export const ProtectedAuthSubmit = z.discriminatedUnion("type", [
|
|
1205
|
+
z.object({ type: z.literal("none") }).strict(),
|
|
1206
|
+
z.object({ type: z.literal("click"), locator: z.lazy(() => BrowserLocator) }).strict(),
|
|
1207
|
+
z
|
|
1208
|
+
.object({
|
|
1209
|
+
type: z.literal("press"),
|
|
1210
|
+
key: z.string().min(1).max(256),
|
|
1211
|
+
locator: z.lazy(() => BrowserLocator).optional(),
|
|
1212
|
+
})
|
|
1213
|
+
.strict(),
|
|
1214
|
+
]);
|
|
1215
|
+
export type ProtectedAuthSubmit = z.infer<typeof ProtectedAuthSubmit>;
|
|
1216
|
+
|
|
1217
|
+
export const ProtectedAuthFillRequest = z
|
|
1218
|
+
.object({
|
|
1219
|
+
operationId: z.string().uuid(),
|
|
1220
|
+
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1221
|
+
expectedTargetGeneration: opaqueGeneration,
|
|
1222
|
+
expectedDocumentGeneration: opaqueGeneration.nullable(),
|
|
1223
|
+
expectedFrameId: opaqueGeneration.nullable(),
|
|
1224
|
+
authorityId: boundedOpaqueId,
|
|
1225
|
+
fields: z.array(ProtectedAuthField).min(1).max(32),
|
|
1226
|
+
submit: ProtectedAuthSubmit.default({ type: "none" }),
|
|
1227
|
+
})
|
|
1228
|
+
.strict();
|
|
1229
|
+
export type ProtectedAuthFillRequest = z.infer<typeof ProtectedAuthFillRequest>;
|
|
1230
|
+
|
|
1231
|
+
export const ProtectedAuthFillResponse = z
|
|
1232
|
+
.object({
|
|
1233
|
+
run: AuthRun,
|
|
1234
|
+
status: z.enum(["submitted", "working", "needs_human", "stale", "failed"]),
|
|
1235
|
+
operationId: z.string().uuid(),
|
|
1236
|
+
replayed: z.boolean(),
|
|
1237
|
+
})
|
|
1238
|
+
.strict();
|
|
1239
|
+
export type ProtectedAuthFillResponse = z.infer<typeof ProtectedAuthFillResponse>;
|
|
1240
|
+
|
|
1241
|
+
/** Public request for advancing a provider-managed AuthRun. Provider secrets,
|
|
1242
|
+
* profile names, browser ids, and hosted-login URLs stay behind browserd. */
|
|
1243
|
+
export const ExternalAuthRunRequest = z
|
|
1244
|
+
.object({
|
|
1245
|
+
operationId: z.string().uuid(),
|
|
1246
|
+
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1247
|
+
action: z.enum(["start", "poll"]),
|
|
1248
|
+
})
|
|
1249
|
+
.strict();
|
|
1250
|
+
export type ExternalAuthRunRequest = z.infer<typeof ExternalAuthRunRequest>;
|
|
1251
|
+
|
|
1252
|
+
export const ExternalAuthRunResponse = z
|
|
1253
|
+
.object({
|
|
1254
|
+
run: AuthRun,
|
|
1255
|
+
status: z.enum(["working", "needs_human", "ready_to_verify", "failed"]),
|
|
1256
|
+
operationId: z.string().uuid(),
|
|
1257
|
+
replayed: z.boolean(),
|
|
1258
|
+
})
|
|
1259
|
+
.strict();
|
|
1260
|
+
export type ExternalAuthRunResponse = z.infer<typeof ExternalAuthRunResponse>;
|
|
1261
|
+
|
|
1262
|
+
/** Human-only request for opening the provider's ephemeral hosted login UI. */
|
|
1263
|
+
export const ExternalAuthInteractiveRequest = z
|
|
1264
|
+
.object({
|
|
1265
|
+
operationId: z.string().uuid(),
|
|
1266
|
+
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1267
|
+
})
|
|
1268
|
+
.strict();
|
|
1269
|
+
export type ExternalAuthInteractiveRequest = z.infer<typeof ExternalAuthInteractiveRequest>;
|
|
1270
|
+
|
|
1271
|
+
export const ExternalAuthInteractiveResponse = z
|
|
1272
|
+
.object({
|
|
1273
|
+
authRunId: z.string().uuid(),
|
|
1274
|
+
url: boundedHttpUrl,
|
|
1275
|
+
expiresAt: z.string().datetime({ offset: true }).nullable(),
|
|
1276
|
+
})
|
|
1277
|
+
.strict();
|
|
1278
|
+
export type ExternalAuthInteractiveResponse = z.infer<typeof ExternalAuthInteractiveResponse>;
|
|
1279
|
+
|
|
1280
|
+
/** Controller-private Kernel/host-adapter command. This crosses only the
|
|
1281
|
+
* authenticated API -> browserd control channel; it is not a model tool. */
|
|
1282
|
+
export const BrowserExternalAuthCommand = z
|
|
1283
|
+
.object({
|
|
1284
|
+
browserSessionId: z.string().uuid(),
|
|
1285
|
+
controllerGeneration: opaqueGeneration,
|
|
1286
|
+
operationId: z.string().uuid(),
|
|
1287
|
+
authRunId: z.string().uuid(),
|
|
1288
|
+
adapterId: boundedOpaqueId,
|
|
1289
|
+
connectionId: boundedOpaqueId,
|
|
1290
|
+
action: z.enum(["start", "poll", "interactive"]),
|
|
1291
|
+
})
|
|
1292
|
+
.strict();
|
|
1293
|
+
export type BrowserExternalAuthCommand = z.infer<typeof BrowserExternalAuthCommand>;
|
|
1294
|
+
|
|
1295
|
+
export const BrowserExternalAuthResult = z
|
|
1296
|
+
.object({
|
|
1297
|
+
state: z.enum(["authenticated", "in_progress", "needs_human", "failed"]),
|
|
1298
|
+
externalAction: AuthRunExternalAction.nullable(),
|
|
1299
|
+
interactiveUrl: boundedHttpUrl.nullable(),
|
|
1300
|
+
failureCode: boundedOpaqueId.nullable(),
|
|
1301
|
+
profileLoaded: z.boolean(),
|
|
1302
|
+
})
|
|
1303
|
+
.strict()
|
|
1304
|
+
.superRefine((result, context) => {
|
|
1305
|
+
if ((result.state === "needs_human") !== (result.externalAction !== null)) {
|
|
1306
|
+
context.addIssue({
|
|
1307
|
+
code: "custom",
|
|
1308
|
+
path: ["externalAction"],
|
|
1309
|
+
message: "human external-auth state requires an action",
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
if ((result.state === "failed") !== (result.failureCode !== null)) {
|
|
1313
|
+
context.addIssue({
|
|
1314
|
+
code: "custom",
|
|
1315
|
+
path: ["failureCode"],
|
|
1316
|
+
message: "failed external-auth state requires a failure code",
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
if (result.interactiveUrl !== null && result.state !== "needs_human") {
|
|
1320
|
+
context.addIssue({
|
|
1321
|
+
code: "custom",
|
|
1322
|
+
path: ["interactiveUrl"],
|
|
1323
|
+
message: "interactive URL requires a human external-auth state",
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
if (result.profileLoaded && result.state !== "authenticated") {
|
|
1327
|
+
context.addIssue({
|
|
1328
|
+
code: "custom",
|
|
1329
|
+
path: ["profileLoaded"],
|
|
1330
|
+
message: "only authenticated external auth can load a profile",
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
export type BrowserExternalAuthResult = z.infer<typeof BrowserExternalAuthResult>;
|
|
1335
|
+
|
|
1336
|
+
export const VerifyAuthRunRequest = z
|
|
1337
|
+
.object({
|
|
1338
|
+
operationId: z.string().uuid(),
|
|
1339
|
+
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1340
|
+
})
|
|
1341
|
+
.strict();
|
|
1342
|
+
export type VerifyAuthRunRequest = z.infer<typeof VerifyAuthRunRequest>;
|
|
1343
|
+
|
|
1344
|
+
export const InteractionInterventionStatus = z.enum([
|
|
1345
|
+
"open",
|
|
1346
|
+
"completed",
|
|
1347
|
+
"dismissed",
|
|
1348
|
+
"expired",
|
|
1349
|
+
"cancelled",
|
|
1350
|
+
]);
|
|
1351
|
+
export type InteractionInterventionStatus = z.infer<typeof InteractionInterventionStatus>;
|
|
1352
|
+
|
|
1353
|
+
export const InteractionIntervention = z
|
|
1354
|
+
.object({
|
|
1355
|
+
id: z.string().uuid(),
|
|
1356
|
+
accountId: z.string().uuid(),
|
|
1357
|
+
workspaceId: z.string().uuid(),
|
|
1358
|
+
resourceKind: z.enum(["browser_session", "computer_session"]),
|
|
1359
|
+
resourceId: z.string().uuid(),
|
|
1360
|
+
targetId: boundedOpaqueId,
|
|
1361
|
+
controllerGeneration: opaqueGeneration,
|
|
1362
|
+
targetGeneration: opaqueGeneration,
|
|
1363
|
+
documentGeneration: opaqueGeneration.nullable(),
|
|
1364
|
+
kind: z.enum(["manual_login", "mfa", "external_action", "confirmation", "other"]),
|
|
1365
|
+
reason: z.string().trim().min(1).max(2_048),
|
|
1366
|
+
status: InteractionInterventionStatus,
|
|
1367
|
+
authRunId: z.string().uuid().nullable(),
|
|
1368
|
+
originatingSessionId: z.string().uuid(),
|
|
1369
|
+
originatingTurnId: z.string().uuid().nullable(),
|
|
1370
|
+
originatingAttemptId: z.string().uuid().nullable(),
|
|
1371
|
+
originatingToolOperationId: z.string().uuid().nullable(),
|
|
1372
|
+
responseActorSubjectId: z.string().min(1).max(1_024).nullable(),
|
|
1373
|
+
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1374
|
+
operationId: z.string().uuid(),
|
|
1375
|
+
expiresAt: z.string().datetime({ offset: true }),
|
|
1376
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
1377
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
1378
|
+
settledAt: z.string().datetime({ offset: true }).nullable(),
|
|
1379
|
+
})
|
|
1380
|
+
.strict();
|
|
1381
|
+
export type InteractionIntervention = z.infer<typeof InteractionIntervention>;
|
|
1382
|
+
|
|
1383
|
+
export const InteractionInterventionListResponse = z
|
|
1384
|
+
.object({ interventions: z.array(InteractionIntervention).max(10_000) })
|
|
1385
|
+
.strict();
|
|
1386
|
+
export type InteractionInterventionListResponse = z.infer<
|
|
1387
|
+
typeof InteractionInterventionListResponse
|
|
1388
|
+
>;
|
|
1389
|
+
|
|
1390
|
+
export const CreateInteractionInterventionRequest = z
|
|
1391
|
+
.object({
|
|
1392
|
+
operationId: z.string().uuid(),
|
|
1393
|
+
resourceKind: z.enum(["browser_session", "computer_session"]),
|
|
1394
|
+
resourceId: z.string().uuid(),
|
|
1395
|
+
targetId: boundedOpaqueId,
|
|
1396
|
+
expectedControllerGeneration: opaqueGeneration,
|
|
1397
|
+
expectedTargetGeneration: opaqueGeneration,
|
|
1398
|
+
expectedDocumentGeneration: opaqueGeneration.nullable(),
|
|
1399
|
+
kind: z.enum(["manual_login", "mfa", "external_action", "confirmation", "other"]),
|
|
1400
|
+
reason: z.string().trim().min(1).max(2_048),
|
|
1401
|
+
authRunId: z.string().uuid().optional(),
|
|
1402
|
+
expiresInSeconds: z.number().int().min(30).max(86_400).default(900),
|
|
1403
|
+
})
|
|
1404
|
+
.strict();
|
|
1405
|
+
export type CreateInteractionInterventionRequest = z.infer<
|
|
1406
|
+
typeof CreateInteractionInterventionRequest
|
|
1407
|
+
>;
|
|
1408
|
+
|
|
1409
|
+
export const ResolveInteractionInterventionRequest = z
|
|
1410
|
+
.object({
|
|
1411
|
+
operationId: z.string().uuid(),
|
|
1412
|
+
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1413
|
+
outcome: z.enum(["completed", "dismissed"]),
|
|
1414
|
+
})
|
|
1415
|
+
.strict();
|
|
1416
|
+
export type ResolveInteractionInterventionRequest = z.infer<
|
|
1417
|
+
typeof ResolveInteractionInterventionRequest
|
|
1418
|
+
>;
|
|
1419
|
+
|
|
1420
|
+
export const InteractionInterventionMutationResponse = z
|
|
1421
|
+
.object({
|
|
1422
|
+
intervention: InteractionIntervention,
|
|
1423
|
+
operationId: z.string().uuid(),
|
|
1424
|
+
replayed: z.boolean(),
|
|
1425
|
+
})
|
|
1426
|
+
.strict();
|
|
1427
|
+
export type InteractionInterventionMutationResponse = z.infer<
|
|
1428
|
+
typeof InteractionInterventionMutationResponse
|
|
1429
|
+
>;
|
|
1430
|
+
|
|
1431
|
+
/** Canonical model/Codemode input for one typed browser/computer human wait. */
|
|
1432
|
+
export const INTERACTION_REQUEST_HUMAN_MODEL_TOOL_NAME = "interaction__interaction_request_human";
|
|
1433
|
+
|
|
1434
|
+
export const RequestHumanInteractionToolInput = z.discriminatedUnion("operation", [
|
|
1435
|
+
z.object({ operation: z.literal("wait"), interventionId: z.string().uuid() }).strict(),
|
|
1436
|
+
z
|
|
1437
|
+
.object({
|
|
1438
|
+
operation: z.literal("request"),
|
|
1439
|
+
resourceKind: z.enum(["browser_session", "computer_session"]),
|
|
1440
|
+
resourceId: z.string().uuid(),
|
|
1441
|
+
targetId: boundedOpaqueId,
|
|
1442
|
+
expectedControllerGeneration: opaqueGeneration,
|
|
1443
|
+
expectedTargetGeneration: opaqueGeneration,
|
|
1444
|
+
expectedDocumentGeneration: opaqueGeneration.nullable(),
|
|
1445
|
+
kind: z.enum(["manual_login", "mfa", "external_action", "confirmation", "other"]),
|
|
1446
|
+
reason: z.string().trim().min(1).max(2_048),
|
|
1447
|
+
authRunId: z.string().uuid().optional(),
|
|
1448
|
+
expiresInSeconds: z.number().int().min(30).max(86_400).default(900),
|
|
1449
|
+
})
|
|
1450
|
+
.strict(),
|
|
1451
|
+
]);
|
|
1452
|
+
export type RequestHumanInteractionToolInput = z.infer<typeof RequestHumanInteractionToolInput>;
|
|
1453
|
+
|
|
1454
|
+
export const RequestHumanInteractionToolOutput = z
|
|
1455
|
+
.object({
|
|
1456
|
+
intervention: InteractionIntervention,
|
|
1457
|
+
observation: z.lazy(() => z.union([BrowserObservation, ComputerObservation])).nullable(),
|
|
1458
|
+
observationErrorCode: z.string().min(1).max(128).nullable(),
|
|
1459
|
+
})
|
|
1460
|
+
.strict();
|
|
1461
|
+
export type RequestHumanInteractionToolOutput = z.infer<typeof RequestHumanInteractionToolOutput>;
|
|
1462
|
+
|
|
1463
|
+
export const BrowserIdentityListResponse = z
|
|
1464
|
+
.object({
|
|
1465
|
+
revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
1466
|
+
identities: z.array(BrowserIdentity).max(10_000),
|
|
1467
|
+
})
|
|
1468
|
+
.strict();
|
|
1469
|
+
export type BrowserIdentityListResponse = z.infer<typeof BrowserIdentityListResponse>;
|
|
1470
|
+
|
|
1471
|
+
export const BrowserRevisionListResponse = z
|
|
1472
|
+
.object({
|
|
1473
|
+
identity: BrowserIdentity,
|
|
1474
|
+
revisions: z.array(BrowserRevision).max(10_000),
|
|
1475
|
+
})
|
|
1476
|
+
.strict();
|
|
1477
|
+
export type BrowserRevisionListResponse = z.infer<typeof BrowserRevisionListResponse>;
|
|
1478
|
+
|
|
1479
|
+
export const CreateBrowserIdentityRequest = z
|
|
1480
|
+
.object({
|
|
1481
|
+
operationId: z.string().uuid(),
|
|
1482
|
+
name: z.string().trim().min(1).max(200),
|
|
1483
|
+
})
|
|
1484
|
+
.strict();
|
|
1485
|
+
export type CreateBrowserIdentityRequest = z.infer<typeof CreateBrowserIdentityRequest>;
|
|
1486
|
+
|
|
1487
|
+
export const UpdateBrowserIdentityRequest = z
|
|
1488
|
+
.object({
|
|
1489
|
+
operationId: z.string().uuid(),
|
|
1490
|
+
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1491
|
+
name: z.string().trim().min(1).max(200).optional(),
|
|
1492
|
+
status: BrowserIdentityStatus.optional(),
|
|
1493
|
+
defaultRevisionId: z.string().uuid().optional(),
|
|
1494
|
+
})
|
|
1495
|
+
.strict()
|
|
1496
|
+
.refine(
|
|
1497
|
+
(value) =>
|
|
1498
|
+
value.name !== undefined ||
|
|
1499
|
+
value.status !== undefined ||
|
|
1500
|
+
value.defaultRevisionId !== undefined,
|
|
1501
|
+
{ message: "browser identity update is empty" },
|
|
1502
|
+
);
|
|
1503
|
+
export type UpdateBrowserIdentityRequest = z.infer<typeof UpdateBrowserIdentityRequest>;
|
|
1504
|
+
|
|
1505
|
+
export const BrowserIdentityMutationResponse = z
|
|
1506
|
+
.object({
|
|
1507
|
+
identity: BrowserIdentity,
|
|
1508
|
+
operationId: z.string().uuid(),
|
|
1509
|
+
replayed: z.boolean(),
|
|
1510
|
+
})
|
|
1511
|
+
.strict();
|
|
1512
|
+
export type BrowserIdentityMutationResponse = z.infer<typeof BrowserIdentityMutationResponse>;
|
|
1513
|
+
|
|
1514
|
+
/** Capture the exact live working profile. The controller derives the parent
|
|
1515
|
+
* from the session's current saved base; callers cannot forge lineage. */
|
|
1516
|
+
export const PublishBrowserRevisionRequest = z
|
|
1517
|
+
.object({
|
|
1518
|
+
operationId: z.string().uuid(),
|
|
1519
|
+
identityId: z.string().uuid(),
|
|
1520
|
+
expectedHeadGeneration: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
1521
|
+
advanceDefault: z.boolean().default(true),
|
|
1522
|
+
})
|
|
1523
|
+
.strict();
|
|
1524
|
+
export type PublishBrowserRevisionRequest = z.infer<typeof PublishBrowserRevisionRequest>;
|
|
1525
|
+
|
|
1526
|
+
export const PublishBrowserRevisionResponse = z
|
|
1527
|
+
.object({
|
|
1528
|
+
identity: BrowserIdentity,
|
|
1529
|
+
revision: BrowserRevision,
|
|
1530
|
+
outcome: z.enum(["saved_as_default", "saved_not_default"]),
|
|
1531
|
+
replayed: z.boolean(),
|
|
1532
|
+
})
|
|
1533
|
+
.strict();
|
|
1534
|
+
export type PublishBrowserRevisionResponse = z.infer<typeof PublishBrowserRevisionResponse>;
|
|
1535
|
+
|
|
1536
|
+
export const BrowserTarget = z
|
|
1537
|
+
.object({
|
|
1538
|
+
id: boundedOpaqueId,
|
|
1539
|
+
browserSessionId: z.string().uuid(),
|
|
1540
|
+
controllerGeneration: opaqueGeneration,
|
|
1541
|
+
targetGeneration: opaqueGeneration,
|
|
1542
|
+
documentGeneration: opaqueGeneration.nullable(),
|
|
1543
|
+
kind: z.enum(["page", "popup", "background_page", "worker"]),
|
|
1544
|
+
title: z.string().max(4_096),
|
|
1545
|
+
url: boundedUrl,
|
|
1546
|
+
selected: z.boolean(),
|
|
1547
|
+
attached: z.boolean(),
|
|
1548
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
1549
|
+
})
|
|
1550
|
+
.strict();
|
|
1551
|
+
export type BrowserTarget = z.infer<typeof BrowserTarget>;
|
|
1552
|
+
|
|
1553
|
+
export const BrowserDownloadStatus = z.enum([
|
|
1554
|
+
"in_progress",
|
|
1555
|
+
"completed",
|
|
1556
|
+
"cancelled",
|
|
1557
|
+
"failed",
|
|
1558
|
+
"unavailable",
|
|
1559
|
+
]);
|
|
1560
|
+
export type BrowserDownloadStatus = z.infer<typeof BrowserDownloadStatus>;
|
|
1561
|
+
|
|
1562
|
+
/** One browser-produced file. Its bytes remain private to the exact controller
|
|
1563
|
+
* until an explicit save publishes and materializes them. No placement path or
|
|
1564
|
+
* source URL crosses this contract. */
|
|
1565
|
+
export const BrowserDownload = z
|
|
1566
|
+
.object({
|
|
1567
|
+
id: z.string().uuid(),
|
|
1568
|
+
browserSessionId: z.string().uuid(),
|
|
1569
|
+
controllerGeneration: opaqueGeneration,
|
|
1570
|
+
targetId: boundedOpaqueId.nullable(),
|
|
1571
|
+
filename: z
|
|
1572
|
+
.string()
|
|
1573
|
+
.min(1)
|
|
1574
|
+
.max(4_096)
|
|
1575
|
+
.regex(/^[^\u0000-\u001f\u007f]+$/u),
|
|
1576
|
+
status: BrowserDownloadStatus,
|
|
1577
|
+
receivedBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
1578
|
+
totalBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
1579
|
+
sha256: sha256Hex.nullable(),
|
|
1580
|
+
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
1581
|
+
startedAt: z.string().datetime({ offset: true }),
|
|
1582
|
+
settledAt: z.string().datetime({ offset: true }).nullable(),
|
|
1583
|
+
failureCode: boundedOpaqueId.nullable(),
|
|
1584
|
+
})
|
|
1585
|
+
.strict()
|
|
1586
|
+
.superRefine((download, context) => {
|
|
1587
|
+
const terminal = download.status !== "in_progress";
|
|
1588
|
+
if (terminal !== (download.settledAt !== null)) {
|
|
1589
|
+
context.addIssue({
|
|
1590
|
+
code: "custom",
|
|
1591
|
+
path: ["settledAt"],
|
|
1592
|
+
message: "download settlement must match its terminal status",
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
if (
|
|
1596
|
+
(download.status === "failed" || download.status === "unavailable") !==
|
|
1597
|
+
(download.failureCode !== null)
|
|
1598
|
+
) {
|
|
1599
|
+
context.addIssue({
|
|
1600
|
+
code: "custom",
|
|
1601
|
+
path: ["failureCode"],
|
|
1602
|
+
message: "failed or unavailable downloads require one failure code",
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
if (download.status === "completed") {
|
|
1606
|
+
if (download.sha256 === null || download.totalBytes !== download.receivedBytes) {
|
|
1607
|
+
context.addIssue({
|
|
1608
|
+
code: "custom",
|
|
1609
|
+
path: ["sha256"],
|
|
1610
|
+
message: "completed downloads require exact bytes and SHA-256",
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
} else if (download.sha256 !== null) {
|
|
1614
|
+
context.addIssue({
|
|
1615
|
+
code: "custom",
|
|
1616
|
+
path: ["sha256"],
|
|
1617
|
+
message: "only completed downloads carry a SHA-256",
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
export type BrowserDownload = z.infer<typeof BrowserDownload>;
|
|
1622
|
+
|
|
1623
|
+
export const BrowserDownloadListResponse = z
|
|
1624
|
+
.object({
|
|
1625
|
+
browserSessionId: z.string().uuid(),
|
|
1626
|
+
controllerGeneration: opaqueGeneration,
|
|
1627
|
+
downloads: z.array(BrowserDownload).max(10_000),
|
|
1628
|
+
})
|
|
1629
|
+
.strict();
|
|
1630
|
+
export type BrowserDownloadListResponse = z.infer<typeof BrowserDownloadListResponse>;
|
|
1631
|
+
|
|
1632
|
+
const workspaceRelativeFilePath = z
|
|
1633
|
+
.string()
|
|
1634
|
+
.min(1)
|
|
1635
|
+
.max(4_096)
|
|
1636
|
+
.refine((value) => {
|
|
1637
|
+
if (value !== value.trim() || value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value)) {
|
|
1638
|
+
return false;
|
|
1639
|
+
}
|
|
1640
|
+
if (value.includes("\\") || value.includes("\0")) return false;
|
|
1641
|
+
const segments = value.split("/");
|
|
1642
|
+
return segments.every(
|
|
1643
|
+
(segment) =>
|
|
1644
|
+
segment.length > 0 &&
|
|
1645
|
+
segment !== "." &&
|
|
1646
|
+
segment !== ".." &&
|
|
1647
|
+
!/[<>:"|?*\u0000-\u001f\u007f]/u.test(segment) &&
|
|
1648
|
+
!/[ .]$/u.test(segment) &&
|
|
1649
|
+
!/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(segment),
|
|
1650
|
+
);
|
|
1651
|
+
}, "destinationPath must be a portable workspace-relative file path");
|
|
1652
|
+
|
|
1653
|
+
export const BrowserDownloadSaveRequest = z
|
|
1654
|
+
.object({
|
|
1655
|
+
operationId: z.string().uuid(),
|
|
1656
|
+
destinationPath: workspaceRelativeFilePath,
|
|
1657
|
+
overwrite: z.boolean().default(false),
|
|
1658
|
+
})
|
|
1659
|
+
.strict();
|
|
1660
|
+
export type BrowserDownloadSaveRequest = z.infer<typeof BrowserDownloadSaveRequest>;
|
|
1661
|
+
|
|
1662
|
+
export const BrowserDownloadSaveResponse = z
|
|
1663
|
+
.object({
|
|
1664
|
+
download: BrowserDownload,
|
|
1665
|
+
destinationPath: workspaceRelativeFilePath,
|
|
1666
|
+
fileId: z.string().uuid(),
|
|
1667
|
+
operationId: z.string().uuid(),
|
|
1668
|
+
replayed: z.boolean(),
|
|
1669
|
+
})
|
|
1670
|
+
.strict();
|
|
1671
|
+
export type BrowserDownloadSaveResponse = z.infer<typeof BrowserDownloadSaveResponse>;
|
|
1672
|
+
|
|
1673
|
+
/** Controller-private, narrow object authority for publishing one exact
|
|
1674
|
+
* completed download. Signed URLs and headers never appear in public SDK or
|
|
1675
|
+
* durable controller receipts. */
|
|
1676
|
+
export const BrowserDownloadExportRequest = z
|
|
1677
|
+
.object({
|
|
1678
|
+
operationId: z.string().uuid(),
|
|
1679
|
+
downloadId: z.string().uuid(),
|
|
1680
|
+
upload: z
|
|
1681
|
+
.object({
|
|
1682
|
+
url: boundedHttpUrl,
|
|
1683
|
+
requiredHeaders: z.record(z.string().min(1).max(256), z.string().max(8_192)),
|
|
1684
|
+
expiresAt: z.string().datetime({ offset: true }),
|
|
1685
|
+
})
|
|
1686
|
+
.strict(),
|
|
1687
|
+
})
|
|
1688
|
+
.strict();
|
|
1689
|
+
export type BrowserDownloadExportRequest = z.infer<typeof BrowserDownloadExportRequest>;
|
|
1690
|
+
|
|
1691
|
+
export const BrowserDownloadExportReceipt = z
|
|
1692
|
+
.object({
|
|
1693
|
+
operationId: z.string().uuid(),
|
|
1694
|
+
downloadId: z.string().uuid(),
|
|
1695
|
+
sizeBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
1696
|
+
sha256: sha256Hex,
|
|
1697
|
+
replayed: z.boolean(),
|
|
1698
|
+
})
|
|
1699
|
+
.strict();
|
|
1700
|
+
export type BrowserDownloadExportReceipt = z.infer<typeof BrowserDownloadExportReceipt>;
|
|
1701
|
+
|
|
1702
|
+
export const InteractionRect = z
|
|
1703
|
+
.object({
|
|
1704
|
+
x: z.number().finite(),
|
|
1705
|
+
y: z.number().finite(),
|
|
1706
|
+
width: z.number().finite().nonnegative(),
|
|
1707
|
+
height: z.number().finite().nonnegative(),
|
|
1708
|
+
})
|
|
1709
|
+
.strict();
|
|
1710
|
+
export type InteractionRect = z.infer<typeof InteractionRect>;
|
|
1711
|
+
|
|
1712
|
+
export const InteractionRedactedValue = z
|
|
1713
|
+
.object({
|
|
1714
|
+
redacted: z.literal(true),
|
|
1715
|
+
reason: z.enum(["password", "payment", "private", "policy"]),
|
|
1716
|
+
})
|
|
1717
|
+
.strict();
|
|
1718
|
+
export type InteractionRedactedValue = z.infer<typeof InteractionRedactedValue>;
|
|
1719
|
+
|
|
1720
|
+
export type InteractionSemanticNodeValue = {
|
|
1721
|
+
ref: string;
|
|
1722
|
+
role: string;
|
|
1723
|
+
identifier?: string | undefined;
|
|
1724
|
+
name?: string | undefined;
|
|
1725
|
+
description?: string | undefined;
|
|
1726
|
+
value?: string | InteractionRedactedValue | undefined;
|
|
1727
|
+
states: string[];
|
|
1728
|
+
bounds?: InteractionRect | undefined;
|
|
1729
|
+
actions: string[];
|
|
1730
|
+
children?: InteractionSemanticNodeValue[] | undefined;
|
|
1731
|
+
native?: { platform: "dom" | "mac_ax" | "at_spi" | "uia"; data: unknown } | undefined;
|
|
1732
|
+
};
|
|
1733
|
+
|
|
1734
|
+
export const InteractionSemanticNode: z.ZodType<InteractionSemanticNodeValue> = z.lazy(() =>
|
|
1735
|
+
z
|
|
1736
|
+
.object({
|
|
1737
|
+
ref: boundedOpaqueId,
|
|
1738
|
+
role: z.string().min(1).max(256),
|
|
1739
|
+
identifier: z.string().min(1).max(2_048).optional(),
|
|
1740
|
+
name: z.string().max(8_192).optional(),
|
|
1741
|
+
description: z.string().max(8_192).optional(),
|
|
1742
|
+
value: z.union([z.string().max(32_768), InteractionRedactedValue]).optional(),
|
|
1743
|
+
states: z.array(z.string().min(1).max(128)).max(64),
|
|
1744
|
+
bounds: InteractionRect.optional(),
|
|
1745
|
+
actions: z.array(z.string().min(1).max(128)).max(64),
|
|
1746
|
+
children: z.array(InteractionSemanticNode).optional(),
|
|
1747
|
+
native: z
|
|
1748
|
+
.object({
|
|
1749
|
+
platform: z.enum(["dom", "mac_ax", "at_spi", "uia"]),
|
|
1750
|
+
data: z.json(),
|
|
1751
|
+
})
|
|
1752
|
+
.strict()
|
|
1753
|
+
.optional(),
|
|
1754
|
+
})
|
|
1755
|
+
.strict(),
|
|
1756
|
+
);
|
|
1757
|
+
|
|
1758
|
+
export const InteractionSemanticSnapshot = z
|
|
1759
|
+
.object({
|
|
1760
|
+
kind: z.literal("snapshot"),
|
|
1761
|
+
roots: z.array(InteractionSemanticNode),
|
|
1762
|
+
nodeCount: z.number().int().nonnegative().max(INTERACTION_MAX_SEMANTIC_NODES),
|
|
1763
|
+
})
|
|
1764
|
+
.strict();
|
|
1765
|
+
|
|
1766
|
+
export const InteractionSemanticDiff = z
|
|
1767
|
+
.object({
|
|
1768
|
+
kind: z.literal("diff"),
|
|
1769
|
+
baseObservationId: boundedOpaqueId,
|
|
1770
|
+
removedRefs: z.array(boundedOpaqueId).max(INTERACTION_MAX_CHANGED_NODES),
|
|
1771
|
+
changed: z.array(InteractionSemanticNode).max(INTERACTION_MAX_CHANGED_NODES),
|
|
1772
|
+
})
|
|
1773
|
+
.strict();
|
|
1774
|
+
|
|
1775
|
+
export const InteractionDiagnosticSummary = z
|
|
1776
|
+
.object({
|
|
1777
|
+
consoleErrorCount: z.number().int().nonnegative(),
|
|
1778
|
+
failedRequestCount: z.number().int().nonnegative(),
|
|
1779
|
+
downloadCount: z.number().int().nonnegative(),
|
|
1780
|
+
pageErrorCount: z.number().int().nonnegative(),
|
|
1781
|
+
})
|
|
1782
|
+
.strict();
|
|
1783
|
+
|
|
1784
|
+
export const BrowserDialog = z
|
|
1785
|
+
.object({
|
|
1786
|
+
type: z.enum(["alert", "confirm", "prompt", "beforeunload"]),
|
|
1787
|
+
message: z.string().max(8_192),
|
|
1788
|
+
defaultPrompt: z.string().max(32_768),
|
|
1789
|
+
openedAt: z.string().datetime({ offset: true }),
|
|
1790
|
+
})
|
|
1791
|
+
.strict();
|
|
1792
|
+
export type BrowserDialog = z.infer<typeof BrowserDialog>;
|
|
1793
|
+
|
|
1794
|
+
export const BrowserObservation = z
|
|
1795
|
+
.object({
|
|
1796
|
+
protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
|
|
1797
|
+
observationId: boundedOpaqueId,
|
|
1798
|
+
browserSessionId: z.string().uuid(),
|
|
1799
|
+
target: BrowserTarget,
|
|
1800
|
+
frameId: opaqueGeneration.nullable(),
|
|
1801
|
+
semantic: z.union([InteractionSemanticSnapshot, InteractionSemanticDiff]).nullable(),
|
|
1802
|
+
screenshot: RetainedArtifactReferenceSchema.nullable(),
|
|
1803
|
+
focusedRef: boundedOpaqueId.nullable(),
|
|
1804
|
+
changedRegions: z.array(InteractionRect).max(INTERACTION_MAX_CHANGED_NODES),
|
|
1805
|
+
diagnostics: InteractionDiagnosticSummary,
|
|
1806
|
+
dialog: BrowserDialog.nullable(),
|
|
1807
|
+
observedAt: z.string().datetime({ offset: true }),
|
|
1808
|
+
})
|
|
1809
|
+
.strict();
|
|
1810
|
+
export type BrowserObservation = z.infer<typeof BrowserObservation>;
|
|
1811
|
+
|
|
1812
|
+
export const BrowserDiagnosticKind = z.enum([
|
|
1813
|
+
"console",
|
|
1814
|
+
"page_error",
|
|
1815
|
+
"failed_request",
|
|
1816
|
+
"download",
|
|
1817
|
+
]);
|
|
1818
|
+
export type BrowserDiagnosticKind = z.infer<typeof BrowserDiagnosticKind>;
|
|
1819
|
+
|
|
1820
|
+
export const BrowserDiagnosticEntry = z
|
|
1821
|
+
.object({
|
|
1822
|
+
sequence: z.number().int().positive(),
|
|
1823
|
+
kind: BrowserDiagnosticKind,
|
|
1824
|
+
level: z.enum(["debug", "info", "warning", "error"]).nullable(),
|
|
1825
|
+
message: z.string().max(8_192),
|
|
1826
|
+
url: boundedUrl.nullable(),
|
|
1827
|
+
method: z.string().min(1).max(32).nullable(),
|
|
1828
|
+
status: z.number().int().min(100).max(999).nullable(),
|
|
1829
|
+
filename: z.string().max(4_096).nullable(),
|
|
1830
|
+
occurredAt: z.string().datetime({ offset: true }),
|
|
1831
|
+
})
|
|
1832
|
+
.strict();
|
|
1833
|
+
export type BrowserDiagnosticEntry = z.infer<typeof BrowserDiagnosticEntry>;
|
|
1834
|
+
|
|
1835
|
+
export const BrowserDiagnosticBatch = z
|
|
1836
|
+
.object({
|
|
1837
|
+
browserSessionId: z.string().uuid(),
|
|
1838
|
+
controllerGeneration: opaqueGeneration,
|
|
1839
|
+
targetId: boundedOpaqueId,
|
|
1840
|
+
targetGeneration: opaqueGeneration,
|
|
1841
|
+
entries: z.array(BrowserDiagnosticEntry).max(INTERACTION_MAX_DIAGNOSTIC_ENTRIES),
|
|
1842
|
+
cursor: z.number().int().nonnegative(),
|
|
1843
|
+
truncated: z.boolean(),
|
|
1844
|
+
})
|
|
1845
|
+
.strict();
|
|
1846
|
+
export type BrowserDiagnosticBatch = z.infer<typeof BrowserDiagnosticBatch>;
|
|
1847
|
+
|
|
1848
|
+
export const BrowserLocator = z.discriminatedUnion("kind", [
|
|
1849
|
+
z.object({ kind: z.literal("ref"), ref: boundedOpaqueId }).strict(),
|
|
1850
|
+
z
|
|
1851
|
+
.object({
|
|
1852
|
+
kind: z.literal("role"),
|
|
1853
|
+
role: z.string().min(1).max(256),
|
|
1854
|
+
name: z.string().max(8_192).optional(),
|
|
1855
|
+
exact: z.boolean().optional(),
|
|
1856
|
+
})
|
|
1857
|
+
.strict(),
|
|
1858
|
+
z.object({ kind: z.literal("label"), text: z.string().min(1).max(8_192) }).strict(),
|
|
1859
|
+
z.object({ kind: z.literal("text"), text: z.string().min(1).max(8_192) }).strict(),
|
|
1860
|
+
z
|
|
1861
|
+
.object({
|
|
1862
|
+
kind: z.literal("placeholder"),
|
|
1863
|
+
text: z.string().min(1).max(8_192),
|
|
1864
|
+
})
|
|
1865
|
+
.strict(),
|
|
1866
|
+
z.object({ kind: z.literal("test_id"), value: z.string().min(1).max(2_048) }).strict(),
|
|
1867
|
+
z.object({ kind: z.literal("css"), selector: z.string().min(1).max(8_192) }).strict(),
|
|
1868
|
+
]);
|
|
1869
|
+
export type BrowserLocator = z.infer<typeof BrowserLocator>;
|
|
1870
|
+
|
|
1871
|
+
/** Provider-neutral locator for native accessibility trees. Native automation
|
|
1872
|
+
* identifiers are intentionally distinct from DOM selectors/test ids: AX,
|
|
1873
|
+
* AT-SPI, and UIA adapters resolve them inside the exact observed target. */
|
|
1874
|
+
export const ComputerLocator = z.discriminatedUnion("kind", [
|
|
1875
|
+
z.object({ kind: z.literal("ref"), ref: boundedOpaqueId }).strict(),
|
|
1876
|
+
z
|
|
1877
|
+
.object({
|
|
1878
|
+
kind: z.literal("role"),
|
|
1879
|
+
role: z.string().min(1).max(256),
|
|
1880
|
+
name: z.string().max(8_192).optional(),
|
|
1881
|
+
exact: z.boolean().optional(),
|
|
1882
|
+
})
|
|
1883
|
+
.strict(),
|
|
1884
|
+
z
|
|
1885
|
+
.object({
|
|
1886
|
+
kind: z.literal("label"),
|
|
1887
|
+
text: z.string().min(1).max(8_192),
|
|
1888
|
+
exact: z.boolean().optional(),
|
|
1889
|
+
})
|
|
1890
|
+
.strict(),
|
|
1891
|
+
z
|
|
1892
|
+
.object({
|
|
1893
|
+
kind: z.literal("text"),
|
|
1894
|
+
text: z.string().min(1).max(8_192),
|
|
1895
|
+
exact: z.boolean().optional(),
|
|
1896
|
+
})
|
|
1897
|
+
.strict(),
|
|
1898
|
+
z
|
|
1899
|
+
.object({
|
|
1900
|
+
kind: z.literal("identifier"),
|
|
1901
|
+
value: z.string().min(1).max(2_048),
|
|
1902
|
+
})
|
|
1903
|
+
.strict(),
|
|
1904
|
+
]);
|
|
1905
|
+
export type ComputerLocator = z.infer<typeof ComputerLocator>;
|
|
1906
|
+
|
|
1907
|
+
/** Web-platform permissions which a managed browser can set for the exact
|
|
1908
|
+
* top-level origin currently fenced by a BrowserActionCommand. Names remain
|
|
1909
|
+
* provider-neutral; drivers map them to their native permission descriptors. */
|
|
1910
|
+
export const BrowserPermission = z.enum([
|
|
1911
|
+
"geolocation",
|
|
1912
|
+
"notifications",
|
|
1913
|
+
"camera",
|
|
1914
|
+
"microphone",
|
|
1915
|
+
"midi",
|
|
1916
|
+
"midi_sysex",
|
|
1917
|
+
"sensors",
|
|
1918
|
+
"idle_detection",
|
|
1919
|
+
"local_fonts",
|
|
1920
|
+
"window_management",
|
|
1921
|
+
]);
|
|
1922
|
+
export type BrowserPermission = z.infer<typeof BrowserPermission>;
|
|
1923
|
+
|
|
1924
|
+
export const BrowserPermissionSetting = z.enum(["granted", "denied", "prompt"]);
|
|
1925
|
+
export type BrowserPermissionSetting = z.infer<typeof BrowserPermissionSetting>;
|
|
1926
|
+
|
|
1927
|
+
const browserActionVariants = [
|
|
1928
|
+
z.object({ type: z.literal("navigate"), url: boundedUrl }).strict(),
|
|
1929
|
+
z
|
|
1930
|
+
.object({
|
|
1931
|
+
type: z.literal("click"),
|
|
1932
|
+
locator: BrowserLocator,
|
|
1933
|
+
button: z.enum(["left", "right", "middle"]).optional(),
|
|
1934
|
+
})
|
|
1935
|
+
.strict(),
|
|
1936
|
+
z.object({ type: z.literal("double_click"), locator: BrowserLocator }).strict(),
|
|
1937
|
+
z.object({ type: z.literal("hover"), locator: BrowserLocator }).strict(),
|
|
1938
|
+
z
|
|
1939
|
+
.object({
|
|
1940
|
+
type: z.literal("fill"),
|
|
1941
|
+
locator: BrowserLocator,
|
|
1942
|
+
value: z.string().max(1_000_000),
|
|
1943
|
+
})
|
|
1944
|
+
.strict(),
|
|
1945
|
+
z
|
|
1946
|
+
.object({
|
|
1947
|
+
type: z.literal("type"),
|
|
1948
|
+
locator: BrowserLocator.optional(),
|
|
1949
|
+
text: z.string().max(1_000_000),
|
|
1950
|
+
})
|
|
1951
|
+
.strict(),
|
|
1952
|
+
z
|
|
1953
|
+
.object({
|
|
1954
|
+
type: z.literal("press"),
|
|
1955
|
+
locator: BrowserLocator.optional(),
|
|
1956
|
+
key: z.string().min(1).max(256),
|
|
1957
|
+
})
|
|
1958
|
+
.strict(),
|
|
1959
|
+
z
|
|
1960
|
+
.object({
|
|
1961
|
+
type: z.literal("select"),
|
|
1962
|
+
locator: BrowserLocator,
|
|
1963
|
+
values: z.array(z.string().max(8_192)).min(1).max(100),
|
|
1964
|
+
})
|
|
1965
|
+
.strict(),
|
|
1966
|
+
z
|
|
1967
|
+
.object({
|
|
1968
|
+
type: z.literal("check"),
|
|
1969
|
+
locator: BrowserLocator,
|
|
1970
|
+
checked: z.boolean(),
|
|
1971
|
+
})
|
|
1972
|
+
.strict(),
|
|
1973
|
+
z
|
|
1974
|
+
.object({
|
|
1975
|
+
type: z.literal("scroll"),
|
|
1976
|
+
locator: BrowserLocator.optional(),
|
|
1977
|
+
deltaX: z.number().finite(),
|
|
1978
|
+
deltaY: z.number().finite(),
|
|
1979
|
+
})
|
|
1980
|
+
.strict(),
|
|
1981
|
+
z
|
|
1982
|
+
.object({
|
|
1983
|
+
type: z.literal("drag"),
|
|
1984
|
+
from: BrowserLocator,
|
|
1985
|
+
to: BrowserLocator,
|
|
1986
|
+
})
|
|
1987
|
+
.strict(),
|
|
1988
|
+
z
|
|
1989
|
+
.object({
|
|
1990
|
+
type: z.literal("pointer"),
|
|
1991
|
+
action: z.enum(["click", "double_click", "move", "scroll", "drag"]),
|
|
1992
|
+
x: z.number().finite().nonnegative().max(1_000_000),
|
|
1993
|
+
y: z.number().finite().nonnegative().max(1_000_000),
|
|
1994
|
+
endX: z.number().finite().nonnegative().max(1_000_000).optional(),
|
|
1995
|
+
endY: z.number().finite().nonnegative().max(1_000_000).optional(),
|
|
1996
|
+
deltaX: z.number().finite().min(-1_000_000).max(1_000_000).optional(),
|
|
1997
|
+
deltaY: z.number().finite().min(-1_000_000).max(1_000_000).optional(),
|
|
1998
|
+
button: z.enum(["left", "right", "middle"]).optional(),
|
|
1999
|
+
clickCount: z.union([z.literal(1), z.literal(2)]).optional(),
|
|
2000
|
+
})
|
|
2001
|
+
.strict()
|
|
2002
|
+
.superRefine((action, context) => {
|
|
2003
|
+
const hasEnd = action.endX !== undefined || action.endY !== undefined;
|
|
2004
|
+
const hasDelta = action.deltaX !== undefined || action.deltaY !== undefined;
|
|
2005
|
+
if (action.action === "drag" && (action.endX === undefined || action.endY === undefined)) {
|
|
2006
|
+
context.addIssue({
|
|
2007
|
+
code: "custom",
|
|
2008
|
+
message: "pointer drag requires endX and endY",
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
if (action.action !== "drag" && hasEnd) {
|
|
2012
|
+
context.addIssue({
|
|
2013
|
+
code: "custom",
|
|
2014
|
+
message: "pointer end coordinates require drag",
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
if (
|
|
2018
|
+
action.action === "scroll" &&
|
|
2019
|
+
action.deltaX === undefined &&
|
|
2020
|
+
action.deltaY === undefined
|
|
2021
|
+
) {
|
|
2022
|
+
context.addIssue({
|
|
2023
|
+
code: "custom",
|
|
2024
|
+
message: "pointer scroll requires a delta",
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
if (action.action !== "scroll" && hasDelta) {
|
|
2028
|
+
context.addIssue({
|
|
2029
|
+
code: "custom",
|
|
2030
|
+
message: "pointer deltas require scroll",
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
if (action.action !== "click" && action.clickCount !== undefined) {
|
|
2034
|
+
context.addIssue({
|
|
2035
|
+
code: "custom",
|
|
2036
|
+
message: "pointer clickCount requires click",
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
}),
|
|
2040
|
+
z
|
|
2041
|
+
.object({
|
|
2042
|
+
type: z.literal("handle_dialog"),
|
|
2043
|
+
response: z.enum(["accept", "dismiss"]),
|
|
2044
|
+
promptText: z.string().max(32_768).optional(),
|
|
2045
|
+
})
|
|
2046
|
+
.strict(),
|
|
2047
|
+
z
|
|
2048
|
+
.object({
|
|
2049
|
+
type: z.literal("upload"),
|
|
2050
|
+
locator: BrowserLocator,
|
|
2051
|
+
workspaceFileIds: z.array(z.string().uuid()).min(1).max(100),
|
|
2052
|
+
})
|
|
2053
|
+
.strict(),
|
|
2054
|
+
z
|
|
2055
|
+
.object({
|
|
2056
|
+
type: z.literal("clipboard"),
|
|
2057
|
+
operation: z.enum(["write", "clear", "copy", "paste"]),
|
|
2058
|
+
text: z
|
|
2059
|
+
.string()
|
|
2060
|
+
.max(INTERACTION_MAX_CLIPBOARD_BYTES)
|
|
2061
|
+
.refine(
|
|
2062
|
+
(value) => new TextEncoder().encode(value).byteLength <= INTERACTION_MAX_CLIPBOARD_BYTES,
|
|
2063
|
+
{ message: "browser clipboard text exceeds its UTF-8 byte envelope" },
|
|
2064
|
+
)
|
|
2065
|
+
.optional(),
|
|
2066
|
+
locator: BrowserLocator.optional(),
|
|
2067
|
+
content: z.enum(["selection", "value", "text"]).optional(),
|
|
2068
|
+
})
|
|
2069
|
+
.strict()
|
|
2070
|
+
.superRefine((action, context) => {
|
|
2071
|
+
if (action.operation === "write") {
|
|
2072
|
+
if (action.text === undefined) {
|
|
2073
|
+
context.addIssue({
|
|
2074
|
+
code: "custom",
|
|
2075
|
+
path: ["text"],
|
|
2076
|
+
message: "clipboard write requires text",
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
if (action.locator !== undefined || action.content !== undefined) {
|
|
2080
|
+
context.addIssue({
|
|
2081
|
+
code: "custom",
|
|
2082
|
+
message: "clipboard write accepts only text",
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
if (action.operation === "clear") {
|
|
2088
|
+
if (
|
|
2089
|
+
action.text !== undefined ||
|
|
2090
|
+
action.locator !== undefined ||
|
|
2091
|
+
action.content !== undefined
|
|
2092
|
+
) {
|
|
2093
|
+
context.addIssue({
|
|
2094
|
+
code: "custom",
|
|
2095
|
+
message: "clipboard clear accepts no payload",
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
if (action.operation === "copy") {
|
|
2101
|
+
if (action.text !== undefined) {
|
|
2102
|
+
context.addIssue({
|
|
2103
|
+
code: "custom",
|
|
2104
|
+
path: ["text"],
|
|
2105
|
+
message: "clipboard copy does not accept text",
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
if (action.content !== undefined && action.content !== "selection" && !action.locator) {
|
|
2109
|
+
context.addIssue({
|
|
2110
|
+
code: "custom",
|
|
2111
|
+
path: ["locator"],
|
|
2112
|
+
message: "copying element value or text requires a locator",
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
if (action.content !== undefined) {
|
|
2118
|
+
context.addIssue({
|
|
2119
|
+
code: "custom",
|
|
2120
|
+
path: ["content"],
|
|
2121
|
+
message: "clipboard paste does not accept content",
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
}),
|
|
2125
|
+
z
|
|
2126
|
+
.object({
|
|
2127
|
+
type: z.literal("permission"),
|
|
2128
|
+
permission: BrowserPermission,
|
|
2129
|
+
setting: BrowserPermissionSetting,
|
|
2130
|
+
})
|
|
2131
|
+
.strict(),
|
|
2132
|
+
z
|
|
2133
|
+
.object({
|
|
2134
|
+
type: z.literal("wait"),
|
|
2135
|
+
condition: z.enum(["load", "network_idle", "visible", "hidden"]),
|
|
2136
|
+
locator: BrowserLocator.optional(),
|
|
2137
|
+
timeoutMs: z.number().int().positive().max(120_000).optional(),
|
|
2138
|
+
})
|
|
2139
|
+
.strict(),
|
|
2140
|
+
] as const;
|
|
2141
|
+
|
|
2142
|
+
export const BrowserAction = z.discriminatedUnion("type", browserActionVariants);
|
|
2143
|
+
export type BrowserAction = z.infer<typeof BrowserAction>;
|
|
2144
|
+
|
|
2145
|
+
export const BrowserActionBatch = z
|
|
2146
|
+
.object({
|
|
2147
|
+
type: z.literal("batch"),
|
|
2148
|
+
actions: z.array(BrowserAction).min(1).max(INTERACTION_MAX_ACTIONS_PER_BATCH),
|
|
2149
|
+
})
|
|
2150
|
+
.strict()
|
|
2151
|
+
.superRefine((batch, context) => {
|
|
2152
|
+
const fileIds = new Set(
|
|
2153
|
+
batch.actions.flatMap((action) => (action.type === "upload" ? action.workspaceFileIds : [])),
|
|
2154
|
+
);
|
|
2155
|
+
if (fileIds.size > INTERACTION_MAX_WORKSPACE_FILES_PER_COMMAND) {
|
|
2156
|
+
context.addIssue({
|
|
2157
|
+
code: "custom",
|
|
2158
|
+
path: ["actions"],
|
|
2159
|
+
message: "browser action references too many workspace files",
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
});
|
|
2163
|
+
export type BrowserActionBatch = z.infer<typeof BrowserActionBatch>;
|
|
2164
|
+
|
|
2165
|
+
export const InteractionError = z
|
|
2166
|
+
.object({
|
|
2167
|
+
code: z.enum([
|
|
2168
|
+
"resource_not_found",
|
|
2169
|
+
"resource_unavailable",
|
|
2170
|
+
"controller_stale",
|
|
2171
|
+
"target_not_found",
|
|
2172
|
+
"target_stale",
|
|
2173
|
+
"observation_stale",
|
|
2174
|
+
"document_stale",
|
|
2175
|
+
"frame_stale",
|
|
2176
|
+
"locator_not_found",
|
|
2177
|
+
"locator_ambiguous",
|
|
2178
|
+
"unsupported",
|
|
2179
|
+
"permission_denied",
|
|
2180
|
+
"machine_locked",
|
|
2181
|
+
"attempt_stale",
|
|
2182
|
+
"operation_conflict",
|
|
2183
|
+
"outcome_unknown",
|
|
2184
|
+
"invalid_action",
|
|
2185
|
+
"timeout",
|
|
2186
|
+
"controller_lost",
|
|
2187
|
+
"driver_failed",
|
|
2188
|
+
]),
|
|
2189
|
+
message: z.string().min(1).max(8_192),
|
|
2190
|
+
retryable: z.boolean(),
|
|
2191
|
+
details: z.record(z.string().max(256), z.json()).optional(),
|
|
2192
|
+
})
|
|
2193
|
+
.strict();
|
|
2194
|
+
export type InteractionError = z.infer<typeof InteractionError>;
|
|
2195
|
+
|
|
2196
|
+
export const BrowserActionCommand = z
|
|
2197
|
+
.object({
|
|
2198
|
+
protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
|
|
2199
|
+
operationId: z.string().uuid(),
|
|
2200
|
+
browserSessionId: z.string().uuid(),
|
|
2201
|
+
controllerGeneration: opaqueGeneration,
|
|
2202
|
+
targetId: boundedOpaqueId,
|
|
2203
|
+
expectedTargetGeneration: opaqueGeneration,
|
|
2204
|
+
expectedDocumentGeneration: opaqueGeneration.nullable(),
|
|
2205
|
+
expectedFrameId: opaqueGeneration.nullable(),
|
|
2206
|
+
actor: InteractionActor,
|
|
2207
|
+
/** Human live-control surfaces already receive the resulting pixels. They
|
|
2208
|
+
* may omit the expensive semantic snapshot; agent actions keep it. */
|
|
2209
|
+
observationMode: z.enum(["full", "none"]).optional(),
|
|
2210
|
+
action: z.union([BrowserAction, BrowserActionBatch]),
|
|
2211
|
+
})
|
|
2212
|
+
.strict();
|
|
2213
|
+
export type BrowserActionCommand = z.infer<typeof BrowserActionCommand>;
|
|
2214
|
+
|
|
2215
|
+
/** Controller-private authority used to materialize immutable workspace files
|
|
2216
|
+
* beside a BrowserSession before an upload action dispatches. Signed URLs and
|
|
2217
|
+
* placement paths never appear in the public BrowserAction or its receipt. */
|
|
2218
|
+
export const BrowserWorkspaceFileAuthority = z
|
|
2219
|
+
.object({
|
|
2220
|
+
fileId: z.string().uuid(),
|
|
2221
|
+
safeFilename: z
|
|
2222
|
+
.string()
|
|
2223
|
+
.min(1)
|
|
2224
|
+
.max(240)
|
|
2225
|
+
.regex(/^[A-Za-z0-9._ -]+$/u)
|
|
2226
|
+
.refine((value) => value !== "." && value !== "..", {
|
|
2227
|
+
message: "safe filename must be one path segment",
|
|
2228
|
+
}),
|
|
2229
|
+
sizeBytes: z.number().int().nonnegative().max(5_000_000_000),
|
|
2230
|
+
sha256: sha256Hex.nullable(),
|
|
2231
|
+
download: z
|
|
2232
|
+
.object({
|
|
2233
|
+
url: boundedHttpUrl,
|
|
2234
|
+
expiresAt: z.string().datetime({ offset: true }),
|
|
2235
|
+
})
|
|
2236
|
+
.strict(),
|
|
2237
|
+
})
|
|
2238
|
+
.strict();
|
|
2239
|
+
export type BrowserWorkspaceFileAuthority = z.infer<typeof BrowserWorkspaceFileAuthority>;
|
|
2240
|
+
|
|
2241
|
+
export const BrowserWorkspaceFileStageRequest = z
|
|
2242
|
+
.object({
|
|
2243
|
+
operationId: z.string().uuid(),
|
|
2244
|
+
files: z.array(BrowserWorkspaceFileAuthority).min(1).max(100),
|
|
2245
|
+
})
|
|
2246
|
+
.strict()
|
|
2247
|
+
.superRefine((request, context) => {
|
|
2248
|
+
const seen = new Set<string>();
|
|
2249
|
+
for (const [index, file] of request.files.entries()) {
|
|
2250
|
+
if (seen.has(file.fileId)) {
|
|
2251
|
+
context.addIssue({
|
|
2252
|
+
code: "custom",
|
|
2253
|
+
path: ["files", index, "fileId"],
|
|
2254
|
+
message: "workspace file authority is duplicated",
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
seen.add(file.fileId);
|
|
2258
|
+
}
|
|
2259
|
+
});
|
|
2260
|
+
export type BrowserWorkspaceFileStageRequest = z.infer<typeof BrowserWorkspaceFileStageRequest>;
|
|
2261
|
+
|
|
2262
|
+
export const BrowserWorkspaceFileStageResponse = z
|
|
2263
|
+
.object({
|
|
2264
|
+
operationId: z.string().uuid(),
|
|
2265
|
+
fileIds: z.array(z.string().uuid()).min(1).max(100),
|
|
2266
|
+
replayed: z.boolean(),
|
|
2267
|
+
})
|
|
2268
|
+
.strict();
|
|
2269
|
+
export type BrowserWorkspaceFileStageResponse = z.infer<typeof BrowserWorkspaceFileStageResponse>;
|
|
2270
|
+
|
|
2271
|
+
export const InteractionOperationState = z.enum([
|
|
2272
|
+
"prepared",
|
|
2273
|
+
"dispatched",
|
|
2274
|
+
"completed",
|
|
2275
|
+
"failed",
|
|
2276
|
+
"outcome_unknown",
|
|
2277
|
+
]);
|
|
2278
|
+
export type InteractionOperationState = z.infer<typeof InteractionOperationState>;
|
|
2279
|
+
|
|
2280
|
+
export const InteractionLifecycleOperationKind = z.enum([
|
|
2281
|
+
"create",
|
|
2282
|
+
"resume",
|
|
2283
|
+
"suspend",
|
|
2284
|
+
"end",
|
|
2285
|
+
"publish",
|
|
2286
|
+
]);
|
|
2287
|
+
export type InteractionLifecycleOperationKind = z.infer<typeof InteractionLifecycleOperationKind>;
|
|
2288
|
+
|
|
2289
|
+
export const InteractionLifecycleOperationReceipt = z
|
|
2290
|
+
.object({
|
|
2291
|
+
operationId: z.string().uuid(),
|
|
2292
|
+
resourceKind: z.enum(["browser_session", "computer_session"]),
|
|
2293
|
+
resourceId: z.string().uuid(),
|
|
2294
|
+
kind: InteractionLifecycleOperationKind,
|
|
2295
|
+
state: InteractionOperationState,
|
|
2296
|
+
replayed: z.boolean(),
|
|
2297
|
+
error: InteractionError.nullable(),
|
|
2298
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
2299
|
+
dispatchedAt: z.string().datetime({ offset: true }).nullable(),
|
|
2300
|
+
settledAt: z.string().datetime({ offset: true }).nullable(),
|
|
2301
|
+
})
|
|
2302
|
+
.strict()
|
|
2303
|
+
.superRefine((receipt, context) => {
|
|
2304
|
+
if (receipt.state === "completed" && (receipt.error !== null || receipt.settledAt === null)) {
|
|
2305
|
+
context.addIssue({
|
|
2306
|
+
code: "custom",
|
|
2307
|
+
message: "completed operation must settle without error",
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
if (
|
|
2311
|
+
(receipt.state === "failed" || receipt.state === "outcome_unknown") &&
|
|
2312
|
+
(receipt.error === null || receipt.settledAt === null)
|
|
2313
|
+
) {
|
|
2314
|
+
context.addIssue({
|
|
2315
|
+
code: "custom",
|
|
2316
|
+
message: "terminal operation error requires error and time",
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
if (receipt.state === "dispatched" && receipt.dispatchedAt === null) {
|
|
2320
|
+
context.addIssue({
|
|
2321
|
+
code: "custom",
|
|
2322
|
+
message: "dispatched operation requires dispatch time",
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
});
|
|
2326
|
+
export type InteractionLifecycleOperationReceipt = z.infer<
|
|
2327
|
+
typeof InteractionLifecycleOperationReceipt
|
|
2328
|
+
>;
|
|
2329
|
+
|
|
2330
|
+
export const CreateBrowserSessionRequest = z
|
|
2331
|
+
.object({
|
|
2332
|
+
operationId: z.string().uuid(),
|
|
2333
|
+
sessionId: z.string().uuid(),
|
|
2334
|
+
name: z.string().trim().min(1).max(200).optional(),
|
|
2335
|
+
initialUrl: boundedUrl.optional(),
|
|
2336
|
+
headless: z.boolean().default(true),
|
|
2337
|
+
/** Managed engine choice. Attached Chrome continues to derive its engine
|
|
2338
|
+
* from the selected device rather than accepting an impersonated value. */
|
|
2339
|
+
engine: z.enum(["chromium", "lightpanda"]).default("chromium"),
|
|
2340
|
+
placement: InteractionPlacement.optional(),
|
|
2341
|
+
identityId: z.string().uuid().optional(),
|
|
2342
|
+
baseRevisionId: z.string().uuid().optional(),
|
|
2343
|
+
networkRouteId: z.string().uuid().optional(),
|
|
2344
|
+
linkedComputerSessionId: z.string().uuid().optional(),
|
|
2345
|
+
})
|
|
2346
|
+
.strict()
|
|
2347
|
+
.superRefine((value, context) => {
|
|
2348
|
+
if (value.baseRevisionId && !value.identityId) {
|
|
2349
|
+
context.addIssue({
|
|
2350
|
+
code: "custom",
|
|
2351
|
+
path: ["baseRevisionId"],
|
|
2352
|
+
message: "baseRevisionId requires identityId",
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
if (value.linkedComputerSessionId && value.headless) {
|
|
2356
|
+
context.addIssue({
|
|
2357
|
+
code: "custom",
|
|
2358
|
+
path: ["linkedComputerSessionId"],
|
|
2359
|
+
message: "a linked computer requires a headed browser",
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
if (value.engine === "lightpanda") {
|
|
2363
|
+
if (!value.headless) {
|
|
2364
|
+
context.addIssue({
|
|
2365
|
+
code: "custom",
|
|
2366
|
+
path: ["headless"],
|
|
2367
|
+
message: "Lightpanda is a headless semantic browser",
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
if (value.identityId || value.baseRevisionId) {
|
|
2371
|
+
context.addIssue({
|
|
2372
|
+
code: "custom",
|
|
2373
|
+
path: ["identityId"],
|
|
2374
|
+
message: "Lightpanda does not support Chromium browser identities",
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
if (value.networkRouteId) {
|
|
2378
|
+
context.addIssue({
|
|
2379
|
+
code: "custom",
|
|
2380
|
+
path: ["networkRouteId"],
|
|
2381
|
+
message: "Lightpanda network routes are not supported yet",
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
if (value.linkedComputerSessionId) {
|
|
2385
|
+
context.addIssue({
|
|
2386
|
+
code: "custom",
|
|
2387
|
+
path: ["linkedComputerSessionId"],
|
|
2388
|
+
message: "Lightpanda has no linked desktop window",
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
if (value.placement?.kind === "attached_device") {
|
|
2393
|
+
if (value.engine !== "chromium") {
|
|
2394
|
+
context.addIssue({
|
|
2395
|
+
code: "custom",
|
|
2396
|
+
path: ["engine"],
|
|
2397
|
+
message: "attached browser placement uses the selected Chrome engine",
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
if (value.headless) {
|
|
2401
|
+
context.addIssue({
|
|
2402
|
+
code: "custom",
|
|
2403
|
+
path: ["headless"],
|
|
2404
|
+
message: "attached browser placement is always headed",
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
if (value.identityId || value.baseRevisionId) {
|
|
2408
|
+
context.addIssue({
|
|
2409
|
+
code: "custom",
|
|
2410
|
+
path: ["identityId"],
|
|
2411
|
+
message: "attached browser placement already has a live profile identity",
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
if (value.placement?.kind === "external_provider") {
|
|
2416
|
+
if (value.engine !== "chromium") {
|
|
2417
|
+
context.addIssue({
|
|
2418
|
+
code: "custom",
|
|
2419
|
+
path: ["engine"],
|
|
2420
|
+
message: "external browser providers use their Chromium-compatible engine",
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
if (value.identityId || value.baseRevisionId) {
|
|
2424
|
+
context.addIssue({
|
|
2425
|
+
code: "custom",
|
|
2426
|
+
path: ["identityId"],
|
|
2427
|
+
message: "external browser providers do not yet support portable BrowserIdentity state",
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
if (value.linkedComputerSessionId) {
|
|
2431
|
+
context.addIssue({
|
|
2432
|
+
code: "custom",
|
|
2433
|
+
path: ["linkedComputerSessionId"],
|
|
2434
|
+
message: "external browser providers cannot link a placement desktop",
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
});
|
|
2439
|
+
export type CreateBrowserSessionRequest = z.infer<typeof CreateBrowserSessionRequest>;
|
|
2440
|
+
|
|
2441
|
+
export const BrowserSessionListResponse = z
|
|
2442
|
+
.object({
|
|
2443
|
+
revision: z.number().int().nonnegative(),
|
|
2444
|
+
sessions: z.array(BrowserSession).max(10_000),
|
|
2445
|
+
})
|
|
2446
|
+
.strict();
|
|
2447
|
+
export type BrowserSessionListResponse = z.infer<typeof BrowserSessionListResponse>;
|
|
2448
|
+
|
|
2449
|
+
export const BrowserSessionMutationResponse = z
|
|
2450
|
+
.object({
|
|
2451
|
+
session: BrowserSession,
|
|
2452
|
+
operation: InteractionLifecycleOperationReceipt,
|
|
2453
|
+
})
|
|
2454
|
+
.strict();
|
|
2455
|
+
export type BrowserSessionMutationResponse = z.infer<typeof BrowserSessionMutationResponse>;
|
|
2456
|
+
|
|
2457
|
+
export const BrowserSessionLifecycleRequest = z
|
|
2458
|
+
.object({
|
|
2459
|
+
operationId: z.string().uuid(),
|
|
2460
|
+
})
|
|
2461
|
+
.strict();
|
|
2462
|
+
export type BrowserSessionLifecycleRequest = z.infer<typeof BrowserSessionLifecycleRequest>;
|
|
2463
|
+
|
|
2464
|
+
export const BrowserOpenTargetRequest = z
|
|
2465
|
+
.object({
|
|
2466
|
+
url: boundedUrl.optional(),
|
|
2467
|
+
})
|
|
2468
|
+
.strict();
|
|
2469
|
+
export type BrowserOpenTargetRequest = z.infer<typeof BrowserOpenTargetRequest>;
|
|
2470
|
+
|
|
2471
|
+
export const BrowserTargetListResponse = z
|
|
2472
|
+
.object({
|
|
2473
|
+
browserSessionId: z.string().uuid(),
|
|
2474
|
+
controllerGeneration: opaqueGeneration,
|
|
2475
|
+
targets: z.array(BrowserTarget).max(10_000),
|
|
2476
|
+
})
|
|
2477
|
+
.strict();
|
|
2478
|
+
export type BrowserTargetListResponse = z.infer<typeof BrowserTargetListResponse>;
|
|
2479
|
+
|
|
2480
|
+
export const InteractionFrameStreamOptions = z
|
|
2481
|
+
.object({
|
|
2482
|
+
format: z.enum(["jpeg", "png"]).optional(),
|
|
2483
|
+
quality: z.number().int().min(1).max(100).optional(),
|
|
2484
|
+
maxWidth: z.number().int().min(1).max(4_096).optional(),
|
|
2485
|
+
maxHeight: z.number().int().min(1).max(4_096).optional(),
|
|
2486
|
+
everyNthFrame: z.number().int().min(1).max(60).optional(),
|
|
2487
|
+
})
|
|
2488
|
+
.strict();
|
|
2489
|
+
export type InteractionFrameStreamOptions = z.infer<typeof InteractionFrameStreamOptions>;
|
|
2490
|
+
|
|
2491
|
+
export const BrowserFrameStreamOptions = InteractionFrameStreamOptions;
|
|
2492
|
+
export type BrowserFrameStreamOptions = z.infer<typeof BrowserFrameStreamOptions>;
|
|
2493
|
+
|
|
2494
|
+
/** Placement-neutral controller attachment. Browser and Computer viewers use
|
|
2495
|
+
* the same authenticated transport shape while negotiating distinct stream
|
|
2496
|
+
* protocols and resource generations. */
|
|
2497
|
+
const DirectInteractionFrameStreamAttachment = z
|
|
2498
|
+
.object({
|
|
2499
|
+
kind: z.literal("direct_websocket"),
|
|
2500
|
+
url: boundedUrl,
|
|
2501
|
+
protocols: z.array(z.string().min(1).max(2_048)).length(2),
|
|
2502
|
+
})
|
|
2503
|
+
.strict();
|
|
2504
|
+
|
|
2505
|
+
const DirectComputerRfbAttachment = z
|
|
2506
|
+
.object({
|
|
2507
|
+
kind: z.literal("direct_rfb"),
|
|
2508
|
+
url: boundedUrl,
|
|
2509
|
+
protocols: z.array(z.string().min(1).max(2_048)).length(3),
|
|
2510
|
+
})
|
|
2511
|
+
.strict();
|
|
2512
|
+
|
|
2513
|
+
function relayInteractionFrameStreamAttachment<const Kind extends 3 | 4>(kind: Kind) {
|
|
2514
|
+
return z
|
|
2515
|
+
.object({
|
|
2516
|
+
kind: z.literal("relay"),
|
|
2517
|
+
url: boundedUrl,
|
|
2518
|
+
token: z.string().min(32).max(8_192),
|
|
2519
|
+
channel: z
|
|
2520
|
+
.object({
|
|
2521
|
+
channelId: boundedOpaqueId,
|
|
2522
|
+
workspaceId: z.string().uuid(),
|
|
2523
|
+
agentId: boundedOpaqueId,
|
|
2524
|
+
kind: z.literal(kind),
|
|
2525
|
+
port: z.number().int().min(1).max(65_535),
|
|
2526
|
+
})
|
|
2527
|
+
.strict(),
|
|
2528
|
+
})
|
|
2529
|
+
.strict();
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
export const BrowserFrameStreamAttachment = z.discriminatedUnion("kind", [
|
|
2533
|
+
DirectInteractionFrameStreamAttachment,
|
|
2534
|
+
relayInteractionFrameStreamAttachment(3),
|
|
2535
|
+
]);
|
|
2536
|
+
export type BrowserFrameStreamAttachment = z.infer<typeof BrowserFrameStreamAttachment>;
|
|
2537
|
+
|
|
2538
|
+
export const ComputerFrameStreamAttachment = z.discriminatedUnion("kind", [
|
|
2539
|
+
DirectInteractionFrameStreamAttachment,
|
|
2540
|
+
DirectComputerRfbAttachment,
|
|
2541
|
+
relayInteractionFrameStreamAttachment(4),
|
|
2542
|
+
]);
|
|
2543
|
+
export type ComputerFrameStreamAttachment = z.infer<typeof ComputerFrameStreamAttachment>;
|
|
2544
|
+
|
|
2545
|
+
export const InteractionFrameStreamAttachment = z.union([
|
|
2546
|
+
BrowserFrameStreamAttachment,
|
|
2547
|
+
ComputerFrameStreamAttachment,
|
|
2548
|
+
]);
|
|
2549
|
+
export type InteractionFrameStreamAttachment = z.infer<typeof InteractionFrameStreamAttachment>;
|
|
2550
|
+
|
|
2551
|
+
export const BrowserSessionAttachment = z
|
|
2552
|
+
.object({
|
|
2553
|
+
browserSessionId: z.string().uuid(),
|
|
2554
|
+
controllerGeneration: opaqueGeneration,
|
|
2555
|
+
targetId: boundedOpaqueId,
|
|
2556
|
+
stream: BrowserFrameStreamAttachment,
|
|
2557
|
+
expiresAt: z.string().datetime({ offset: true }),
|
|
2558
|
+
})
|
|
2559
|
+
.strict();
|
|
2560
|
+
export type BrowserSessionAttachment = z.infer<typeof BrowserSessionAttachment>;
|
|
2561
|
+
|
|
2562
|
+
export const BrowserSessionAttachmentRequest = z
|
|
2563
|
+
.object({
|
|
2564
|
+
targetId: boundedOpaqueId,
|
|
2565
|
+
expiresInSeconds: z.number().int().min(15).max(600).default(120),
|
|
2566
|
+
stream: BrowserFrameStreamOptions.optional(),
|
|
2567
|
+
})
|
|
2568
|
+
.strict();
|
|
2569
|
+
export type BrowserSessionAttachmentRequest = z.infer<typeof BrowserSessionAttachmentRequest>;
|
|
2570
|
+
|
|
2571
|
+
/** Human/API action request. The API supplies resource/controller/actor fields
|
|
2572
|
+
* from authenticated durable authority before dispatching the canonical
|
|
2573
|
+
* BrowserActionCommand to the placement. */
|
|
2574
|
+
export const BrowserActionRequest = z
|
|
2575
|
+
.object({
|
|
2576
|
+
operationId: z.string().uuid(),
|
|
2577
|
+
targetId: boundedOpaqueId,
|
|
2578
|
+
expectedTargetGeneration: opaqueGeneration,
|
|
2579
|
+
expectedDocumentGeneration: opaqueGeneration.nullable(),
|
|
2580
|
+
expectedFrameId: opaqueGeneration.nullable(),
|
|
2581
|
+
observationMode: z.enum(["full", "none"]).default("full"),
|
|
2582
|
+
action: z.union([BrowserAction, BrowserActionBatch]),
|
|
2583
|
+
})
|
|
2584
|
+
.strict();
|
|
2585
|
+
export type BrowserActionRequest = z.infer<typeof BrowserActionRequest>;
|
|
2586
|
+
|
|
2587
|
+
export const BrowserSessionHeartbeatResponse = z
|
|
2588
|
+
.object({
|
|
2589
|
+
browserSessionId: z.string().uuid(),
|
|
2590
|
+
controllerGeneration: opaqueGeneration,
|
|
2591
|
+
alive: z.literal(true),
|
|
2592
|
+
})
|
|
2593
|
+
.strict();
|
|
2594
|
+
export type BrowserSessionHeartbeatResponse = z.infer<typeof BrowserSessionHeartbeatResponse>;
|
|
2595
|
+
|
|
2596
|
+
export const BrowserActionReceipt = z
|
|
2597
|
+
.object({
|
|
2598
|
+
protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
|
|
2599
|
+
operationId: z.string().uuid(),
|
|
2600
|
+
browserSessionId: z.string().uuid(),
|
|
2601
|
+
controllerGeneration: opaqueGeneration,
|
|
2602
|
+
targetId: boundedOpaqueId,
|
|
2603
|
+
state: InteractionOperationState,
|
|
2604
|
+
dispatchedAt: z.string().datetime({ offset: true }).nullable(),
|
|
2605
|
+
settledAt: z.string().datetime({ offset: true }).nullable(),
|
|
2606
|
+
observation: BrowserObservation.nullable(),
|
|
2607
|
+
error: InteractionError.nullable(),
|
|
2608
|
+
})
|
|
2609
|
+
.strict()
|
|
2610
|
+
.superRefine((receipt, context) => {
|
|
2611
|
+
if (receipt.state === "completed" && (receipt.error !== null || receipt.settledAt === null)) {
|
|
2612
|
+
context.addIssue({
|
|
2613
|
+
code: "custom",
|
|
2614
|
+
message: "completed receipt must settle without error",
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
if (
|
|
2618
|
+
(receipt.state === "failed" || receipt.state === "outcome_unknown") &&
|
|
2619
|
+
(receipt.error === null || receipt.settledAt === null)
|
|
2620
|
+
) {
|
|
2621
|
+
context.addIssue({
|
|
2622
|
+
code: "custom",
|
|
2623
|
+
message: "terminal error receipt requires error and time",
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
if (receipt.state === "dispatched" && receipt.dispatchedAt === null) {
|
|
2627
|
+
context.addIssue({
|
|
2628
|
+
code: "custom",
|
|
2629
|
+
message: "dispatched receipt requires dispatch time",
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
});
|
|
2633
|
+
export type BrowserActionReceipt = z.infer<typeof BrowserActionReceipt>;
|
|
2634
|
+
|
|
2635
|
+
/** Controller-private protected-fill wire contract. Secret values cross only
|
|
2636
|
+
* the credential broker -> exact placement controller boundary; this command
|
|
2637
|
+
* is never projected into model MCP, Codemode, public action history, or UI. */
|
|
2638
|
+
export const BrowserProtectedAuthFieldValue = ProtectedAuthField.extend({
|
|
2639
|
+
purpose: SiteAuthFieldPurpose,
|
|
2640
|
+
value: z.string().min(1).max(65_536),
|
|
2641
|
+
}).strict();
|
|
2642
|
+
export type BrowserProtectedAuthFieldValue = z.infer<typeof BrowserProtectedAuthFieldValue>;
|
|
2643
|
+
|
|
2644
|
+
export const BrowserProtectedAuthFillCommand = z
|
|
2645
|
+
.object({
|
|
2646
|
+
protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
|
|
2647
|
+
operationId: z.string().uuid(),
|
|
2648
|
+
browserSessionId: z.string().uuid(),
|
|
2649
|
+
controllerGeneration: opaqueGeneration,
|
|
2650
|
+
targetId: boundedOpaqueId,
|
|
2651
|
+
expectedTargetGeneration: opaqueGeneration,
|
|
2652
|
+
expectedDocumentGeneration: opaqueGeneration,
|
|
2653
|
+
expectedFrameId: opaqueGeneration,
|
|
2654
|
+
actor: InteractionActor,
|
|
2655
|
+
authorityId: boundedOpaqueId,
|
|
2656
|
+
credentialVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
2657
|
+
allowedOrigins: z.array(canonicalWebOrigin).min(1).max(64),
|
|
2658
|
+
fields: z.array(BrowserProtectedAuthFieldValue).min(1).max(32),
|
|
2659
|
+
submit: ProtectedAuthSubmit,
|
|
2660
|
+
})
|
|
2661
|
+
.strict()
|
|
2662
|
+
.superRefine((command, context) => {
|
|
2663
|
+
if (new Set(command.allowedOrigins).size !== command.allowedOrigins.length) {
|
|
2664
|
+
context.addIssue({
|
|
2665
|
+
code: "custom",
|
|
2666
|
+
path: ["allowedOrigins"],
|
|
2667
|
+
message: "protected-fill origins repeat",
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
const fieldIds = command.fields.map((field) => field.fieldId);
|
|
2671
|
+
if (new Set(fieldIds).size !== fieldIds.length) {
|
|
2672
|
+
context.addIssue({
|
|
2673
|
+
code: "custom",
|
|
2674
|
+
path: ["fields"],
|
|
2675
|
+
message: "protected-fill field ids repeat",
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
});
|
|
2679
|
+
export type BrowserProtectedAuthFillCommand = z.infer<typeof BrowserProtectedAuthFillCommand>;
|
|
2680
|
+
|
|
2681
|
+
export const BrowserProtectedAuthObservation = z
|
|
2682
|
+
.object({
|
|
2683
|
+
target: BrowserTarget,
|
|
2684
|
+
status: z.enum(["submitted", "working"]),
|
|
2685
|
+
})
|
|
2686
|
+
.strict();
|
|
2687
|
+
export type BrowserProtectedAuthObservation = z.infer<typeof BrowserProtectedAuthObservation>;
|
|
2688
|
+
|
|
2689
|
+
/** Secret-free placement receipt. It can be journaled, replayed, and returned
|
|
2690
|
+
* to the broker without retaining any field value or model-visible page tree. */
|
|
2691
|
+
export const BrowserProtectedAuthFillReceipt = z
|
|
2692
|
+
.object({
|
|
2693
|
+
protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
|
|
2694
|
+
operationId: z.string().uuid(),
|
|
2695
|
+
browserSessionId: z.string().uuid(),
|
|
2696
|
+
controllerGeneration: opaqueGeneration,
|
|
2697
|
+
targetId: boundedOpaqueId,
|
|
2698
|
+
state: InteractionOperationState,
|
|
2699
|
+
dispatchedAt: z.string().datetime({ offset: true }).nullable(),
|
|
2700
|
+
settledAt: z.string().datetime({ offset: true }).nullable(),
|
|
2701
|
+
observation: BrowserProtectedAuthObservation.nullable(),
|
|
2702
|
+
error: InteractionError.nullable(),
|
|
2703
|
+
})
|
|
2704
|
+
.strict()
|
|
2705
|
+
.superRefine((receipt, context) => {
|
|
2706
|
+
if (
|
|
2707
|
+
receipt.state === "completed" &&
|
|
2708
|
+
(receipt.error !== null || receipt.observation === null || receipt.settledAt === null)
|
|
2709
|
+
) {
|
|
2710
|
+
context.addIssue({
|
|
2711
|
+
code: "custom",
|
|
2712
|
+
message: "completed protected-fill receipt requires a result and no error",
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
if (
|
|
2716
|
+
(receipt.state === "failed" || receipt.state === "outcome_unknown") &&
|
|
2717
|
+
(receipt.error === null || receipt.observation !== null || receipt.settledAt === null)
|
|
2718
|
+
) {
|
|
2719
|
+
context.addIssue({
|
|
2720
|
+
code: "custom",
|
|
2721
|
+
message: "terminal protected-fill error requires error, no result, and time",
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
if (receipt.state === "dispatched" && receipt.dispatchedAt === null) {
|
|
2725
|
+
context.addIssue({
|
|
2726
|
+
code: "custom",
|
|
2727
|
+
message: "dispatched protected-fill receipt requires dispatch time",
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
});
|
|
2731
|
+
export type BrowserProtectedAuthFillReceipt = z.infer<typeof BrowserProtectedAuthFillReceipt>;
|
|
2732
|
+
|
|
2733
|
+
export const ComputerSessionCapabilities = z
|
|
2734
|
+
.object({
|
|
2735
|
+
semanticObservation: z.boolean(),
|
|
2736
|
+
appDiscovery: z.boolean(),
|
|
2737
|
+
appLaunch: z.boolean(),
|
|
2738
|
+
windowCapture: z.boolean(),
|
|
2739
|
+
screenCapture: z.boolean(),
|
|
2740
|
+
semanticActions: z.boolean(),
|
|
2741
|
+
pointerInput: z.boolean(),
|
|
2742
|
+
keyboardInput: z.boolean(),
|
|
2743
|
+
clipboard: z.boolean(),
|
|
2744
|
+
backgroundActions: z.boolean(),
|
|
2745
|
+
parallelApps: z.boolean(),
|
|
2746
|
+
})
|
|
2747
|
+
.strict();
|
|
2748
|
+
export type ComputerSessionCapabilities = z.infer<typeof ComputerSessionCapabilities>;
|
|
2749
|
+
|
|
2750
|
+
/** A fresh bounded read of the native clipboard for the ComputerSession's
|
|
2751
|
+
* graphical seat. Physical ComputerSessions on the same login seat intentionally
|
|
2752
|
+
* observe the same OS clipboard; BrowserSession private clipboards never do. */
|
|
2753
|
+
export const ComputerClipboard = z
|
|
2754
|
+
.object({
|
|
2755
|
+
computerSessionId: z.string().uuid(),
|
|
2756
|
+
controllerGeneration: opaqueGeneration,
|
|
2757
|
+
text: z
|
|
2758
|
+
.string()
|
|
2759
|
+
.max(INTERACTION_MAX_CLIPBOARD_BYTES)
|
|
2760
|
+
.refine(
|
|
2761
|
+
(value) => new TextEncoder().encode(value).byteLength <= INTERACTION_MAX_CLIPBOARD_BYTES,
|
|
2762
|
+
{ message: "computer clipboard text exceeds its UTF-8 byte envelope" },
|
|
2763
|
+
)
|
|
2764
|
+
.nullable(),
|
|
2765
|
+
truncated: z.boolean(),
|
|
2766
|
+
observedAt: z.string().datetime({ offset: true }),
|
|
2767
|
+
})
|
|
2768
|
+
.strict()
|
|
2769
|
+
.superRefine((clipboard, context) => {
|
|
2770
|
+
if (clipboard.text === null && clipboard.truncated) {
|
|
2771
|
+
context.addIssue({
|
|
2772
|
+
code: "custom",
|
|
2773
|
+
path: ["truncated"],
|
|
2774
|
+
message: "an unavailable computer clipboard value cannot be truncated",
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
});
|
|
2778
|
+
export type ComputerClipboard = z.infer<typeof ComputerClipboard>;
|
|
2779
|
+
|
|
2780
|
+
export const ComputerSession = z
|
|
2781
|
+
.object({
|
|
2782
|
+
id: z.string().uuid(),
|
|
2783
|
+
accountId: z.string().uuid(),
|
|
2784
|
+
workspaceId: z.string().uuid(),
|
|
2785
|
+
name: z.string().trim().min(1).max(200),
|
|
2786
|
+
lifecycle: InteractionLifecycle,
|
|
2787
|
+
placement: InteractionPlacement,
|
|
2788
|
+
controller: InteractionControllerBinding.nullable(),
|
|
2789
|
+
platform: z.enum(["linux", "macos", "windows"]).nullable(),
|
|
2790
|
+
adapter: boundedOpaqueId.nullable(),
|
|
2791
|
+
seatId: boundedOpaqueId.nullable(),
|
|
2792
|
+
displayId: boundedOpaqueId.nullable(),
|
|
2793
|
+
capabilities: ComputerSessionCapabilities.nullable(),
|
|
2794
|
+
associations: z.array(InteractionAssociation).max(1_000),
|
|
2795
|
+
createdBySubjectId: z.string().min(1).max(1_024),
|
|
2796
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
2797
|
+
lastUsedAt: z.string().datetime({ offset: true }),
|
|
2798
|
+
failureCode: boundedOpaqueId.nullable(),
|
|
2799
|
+
})
|
|
2800
|
+
.strict()
|
|
2801
|
+
.superRefine((session, context) => {
|
|
2802
|
+
const nativeBinding = [
|
|
2803
|
+
session.platform,
|
|
2804
|
+
session.adapter,
|
|
2805
|
+
session.seatId,
|
|
2806
|
+
session.displayId,
|
|
2807
|
+
session.capabilities,
|
|
2808
|
+
];
|
|
2809
|
+
const populated = nativeBinding.filter((value) => value !== null).length;
|
|
2810
|
+
if (populated !== 0 && populated !== nativeBinding.length) {
|
|
2811
|
+
context.addIssue({
|
|
2812
|
+
code: "custom",
|
|
2813
|
+
message: "ComputerSession native binding must be absent or complete",
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2816
|
+
if (session.lifecycle === "active" && (session.controller === null || populated === 0)) {
|
|
2817
|
+
context.addIssue({
|
|
2818
|
+
code: "custom",
|
|
2819
|
+
message: "active ComputerSession requires controller and native bindings",
|
|
2820
|
+
});
|
|
2821
|
+
}
|
|
2822
|
+
});
|
|
2823
|
+
export type ComputerSession = z.infer<typeof ComputerSession>;
|
|
2824
|
+
|
|
2825
|
+
export const ComputerTarget = z
|
|
2826
|
+
.object({
|
|
2827
|
+
id: boundedOpaqueId,
|
|
2828
|
+
computerSessionId: z.string().uuid(),
|
|
2829
|
+
controllerGeneration: opaqueGeneration,
|
|
2830
|
+
targetGeneration: opaqueGeneration,
|
|
2831
|
+
kind: z.enum(["app", "window", "screen"]),
|
|
2832
|
+
applicationId: z.string().max(1_024).nullable(),
|
|
2833
|
+
processId: z.number().int().positive().nullable(),
|
|
2834
|
+
title: z.string().max(4_096),
|
|
2835
|
+
bounds: InteractionRect.nullable(),
|
|
2836
|
+
focused: z.boolean(),
|
|
2837
|
+
})
|
|
2838
|
+
.strict();
|
|
2839
|
+
export type ComputerTarget = z.infer<typeof ComputerTarget>;
|
|
2840
|
+
|
|
2841
|
+
export const ComputerObservation = z
|
|
2842
|
+
.object({
|
|
2843
|
+
protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
|
|
2844
|
+
observationId: boundedOpaqueId,
|
|
2845
|
+
computerSessionId: z.string().uuid(),
|
|
2846
|
+
target: ComputerTarget,
|
|
2847
|
+
frameId: opaqueGeneration.nullable(),
|
|
2848
|
+
semantic: z.union([InteractionSemanticSnapshot, InteractionSemanticDiff]).nullable(),
|
|
2849
|
+
screenshot: RetainedArtifactReferenceSchema.nullable(),
|
|
2850
|
+
focusedRef: boundedOpaqueId.nullable(),
|
|
2851
|
+
changedRegions: z.array(InteractionRect).max(INTERACTION_MAX_CHANGED_NODES),
|
|
2852
|
+
observedAt: z.string().datetime({ offset: true }),
|
|
2853
|
+
})
|
|
2854
|
+
.strict();
|
|
2855
|
+
export type ComputerObservation = z.infer<typeof ComputerObservation>;
|
|
2856
|
+
|
|
2857
|
+
export const ComputerAction = z.discriminatedUnion("type", [
|
|
2858
|
+
z
|
|
2859
|
+
.object({
|
|
2860
|
+
type: z.literal("semantic"),
|
|
2861
|
+
locator: ComputerLocator,
|
|
2862
|
+
action: z.enum([
|
|
2863
|
+
"invoke",
|
|
2864
|
+
"focus",
|
|
2865
|
+
"set_value",
|
|
2866
|
+
"increment",
|
|
2867
|
+
"decrement",
|
|
2868
|
+
"select",
|
|
2869
|
+
"deselect",
|
|
2870
|
+
"expand",
|
|
2871
|
+
"collapse",
|
|
2872
|
+
"show_menu",
|
|
2873
|
+
"scroll_into_view",
|
|
2874
|
+
]),
|
|
2875
|
+
value: z.union([z.string().max(1_000_000), z.number().finite(), z.boolean()]).optional(),
|
|
2876
|
+
})
|
|
2877
|
+
.strict()
|
|
2878
|
+
.superRefine((action, context) => {
|
|
2879
|
+
if ((action.action === "set_value") !== (action.value !== undefined)) {
|
|
2880
|
+
context.addIssue({
|
|
2881
|
+
code: "custom",
|
|
2882
|
+
path: ["value"],
|
|
2883
|
+
message: "value is required exactly for set_value",
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
}),
|
|
2887
|
+
z
|
|
2888
|
+
.object({
|
|
2889
|
+
type: z.literal("pointer"),
|
|
2890
|
+
frameId: opaqueGeneration,
|
|
2891
|
+
action: z.enum(["click", "double_click", "move", "scroll", "drag"]),
|
|
2892
|
+
x: z.number().finite(),
|
|
2893
|
+
y: z.number().finite(),
|
|
2894
|
+
endX: z.number().finite().optional(),
|
|
2895
|
+
endY: z.number().finite().optional(),
|
|
2896
|
+
deltaX: z.number().finite().optional(),
|
|
2897
|
+
deltaY: z.number().finite().optional(),
|
|
2898
|
+
button: z.enum(["left", "right", "middle"]).optional(),
|
|
2899
|
+
})
|
|
2900
|
+
.strict()
|
|
2901
|
+
.superRefine((action, context) => {
|
|
2902
|
+
const hasEnd = action.endX !== undefined || action.endY !== undefined;
|
|
2903
|
+
const hasDelta = action.deltaX !== undefined || action.deltaY !== undefined;
|
|
2904
|
+
if (action.action === "drag" && (action.endX === undefined || action.endY === undefined)) {
|
|
2905
|
+
context.addIssue({
|
|
2906
|
+
code: "custom",
|
|
2907
|
+
message: "pointer drag requires endX and endY",
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
if (action.action !== "drag" && hasEnd) {
|
|
2911
|
+
context.addIssue({
|
|
2912
|
+
code: "custom",
|
|
2913
|
+
message: "pointer end coordinates require drag",
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
if (
|
|
2917
|
+
action.action === "scroll" &&
|
|
2918
|
+
action.deltaX === undefined &&
|
|
2919
|
+
action.deltaY === undefined
|
|
2920
|
+
) {
|
|
2921
|
+
context.addIssue({
|
|
2922
|
+
code: "custom",
|
|
2923
|
+
message: "pointer scroll requires a delta",
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
if (action.action !== "scroll" && hasDelta) {
|
|
2927
|
+
context.addIssue({
|
|
2928
|
+
code: "custom",
|
|
2929
|
+
message: "pointer deltas require scroll",
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
}),
|
|
2933
|
+
z
|
|
2934
|
+
.object({
|
|
2935
|
+
type: z.literal("keyboard"),
|
|
2936
|
+
action: z.enum(["type", "press"]),
|
|
2937
|
+
value: z.string().max(1_000_000),
|
|
2938
|
+
})
|
|
2939
|
+
.strict(),
|
|
2940
|
+
z
|
|
2941
|
+
.object({
|
|
2942
|
+
type: z.literal("clipboard"),
|
|
2943
|
+
operation: z.enum(["write", "clear", "copy", "paste"]),
|
|
2944
|
+
text: z
|
|
2945
|
+
.string()
|
|
2946
|
+
.max(INTERACTION_MAX_CLIPBOARD_BYTES)
|
|
2947
|
+
.refine(
|
|
2948
|
+
(value) => new TextEncoder().encode(value).byteLength <= INTERACTION_MAX_CLIPBOARD_BYTES,
|
|
2949
|
+
{ message: "computer clipboard text exceeds its UTF-8 byte envelope" },
|
|
2950
|
+
)
|
|
2951
|
+
.optional(),
|
|
2952
|
+
})
|
|
2953
|
+
.strict()
|
|
2954
|
+
.superRefine((action, context) => {
|
|
2955
|
+
if ((action.operation === "write") !== (action.text !== undefined)) {
|
|
2956
|
+
context.addIssue({
|
|
2957
|
+
code: "custom",
|
|
2958
|
+
path: ["text"],
|
|
2959
|
+
message: "computer clipboard text is required exactly for write",
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
}),
|
|
2963
|
+
z.object({ type: z.literal("focus"), targetId: boundedOpaqueId }).strict(),
|
|
2964
|
+
z
|
|
2965
|
+
.object({
|
|
2966
|
+
type: z.literal("launch"),
|
|
2967
|
+
applicationId: z.string().min(1).max(1_024),
|
|
2968
|
+
})
|
|
2969
|
+
.strict(),
|
|
2970
|
+
]);
|
|
2971
|
+
export type ComputerAction = z.infer<typeof ComputerAction>;
|
|
2972
|
+
|
|
2973
|
+
export const ComputerActionCommand = z
|
|
2974
|
+
.object({
|
|
2975
|
+
protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
|
|
2976
|
+
operationId: z.string().uuid(),
|
|
2977
|
+
computerSessionId: z.string().uuid(),
|
|
2978
|
+
controllerGeneration: opaqueGeneration,
|
|
2979
|
+
targetId: boundedOpaqueId,
|
|
2980
|
+
expectedTargetGeneration: opaqueGeneration,
|
|
2981
|
+
expectedObservationId: boundedOpaqueId.nullable(),
|
|
2982
|
+
expectedFrameId: opaqueGeneration.nullable(),
|
|
2983
|
+
actor: InteractionActor,
|
|
2984
|
+
action: ComputerAction,
|
|
2985
|
+
})
|
|
2986
|
+
.strict()
|
|
2987
|
+
.superRefine((command, context) => {
|
|
2988
|
+
if (
|
|
2989
|
+
command.action.type === "semantic" &&
|
|
2990
|
+
command.action.locator.kind === "ref" &&
|
|
2991
|
+
command.expectedObservationId === null
|
|
2992
|
+
) {
|
|
2993
|
+
context.addIssue({
|
|
2994
|
+
code: "custom",
|
|
2995
|
+
path: ["expectedObservationId"],
|
|
2996
|
+
message: "semantic ref actions require expectedObservationId",
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2999
|
+
if (command.action.type === "pointer") {
|
|
3000
|
+
if (command.expectedFrameId !== command.action.frameId) {
|
|
3001
|
+
context.addIssue({
|
|
3002
|
+
code: "custom",
|
|
3003
|
+
path: ["expectedFrameId"],
|
|
3004
|
+
message: "pointer action frameId must match expectedFrameId",
|
|
3005
|
+
});
|
|
3006
|
+
}
|
|
3007
|
+
} else if (command.expectedFrameId !== null) {
|
|
3008
|
+
context.addIssue({
|
|
3009
|
+
code: "custom",
|
|
3010
|
+
path: ["expectedFrameId"],
|
|
3011
|
+
message: "expectedFrameId is only valid for pointer actions",
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
export type ComputerActionCommand = z.infer<typeof ComputerActionCommand>;
|
|
3016
|
+
|
|
3017
|
+
export const ComputerActionReceipt = z
|
|
3018
|
+
.object({
|
|
3019
|
+
protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
|
|
3020
|
+
operationId: z.string().uuid(),
|
|
3021
|
+
computerSessionId: z.string().uuid(),
|
|
3022
|
+
controllerGeneration: opaqueGeneration,
|
|
3023
|
+
targetId: boundedOpaqueId,
|
|
3024
|
+
state: InteractionOperationState,
|
|
3025
|
+
dispatchedAt: z.string().datetime({ offset: true }).nullable(),
|
|
3026
|
+
settledAt: z.string().datetime({ offset: true }).nullable(),
|
|
3027
|
+
observation: ComputerObservation.nullable(),
|
|
3028
|
+
error: InteractionError.nullable(),
|
|
3029
|
+
})
|
|
3030
|
+
.strict()
|
|
3031
|
+
.superRefine((receipt, context) => {
|
|
3032
|
+
if (receipt.state === "completed" && (receipt.error !== null || receipt.settledAt === null)) {
|
|
3033
|
+
context.addIssue({
|
|
3034
|
+
code: "custom",
|
|
3035
|
+
message: "completed receipt must settle without error",
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
if (
|
|
3039
|
+
(receipt.state === "failed" || receipt.state === "outcome_unknown") &&
|
|
3040
|
+
(receipt.error === null || receipt.settledAt === null)
|
|
3041
|
+
) {
|
|
3042
|
+
context.addIssue({
|
|
3043
|
+
code: "custom",
|
|
3044
|
+
message: "terminal error receipt requires error and time",
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
if (receipt.state === "dispatched" && receipt.dispatchedAt === null) {
|
|
3048
|
+
context.addIssue({
|
|
3049
|
+
code: "custom",
|
|
3050
|
+
message: "dispatched receipt requires dispatch time",
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
});
|
|
3054
|
+
export type ComputerActionReceipt = z.infer<typeof ComputerActionReceipt>;
|
|
3055
|
+
|
|
3056
|
+
/** Public ComputerSession creation request. Placement chooses the concrete
|
|
3057
|
+
* platform adapter and allocates its default physical or isolated virtual seat;
|
|
3058
|
+
* callers may discover and reuse an existing ComputerSession instead. */
|
|
3059
|
+
export const CreateComputerSessionRequest = z
|
|
3060
|
+
.object({
|
|
3061
|
+
operationId: z.string().uuid(),
|
|
3062
|
+
sessionId: z.string().uuid(),
|
|
3063
|
+
name: z.string().trim().min(1).max(200).optional(),
|
|
3064
|
+
placement: InteractionPlacement.optional(),
|
|
3065
|
+
})
|
|
3066
|
+
.strict();
|
|
3067
|
+
export type CreateComputerSessionRequest = z.infer<typeof CreateComputerSessionRequest>;
|
|
3068
|
+
|
|
3069
|
+
export const ComputerSessionListResponse = z
|
|
3070
|
+
.object({
|
|
3071
|
+
revision: z.number().int().nonnegative(),
|
|
3072
|
+
sessions: z.array(ComputerSession).max(10_000),
|
|
3073
|
+
})
|
|
3074
|
+
.strict();
|
|
3075
|
+
export type ComputerSessionListResponse = z.infer<typeof ComputerSessionListResponse>;
|
|
3076
|
+
|
|
3077
|
+
export const ComputerSessionMutationResponse = z
|
|
3078
|
+
.object({
|
|
3079
|
+
session: ComputerSession,
|
|
3080
|
+
operation: InteractionLifecycleOperationReceipt,
|
|
3081
|
+
})
|
|
3082
|
+
.strict();
|
|
3083
|
+
export type ComputerSessionMutationResponse = z.infer<typeof ComputerSessionMutationResponse>;
|
|
3084
|
+
|
|
3085
|
+
export const ComputerSessionLifecycleRequest = z
|
|
3086
|
+
.object({
|
|
3087
|
+
operationId: z.string().uuid(),
|
|
3088
|
+
})
|
|
3089
|
+
.strict();
|
|
3090
|
+
export type ComputerSessionLifecycleRequest = z.infer<typeof ComputerSessionLifecycleRequest>;
|
|
3091
|
+
|
|
3092
|
+
export const ComputerTargetListResponse = z
|
|
3093
|
+
.object({
|
|
3094
|
+
computerSessionId: z.string().uuid(),
|
|
3095
|
+
controllerGeneration: opaqueGeneration,
|
|
3096
|
+
targets: z.array(ComputerTarget).max(10_000),
|
|
3097
|
+
})
|
|
3098
|
+
.strict();
|
|
3099
|
+
export type ComputerTargetListResponse = z.infer<typeof ComputerTargetListResponse>;
|
|
3100
|
+
|
|
3101
|
+
export const ComputerFrameStreamOptions = InteractionFrameStreamOptions;
|
|
3102
|
+
export type ComputerFrameStreamOptions = z.infer<typeof ComputerFrameStreamOptions>;
|
|
3103
|
+
|
|
3104
|
+
export const ComputerSessionAttachment = z
|
|
3105
|
+
.object({
|
|
3106
|
+
computerSessionId: z.string().uuid(),
|
|
3107
|
+
controllerGeneration: opaqueGeneration,
|
|
3108
|
+
targetId: boundedOpaqueId,
|
|
3109
|
+
stream: ComputerFrameStreamAttachment,
|
|
3110
|
+
expiresAt: z.string().datetime({ offset: true }),
|
|
3111
|
+
})
|
|
3112
|
+
.strict();
|
|
3113
|
+
export type ComputerSessionAttachment = z.infer<typeof ComputerSessionAttachment>;
|
|
3114
|
+
|
|
3115
|
+
export const ComputerSessionAttachmentRequest = z
|
|
3116
|
+
.object({
|
|
3117
|
+
targetId: boundedOpaqueId,
|
|
3118
|
+
expiresInSeconds: z.number().int().min(15).max(600).default(120),
|
|
3119
|
+
stream: ComputerFrameStreamOptions.optional(),
|
|
3120
|
+
})
|
|
3121
|
+
.strict();
|
|
3122
|
+
export type ComputerSessionAttachmentRequest = z.infer<typeof ComputerSessionAttachmentRequest>;
|
|
3123
|
+
|
|
3124
|
+
/** Human/API action request. Authenticated durable authority supplies the
|
|
3125
|
+
* resource, controller and actor fences before controller dispatch. */
|
|
3126
|
+
export const ComputerActionRequest = z
|
|
3127
|
+
.object({
|
|
3128
|
+
operationId: z.string().uuid(),
|
|
3129
|
+
targetId: boundedOpaqueId,
|
|
3130
|
+
expectedTargetGeneration: opaqueGeneration,
|
|
3131
|
+
expectedObservationId: boundedOpaqueId.nullable(),
|
|
3132
|
+
expectedFrameId: opaqueGeneration.nullable(),
|
|
3133
|
+
action: ComputerAction,
|
|
3134
|
+
})
|
|
3135
|
+
.strict()
|
|
3136
|
+
.superRefine((request, context) => {
|
|
3137
|
+
if (
|
|
3138
|
+
request.action.type === "semantic" &&
|
|
3139
|
+
request.action.locator.kind === "ref" &&
|
|
3140
|
+
request.expectedObservationId === null
|
|
3141
|
+
) {
|
|
3142
|
+
context.addIssue({
|
|
3143
|
+
code: "custom",
|
|
3144
|
+
path: ["expectedObservationId"],
|
|
3145
|
+
message: "semantic ref actions require expectedObservationId",
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
if (request.action.type === "pointer") {
|
|
3149
|
+
if (request.expectedFrameId !== request.action.frameId) {
|
|
3150
|
+
context.addIssue({
|
|
3151
|
+
code: "custom",
|
|
3152
|
+
path: ["expectedFrameId"],
|
|
3153
|
+
message: "pointer action frameId must match expectedFrameId",
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
} else if (request.expectedFrameId !== null) {
|
|
3157
|
+
context.addIssue({
|
|
3158
|
+
code: "custom",
|
|
3159
|
+
path: ["expectedFrameId"],
|
|
3160
|
+
message: "expectedFrameId is only valid for pointer actions",
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
3163
|
+
});
|
|
3164
|
+
export type ComputerActionRequest = z.infer<typeof ComputerActionRequest>;
|
|
3165
|
+
|
|
3166
|
+
export const ComputerSessionHeartbeatResponse = z
|
|
3167
|
+
.object({
|
|
3168
|
+
computerSessionId: z.string().uuid(),
|
|
3169
|
+
controllerGeneration: opaqueGeneration,
|
|
3170
|
+
alive: z.literal(true),
|
|
3171
|
+
})
|
|
3172
|
+
.strict();
|
|
3173
|
+
export type ComputerSessionHeartbeatResponse = z.infer<typeof ComputerSessionHeartbeatResponse>;
|