@opengeni/contracts 0.44.1 → 1.0.1
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.js +10 -8
- 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-CM6BMECR.js +24 -0
- package/dist/chunk-CM6BMECR.js.map +1 -0
- package/dist/{chunk-FJBG5E4O.js → chunk-JBCIY7QD.js} +12 -12
- package/dist/chunk-JUFK3T4Q.js +80 -0
- package/dist/chunk-JUFK3T4Q.js.map +1 -0
- package/dist/{chunk-H5BVCZKF.js → chunk-LW7OH6CS.js} +2 -2
- package/dist/{chunk-52SGB2QO.js → chunk-M5ZGRVIQ.js} +37 -6
- package/dist/chunk-M5ZGRVIQ.js.map +1 -0
- package/dist/{chunk-VA22KRHS.js → chunk-NX5JNF4L.js} +6533 -4923
- package/dist/chunk-NX5JNF4L.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/editable-artifact-codec-registry.js +5 -5
- package/dist/editable-artifact-live.js +3 -3
- package/dist/editable-artifact-serialized-commit.js +4 -4
- package/dist/editable-artifacts.js +27 -27
- package/dist/google-drive.d.ts +88 -1
- package/dist/google-drive.js +75 -13
- package/dist/google-drive.js.map +1 -1
- package/dist/index.d.ts +1385 -122
- package/dist/index.js +406 -80
- package/dist/interaction.d.ts +1415 -20
- package/dist/knowledge.d.ts +616 -0
- package/dist/slack-task-policy.d.ts +294 -0
- package/dist/task-notes.d.ts +128 -0
- package/dist/video-generation.d.ts +48 -0
- package/dist/video-generation.js +9 -1
- 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 +13 -1
- package/src/artifacts.ts +1 -1
- package/src/canonical-human-identities.ts +93 -0
- package/src/codex-provider-account-authority.ts +37 -0
- package/src/google-drive.ts +76 -4
- package/src/index.ts +921 -270
- package/src/interaction.ts +886 -5
- package/src/knowledge.ts +222 -0
- package/src/slack-task-policy.ts +215 -0
- package/src/task-notes.ts +92 -0
- package/src/video-generation.ts +43 -6
- package/src/xai-provider-account-authority.ts +37 -0
- package/dist/chunk-52SGB2QO.js.map +0 -1
- package/dist/chunk-VA22KRHS.js.map +0 -1
- /package/dist/{chunk-FJBG5E4O.js.map → chunk-JBCIY7QD.js.map} +0 -0
- /package/dist/{chunk-H5BVCZKF.js.map → chunk-LW7OH6CS.js.map} +0 -0
package/src/interaction.ts
CHANGED
|
@@ -5,6 +5,7 @@ export const INTERACTION_PROTOCOL_VERSION = 1 as const;
|
|
|
5
5
|
export const BROWSER_CONTROL_PROTOCOL_VERSION = INTERACTION_PROTOCOL_VERSION;
|
|
6
6
|
export const BROWSER_CONTROL_WEBSOCKET_PROTOCOL = "opengeni.browser.v1" as const;
|
|
7
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;
|
|
8
9
|
export const BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX = "opengeni.auth." as const;
|
|
9
10
|
export const BROWSER_CONTROL_MAX_JSON_BYTES = 40 * 1024 * 1024;
|
|
10
11
|
export const BROWSER_CONTROL_MAX_FRAME_HEADER_BYTES = 64 * 1024;
|
|
@@ -16,6 +17,28 @@ export const INTERACTION_MAX_SEMANTIC_NODES = 10_000;
|
|
|
16
17
|
export const INTERACTION_MAX_CHANGED_NODES = 2_000;
|
|
17
18
|
export const INTERACTION_MAX_DIAGNOSTIC_ENTRIES = 1_000;
|
|
18
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>;
|
|
19
42
|
|
|
20
43
|
const opaqueGeneration = z
|
|
21
44
|
.string()
|
|
@@ -168,6 +191,7 @@ export const BrowserSessionCapabilities = z
|
|
|
168
191
|
downloads: z.boolean(),
|
|
169
192
|
uploads: z.boolean(),
|
|
170
193
|
clipboard: z.boolean(),
|
|
194
|
+
permissions: z.boolean(),
|
|
171
195
|
diagnostics: z.boolean(),
|
|
172
196
|
rawCdp: z.boolean(),
|
|
173
197
|
linkedComputer: z.boolean(),
|
|
@@ -205,6 +229,48 @@ export const BrowserSession = z
|
|
|
205
229
|
.strict();
|
|
206
230
|
export type BrowserSession = z.infer<typeof BrowserSession>;
|
|
207
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
|
+
|
|
208
274
|
/** One live Chrome-profile bridge installed on an enrolled machine. This is a
|
|
209
275
|
* transport endpoint, not saved browser/login state: BrowserIdentity remains
|
|
210
276
|
* the immutable, reusable state abstraction. */
|
|
@@ -444,6 +510,7 @@ export const BrowserIdentity = z
|
|
|
444
510
|
workspaceId: z.string().uuid(),
|
|
445
511
|
name: z.string().trim().min(1).max(200),
|
|
446
512
|
status: BrowserIdentityStatus,
|
|
513
|
+
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
447
514
|
defaultRevisionId: z.string().uuid().nullable(),
|
|
448
515
|
headGeneration: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
449
516
|
revisionCount: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
@@ -521,6 +588,93 @@ export const NetworkRouteConsistency = z
|
|
|
521
588
|
.strict();
|
|
522
589
|
export type NetworkRouteConsistency = z.infer<typeof NetworkRouteConsistency>;
|
|
523
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
|
+
|
|
524
678
|
export const NetworkRoute = z
|
|
525
679
|
.object({
|
|
526
680
|
id: z.string().uuid(),
|
|
@@ -637,6 +791,9 @@ export const SiteAuthAuthority = z.discriminatedUnion("kind", [
|
|
|
637
791
|
kind: z.literal("external_provider"),
|
|
638
792
|
label: z.string().trim().min(1).max(200),
|
|
639
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,
|
|
640
797
|
credential: InteractionCredentialAuthorityRef.nullable(),
|
|
641
798
|
})
|
|
642
799
|
.strict(),
|
|
@@ -673,6 +830,22 @@ export type SiteAuthHealthPolicy = z.infer<typeof SiteAuthHealthPolicy>;
|
|
|
673
830
|
export const SiteAuthVerificationState = z.enum(["unknown", "verified", "needs_repair", "failed"]);
|
|
674
831
|
export type SiteAuthVerificationState = z.infer<typeof SiteAuthVerificationState>;
|
|
675
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
|
+
|
|
676
849
|
const SiteAuthConnectionConfiguration = z
|
|
677
850
|
.object({
|
|
678
851
|
name: z.string().trim().min(1).max(200),
|
|
@@ -693,6 +866,17 @@ function validateSiteAuthConfiguration(
|
|
|
693
866
|
connection: z.infer<typeof SiteAuthConnectionConfiguration>,
|
|
694
867
|
context: z.RefinementCtx,
|
|
695
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
|
+
}
|
|
696
880
|
if (new Set(connection.origins).size !== connection.origins.length) {
|
|
697
881
|
context.addIssue({ code: "custom", path: ["origins"], message: "origins repeat" });
|
|
698
882
|
}
|
|
@@ -767,6 +951,9 @@ export const SiteAuthConnection = SiteAuthConnectionConfiguration.extend({
|
|
|
767
951
|
verificationState: SiteAuthVerificationState,
|
|
768
952
|
lastVerifiedAt: z.string().datetime({ offset: true }).nullable(),
|
|
769
953
|
lastVerifiedUrl: boundedUrl.nullable(),
|
|
954
|
+
lastCheckedAt: z.string().datetime({ offset: true }).nullable(),
|
|
955
|
+
nextCheckAt: z.string().datetime({ offset: true }).nullable(),
|
|
956
|
+
maintenance: SiteAuthMaintenance.nullable(),
|
|
770
957
|
repairCode: boundedOpaqueId.nullable(),
|
|
771
958
|
version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
772
959
|
createdBySubjectId: z.string().min(1).max(1_024),
|
|
@@ -862,6 +1049,7 @@ export const AuthRun = z
|
|
|
862
1049
|
controllerGeneration: opaqueGeneration,
|
|
863
1050
|
targetGeneration: opaqueGeneration,
|
|
864
1051
|
documentGeneration: opaqueGeneration.nullable(),
|
|
1052
|
+
purpose: z.enum(["authenticate", "health_check", "repair"]),
|
|
865
1053
|
methodId: boundedOpaqueId.nullable(),
|
|
866
1054
|
authorityId: boundedOpaqueId.nullable(),
|
|
867
1055
|
state: AuthRunState,
|
|
@@ -945,15 +1133,15 @@ export const StartAuthRunRequest = z
|
|
|
945
1133
|
targetId: boundedOpaqueId,
|
|
946
1134
|
expectedTargetGeneration: opaqueGeneration,
|
|
947
1135
|
expectedDocumentGeneration: opaqueGeneration.nullable(),
|
|
1136
|
+
purpose: z.enum(["authenticate", "health_check", "repair"]).optional(),
|
|
948
1137
|
methodId: boundedOpaqueId.optional(),
|
|
949
1138
|
authorityId: boundedOpaqueId.optional(),
|
|
950
1139
|
})
|
|
951
1140
|
.strict();
|
|
952
1141
|
export type StartAuthRunRequest = z.infer<typeof StartAuthRunRequest>;
|
|
953
1142
|
|
|
954
|
-
export const
|
|
1143
|
+
export const ReportAuthRunPayload = z
|
|
955
1144
|
.object({
|
|
956
|
-
operationId: z.string().uuid(),
|
|
957
1145
|
expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
958
1146
|
methodId: boundedOpaqueId.optional(),
|
|
959
1147
|
authorityId: boundedOpaqueId.optional(),
|
|
@@ -1001,6 +1189,11 @@ export const ReportAuthRunRequest = z
|
|
|
1001
1189
|
});
|
|
1002
1190
|
}
|
|
1003
1191
|
});
|
|
1192
|
+
export type ReportAuthRunPayload = z.infer<typeof ReportAuthRunPayload>;
|
|
1193
|
+
|
|
1194
|
+
export const ReportAuthRunRequest = ReportAuthRunPayload.safeExtend({
|
|
1195
|
+
operationId: z.string().uuid(),
|
|
1196
|
+
});
|
|
1004
1197
|
export type ReportAuthRunRequest = z.infer<typeof ReportAuthRunRequest>;
|
|
1005
1198
|
|
|
1006
1199
|
export const ProtectedAuthField = z
|
|
@@ -1045,6 +1238,101 @@ export const ProtectedAuthFillResponse = z
|
|
|
1045
1238
|
.strict();
|
|
1046
1239
|
export type ProtectedAuthFillResponse = z.infer<typeof ProtectedAuthFillResponse>;
|
|
1047
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
|
+
|
|
1048
1336
|
export const VerifyAuthRunRequest = z
|
|
1049
1337
|
.object({
|
|
1050
1338
|
operationId: z.string().uuid(),
|
|
@@ -1140,6 +1428,38 @@ export type InteractionInterventionMutationResponse = z.infer<
|
|
|
1140
1428
|
typeof InteractionInterventionMutationResponse
|
|
1141
1429
|
>;
|
|
1142
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
|
+
|
|
1143
1463
|
export const BrowserIdentityListResponse = z
|
|
1144
1464
|
.object({
|
|
1145
1465
|
revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
@@ -1164,6 +1484,24 @@ export const CreateBrowserIdentityRequest = z
|
|
|
1164
1484
|
.strict();
|
|
1165
1485
|
export type CreateBrowserIdentityRequest = z.infer<typeof CreateBrowserIdentityRequest>;
|
|
1166
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
|
+
|
|
1167
1505
|
export const BrowserIdentityMutationResponse = z
|
|
1168
1506
|
.object({
|
|
1169
1507
|
identity: BrowserIdentity,
|
|
@@ -1212,6 +1550,155 @@ export const BrowserTarget = z
|
|
|
1212
1550
|
.strict();
|
|
1213
1551
|
export type BrowserTarget = z.infer<typeof BrowserTarget>;
|
|
1214
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
|
+
|
|
1215
1702
|
export const InteractionRect = z
|
|
1216
1703
|
.object({
|
|
1217
1704
|
x: z.number().finite(),
|
|
@@ -1417,6 +1904,26 @@ export const ComputerLocator = z.discriminatedUnion("kind", [
|
|
|
1417
1904
|
]);
|
|
1418
1905
|
export type ComputerLocator = z.infer<typeof ComputerLocator>;
|
|
1419
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
|
+
|
|
1420
1927
|
const browserActionVariants = [
|
|
1421
1928
|
z.object({ type: z.literal("navigate"), url: boundedUrl }).strict(),
|
|
1422
1929
|
z
|
|
@@ -1489,6 +1996,7 @@ const browserActionVariants = [
|
|
|
1489
1996
|
deltaX: z.number().finite().min(-1_000_000).max(1_000_000).optional(),
|
|
1490
1997
|
deltaY: z.number().finite().min(-1_000_000).max(1_000_000).optional(),
|
|
1491
1998
|
button: z.enum(["left", "right", "middle"]).optional(),
|
|
1999
|
+
clickCount: z.union([z.literal(1), z.literal(2)]).optional(),
|
|
1492
2000
|
})
|
|
1493
2001
|
.strict()
|
|
1494
2002
|
.superRefine((action, context) => {
|
|
@@ -1522,6 +2030,12 @@ const browserActionVariants = [
|
|
|
1522
2030
|
message: "pointer deltas require scroll",
|
|
1523
2031
|
});
|
|
1524
2032
|
}
|
|
2033
|
+
if (action.action !== "click" && action.clickCount !== undefined) {
|
|
2034
|
+
context.addIssue({
|
|
2035
|
+
code: "custom",
|
|
2036
|
+
message: "pointer clickCount requires click",
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
1525
2039
|
}),
|
|
1526
2040
|
z
|
|
1527
2041
|
.object({
|
|
@@ -1537,6 +2051,84 @@ const browserActionVariants = [
|
|
|
1537
2051
|
workspaceFileIds: z.array(z.string().uuid()).min(1).max(100),
|
|
1538
2052
|
})
|
|
1539
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(),
|
|
1540
2132
|
z
|
|
1541
2133
|
.object({
|
|
1542
2134
|
type: z.literal("wait"),
|
|
@@ -1555,7 +2147,19 @@ export const BrowserActionBatch = z
|
|
|
1555
2147
|
type: z.literal("batch"),
|
|
1556
2148
|
actions: z.array(BrowserAction).min(1).max(INTERACTION_MAX_ACTIONS_PER_BATCH),
|
|
1557
2149
|
})
|
|
1558
|
-
.strict()
|
|
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
|
+
});
|
|
1559
2163
|
export type BrowserActionBatch = z.infer<typeof BrowserActionBatch>;
|
|
1560
2164
|
|
|
1561
2165
|
export const InteractionError = z
|
|
@@ -1600,11 +2204,70 @@ export const BrowserActionCommand = z
|
|
|
1600
2204
|
expectedDocumentGeneration: opaqueGeneration.nullable(),
|
|
1601
2205
|
expectedFrameId: opaqueGeneration.nullable(),
|
|
1602
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(),
|
|
1603
2210
|
action: z.union([BrowserAction, BrowserActionBatch]),
|
|
1604
2211
|
})
|
|
1605
2212
|
.strict();
|
|
1606
2213
|
export type BrowserActionCommand = z.infer<typeof BrowserActionCommand>;
|
|
1607
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
|
+
|
|
1608
2271
|
export const InteractionOperationState = z.enum([
|
|
1609
2272
|
"prepared",
|
|
1610
2273
|
"dispatched",
|
|
@@ -1671,6 +2334,9 @@ export const CreateBrowserSessionRequest = z
|
|
|
1671
2334
|
name: z.string().trim().min(1).max(200).optional(),
|
|
1672
2335
|
initialUrl: boundedUrl.optional(),
|
|
1673
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"),
|
|
1674
2340
|
placement: InteractionPlacement.optional(),
|
|
1675
2341
|
identityId: z.string().uuid().optional(),
|
|
1676
2342
|
baseRevisionId: z.string().uuid().optional(),
|
|
@@ -1693,12 +2359,42 @@ export const CreateBrowserSessionRequest = z
|
|
|
1693
2359
|
message: "a linked computer requires a headed browser",
|
|
1694
2360
|
});
|
|
1695
2361
|
}
|
|
1696
|
-
if (value.
|
|
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
|
+
}
|
|
1697
2384
|
if (value.linkedComputerSessionId) {
|
|
1698
2385
|
context.addIssue({
|
|
1699
2386
|
code: "custom",
|
|
1700
2387
|
path: ["linkedComputerSessionId"],
|
|
1701
|
-
message: "
|
|
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",
|
|
1702
2398
|
});
|
|
1703
2399
|
}
|
|
1704
2400
|
if (value.headless) {
|
|
@@ -1716,6 +2412,29 @@ export const CreateBrowserSessionRequest = z
|
|
|
1716
2412
|
});
|
|
1717
2413
|
}
|
|
1718
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
|
+
}
|
|
1719
2438
|
});
|
|
1720
2439
|
export type CreateBrowserSessionRequest = z.infer<typeof CreateBrowserSessionRequest>;
|
|
1721
2440
|
|
|
@@ -1783,6 +2502,14 @@ const DirectInteractionFrameStreamAttachment = z
|
|
|
1783
2502
|
})
|
|
1784
2503
|
.strict();
|
|
1785
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
|
+
|
|
1786
2513
|
function relayInteractionFrameStreamAttachment<const Kind extends 3 | 4>(kind: Kind) {
|
|
1787
2514
|
return z
|
|
1788
2515
|
.object({
|
|
@@ -1810,6 +2537,7 @@ export type BrowserFrameStreamAttachment = z.infer<typeof BrowserFrameStreamAtta
|
|
|
1810
2537
|
|
|
1811
2538
|
export const ComputerFrameStreamAttachment = z.discriminatedUnion("kind", [
|
|
1812
2539
|
DirectInteractionFrameStreamAttachment,
|
|
2540
|
+
DirectComputerRfbAttachment,
|
|
1813
2541
|
relayInteractionFrameStreamAttachment(4),
|
|
1814
2542
|
]);
|
|
1815
2543
|
export type ComputerFrameStreamAttachment = z.infer<typeof ComputerFrameStreamAttachment>;
|
|
@@ -1850,6 +2578,7 @@ export const BrowserActionRequest = z
|
|
|
1850
2578
|
expectedTargetGeneration: opaqueGeneration,
|
|
1851
2579
|
expectedDocumentGeneration: opaqueGeneration.nullable(),
|
|
1852
2580
|
expectedFrameId: opaqueGeneration.nullable(),
|
|
2581
|
+
observationMode: z.enum(["full", "none"]).default("full"),
|
|
1853
2582
|
action: z.union([BrowserAction, BrowserActionBatch]),
|
|
1854
2583
|
})
|
|
1855
2584
|
.strict();
|
|
@@ -1903,6 +2632,104 @@ export const BrowserActionReceipt = z
|
|
|
1903
2632
|
});
|
|
1904
2633
|
export type BrowserActionReceipt = z.infer<typeof BrowserActionReceipt>;
|
|
1905
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
|
+
|
|
1906
2733
|
export const ComputerSessionCapabilities = z
|
|
1907
2734
|
.object({
|
|
1908
2735
|
semanticObservation: z.boolean(),
|
|
@@ -1913,12 +2740,43 @@ export const ComputerSessionCapabilities = z
|
|
|
1913
2740
|
semanticActions: z.boolean(),
|
|
1914
2741
|
pointerInput: z.boolean(),
|
|
1915
2742
|
keyboardInput: z.boolean(),
|
|
2743
|
+
clipboard: z.boolean(),
|
|
1916
2744
|
backgroundActions: z.boolean(),
|
|
1917
2745
|
parallelApps: z.boolean(),
|
|
1918
2746
|
})
|
|
1919
2747
|
.strict();
|
|
1920
2748
|
export type ComputerSessionCapabilities = z.infer<typeof ComputerSessionCapabilities>;
|
|
1921
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
|
+
|
|
1922
2780
|
export const ComputerSession = z
|
|
1923
2781
|
.object({
|
|
1924
2782
|
id: z.string().uuid(),
|
|
@@ -2079,6 +2937,29 @@ export const ComputerAction = z.discriminatedUnion("type", [
|
|
|
2079
2937
|
value: z.string().max(1_000_000),
|
|
2080
2938
|
})
|
|
2081
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
|
+
}),
|
|
2082
2963
|
z.object({ type: z.literal("focus"), targetId: boundedOpaqueId }).strict(),
|
|
2083
2964
|
z
|
|
2084
2965
|
.object({
|