@roll-agent/relay-protocol 0.1.1 → 0.3.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.
Files changed (51) hide show
  1. package/README.md +134 -39
  2. package/dist/conformance.d.ts +571 -4
  3. package/dist/conformance.js +677 -8
  4. package/dist/control.d.ts +188 -0
  5. package/dist/control.js +180 -0
  6. package/dist/index.d.ts +3991 -287
  7. package/dist/index.js +676 -77
  8. package/dist/reference-adapter.d.ts +56 -0
  9. package/dist/reference-adapter.js +122 -0
  10. package/dist/schema/roll-relay-browser-session-v1.schema.json +23 -0
  11. package/dist/schema/roll-relay-control-v1.schema.json +127 -0
  12. package/dist/schema/roll-relay-protocol-v1.1.schema.json +3791 -0
  13. package/dist/schema/roll-relay-protocol-v1.schema.json +1 -1
  14. package/fixtures/control/v1.0/invalid-browser-session-trailing-whitespace.json +4 -0
  15. package/fixtures/control/v1.0/invalid-browser-session-uppercase-scheme.json +4 -0
  16. package/fixtures/control/v1.0/invalid-browser-session-url.json +4 -0
  17. package/fixtures/control/v1.0/invalid-browser-session-ws.json +4 -0
  18. package/fixtures/control/v1.0/invalid-control-extra-field.json +6 -0
  19. package/fixtures/control/v1.0/invalid-session-ready-wire-version.json +8 -0
  20. package/fixtures/control/v1.0/manifest.json +52 -0
  21. package/fixtures/control/v1.0/valid-browser-session.json +4 -0
  22. package/fixtures/control/v1.0/valid-session-error.json +5 -0
  23. package/fixtures/control/v1.0/valid-session-ready.json +8 -0
  24. package/fixtures/control/v1.0/valid-workspace-status.json +5 -0
  25. package/fixtures/v1.1/invalid-approval-projection-raw-input-leak.json +19 -0
  26. package/fixtures/v1.1/invalid-authentication-local-only.json +12 -0
  27. package/fixtures/v1.1/invalid-file-picker-local-only.json +12 -0
  28. package/fixtures/v1.1/invalid-interaction-cancelled-reason-leak.json +10 -0
  29. package/fixtures/v1.1/invalid-interaction-resolved-result-leak.json +18 -0
  30. package/fixtures/v1.1/invalid-legacy-approval-candidate.json +7 -0
  31. package/fixtures/v1.1/invalid-legacy-approval-respond.json +7 -0
  32. package/fixtures/v1.1/invalid-n-minus-one-device-connect.json +6 -0
  33. package/fixtures/v1.1/invalid-non-normal-sensitivity.json +21 -0
  34. package/fixtures/v1.1/invalid-runtime-event-tool-input-leak.json +22 -0
  35. package/fixtures/v1.1/invalid-runtime-event-tool-output-leak.json +21 -0
  36. package/fixtures/v1.1/invalid-user-input-projection-secret-leak.json +22 -0
  37. package/fixtures/v1.1/manifest.json +673 -0
  38. package/fixtures/v1.1/valid-device-connect.json +6 -0
  39. package/fixtures/v1.1/valid-interaction-cancelled.json +9 -0
  40. package/fixtures/v1.1/valid-interaction-candidate-user-input.json +25 -0
  41. package/fixtures/v1.1/valid-interaction-request-approval.json +17 -0
  42. package/fixtures/v1.1/valid-interaction-request-user-input.json +40 -0
  43. package/fixtures/v1.1/valid-interaction-resolved.json +9 -0
  44. package/fixtures/v1.1/valid-runtime-ack.json +5 -0
  45. package/fixtures/v1.1/valid-runtime-encrypted-interaction.json +10 -0
  46. package/fixtures/v1.1/valid-runtime-event-runtime-n-minus-one.json +16 -0
  47. package/fixtures/v1.1/valid-runtime-event.json +16 -0
  48. package/fixtures/v1.1/valid-runtime-gap.json +7 -0
  49. package/fixtures/v1.1/valid-runtime-request.json +10 -0
  50. package/fixtures/v1.1/valid-runtime-response.json +8 -0
  51. package/package.json +16 -3
