@claxedo/workspace-relay-protocol 0.1.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.
@@ -0,0 +1,78 @@
1
+ export declare const TUNNEL_PROTOCOL_VERSION = 1;
2
+ export { type TokenClaims, type TokenVerifier, TokenVerifierError, type HttpTokenVerifierOptions, createHttpTokenVerifier, type StaticTokenVerifierOptions, createStaticTokenVerifier, } from "./token-verifier";
3
+ export type TunnelHeaderMap = Record<string, string>;
4
+ export type TunnelPing = {
5
+ type: "ping";
6
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
7
+ id: string;
8
+ sent_at: number;
9
+ };
10
+ export type TunnelPong = {
11
+ type: "pong";
12
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
13
+ id: string;
14
+ sent_at: number;
15
+ received_at: number;
16
+ };
17
+ export type TunnelHttpRequest = {
18
+ type: "http.request";
19
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
20
+ request_id: string;
21
+ workspace_id: string;
22
+ method: string;
23
+ path: string;
24
+ headers: TunnelHeaderMap;
25
+ body_base64?: string;
26
+ end: boolean;
27
+ };
28
+ export type TunnelHttpResponseStart = {
29
+ type: "http.response.start";
30
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
31
+ request_id: string;
32
+ status: number;
33
+ headers: TunnelHeaderMap;
34
+ };
35
+ export type TunnelHttpResponseChunk = {
36
+ type: "http.response.chunk";
37
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
38
+ request_id: string;
39
+ body_base64: string;
40
+ };
41
+ export type TunnelHttpResponseEnd = {
42
+ type: "http.response.end";
43
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
44
+ request_id: string;
45
+ };
46
+ export type TunnelWsOpen = {
47
+ type: "ws.open";
48
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
49
+ channel_id: string;
50
+ workspace_id: string;
51
+ path: string;
52
+ headers: TunnelHeaderMap;
53
+ };
54
+ export type TunnelWsFrame = {
55
+ type: "ws.frame";
56
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
57
+ channel_id: string;
58
+ data_base64: string;
59
+ binary: boolean;
60
+ };
61
+ export type TunnelWsClose = {
62
+ type: "ws.close";
63
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
64
+ channel_id: string;
65
+ code?: number;
66
+ reason?: string;
67
+ };
68
+ export type TunnelError = {
69
+ type: "error";
70
+ protocol: typeof TUNNEL_PROTOCOL_VERSION;
71
+ request_id?: string;
72
+ channel_id?: string;
73
+ code: string;
74
+ message: string;
75
+ };
76
+ export type TunnelMessage = TunnelPing | TunnelPong | TunnelHttpRequest | TunnelHttpResponseStart | TunnelHttpResponseChunk | TunnelHttpResponseEnd | TunnelWsOpen | TunnelWsFrame | TunnelWsClose | TunnelError;
77
+ export declare function isTunnelMessage(input: unknown): input is TunnelMessage;
78
+ export declare function makeTunnelPong(input: TunnelPing, receivedAt?: number): TunnelPong;
package/dist/index.mjs ADDED
@@ -0,0 +1,119 @@
1
+ // src/token-verifier.ts
2
+ var TokenVerifierError = class extends Error {
3
+ code;
4
+ status;
5
+ constructor(input) {
6
+ super(input.message);
7
+ this.name = "TokenVerifierError";
8
+ this.code = input.code;
9
+ this.status = input.status ?? 401;
10
+ if (input.cause !== void 0) this.cause = input.cause;
11
+ }
12
+ };
13
+ function createHttpTokenVerifier(options) {
14
+ const fetchImpl = options.fetch ?? globalThis.fetch;
15
+ if (!fetchImpl) {
16
+ throw new TokenVerifierError({
17
+ code: "fetch_unavailable",
18
+ message: "createHttpTokenVerifier requires a fetch implementation in this runtime",
19
+ status: 500
20
+ });
21
+ }
22
+ const timeoutMs = options.timeoutMs ?? 5e3;
23
+ return {
24
+ async verify(token) {
25
+ const controller = new AbortController();
26
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
27
+ try {
28
+ const res = await fetchImpl(options.endpoint, {
29
+ method: "POST",
30
+ headers: {
31
+ "content-type": "application/json",
32
+ ...options.headers ?? {}
33
+ },
34
+ body: JSON.stringify({ token }),
35
+ signal: controller.signal
36
+ });
37
+ if (!res.ok) {
38
+ throw new TokenVerifierError({
39
+ code: "verifier_rejected",
40
+ message: `Verifier endpoint returned ${res.status}`,
41
+ status: res.status === 401 || res.status === 403 ? res.status : 401
42
+ });
43
+ }
44
+ const body = await res.json().catch(() => null);
45
+ if (!body || typeof body.subject !== "string" || !body.subject) {
46
+ throw new TokenVerifierError({
47
+ code: "verifier_response_invalid",
48
+ message: "Verifier response is missing a subject"
49
+ });
50
+ }
51
+ return {
52
+ subject: body.subject,
53
+ scopes: Array.isArray(body.scopes) ? body.scopes.filter((s) => typeof s === "string") : [],
54
+ claims: body.claims && typeof body.claims === "object" ? body.claims : {}
55
+ };
56
+ } catch (err) {
57
+ if (err instanceof TokenVerifierError) throw err;
58
+ throw new TokenVerifierError({
59
+ code: "verifier_unreachable",
60
+ message: err instanceof Error ? err.message : String(err),
61
+ cause: err
62
+ });
63
+ } finally {
64
+ clearTimeout(timer);
65
+ }
66
+ }
67
+ };
68
+ }
69
+ function createStaticTokenVerifier(options) {
70
+ return {
71
+ async verify(token) {
72
+ const claims = options.tokens[token];
73
+ if (!claims) {
74
+ throw new TokenVerifierError({
75
+ code: "token_not_in_static_table",
76
+ message: "Token is not present in the static verifier's accepted set"
77
+ });
78
+ }
79
+ return claims;
80
+ }
81
+ };
82
+ }
83
+
84
+ // src/index.ts
85
+ var TUNNEL_PROTOCOL_VERSION = 1;
86
+ var tunnelMessageTypes = /* @__PURE__ */ new Set([
87
+ "ping",
88
+ "pong",
89
+ "http.request",
90
+ "http.response.start",
91
+ "http.response.chunk",
92
+ "http.response.end",
93
+ "ws.open",
94
+ "ws.frame",
95
+ "ws.close",
96
+ "error"
97
+ ]);
98
+ function isTunnelMessage(input) {
99
+ if (!input || typeof input !== "object") return false;
100
+ const row = input;
101
+ return row.protocol === TUNNEL_PROTOCOL_VERSION && typeof row.type === "string" && tunnelMessageTypes.has(row.type);
102
+ }
103
+ function makeTunnelPong(input, receivedAt = Date.now()) {
104
+ return {
105
+ type: "pong",
106
+ protocol: TUNNEL_PROTOCOL_VERSION,
107
+ id: input.id,
108
+ sent_at: input.sent_at,
109
+ received_at: receivedAt
110
+ };
111
+ }
112
+ export {
113
+ TUNNEL_PROTOCOL_VERSION,
114
+ TokenVerifierError,
115
+ createHttpTokenVerifier,
116
+ createStaticTokenVerifier,
117
+ isTunnelMessage,
118
+ makeTunnelPong
119
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Unified token-verifier interface (P-shared).
3
+ *
4
+ * Each of the four Claxedo boundaries (relay/tunnel, agent extension,
5
+ * SDK runner, workspace host) historically defined its own ad-hoc
6
+ * verifier shape. This module is the single seam every downstream user
7
+ * can swap a custom implementation through.
8
+ *
9
+ * The interface is intentionally narrow: token in, claims out. It does
10
+ * not expose JWKS plumbing, audience-binding sugar, or replay-cache
11
+ * details — those belong to the impl, not the contract.
12
+ *
13
+ * See the boundary READMEs for wiring rules.
14
+ */
15
+ export type TokenClaims = {
16
+ /** Stable subject id. The boundary is responsible for mapping this
17
+ * to a domain principal (user, workspace, runtime, etc.). */
18
+ subject: string;
19
+ /** Scopes / capabilities asserted by the issuer. May be empty. */
20
+ scopes: string[];
21
+ /** Full claim payload. Carries audience, issuer, expiry, plus any
22
+ * boundary-specific custom claims. */
23
+ claims: Record<string, unknown>;
24
+ };
25
+ export type TokenVerifier = {
26
+ verify(token: string): Promise<TokenClaims>;
27
+ };
28
+ export declare class TokenVerifierError extends Error {
29
+ readonly code: string;
30
+ readonly status: number;
31
+ constructor(input: {
32
+ code: string;
33
+ message: string;
34
+ status?: number;
35
+ cause?: unknown;
36
+ });
37
+ }
38
+ export type HttpTokenVerifierOptions = {
39
+ /** Verifier endpoint. Must accept POST with `{ token }` JSON body
40
+ * and return `{ subject: string; scopes?: string[]; claims?: Record<string, unknown> }`
41
+ * on success, or HTTP non-2xx on failure. */
42
+ endpoint: string;
43
+ /** Optional fixed headers (e.g., bearer auth on the verifier itself). */
44
+ headers?: Record<string, string>;
45
+ /** fetch override for tests. */
46
+ fetch?: typeof fetch;
47
+ /** Request timeout in ms. Default 5000. */
48
+ timeoutMs?: number;
49
+ };
50
+ export declare function createHttpTokenVerifier(options: HttpTokenVerifierOptions): TokenVerifier;
51
+ export type StaticTokenVerifierOptions = {
52
+ /** Map of token string → claims to return. */
53
+ tokens: Record<string, TokenClaims>;
54
+ };
55
+ export declare function createStaticTokenVerifier(options: StaticTokenVerifierOptions): TokenVerifier;
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@claxedo/workspace-relay-protocol",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.mjs",
6
+ "types": "./dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "scripts": {
14
+ "build": "tsx scripts/build.ts",
15
+ "prepublishOnly": "npm run build",
16
+ "typecheck": "tsc --noEmit -p tsconfig.json",
17
+ "test": "bun test src"
18
+ },
19
+ "devDependencies": {
20
+ "@tsconfig/node-lts": "^22.0.0",
21
+ "@types/bun": "catalog:",
22
+ "esbuild": "^0.25.0",
23
+ "tsx": "^4.0.0",
24
+ "typescript": "catalog:"
25
+ }
26
+ }