@yefengr/pi-reach 0.0.0-stage → 0.0.2
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/LICENSE +22 -0
- package/README.md +110 -2
- package/dist/actions/handlers.d.ts +143 -0
- package/dist/actions/handlers.js +200 -0
- package/dist/actions/handlers.js.map +1 -0
- package/dist/actions/registry.d.ts +39 -0
- package/dist/actions/registry.js +58 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/actions/session_new_bridge.d.ts +35 -0
- package/dist/actions/session_new_bridge.js +88 -0
- package/dist/actions/session_new_bridge.js.map +1 -0
- package/dist/commands.d.ts +29 -0
- package/dist/commands.js +169 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +61 -0
- package/dist/config.js +115 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +618 -0
- package/dist/index.js.map +1 -0
- package/dist/pairing/crypto.d.ts +15 -0
- package/dist/pairing/crypto.js +72 -0
- package/dist/pairing/crypto.js.map +1 -0
- package/dist/pairing/identity-keyring.d.ts +22 -0
- package/dist/pairing/identity-keyring.js +94 -0
- package/dist/pairing/identity-keyring.js.map +1 -0
- package/dist/pairing/identity-lock.d.ts +14 -0
- package/dist/pairing/identity-lock.js +116 -0
- package/dist/pairing/identity-lock.js.map +1 -0
- package/dist/pairing/owner_storage.d.ts +61 -0
- package/dist/pairing/owner_storage.js +263 -0
- package/dist/pairing/owner_storage.js.map +1 -0
- package/dist/pairing/qr.d.ts +39 -0
- package/dist/pairing/qr.js +113 -0
- package/dist/pairing/qr.js.map +1 -0
- package/dist/pairing/storage.d.ts +36 -0
- package/dist/pairing/storage.js +263 -0
- package/dist/pairing/storage.js.map +1 -0
- package/dist/protocol/types.d.ts +225 -0
- package/dist/protocol/types.js +2 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/protocol/v2/codec.d.ts +6 -0
- package/dist/protocol/v2/codec.js +4 -0
- package/dist/protocol/v2/codec.js.map +1 -0
- package/dist/protocol/v2/index.d.ts +4 -0
- package/dist/protocol/v2/index.js +5 -0
- package/dist/protocol/v2/index.js.map +1 -0
- package/dist/protocol/v2/marker.d.ts +45 -0
- package/dist/protocol/v2/marker.js +47 -0
- package/dist/protocol/v2/marker.js.map +1 -0
- package/dist/protocol/v2/schemas.d.ts +3 -0
- package/dist/protocol/v2/schemas.js +4 -0
- package/dist/protocol/v2/schemas.js.map +1 -0
- package/dist/protocol/v2/session_state.d.ts +79 -0
- package/dist/protocol/v2/session_state.js +166 -0
- package/dist/protocol/v2/session_state.js.map +1 -0
- package/dist/runtime/endpoint_metadata.d.ts +14 -0
- package/dist/runtime/endpoint_metadata.js +18 -0
- package/dist/runtime/endpoint_metadata.js.map +1 -0
- package/dist/runtime/extension_owner.d.ts +12 -0
- package/dist/runtime/extension_owner.js +80 -0
- package/dist/runtime/extension_owner.js.map +1 -0
- package/dist/runtime/footer.d.ts +6 -0
- package/dist/runtime/footer.js +21 -0
- package/dist/runtime/footer.js.map +1 -0
- package/dist/runtime/owner_router.d.ts +16 -0
- package/dist/runtime/owner_router.js +41 -0
- package/dist/runtime/owner_router.js.map +1 -0
- package/dist/runtime/pairing_coordinator.d.ts +42 -0
- package/dist/runtime/pairing_coordinator.js +136 -0
- package/dist/runtime/pairing_coordinator.js.map +1 -0
- package/dist/runtime/relay_lifecycle.d.ts +64 -0
- package/dist/runtime/relay_lifecycle.js +275 -0
- package/dist/runtime/relay_lifecycle.js.map +1 -0
- package/dist/session/local_config.d.ts +7 -0
- package/dist/session/local_config.js +52 -0
- package/dist/session/local_config.js.map +1 -0
- package/dist/timeline/history.d.ts +30 -0
- package/dist/timeline/history.js +170 -0
- package/dist/timeline/history.js.map +1 -0
- package/dist/timeline/partial_delta.d.ts +2 -0
- package/dist/timeline/partial_delta.js +16 -0
- package/dist/timeline/partial_delta.js.map +1 -0
- package/dist/timeline/run_end.d.ts +16 -0
- package/dist/timeline/run_end.js +23 -0
- package/dist/timeline/run_end.js.map +1 -0
- package/dist/timeline/runtime.d.ts +89 -0
- package/dist/timeline/runtime.js +546 -0
- package/dist/timeline/runtime.js.map +1 -0
- package/dist/timeline/sequence.d.ts +7 -0
- package/dist/timeline/sequence.js +16 -0
- package/dist/timeline/sequence.js.map +1 -0
- package/dist/timeline/tool_lifecycle.d.ts +36 -0
- package/dist/timeline/tool_lifecycle.js +171 -0
- package/dist/timeline/tool_lifecycle.js.map +1 -0
- package/dist/timeline/user_delivery.d.ts +96 -0
- package/dist/timeline/user_delivery.js +360 -0
- package/dist/timeline/user_delivery.js.map +1 -0
- package/dist/timeline/user_delivery_binding.d.ts +43 -0
- package/dist/timeline/user_delivery_binding.js +107 -0
- package/dist/timeline/user_delivery_binding.js.map +1 -0
- package/dist/timeline/v2_service.d.ts +82 -0
- package/dist/timeline/v2_service.js +512 -0
- package/dist/timeline/v2_service.js.map +1 -0
- package/dist/transport/peer_channel.d.ts +34 -0
- package/dist/transport/peer_channel.js +116 -0
- package/dist/transport/peer_channel.js.map +1 -0
- package/dist/transport/relay_client.d.ts +54 -0
- package/dist/transport/relay_client.js +270 -0
- package/dist/transport/relay_client.js.map +1 -0
- package/dist/vendor/protocol/constants.d.ts +4 -0
- package/dist/vendor/protocol/constants.js +4 -0
- package/dist/vendor/protocol/encoding.d.ts +9 -0
- package/dist/vendor/protocol/encoding.js +66 -0
- package/dist/vendor/protocol/outer/codec.d.ts +13 -0
- package/dist/vendor/protocol/outer/codec.js +105 -0
- package/dist/vendor/protocol/outer/index.d.ts +4 -0
- package/dist/vendor/protocol/outer/index.js +4 -0
- package/dist/vendor/protocol/outer/types.d.ts +120 -0
- package/dist/vendor/protocol/outer/types.js +1 -0
- package/dist/vendor/protocol/session/codec.d.ts +41 -0
- package/dist/vendor/protocol/session/codec.js +236 -0
- package/dist/vendor/protocol/session/frames.d.ts +5220 -0
- package/dist/vendor/protocol/session/frames.js +83 -0
- package/dist/vendor/protocol/session/index.d.ts +3 -0
- package/dist/vendor/protocol/session/index.js +3 -0
- package/dist/vendor/protocol/session/schema.d.ts +858 -0
- package/dist/vendor/protocol/session/schema.js +256 -0
- package/package.json +74 -3
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { decodeBase64, decodeUtf8, encodeBase64, encodeUtf8 } from "../encoding.js";
|
|
2
|
+
export function isRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
export function parseJson(value) {
|
|
6
|
+
if (typeof value !== "string" && !(value instanceof Uint8Array))
|
|
7
|
+
return value;
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(typeof value === "string" ? value : decodeUtf8(value));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function isId(value) {
|
|
16
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256;
|
|
17
|
+
}
|
|
18
|
+
function isUuid(value) {
|
|
19
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
20
|
+
}
|
|
21
|
+
function hasOnlyKeys(value, allowed) {
|
|
22
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
23
|
+
}
|
|
24
|
+
function decodeMetadata(value) {
|
|
25
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["kind", "name", "cwd", "pid", "started_at", "model", "thinking", "working"]) || (value.kind !== "daemon" && value.kind !== "interactive"))
|
|
26
|
+
return undefined;
|
|
27
|
+
const scalar = (key, type) => value[key] === undefined || typeof value[key] === type;
|
|
28
|
+
if (!scalar("name", "string") || !scalar("cwd", "string") || !scalar("pid", "number") || !scalar("started_at", "number") || !scalar("model", "string") || !scalar("thinking", "string") || !scalar("working", "boolean"))
|
|
29
|
+
return undefined;
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function decodeEndpoint(value) {
|
|
33
|
+
if (!isRecord(value) || !isUuid(value.endpoint_id) || !isUuid(value.runtime_instance_id))
|
|
34
|
+
return undefined;
|
|
35
|
+
const metadata = decodeMetadata(value.metadata);
|
|
36
|
+
return metadata ? { endpoint_id: value.endpoint_id, runtime_instance_id: value.runtime_instance_id, metadata } : undefined;
|
|
37
|
+
}
|
|
38
|
+
export function decodeRoute(value) {
|
|
39
|
+
if (!isRecord(value) || value.type !== "route" || (value.purpose !== "pairing" && value.purpose !== "session") || !isId(value.device_id) || !isUuid(value.endpoint_id) || !isUuid(value.runtime_instance_id) || typeof value.ct !== "string")
|
|
40
|
+
return undefined;
|
|
41
|
+
const allowed = new Set(["type", "purpose", "device_id", "endpoint_id", "runtime_instance_id", "target_owner_id", "source_owner_id", "ct"]);
|
|
42
|
+
if (Object.keys(value).some((key) => !allowed.has(key)))
|
|
43
|
+
return undefined;
|
|
44
|
+
if (value.target_owner_id !== undefined && !isId(value.target_owner_id))
|
|
45
|
+
return undefined;
|
|
46
|
+
if (value.source_owner_id !== undefined && !isId(value.source_owner_id))
|
|
47
|
+
return undefined;
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
export function decodeControlFrame(value) {
|
|
51
|
+
if (!isRecord(value) || typeof value.type !== "string")
|
|
52
|
+
return undefined;
|
|
53
|
+
if (value.type === "pairing_target" && hasOnlyKeys(value, ["type", "in_reply_to", "code", "device_id", "endpoint_id", "runtime_instance_id"]) && isId(value.in_reply_to) && isId(value.code) && isId(value.device_id) && isUuid(value.endpoint_id) && isUuid(value.runtime_instance_id)) {
|
|
54
|
+
return { type: "pairing_target", in_reply_to: value.in_reply_to, code: value.code, device_id: value.device_id, endpoint_id: value.endpoint_id, runtime_instance_id: value.runtime_instance_id };
|
|
55
|
+
}
|
|
56
|
+
if (value.type === "pairing_code_error" && hasOnlyKeys(value, ["type", "in_reply_to", "reason"]) && isId(value.in_reply_to) && (value.reason === "unknown_code" || value.reason === "expired_code" || value.reason === "stale_target" || value.reason === "rate_limited")) {
|
|
57
|
+
return { type: "pairing_code_error", in_reply_to: value.in_reply_to, reason: value.reason };
|
|
58
|
+
}
|
|
59
|
+
if (!isId(value.device_id))
|
|
60
|
+
return undefined;
|
|
61
|
+
if (value.type === "endpoints" && hasOnlyKeys(value, ["type", "device_id", "endpoints"]) && Array.isArray(value.endpoints)) {
|
|
62
|
+
const endpoints = value.endpoints.map(decodeEndpoint);
|
|
63
|
+
return endpoints.every((endpoint) => endpoint !== undefined) ? { type: "endpoints", device_id: value.device_id, endpoints } : undefined;
|
|
64
|
+
}
|
|
65
|
+
if ((value.type === "endpoint_announced" || value.type === "endpoint_updated") && hasOnlyKeys(value, ["type", "device_id", "endpoint_id", "runtime_instance_id", "metadata"]) && isUuid(value.endpoint_id) && isUuid(value.runtime_instance_id)) {
|
|
66
|
+
const metadata = decodeMetadata(value.metadata);
|
|
67
|
+
return metadata ? { type: value.type, device_id: value.device_id, endpoint_id: value.endpoint_id, runtime_instance_id: value.runtime_instance_id, metadata } : undefined;
|
|
68
|
+
}
|
|
69
|
+
if (value.type === "endpoint_ended" && hasOnlyKeys(value, ["type", "device_id", "endpoint_id", "runtime_instance_id"]) && isUuid(value.endpoint_id) && isUuid(value.runtime_instance_id))
|
|
70
|
+
return { type: "endpoint_ended", device_id: value.device_id, endpoint_id: value.endpoint_id, runtime_instance_id: value.runtime_instance_id };
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
export function decodeRelayFrame(value) {
|
|
74
|
+
const route = decodeRoute(value);
|
|
75
|
+
if (route)
|
|
76
|
+
return { kind: "route", route };
|
|
77
|
+
const control = decodeControlFrame(value);
|
|
78
|
+
return control ? { kind: "control", frame: control } : undefined;
|
|
79
|
+
}
|
|
80
|
+
export function encodeRoutePayload(payload) {
|
|
81
|
+
return encodeBase64(typeof payload === "string" ? encodeUtf8(payload) : payload, "standard");
|
|
82
|
+
}
|
|
83
|
+
export function decodeRoutePayload(route) {
|
|
84
|
+
try {
|
|
85
|
+
return decodeBase64(route.ct, "standard");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function encodeControlFrame(frame) {
|
|
92
|
+
return JSON.stringify(frame);
|
|
93
|
+
}
|
|
94
|
+
export function decodeChallenge(value) {
|
|
95
|
+
const parsed = parseJson(value);
|
|
96
|
+
if (!isRecord(parsed) || !hasOnlyKeys(parsed, ["type", "nonce"]) || parsed.type !== "challenge" || typeof parsed.nonce !== "string")
|
|
97
|
+
return undefined;
|
|
98
|
+
try {
|
|
99
|
+
decodeBase64(parsed.nonce, "standard");
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return { type: "challenge", nonce: parsed.nonce };
|
|
105
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { PROTOCOL_VERSION } from "../constants.js";
|
|
2
|
+
export type EndpointMetadata = {
|
|
3
|
+
kind: "daemon" | "interactive";
|
|
4
|
+
name?: string | null;
|
|
5
|
+
cwd?: string | null;
|
|
6
|
+
pid?: number | null;
|
|
7
|
+
started_at?: number | null;
|
|
8
|
+
model?: string | null;
|
|
9
|
+
thinking?: string | null;
|
|
10
|
+
working?: boolean | null;
|
|
11
|
+
};
|
|
12
|
+
export type EndpointInfo = {
|
|
13
|
+
endpoint_id: string;
|
|
14
|
+
runtime_instance_id: string;
|
|
15
|
+
metadata: EndpointMetadata;
|
|
16
|
+
};
|
|
17
|
+
export type RoutePurpose = "pairing" | "session";
|
|
18
|
+
export type RouteFrame = {
|
|
19
|
+
type: "route";
|
|
20
|
+
purpose: RoutePurpose;
|
|
21
|
+
device_id: string;
|
|
22
|
+
endpoint_id: string;
|
|
23
|
+
runtime_instance_id: string;
|
|
24
|
+
target_owner_id?: string;
|
|
25
|
+
source_owner_id?: string;
|
|
26
|
+
ct: string;
|
|
27
|
+
};
|
|
28
|
+
export type OwnerHelloFrame = {
|
|
29
|
+
type: "hello";
|
|
30
|
+
protocol_version: typeof PROTOCOL_VERSION;
|
|
31
|
+
role: "owner";
|
|
32
|
+
pubkey: string;
|
|
33
|
+
};
|
|
34
|
+
export type HostHelloFrame = {
|
|
35
|
+
type: "hello";
|
|
36
|
+
protocol_version: typeof PROTOCOL_VERSION;
|
|
37
|
+
role: "host";
|
|
38
|
+
pubkey: string;
|
|
39
|
+
endpoint_id: string;
|
|
40
|
+
runtime_instance_id: string;
|
|
41
|
+
metadata: EndpointMetadata;
|
|
42
|
+
authorized_owner_ids: readonly string[];
|
|
43
|
+
};
|
|
44
|
+
export type HelloFrame = OwnerHelloFrame | HostHelloFrame;
|
|
45
|
+
export type ChallengeFrame = {
|
|
46
|
+
type: "challenge";
|
|
47
|
+
nonce: string;
|
|
48
|
+
};
|
|
49
|
+
export type AuthFrame = {
|
|
50
|
+
type: "auth";
|
|
51
|
+
sig: string;
|
|
52
|
+
};
|
|
53
|
+
export type EndpointUpdateFrame = {
|
|
54
|
+
type: "endpoint_update";
|
|
55
|
+
metadata?: EndpointMetadata;
|
|
56
|
+
authorized_owner_ids?: readonly string[];
|
|
57
|
+
};
|
|
58
|
+
export type PairingOfferFrame = {
|
|
59
|
+
type: "pairing_offer";
|
|
60
|
+
code: string;
|
|
61
|
+
endpoint_id: string;
|
|
62
|
+
runtime_instance_id: string;
|
|
63
|
+
expires_at: number;
|
|
64
|
+
};
|
|
65
|
+
export type HostControlOutbound = EndpointUpdateFrame | PairingOfferFrame;
|
|
66
|
+
export type PairingTargetControl = {
|
|
67
|
+
type: "pairing_target";
|
|
68
|
+
in_reply_to: string;
|
|
69
|
+
code: string;
|
|
70
|
+
device_id: string;
|
|
71
|
+
endpoint_id: string;
|
|
72
|
+
runtime_instance_id: string;
|
|
73
|
+
};
|
|
74
|
+
export type PairingCodeErrorReason = "unknown_code" | "expired_code" | "stale_target" | "rate_limited";
|
|
75
|
+
export type PairingCodeErrorControl = {
|
|
76
|
+
type: "pairing_code_error";
|
|
77
|
+
in_reply_to: string;
|
|
78
|
+
reason: PairingCodeErrorReason;
|
|
79
|
+
};
|
|
80
|
+
export type EndpointsControl = {
|
|
81
|
+
type: "endpoints";
|
|
82
|
+
device_id: string;
|
|
83
|
+
endpoints: EndpointInfo[];
|
|
84
|
+
};
|
|
85
|
+
export type EndpointAnnouncedControl = {
|
|
86
|
+
type: "endpoint_announced";
|
|
87
|
+
device_id: string;
|
|
88
|
+
endpoint_id: string;
|
|
89
|
+
runtime_instance_id: string;
|
|
90
|
+
metadata: EndpointMetadata;
|
|
91
|
+
};
|
|
92
|
+
export type EndpointUpdatedControl = {
|
|
93
|
+
type: "endpoint_updated";
|
|
94
|
+
device_id: string;
|
|
95
|
+
endpoint_id: string;
|
|
96
|
+
runtime_instance_id: string;
|
|
97
|
+
metadata: EndpointMetadata;
|
|
98
|
+
};
|
|
99
|
+
export type EndpointEndedControl = {
|
|
100
|
+
type: "endpoint_ended";
|
|
101
|
+
device_id: string;
|
|
102
|
+
endpoint_id: string;
|
|
103
|
+
runtime_instance_id: string;
|
|
104
|
+
};
|
|
105
|
+
export type ControlFrame = PairingTargetControl | PairingCodeErrorControl | EndpointsControl | EndpointAnnouncedControl | EndpointUpdatedControl | EndpointEndedControl;
|
|
106
|
+
export type ControlOutbound = {
|
|
107
|
+
type: "resolve_pairing_code";
|
|
108
|
+
request_id: string;
|
|
109
|
+
code: string;
|
|
110
|
+
} | {
|
|
111
|
+
type: "subscribe_endpoints";
|
|
112
|
+
device_ids: string[];
|
|
113
|
+
};
|
|
114
|
+
export type RelayFrame = {
|
|
115
|
+
kind: "route";
|
|
116
|
+
route: RouteFrame;
|
|
117
|
+
} | {
|
|
118
|
+
kind: "control";
|
|
119
|
+
frame: ControlFrame;
|
|
120
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ClientFrame, ServerFrame } from "./frames.js";
|
|
2
|
+
import type { TimelineEvent, TimelinePartial } from "./schema.js";
|
|
3
|
+
export declare const DECODE_ERROR_CODES: {
|
|
4
|
+
readonly invalid: "invalid";
|
|
5
|
+
readonly unsupported: "unsupported";
|
|
6
|
+
readonly version: "version";
|
|
7
|
+
readonly schema: "schema";
|
|
8
|
+
readonly size: "size";
|
|
9
|
+
readonly direction: "direction";
|
|
10
|
+
};
|
|
11
|
+
export type DecodeErrorCode = (typeof DECODE_ERROR_CODES)[keyof typeof DECODE_ERROR_CODES];
|
|
12
|
+
export declare class DecodeError extends Error {
|
|
13
|
+
readonly code: DecodeErrorCode;
|
|
14
|
+
readonly name = "DecodeError";
|
|
15
|
+
constructor(code: DecodeErrorCode, message: string, options?: {
|
|
16
|
+
cause?: unknown;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
20
|
+
export declare function getUtf8ByteLengthV2(value: unknown): number;
|
|
21
|
+
export declare function parseProtocolJsonV2(raw: unknown, maxBytes?: number): unknown;
|
|
22
|
+
export declare function decodeClientFrameV2(raw: unknown): ClientFrame;
|
|
23
|
+
export declare function decodeServerFrameV2(raw: unknown): ServerFrame;
|
|
24
|
+
export declare function encodeClientFrameV2(frame: ClientFrame): Uint8Array;
|
|
25
|
+
export declare function encodeServerFrameV2(frame: ServerFrame): Uint8Array;
|
|
26
|
+
export declare function encodeClientFrameTextV2(frame: ClientFrame): string;
|
|
27
|
+
export declare function encodeServerFrameTextV2(frame: ServerFrame): string;
|
|
28
|
+
export declare function validateFragmentSizeV2(fragment: unknown): number;
|
|
29
|
+
export declare function validateHistoryChunkSizeV2(chunk: unknown): number;
|
|
30
|
+
export declare function validateWindowSizeV2(events: readonly unknown[]): number;
|
|
31
|
+
export declare function validateEncodedSizeV2(value: unknown, maxBytes?: number): number;
|
|
32
|
+
export declare function parseTimelineEventV2(raw: unknown): TimelineEvent;
|
|
33
|
+
export declare function parseTimelinePartialV2(raw: unknown): TimelinePartial;
|
|
34
|
+
export declare function validateFrameSizeV2(raw: unknown, maxBytes?: number): number;
|
|
35
|
+
export declare const validateTimelineEventFragmentV2: typeof validateFragmentSizeV2;
|
|
36
|
+
export declare const validateSessionHistoryChunkV2: typeof validateHistoryChunkSizeV2;
|
|
37
|
+
export declare const validateTimelineWindowV2: typeof validateWindowSizeV2;
|
|
38
|
+
export declare const validateFrameSize: typeof validateFrameSizeV2;
|
|
39
|
+
export declare const validateFragmentSize: typeof validateFragmentSizeV2;
|
|
40
|
+
export declare const validateSessionHistoryChunkSize: typeof validateHistoryChunkSizeV2;
|
|
41
|
+
export declare const validateWindowSize: typeof validateWindowSizeV2;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { clientFrameSchema, clientFrameTypes, serverFrameSchema, serverFrameTypes, sessionHistoryChunkFrameSchema, timelineEventFragmentFrameSchema, } from "./frames.js";
|
|
2
|
+
import { historyFragmentSchema, timelineEventFragmentSchema, timelineEventSchema, timelinePartialSchema, } from "./schema.js";
|
|
3
|
+
import { MAX_FRAGMENT_BYTES, MAX_FRAME_BYTES, MAX_HISTORY_CHUNK_BYTES, MAX_WINDOW_BYTES, } from "./schema.js";
|
|
4
|
+
export const DECODE_ERROR_CODES = {
|
|
5
|
+
invalid: "invalid",
|
|
6
|
+
unsupported: "unsupported",
|
|
7
|
+
version: "version",
|
|
8
|
+
schema: "schema",
|
|
9
|
+
size: "size",
|
|
10
|
+
direction: "direction",
|
|
11
|
+
};
|
|
12
|
+
export class DecodeError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
name = "DecodeError";
|
|
15
|
+
constructor(code, message, options) {
|
|
16
|
+
super(message, options);
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const textEncoder = new TextEncoder();
|
|
21
|
+
const textDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
22
|
+
export function isRecord(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function encodeJson(value) {
|
|
26
|
+
try {
|
|
27
|
+
const encoded = JSON.stringify(value);
|
|
28
|
+
if (encoded === undefined)
|
|
29
|
+
throw new Error("value is not JSON serializable");
|
|
30
|
+
return textEncoder.encode(encoded);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof DecodeError)
|
|
34
|
+
throw error;
|
|
35
|
+
throw new DecodeError("invalid", "value is not JSON serializable", { cause: error });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function getUtf8ByteLengthV2(value) {
|
|
39
|
+
return encodeJson(value).byteLength;
|
|
40
|
+
}
|
|
41
|
+
function parseRaw(raw, maxBytes = MAX_WINDOW_BYTES) {
|
|
42
|
+
if (typeof raw === "string") {
|
|
43
|
+
const bytes = textEncoder.encode(raw).byteLength;
|
|
44
|
+
if (bytes > maxBytes)
|
|
45
|
+
throw new DecodeError("size", `raw value exceeds ${maxBytes} bytes`);
|
|
46
|
+
try {
|
|
47
|
+
return { value: JSON.parse(raw), bytes };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new DecodeError("invalid", "raw string is not valid JSON", { cause: error });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (raw instanceof Uint8Array) {
|
|
54
|
+
if (raw.byteLength > maxBytes)
|
|
55
|
+
throw new DecodeError("size", `raw value exceeds ${maxBytes} bytes`);
|
|
56
|
+
try {
|
|
57
|
+
return { value: JSON.parse(textDecoder.decode(raw)), bytes: raw.byteLength };
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw new DecodeError("invalid", "raw bytes are not valid UTF-8 JSON", { cause: error });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (isRecord(raw)) {
|
|
64
|
+
const bytes = getUtf8ByteLengthV2(raw);
|
|
65
|
+
if (bytes > maxBytes)
|
|
66
|
+
throw new DecodeError("size", `raw value exceeds ${maxBytes} bytes`);
|
|
67
|
+
return { value: raw, bytes };
|
|
68
|
+
}
|
|
69
|
+
throw new DecodeError("invalid", "raw frame must be an object, JSON string, or Uint8Array");
|
|
70
|
+
}
|
|
71
|
+
export function parseProtocolJsonV2(raw, maxBytes = MAX_WINDOW_BYTES) {
|
|
72
|
+
return parseRaw(raw, maxBytes).value;
|
|
73
|
+
}
|
|
74
|
+
function schemaMessage(error) {
|
|
75
|
+
return error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
|
|
76
|
+
}
|
|
77
|
+
function assertByteLimit(value, bytes, limit = MAX_FRAME_BYTES) {
|
|
78
|
+
if (bytes > limit) {
|
|
79
|
+
throw new DecodeError("size", `${String(value.type)} exceeds ${limit} encoded UTF-8 bytes`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function assertVersion(value) {
|
|
83
|
+
if (value.protocol_version !== 2) {
|
|
84
|
+
throw new DecodeError("version", "protocol_version must be 2");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function assertDirection(type, direction) {
|
|
88
|
+
const knownClient = clientFrameTypes.has(type);
|
|
89
|
+
const knownServer = serverFrameTypes.has(type);
|
|
90
|
+
if (!knownClient && !knownServer) {
|
|
91
|
+
throw new DecodeError("unsupported", `unsupported frame type: ${type}`);
|
|
92
|
+
}
|
|
93
|
+
if ((direction === "client" && !knownClient) || (direction === "server" && !knownServer)) {
|
|
94
|
+
throw new DecodeError("direction", `${type} is not a ${direction} frame`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function frameType(value) {
|
|
98
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
99
|
+
throw new DecodeError("invalid", "frame must contain a string type");
|
|
100
|
+
}
|
|
101
|
+
return value.type;
|
|
102
|
+
}
|
|
103
|
+
function parseFrame(raw, direction, schema) {
|
|
104
|
+
const parsed = parseRaw(raw, MAX_FRAME_BYTES);
|
|
105
|
+
const type = frameType(parsed.value);
|
|
106
|
+
assertDirection(type, direction);
|
|
107
|
+
assertVersion(parsed.value);
|
|
108
|
+
const result = schema.safeParse(parsed.value);
|
|
109
|
+
if (!result.success) {
|
|
110
|
+
throw new DecodeError("schema", schemaMessage(result.error), { cause: result.error });
|
|
111
|
+
}
|
|
112
|
+
assertByteLimit(parsed.value, parsed.bytes);
|
|
113
|
+
if (type === "timeline_event_fragment")
|
|
114
|
+
validateFragmentSizeV2(parsed.value);
|
|
115
|
+
if (type === "session_history_chunk")
|
|
116
|
+
validateHistoryChunkSizeV2(parsed.value);
|
|
117
|
+
return result.data;
|
|
118
|
+
}
|
|
119
|
+
export function decodeClientFrameV2(raw) {
|
|
120
|
+
return parseFrame(raw, "client", clientFrameSchema);
|
|
121
|
+
}
|
|
122
|
+
export function decodeServerFrameV2(raw) {
|
|
123
|
+
return parseFrame(raw, "server", serverFrameSchema);
|
|
124
|
+
}
|
|
125
|
+
function encodeFrame(frame, direction) {
|
|
126
|
+
const parsed = direction === "client"
|
|
127
|
+
? parseFrame(frame, direction, clientFrameSchema)
|
|
128
|
+
: parseFrame(frame, direction, serverFrameSchema);
|
|
129
|
+
const encoded = encodeJson(parsed);
|
|
130
|
+
assertByteLimit(isRecord(parsed) ? parsed : { type: "frame" }, encoded.byteLength);
|
|
131
|
+
return encoded;
|
|
132
|
+
}
|
|
133
|
+
export function encodeClientFrameV2(frame) {
|
|
134
|
+
return encodeFrame(frame, "client");
|
|
135
|
+
}
|
|
136
|
+
export function encodeServerFrameV2(frame) {
|
|
137
|
+
return encodeFrame(frame, "server");
|
|
138
|
+
}
|
|
139
|
+
export function encodeClientFrameTextV2(frame) {
|
|
140
|
+
return textDecoder.decode(encodeClientFrameV2(frame));
|
|
141
|
+
}
|
|
142
|
+
export function encodeServerFrameTextV2(frame) {
|
|
143
|
+
return textDecoder.decode(encodeServerFrameV2(frame));
|
|
144
|
+
}
|
|
145
|
+
function decodeBase64Bytes(value) {
|
|
146
|
+
try {
|
|
147
|
+
const binary = atob(value);
|
|
148
|
+
const bytes = new Uint8Array(binary.length);
|
|
149
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
150
|
+
bytes[index] = binary.charCodeAt(index);
|
|
151
|
+
}
|
|
152
|
+
return bytes;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw new DecodeError("schema", "data_base64 is not valid base64", { cause: error });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export function validateFragmentSizeV2(fragment) {
|
|
159
|
+
const realtimeFrameResult = timelineEventFragmentFrameSchema.safeParse(fragment);
|
|
160
|
+
const realtimeResult = timelineEventFragmentSchema.safeParse(fragment);
|
|
161
|
+
const historyResult = historyFragmentSchema.safeParse(fragment);
|
|
162
|
+
if (!realtimeFrameResult.success && !realtimeResult.success && !historyResult.success) {
|
|
163
|
+
throw new DecodeError("schema", schemaMessage(realtimeFrameResult.error), { cause: realtimeFrameResult.error });
|
|
164
|
+
}
|
|
165
|
+
const dataBase64 = realtimeFrameResult.success
|
|
166
|
+
? realtimeFrameResult.data.data_base64
|
|
167
|
+
: realtimeResult.success
|
|
168
|
+
? realtimeResult.data.data_base64
|
|
169
|
+
: historyResult.success
|
|
170
|
+
? historyResult.data.data_base64
|
|
171
|
+
: undefined;
|
|
172
|
+
if (dataBase64 === undefined)
|
|
173
|
+
throw new DecodeError("schema", "fragment data is missing");
|
|
174
|
+
const bytes = decodeBase64Bytes(dataBase64).byteLength;
|
|
175
|
+
if (bytes > MAX_FRAGMENT_BYTES) {
|
|
176
|
+
throw new DecodeError("size", `fragment exceeds ${MAX_FRAGMENT_BYTES} decoded bytes`);
|
|
177
|
+
}
|
|
178
|
+
return bytes;
|
|
179
|
+
}
|
|
180
|
+
export function validateHistoryChunkSizeV2(chunk) {
|
|
181
|
+
const result = sessionHistoryChunkFrameSchema.safeParse(chunk);
|
|
182
|
+
if (!result.success)
|
|
183
|
+
throw new DecodeError("schema", schemaMessage(result.error), { cause: result.error });
|
|
184
|
+
const bytes = getUtf8ByteLengthV2(result.data);
|
|
185
|
+
if (bytes > MAX_HISTORY_CHUNK_BYTES) {
|
|
186
|
+
throw new DecodeError("size", `history chunk exceeds ${MAX_HISTORY_CHUNK_BYTES} encoded UTF-8 bytes`);
|
|
187
|
+
}
|
|
188
|
+
for (const fragment of result.data.fragments)
|
|
189
|
+
validateFragmentSizeV2(fragment);
|
|
190
|
+
return bytes;
|
|
191
|
+
}
|
|
192
|
+
export function validateWindowSizeV2(events) {
|
|
193
|
+
let total = 0;
|
|
194
|
+
for (const event of events) {
|
|
195
|
+
const result = timelineEventSchema.safeParse(event);
|
|
196
|
+
if (!result.success)
|
|
197
|
+
throw new DecodeError("schema", schemaMessage(result.error), { cause: result.error });
|
|
198
|
+
total += getUtf8ByteLengthV2(result.data);
|
|
199
|
+
if (total > MAX_WINDOW_BYTES) {
|
|
200
|
+
throw new DecodeError("size", `decoded window exceeds ${MAX_WINDOW_BYTES} bytes`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return total;
|
|
204
|
+
}
|
|
205
|
+
export function validateEncodedSizeV2(value, maxBytes = MAX_WINDOW_BYTES) {
|
|
206
|
+
const bytes = getUtf8ByteLengthV2(value);
|
|
207
|
+
if (bytes > maxBytes)
|
|
208
|
+
throw new DecodeError("size", `encoded value exceeds ${maxBytes} bytes`);
|
|
209
|
+
return bytes;
|
|
210
|
+
}
|
|
211
|
+
export function parseTimelineEventV2(raw) {
|
|
212
|
+
const value = parseProtocolJsonV2(raw);
|
|
213
|
+
const result = timelineEventSchema.safeParse(value);
|
|
214
|
+
if (!result.success)
|
|
215
|
+
throw new DecodeError("schema", schemaMessage(result.error), { cause: result.error });
|
|
216
|
+
validateEncodedSizeV2(result.data, MAX_WINDOW_BYTES);
|
|
217
|
+
return result.data;
|
|
218
|
+
}
|
|
219
|
+
export function parseTimelinePartialV2(raw) {
|
|
220
|
+
const value = parseProtocolJsonV2(raw);
|
|
221
|
+
const result = timelinePartialSchema.safeParse(value);
|
|
222
|
+
if (!result.success)
|
|
223
|
+
throw new DecodeError("schema", schemaMessage(result.error), { cause: result.error });
|
|
224
|
+
validateEncodedSizeV2(result.data, MAX_HISTORY_CHUNK_BYTES);
|
|
225
|
+
return result.data;
|
|
226
|
+
}
|
|
227
|
+
export function validateFrameSizeV2(raw, maxBytes = MAX_FRAME_BYTES) {
|
|
228
|
+
return parseRaw(raw, maxBytes).bytes;
|
|
229
|
+
}
|
|
230
|
+
export const validateTimelineEventFragmentV2 = validateFragmentSizeV2;
|
|
231
|
+
export const validateSessionHistoryChunkV2 = validateHistoryChunkSizeV2;
|
|
232
|
+
export const validateTimelineWindowV2 = validateWindowSizeV2;
|
|
233
|
+
export const validateFrameSize = validateFrameSizeV2;
|
|
234
|
+
export const validateFragmentSize = validateFragmentSizeV2;
|
|
235
|
+
export const validateSessionHistoryChunkSize = validateHistoryChunkSizeV2;
|
|
236
|
+
export const validateWindowSize = validateWindowSizeV2;
|