@@ -0,0 +1,188 @@
1
+ import { z } from "zod/v4";
2
+ export declare const RELAY_CONTROL_VERSION: "1.0";
3
+ export declare const RELAY_BROWSER_PROTOCOL_VERSION: "1.1";
4
+ /**
5
+ * Authentication failures happen before the Browser WebSocket is accepted. They are HTTP upgrade
6
+ * errors and must never be sent as an authenticated Control frame.
7
+ */
8
+ export declare const RELAY_BROWSER_HANDSHAKE_ERROR_CODES: {
9
+ readonly sessionTicketInvalid: "SESSION_TICKET_INVALID";
10
+ readonly sessionTicketExpired: "SESSION_TICKET_EXPIRED";
11
+ readonly sessionTicketAlreadyUsed: "SESSION_TICKET_ALREADY_USED";
12
+ readonly originNotAllowed: "ORIGIN_NOT_ALLOWED";
13
+ };
14
+ export declare const RELAY_BROWSER_HANDSHAKE_ERROR_CODE_VALUES: readonly ["SESSION_TICKET_INVALID", "SESSION_TICKET_EXPIRED", "SESSION_TICKET_ALREADY_USED", "ORIGIN_NOT_ALLOWED"];
15
+ export declare const relayBrowserHandshakeErrorCodeSchema: z.ZodEnum<{
16
+ SESSION_TICKET_INVALID: "SESSION_TICKET_INVALID";
17
+ SESSION_TICKET_EXPIRED: "SESSION_TICKET_EXPIRED";
18
+ SESSION_TICKET_ALREADY_USED: "SESSION_TICKET_ALREADY_USED";
19
+ ORIGIN_NOT_ALLOWED: "ORIGIN_NOT_ALLOWED";
20
+ }>;
21
+ /** Errors observable only after the authenticated `session.ready` first frame. */
22
+ export declare const RELAY_CLIENT_ERROR_CODES: {
23
+ readonly workspaceNotFound: "WORKSPACE_NOT_FOUND";
24
+ readonly workspaceOffline: "WORKSPACE_OFFLINE";
25
+ readonly controllerConflict: "CONTROLLER_CONFLICT";
26
+ readonly protocolVersionUnsupported: "RELAY_PROTOCOL_VERSION_UNSUPPORTED";
27
+ readonly rateLimited: "RATE_LIMITED";
28
+ readonly internalError: "RELAY_INTERNAL_ERROR";
29
+ };
30
+ export declare const RELAY_CLIENT_ERROR_CODE_VALUES: readonly ["WORKSPACE_NOT_FOUND", "WORKSPACE_OFFLINE", "CONTROLLER_CONFLICT", "RELAY_PROTOCOL_VERSION_UNSUPPORTED", "RATE_LIMITED", "RELAY_INTERNAL_ERROR"];
31
+ export declare const relayClientErrorCodeSchema: z.ZodEnum<{
32
+ RELAY_PROTOCOL_VERSION_UNSUPPORTED: "RELAY_PROTOCOL_VERSION_UNSUPPORTED";
33
+ WORKSPACE_NOT_FOUND: "WORKSPACE_NOT_FOUND";
34
+ WORKSPACE_OFFLINE: "WORKSPACE_OFFLINE";
35
+ CONTROLLER_CONFLICT: "CONTROLLER_CONFLICT";
36
+ RATE_LIMITED: "RATE_LIMITED";
37
+ RELAY_INTERNAL_ERROR: "RELAY_INTERNAL_ERROR";
38
+ }>;
39
+ export declare const relayBrowserSessionIdSchema: z.core.$ZodBranded<z.ZodString, "RelayBrowserSessionId">;
40
+ export declare const relaySessionDescriptorSchema: z.ZodReadonly<z.ZodObject<{
41
+ connectUrl: z.ZodString;
42
+ expiresAt: z.ZodString;
43
+ }, z.core.$strict>>;
44
+ export declare const RELAY_WORKSPACE_STATUSES: readonly ["online", "offline"];
45
+ export declare const relayWorkspaceStatusSchema: z.ZodEnum<{
46
+ online: "online";
47
+ offline: "offline";
48
+ }>;
49
+ export declare const RELAY_BROWSER_CONTROL_MESSAGE_TYPES: {
50
+ readonly sessionReady: "session.ready";
51
+ readonly workspaceStatus: "workspace.status";
52
+ readonly sessionError: "session.error";
53
+ };
54
+ export declare const RELAY_BROWSER_CONTROL_MESSAGE_TYPE_VALUES: readonly ["session.ready", "workspace.status", "session.error"];
55
+ export declare const relaySessionReadyControlMessageSchema: z.ZodReadonly<z.ZodObject<{
56
+ type: z.ZodLiteral<"session.ready">;
57
+ controlVersion: z.ZodLiteral<"1.0">;
58
+ relayProtocolVersion: z.ZodLiteral<"1.1">;
59
+ sessionId: z.core.$ZodBranded<z.ZodString, "RelayBrowserSessionId">;
60
+ workspaceId: z.core.$ZodBranded<z.ZodString, "WorkspaceId">;
61
+ workspaceStatus: z.ZodEnum<{
62
+ online: "online";
63
+ offline: "offline";
64
+ }>;
65
+ }, z.core.$strict>>;
66
+ export declare const relayWorkspaceStatusControlMessageSchema: z.ZodReadonly<z.ZodObject<{
67
+ type: z.ZodLiteral<"workspace.status">;
68
+ workspaceId: z.core.$ZodBranded<z.ZodString, "WorkspaceId">;
69
+ status: z.ZodEnum<{
70
+ online: "online";
71
+ offline: "offline";
72
+ }>;
73
+ }, z.core.$strict>>;
74
+ export declare const relaySessionErrorControlMessageSchema: z.ZodReadonly<z.ZodObject<{
75
+ type: z.ZodLiteral<"session.error">;
76
+ code: z.ZodEnum<{
77
+ RELAY_PROTOCOL_VERSION_UNSUPPORTED: "RELAY_PROTOCOL_VERSION_UNSUPPORTED";
78
+ WORKSPACE_NOT_FOUND: "WORKSPACE_NOT_FOUND";
79
+ WORKSPACE_OFFLINE: "WORKSPACE_OFFLINE";
80
+ CONTROLLER_CONFLICT: "CONTROLLER_CONFLICT";
81
+ RATE_LIMITED: "RATE_LIMITED";
82
+ RELAY_INTERNAL_ERROR: "RELAY_INTERNAL_ERROR";
83
+ }>;
84
+ retryable: z.ZodBoolean;
85
+ }, z.core.$strict>>;
86
+ export declare const relayBrowserControlMessageSchema: z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodObject<{
87
+ type: z.ZodLiteral<"session.ready">;
88
+ controlVersion: z.ZodLiteral<"1.0">;
89
+ relayProtocolVersion: z.ZodLiteral<"1.1">;
90
+ sessionId: z.core.$ZodBranded<z.ZodString, "RelayBrowserSessionId">;
91
+ workspaceId: z.core.$ZodBranded<z.ZodString, "WorkspaceId">;
92
+ workspaceStatus: z.ZodEnum<{
93
+ online: "online";
94
+ offline: "offline";
95
+ }>;
96
+ }, z.core.$strict>>, z.ZodReadonly<z.ZodObject<{
97
+ type: z.ZodLiteral<"workspace.status">;
98
+ workspaceId: z.core.$ZodBranded<z.ZodString, "WorkspaceId">;
99
+ status: z.ZodEnum<{
100
+ online: "online";
101
+ offline: "offline";
102
+ }>;
103
+ }, z.core.$strict>>, z.ZodReadonly<z.ZodObject<{
104
+ type: z.ZodLiteral<"session.error">;
105
+ code: z.ZodEnum<{
106
+ RELAY_PROTOCOL_VERSION_UNSUPPORTED: "RELAY_PROTOCOL_VERSION_UNSUPPORTED";
107
+ WORKSPACE_NOT_FOUND: "WORKSPACE_NOT_FOUND";
108
+ WORKSPACE_OFFLINE: "WORKSPACE_OFFLINE";
109
+ CONTROLLER_CONFLICT: "CONTROLLER_CONFLICT";
110
+ RATE_LIMITED: "RATE_LIMITED";
111
+ RELAY_INTERNAL_ERROR: "RELAY_INTERNAL_ERROR";
112
+ }>;
113
+ retryable: z.ZodBoolean;
114
+ }, z.core.$strict>>]>;
115
+ /** Browser WebSocket connections must validate their first received frame with this schema. */
116
+ export declare const relayBrowserFirstControlFrameSchema: z.ZodReadonly<z.ZodObject<{
117
+ type: z.ZodLiteral<"session.ready">;
118
+ controlVersion: z.ZodLiteral<"1.0">;
119
+ relayProtocolVersion: z.ZodLiteral<"1.1">;
120
+ sessionId: z.core.$ZodBranded<z.ZodString, "RelayBrowserSessionId">;
121
+ workspaceId: z.core.$ZodBranded<z.ZodString, "WorkspaceId">;
122
+ workspaceStatus: z.ZodEnum<{
123
+ online: "online";
124
+ offline: "offline";
125
+ }>;
126
+ }, z.core.$strict>>;
127
+ export declare function parseRelayBrowserFirstControlFrame(value: unknown): RelaySessionReadyControlMessage;
128
+ export declare const RELAY_CONTROL_DIRECTIONS: {
129
+ readonly relayToBrowser: "relay-to-browser";
130
+ readonly browserToRelay: "browser-to-relay";
131
+ readonly relayToCompanion: "relay-to-companion";
132
+ readonly companionToRelay: "companion-to-relay";
133
+ };
134
+ export declare const RELAY_CONTROL_DIRECTION_VALUES: readonly ["relay-to-browser", "browser-to-relay", "relay-to-companion", "companion-to-relay"];
135
+ export declare const relayControlDirectionSchema: z.ZodEnum<{
136
+ "relay-to-browser": "relay-to-browser";
137
+ "browser-to-relay": "browser-to-relay";
138
+ "relay-to-companion": "relay-to-companion";
139
+ "companion-to-relay": "companion-to-relay";
140
+ }>;
141
+ export type RelayControlDirection = z.infer<typeof relayControlDirectionSchema>;
142
+ export type RelayBrowserControlMessageType = (typeof RELAY_BROWSER_CONTROL_MESSAGE_TYPE_VALUES)[number];
143
+ /** Control 1.0 is Relay-to-Browser only. Data-plane frames remain governed by Relay Wire 1.1. */
144
+ export declare const RELAY_CONTROL_MESSAGE_TYPES_BY_DIRECTION: {
145
+ readonly "relay-to-browser": readonly ["session.ready", "workspace.status", "session.error"];
146
+ readonly "browser-to-relay": readonly [];
147
+ readonly "relay-to-companion": readonly [];
148
+ readonly "companion-to-relay": readonly [];
149
+ };
150
+ export declare function isRelayControlMessageTypeAllowed(direction: RelayControlDirection, messageType: string): messageType is RelayBrowserControlMessageType;
151
+ export declare const RELAY_WSS_DIRECTIONS: {
152
+ readonly relayToPeer: "relay-to-peer";
153
+ readonly peerToRelay: "peer-to-relay";
154
+ };
155
+ export declare const RELAY_WSS_DIRECTION_VALUES: readonly ["relay-to-peer", "peer-to-relay"];
156
+ export declare const relayWssDirectionSchema: z.ZodEnum<{
157
+ "relay-to-peer": "relay-to-peer";
158
+ "peer-to-relay": "peer-to-relay";
159
+ }>;
160
+ export type RelayWssDirection = z.infer<typeof relayWssDirectionSchema>;
161
+ /**
162
+ * P0 Browser WebSocket direction contract. `runtime.ack` is a Wire 1.1 data-plane frame,
163
+ * not a Control frame. `runtime.encrypted` is intentionally absent because P0 does not offer E2EE.
164
+ */
165
+ export declare const RELAY_BROWSER_WSS_MESSAGE_TYPES_BY_DIRECTION: {
166
+ readonly "relay-to-peer": readonly ["session.ready", "workspace.status", "session.error", "runtime.response", "runtime.event", "runtime.gap", "interaction.request", "interaction.resolved", "interaction.cancelled"];
167
+ readonly "peer-to-relay": readonly ["runtime.request", "runtime.ack"];
168
+ };
169
+ /**
170
+ * P0 Companion WebSocket direction contract. A Cloud Relay must additionally require
171
+ * `device.connect` as the Companion connection's first frame.
172
+ */
173
+ export declare const RELAY_COMPANION_WSS_MESSAGE_TYPES_BY_DIRECTION: {
174
+ readonly "relay-to-peer": readonly ["runtime.request", "runtime.ack"];
175
+ readonly "peer-to-relay": readonly ["device.connect", "runtime.response", "runtime.event", "runtime.gap", "interaction.request", "interaction.resolved", "interaction.cancelled"];
176
+ };
177
+ export declare function isRelayBrowserWssMessageTypeAllowed(direction: RelayWssDirection, messageType: string): boolean;
178
+ export declare function isRelayCompanionWssMessageTypeAllowed(direction: RelayWssDirection, messageType: string): boolean;
179
+ export type RelayClientErrorCode = z.infer<typeof relayClientErrorCodeSchema>;
180
+ export type RelayBrowserHandshakeErrorCode = z.infer<typeof relayBrowserHandshakeErrorCodeSchema>;
181
+ export type RelayBrowserSessionId = z.infer<typeof relayBrowserSessionIdSchema>;
182
+ export type RelaySessionDescriptor = z.infer<typeof relaySessionDescriptorSchema>;
183
+ export type RelayWorkspaceStatus = z.infer<typeof relayWorkspaceStatusSchema>;
184
+ export type RelaySessionReadyControlMessage = z.infer<typeof relaySessionReadyControlMessageSchema>;
185
+ export type RelayWorkspaceStatusControlMessage = z.infer<typeof relayWorkspaceStatusControlMessageSchema>;
186
+ export type RelaySessionErrorControlMessage = z.infer<typeof relaySessionErrorControlMessageSchema>;
187
+ export type RelayBrowserControlMessage = z.infer<typeof relayBrowserControlMessageSchema>;
188
+ export type RelayBrowserFirstControlFrame = z.infer<typeof relayBrowserFirstControlFrameSchema>;
@@ -0,0 +1,180 @@
1
+ import { z } from "zod/v4";
2
+ import { RELAY_MESSAGE_TYPES_V11, workspaceIdSchema } from "./index.js";
3
+ export const RELAY_CONTROL_VERSION = "1.0";
4
+ export const RELAY_BROWSER_PROTOCOL_VERSION = "1.1";
5
+ /**
6
+ * Authentication failures happen before the Browser WebSocket is accepted. They are HTTP upgrade
7
+ * errors and must never be sent as an authenticated Control frame.
8
+ */
9
+ export const RELAY_BROWSER_HANDSHAKE_ERROR_CODES = {
10
+ sessionTicketInvalid: "SESSION_TICKET_INVALID",
11
+ sessionTicketExpired: "SESSION_TICKET_EXPIRED",
12
+ sessionTicketAlreadyUsed: "SESSION_TICKET_ALREADY_USED",
13
+ originNotAllowed: "ORIGIN_NOT_ALLOWED",
14
+ };
15
+ export const RELAY_BROWSER_HANDSHAKE_ERROR_CODE_VALUES = [
16
+ RELAY_BROWSER_HANDSHAKE_ERROR_CODES.sessionTicketInvalid,
17
+ RELAY_BROWSER_HANDSHAKE_ERROR_CODES.sessionTicketExpired,
18
+ RELAY_BROWSER_HANDSHAKE_ERROR_CODES.sessionTicketAlreadyUsed,
19
+ RELAY_BROWSER_HANDSHAKE_ERROR_CODES.originNotAllowed,
20
+ ];
21
+ export const relayBrowserHandshakeErrorCodeSchema = z.enum(RELAY_BROWSER_HANDSHAKE_ERROR_CODE_VALUES);
22
+ /** Errors observable only after the authenticated `session.ready` first frame. */
23
+ export const RELAY_CLIENT_ERROR_CODES = {
24
+ workspaceNotFound: "WORKSPACE_NOT_FOUND",
25
+ workspaceOffline: "WORKSPACE_OFFLINE",
26
+ controllerConflict: "CONTROLLER_CONFLICT",
27
+ protocolVersionUnsupported: "RELAY_PROTOCOL_VERSION_UNSUPPORTED",
28
+ rateLimited: "RATE_LIMITED",
29
+ internalError: "RELAY_INTERNAL_ERROR",
30
+ };
31
+ export const RELAY_CLIENT_ERROR_CODE_VALUES = [
32
+ RELAY_CLIENT_ERROR_CODES.workspaceNotFound,
33
+ RELAY_CLIENT_ERROR_CODES.workspaceOffline,
34
+ RELAY_CLIENT_ERROR_CODES.controllerConflict,
35
+ RELAY_CLIENT_ERROR_CODES.protocolVersionUnsupported,
36
+ RELAY_CLIENT_ERROR_CODES.rateLimited,
37
+ RELAY_CLIENT_ERROR_CODES.internalError,
38
+ ];
39
+ export const relayClientErrorCodeSchema = z.enum(RELAY_CLIENT_ERROR_CODE_VALUES);
40
+ export const relayBrowserSessionIdSchema = z.string().min(1).brand();
41
+ const secureWebSocketUrlSchema = z
42
+ .string()
43
+ .regex(/^wss:\/\/\S+$/u, "connectUrl must use wss:// without whitespace")
44
+ .url();
45
+ export const relaySessionDescriptorSchema = z
46
+ .object({
47
+ connectUrl: secureWebSocketUrlSchema,
48
+ expiresAt: z.string().datetime({ offset: true }),
49
+ })
50
+ .strict()
51
+ .readonly();
52
+ export const RELAY_WORKSPACE_STATUSES = ["online", "offline"];
53
+ export const relayWorkspaceStatusSchema = z.enum(RELAY_WORKSPACE_STATUSES);
54
+ export const RELAY_BROWSER_CONTROL_MESSAGE_TYPES = {
55
+ sessionReady: "session.ready",
56
+ workspaceStatus: "workspace.status",
57
+ sessionError: "session.error",
58
+ };
59
+ export const RELAY_BROWSER_CONTROL_MESSAGE_TYPE_VALUES = [
60
+ RELAY_BROWSER_CONTROL_MESSAGE_TYPES.sessionReady,
61
+ RELAY_BROWSER_CONTROL_MESSAGE_TYPES.workspaceStatus,
62
+ RELAY_BROWSER_CONTROL_MESSAGE_TYPES.sessionError,
63
+ ];
64
+ export const relaySessionReadyControlMessageSchema = z
65
+ .object({
66
+ type: z.literal(RELAY_BROWSER_CONTROL_MESSAGE_TYPES.sessionReady),
67
+ controlVersion: z.literal(RELAY_CONTROL_VERSION),
68
+ relayProtocolVersion: z.literal(RELAY_BROWSER_PROTOCOL_VERSION),
69
+ sessionId: relayBrowserSessionIdSchema,
70
+ workspaceId: workspaceIdSchema,
71
+ workspaceStatus: relayWorkspaceStatusSchema,
72
+ })
73
+ .strict()
74
+ .readonly();
75
+ export const relayWorkspaceStatusControlMessageSchema = z
76
+ .object({
77
+ type: z.literal(RELAY_BROWSER_CONTROL_MESSAGE_TYPES.workspaceStatus),
78
+ workspaceId: workspaceIdSchema,
79
+ status: relayWorkspaceStatusSchema,
80
+ })
81
+ .strict()
82
+ .readonly();
83
+ export const relaySessionErrorControlMessageSchema = z
84
+ .object({
85
+ type: z.literal(RELAY_BROWSER_CONTROL_MESSAGE_TYPES.sessionError),
86
+ code: relayClientErrorCodeSchema,
87
+ retryable: z.boolean(),
88
+ })
89
+ .strict()
90
+ .readonly();
91
+ export const relayBrowserControlMessageSchema = z.discriminatedUnion("type", [
92
+ relaySessionReadyControlMessageSchema,
93
+ relayWorkspaceStatusControlMessageSchema,
94
+ relaySessionErrorControlMessageSchema,
95
+ ]);
96
+ /** Browser WebSocket connections must validate their first received frame with this schema. */
97
+ export const relayBrowserFirstControlFrameSchema = relaySessionReadyControlMessageSchema;
98
+ export function parseRelayBrowserFirstControlFrame(value) {
99
+ return relayBrowserFirstControlFrameSchema.parse(value);
100
+ }
101
+ export const RELAY_CONTROL_DIRECTIONS = {
102
+ relayToBrowser: "relay-to-browser",
103
+ browserToRelay: "browser-to-relay",
104
+ relayToCompanion: "relay-to-companion",
105
+ companionToRelay: "companion-to-relay",
106
+ };
107
+ export const RELAY_CONTROL_DIRECTION_VALUES = [
108
+ RELAY_CONTROL_DIRECTIONS.relayToBrowser,
109
+ RELAY_CONTROL_DIRECTIONS.browserToRelay,
110
+ RELAY_CONTROL_DIRECTIONS.relayToCompanion,
111
+ RELAY_CONTROL_DIRECTIONS.companionToRelay,
112
+ ];
113
+ export const relayControlDirectionSchema = z.enum(RELAY_CONTROL_DIRECTION_VALUES);
114
+ /** Control 1.0 is Relay-to-Browser only. Data-plane frames remain governed by Relay Wire 1.1. */
115
+ export const RELAY_CONTROL_MESSAGE_TYPES_BY_DIRECTION = {
116
+ [RELAY_CONTROL_DIRECTIONS.relayToBrowser]: RELAY_BROWSER_CONTROL_MESSAGE_TYPE_VALUES,
117
+ [RELAY_CONTROL_DIRECTIONS.browserToRelay]: [],
118
+ [RELAY_CONTROL_DIRECTIONS.relayToCompanion]: [],
119
+ [RELAY_CONTROL_DIRECTIONS.companionToRelay]: [],
120
+ };
121
+ export function isRelayControlMessageTypeAllowed(direction, messageType) {
122
+ const allowed = RELAY_CONTROL_MESSAGE_TYPES_BY_DIRECTION[direction];
123
+ return allowed.includes(messageType);
124
+ }
125
+ export const RELAY_WSS_DIRECTIONS = {
126
+ relayToPeer: "relay-to-peer",
127
+ peerToRelay: "peer-to-relay",
128
+ };
129
+ export const RELAY_WSS_DIRECTION_VALUES = [
130
+ RELAY_WSS_DIRECTIONS.relayToPeer,
131
+ RELAY_WSS_DIRECTIONS.peerToRelay,
132
+ ];
133
+ export const relayWssDirectionSchema = z.enum(RELAY_WSS_DIRECTION_VALUES);
134
+ /**
135
+ * P0 Browser WebSocket direction contract. `runtime.ack` is a Wire 1.1 data-plane frame,
136
+ * not a Control frame. `runtime.encrypted` is intentionally absent because P0 does not offer E2EE.
137
+ */
138
+ export const RELAY_BROWSER_WSS_MESSAGE_TYPES_BY_DIRECTION = {
139
+ [RELAY_WSS_DIRECTIONS.relayToPeer]: [
140
+ ...RELAY_BROWSER_CONTROL_MESSAGE_TYPE_VALUES,
141
+ RELAY_MESSAGE_TYPES_V11.runtimeResponse,
142
+ RELAY_MESSAGE_TYPES_V11.runtimeEvent,
143
+ RELAY_MESSAGE_TYPES_V11.runtimeGap,
144
+ RELAY_MESSAGE_TYPES_V11.interactionRequest,
145
+ RELAY_MESSAGE_TYPES_V11.interactionResolved,
146
+ RELAY_MESSAGE_TYPES_V11.interactionCancelled,
147
+ ],
148
+ [RELAY_WSS_DIRECTIONS.peerToRelay]: [
149
+ RELAY_MESSAGE_TYPES_V11.runtimeRequest,
150
+ RELAY_MESSAGE_TYPES_V11.runtimeAck,
151
+ ],
152
+ };
153
+ /**
154
+ * P0 Companion WebSocket direction contract. A Cloud Relay must additionally require
155
+ * `device.connect` as the Companion connection's first frame.
156
+ */
157
+ export const RELAY_COMPANION_WSS_MESSAGE_TYPES_BY_DIRECTION = {
158
+ [RELAY_WSS_DIRECTIONS.relayToPeer]: [
159
+ RELAY_MESSAGE_TYPES_V11.runtimeRequest,
160
+ RELAY_MESSAGE_TYPES_V11.runtimeAck,
161
+ ],
162
+ [RELAY_WSS_DIRECTIONS.peerToRelay]: [
163
+ RELAY_MESSAGE_TYPES_V11.deviceConnect,
164
+ RELAY_MESSAGE_TYPES_V11.runtimeResponse,
165
+ RELAY_MESSAGE_TYPES_V11.runtimeEvent,
166
+ RELAY_MESSAGE_TYPES_V11.runtimeGap,
167
+ RELAY_MESSAGE_TYPES_V11.interactionRequest,
168
+ RELAY_MESSAGE_TYPES_V11.interactionResolved,
169
+ RELAY_MESSAGE_TYPES_V11.interactionCancelled,
170
+ ],
171
+ };
172
+ function includesMessageType(allowedByDirection, direction, messageType) {
173
+ return allowedByDirection[direction].includes(messageType);
174
+ }
175
+ export function isRelayBrowserWssMessageTypeAllowed(direction, messageType) {
176
+ return includesMessageType(RELAY_BROWSER_WSS_MESSAGE_TYPES_BY_DIRECTION, direction, messageType);
177
+ }
178
+ export function isRelayCompanionWssMessageTypeAllowed(direction, messageType) {
179
+ return includesMessageType(RELAY_COMPANION_WSS_MESSAGE_TYPES_BY_DIRECTION, direction, messageType);
180
+ }