@tangle-network/agent-core 0.9.3 → 0.9.5

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.
@@ -1,22 +1,2 @@
1
- import { C as ReadTokenAuth, S as NoAuth, T as StaticTokenAuth, a as CreateSessionResponse, b as AuthProvider, c as ProductSchema, f as ReadTokenPayload, h as SendMessageRequestSchema, i as CreateSessionRequestSchema, l as ProductSecrets, m as SendMessageRequest, o as Product, p as ReadTokenPayloadSchema, r as CreateSessionRequest, s as ProductAuthInfo, w as RefreshableTokenAuth, x as CallbackAuth, y as TokenValidationResult } from "../types-Cd5jUzcr.js";
2
-
3
- //#region src/auth/tokens-browser.d.ts
4
- /**
5
- * Decode a JWT without verification (to extract claims for lookup).
6
- * Returns null if the token is malformed.
7
- *
8
- * This is safe to use in browsers - it only parses the token, doesn't verify.
9
- */
10
- declare function decodeToken(token: string): ReadTokenPayload | null;
11
- /**
12
- * Get time until token expires (in seconds).
13
- * Returns negative if expired.
14
- */
15
- declare function getTokenTTL(payload: ReadTokenPayload): number;
16
- /**
17
- * Check if token is expiring soon (within buffer seconds).
18
- */
19
- declare function isTokenExpiringSoon(payload: ReadTokenPayload, bufferSeconds?: number): boolean;
20
- //#endregion
21
- export { type AuthProvider, CallbackAuth, type CreateSessionRequest, CreateSessionRequestSchema, type CreateSessionResponse, NoAuth, type Product, type ProductAuthInfo, ProductSchema, type ProductSecrets, ReadTokenAuth, type ReadTokenPayload, ReadTokenPayloadSchema, RefreshableTokenAuth, type SendMessageRequest, SendMessageRequestSchema, StaticTokenAuth, type TokenValidationResult, decodeToken, getTokenTTL, isTokenExpiringSoon };
22
- //# sourceMappingURL=browser.d.ts.map
1
+ import { C as AuthProvider, D as RefreshableTokenAuth, E as ReadTokenAuth, O as StaticTokenAuth, S as TokenValidationResult, T as NoAuth, _ as SendMessageRequest, c as CreateSessionResponse, d as ProductSchema, f as ProductSecrets, g as ReadTokenPayloadSchema, h as ReadTokenPayload, l as Product, n as getTokenTTL, o as CreateSessionRequest, r as isTokenExpiringSoon, s as CreateSessionRequestSchema, t as decodeToken, u as ProductAuthInfo, v as SendMessageRequestSchema, w as CallbackAuth } from "../tokens-browser-EiSc2dah.js";
2
+ export { type AuthProvider, CallbackAuth, type CreateSessionRequest, CreateSessionRequestSchema, type CreateSessionResponse, NoAuth, type Product, type ProductAuthInfo, ProductSchema, type ProductSecrets, ReadTokenAuth, type ReadTokenPayload, ReadTokenPayloadSchema, RefreshableTokenAuth, type SendMessageRequest, SendMessageRequestSchema, StaticTokenAuth, type TokenValidationResult, decodeToken, getTokenTTL, isTokenExpiringSoon };
@@ -1,43 +1,2 @@
1
- import { a as ReadTokenPayloadSchema, c as CallbackAuth, d as RefreshableTokenAuth, f as StaticTokenAuth, l as NoAuth, n as CreateSessionRequestSchema, o as SendMessageRequestSchema, r as ProductSchema, u as ReadTokenAuth } from "../types-DC6OJ1um.js";
2
- //#region src/auth/tokens-browser.ts
3
- /**
4
- * Base64URL decode (browser-safe).
5
- */
6
- function base64UrlDecode(data) {
7
- const base64 = (data + "=".repeat((4 - data.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
8
- if (typeof atob === "function") return atob(base64);
9
- return Buffer.from(base64, "base64").toString();
10
- }
11
- /**
12
- * Decode a JWT without verification (to extract claims for lookup).
13
- * Returns null if the token is malformed.
14
- *
15
- * This is safe to use in browsers - it only parses the token, doesn't verify.
16
- */
17
- function decodeToken(token) {
18
- try {
19
- const parts = token.split(".");
20
- if (parts.length !== 3) return null;
21
- return JSON.parse(base64UrlDecode(parts[1]));
22
- } catch {
23
- return null;
24
- }
25
- }
26
- /**
27
- * Get time until token expires (in seconds).
28
- * Returns negative if expired.
29
- */
30
- function getTokenTTL(payload) {
31
- const now = Math.floor(Date.now() / 1e3);
32
- return payload.exp - now;
33
- }
34
- /**
35
- * Check if token is expiring soon (within buffer seconds).
36
- */
37
- function isTokenExpiringSoon(payload, bufferSeconds = 60) {
38
- return getTokenTTL(payload) <= bufferSeconds;
39
- }
40
- //#endregion
1
+ import { _ as isTokenExpiringSoon, a as ReadTokenPayloadSchema, c as CallbackAuth, d as RefreshableTokenAuth, f as StaticTokenAuth, g as getTokenTTL, h as decodeToken, l as NoAuth, n as CreateSessionRequestSchema, o as SendMessageRequestSchema, r as ProductSchema, u as ReadTokenAuth } from "../types-Boft7Ohf.js";
41
2
  export { CallbackAuth, CreateSessionRequestSchema, NoAuth, ProductSchema, ReadTokenAuth, ReadTokenPayloadSchema, RefreshableTokenAuth, SendMessageRequestSchema, StaticTokenAuth, decodeToken, getTokenTTL, isTokenExpiringSoon };
42
-
43
- //# sourceMappingURL=browser.js.map
@@ -1,4 +1,4 @@
1
- import { C as ReadTokenAuth, S as NoAuth, T as StaticTokenAuth, _ as SessionScopedTokenPayloadSchema, a as CreateSessionResponse, b as AuthProvider, c as ProductSchema, d as ProjectScopedTokenPayloadSchema, f as ReadTokenPayload, g as SessionScopedTokenPayload, h as SendMessageRequestSchema, i as CreateSessionRequestSchema, l as ProductSecrets, m as SendMessageRequest, n as BatchScopedTokenPayloadSchema, o as Product, p as ReadTokenPayloadSchema, r as CreateSessionRequest, s as ProductAuthInfo, t as BatchScopedTokenPayload, u as ProjectScopedTokenPayload, v as TokenScope, w as RefreshableTokenAuth, x as CallbackAuth, y as TokenValidationResult } from "../types-Cd5jUzcr.js";
1
+ import { C as AuthProvider, D as RefreshableTokenAuth, E as ReadTokenAuth, O as StaticTokenAuth, S as TokenValidationResult, T as NoAuth, _ as SendMessageRequest, a as BatchScopedTokenPayloadSchema, b as SessionScopedTokenPayloadSchema, c as CreateSessionResponse, d as ProductSchema, f as ProductSecrets, g as ReadTokenPayloadSchema, h as ReadTokenPayload, i as BatchScopedTokenPayload, l as Product, m as ProjectScopedTokenPayloadSchema, n as getTokenTTL, o as CreateSessionRequest, p as ProjectScopedTokenPayload, r as isTokenExpiringSoon, s as CreateSessionRequestSchema, t as decodeToken, u as ProductAuthInfo, v as SendMessageRequestSchema, w as CallbackAuth, x as TokenScope, y as SessionScopedTokenPayload } from "../tokens-browser-EiSc2dah.js";
2
2
 
3
3
  //#region src/auth/channel-access.d.ts
4
4
  /**
@@ -299,11 +299,6 @@ declare function getTokenScope(payload: ReadTokenPayload): TokenScope | null;
299
299
  * Returns an error message if invalid, null if valid.
300
300
  */
301
301
  declare function validateTokenScope(payload: ReadTokenPayload): string | null;
302
- /**
303
- * Decode a JWT without verification (to extract claims for lookup).
304
- * Returns null if the token is malformed.
305
- */
306
- declare function decodeToken(token: string): ReadTokenPayload | null;
307
302
  /**
308
303
  * Verify a read token against a product's signing secrets.
309
304
  *
@@ -311,15 +306,6 @@ declare function decodeToken(token: string): ReadTokenPayload | null;
311
306
  * @param product - Product auth info containing secrets
312
307
  */
313
308
  declare function verifyReadToken(token: string, product: ProductAuthInfo): TokenValidationResult;
314
- /**
315
- * Get time until token expires (in seconds).
316
- * Returns negative if expired.
317
- */
318
- declare function getTokenTTL(payload: ReadTokenPayload): number;
319
- /**
320
- * Check if token is expiring soon (within buffer seconds).
321
- */
322
- declare function isTokenExpiringSoon(payload: ReadTokenPayload, bufferSeconds?: number): boolean;
323
309
  /**
324
310
  * Hash an API key for storage/lookup.
325
311
  * Uses HMAC-SHA256 for consistent, fast hashing.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/auth/channel-access.ts","../../src/auth/tokens.ts","../../src/auth/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/auth/channel-access.ts","../../src/auth/tokens.ts","../../src/auth/index.ts"],"mappings":"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"}
@@ -1,7 +1,13 @@
1
- import { a as ReadTokenPayloadSchema, c as CallbackAuth, d as RefreshableTokenAuth, f as StaticTokenAuth, i as ProjectScopedTokenPayloadSchema, l as NoAuth, n as CreateSessionRequestSchema, o as SendMessageRequestSchema, r as ProductSchema, s as SessionScopedTokenPayloadSchema, t as BatchScopedTokenPayloadSchema, u as ReadTokenAuth } from "../types-DC6OJ1um.js";
1
+ import { _ as isTokenExpiringSoon, a as ReadTokenPayloadSchema, c as CallbackAuth, d as RefreshableTokenAuth, f as StaticTokenAuth, g as getTokenTTL, h as decodeToken, i as ProjectScopedTokenPayloadSchema, l as NoAuth, m as base64UrlToBytes, n as CreateSessionRequestSchema, o as SendMessageRequestSchema, p as base64UrlDecode, r as ProductSchema, s as SessionScopedTokenPayloadSchema, t as BatchScopedTokenPayloadSchema, u as ReadTokenAuth } from "../types-Boft7Ohf.js";
2
2
  //#region src/auth/tokens.ts
3
+ /**
4
+ * JWT Token Utilities
5
+ *
6
+ * Token generation and verification for multi-tenant auth.
7
+ * - Read/product tokens: HMAC-SHA256 (symmetric, fast)
8
+ * - Sidecar tokens: Ed25519 (asymmetric — sidecar cannot forge tokens)
9
+ */
3
10
  const textEncoder = new TextEncoder();
4
- const textDecoder = new TextDecoder();
5
11
  let nodeCryptoCache;
6
12
  /**
7
13
  * Resolve Node's `crypto` lazily and synchronously. Signing, verification, and
@@ -50,22 +56,6 @@ function base64UrlEncode(data) {
50
56
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
51
57
  }
52
58
  /**
53
- * Base64URL decode (RFC 7515) to raw bytes.
54
- */
55
- function base64UrlToBytes(data) {
56
- const padded = data + "=".repeat((4 - data.length % 4) % 4);
57
- const binary = atob(padded.replace(/-/g, "+").replace(/_/g, "/"));
58
- const bytes = new Uint8Array(binary.length);
59
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
60
- return bytes;
61
- }
62
- /**
63
- * Base64URL decode to a UTF-8 string.
64
- */
65
- function base64UrlDecode(data) {
66
- return textDecoder.decode(base64UrlToBytes(data));
67
- }
68
- /**
69
59
  * Create HMAC-SHA256 signature.
70
60
  */
71
61
  function createSignature(data, secret) {
@@ -340,19 +330,6 @@ function validateTokenScope(payload) {
340
330
  return null;
341
331
  }
342
332
  /**
343
- * Decode a JWT without verification (to extract claims for lookup).
344
- * Returns null if the token is malformed.
345
- */
346
- function decodeToken(token) {
347
- try {
348
- const parts = token.split(".");
349
- if (parts.length !== 3) return null;
350
- return JSON.parse(base64UrlDecode(parts[1]));
351
- } catch {
352
- return null;
353
- }
354
- }
355
- /**
356
333
  * Verify a read token against a product's signing secrets.
357
334
  *
358
335
  * @param token - The JWT token to verify
@@ -432,20 +409,6 @@ function verifyReadToken(token, product) {
432
409
  };
433
410
  }
434
411
  /**
435
- * Get time until token expires (in seconds).
436
- * Returns negative if expired.
437
- */
438
- function getTokenTTL(payload) {
439
- const now = Math.floor(Date.now() / 1e3);
440
- return payload.exp - now;
441
- }
442
- /**
443
- * Check if token is expiring soon (within buffer seconds).
444
- */
445
- function isTokenExpiringSoon(payload, bufferSeconds = 60) {
446
- return getTokenTTL(payload) <= bufferSeconds;
447
- }
448
- /**
449
412
  * Default salt for API key hashing (development only).
450
413
  * Production MUST set API_KEY_HASH_SALT environment variable.
451
414
  * WARNING: Changing the salt will invalidate all existing API key hashes.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/auth/tokens.ts","../../src/auth/channel-access.ts","../../src/auth/index.ts"],"sourcesContent":["/**\n * JWT Token Utilities\n *\n * Token generation and verification for multi-tenant auth.\n * - Read/product tokens: HMAC-SHA256 (symmetric, fast)\n * - Sidecar tokens: Ed25519 (asymmetric — sidecar cannot forge tokens)\n */\n\nimport type {\n BatchScopedTokenPayload,\n ProductAuthInfo,\n ProjectScopedTokenPayload,\n ReadTokenPayload,\n SessionScopedTokenPayload,\n TokenScope,\n TokenValidationResult,\n} from \"./types.js\";\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\ntype NodeCrypto = typeof import(\"node:crypto\");\nlet nodeCryptoCache: NodeCrypto | undefined;\n\n/**\n * Resolve Node's `crypto` lazily and synchronously. Signing, verification, and\n * key generation are server-only, but this module must stay importable in a\n * browser bundle — a static `import \"node:crypto\"` forces bundlers to resolve\n * the builtin at load and blanks the page. `process.getBuiltinModule` exists\n * only on a Node runtime (Node >= 22.3); in a browser `globalThis.process` is\n * undefined, so callers get a clear server-only error instead of a crash.\n */\nfunction nodeCrypto(): NodeCrypto {\n if (nodeCryptoCache) return nodeCryptoCache;\n const resolved = (\n globalThis as {\n process?: { getBuiltinModule?: (id: string) => unknown };\n }\n ).process?.getBuiltinModule?.(\"node:crypto\") as NodeCrypto | undefined;\n if (!resolved) {\n throw new Error(\n \"@tangle-network/agent-core/auth: token signing, verification, and key generation are server-only (Node.js crypto) and cannot run in a browser.\",\n );\n }\n nodeCryptoCache = resolved;\n return resolved;\n}\n\n/**\n * Generate a cryptographically secure random string.\n * @param prefix - Prefix for the generated string (e.g., \"orch_prod_\")\n * @param bytes - Number of random bytes (default: 32 = 256 bits)\n */\nexport function generateSecureToken(prefix: string, bytes = 32): string {\n return `${prefix}${base64UrlEncode(nodeCrypto().randomBytes(bytes))}`;\n}\n\n/**\n * Generate a product API key.\n */\nexport function generateApiKey(): string {\n return generateSecureToken(\"orch_prod_\");\n}\n\n/**\n * Generate a signing secret.\n */\nexport function generateSigningSecret(): string {\n return generateSecureToken(\"orch_sign_\");\n}\n\n/**\n * Base64URL encode (RFC 7515). Isomorphic: `btoa` + `TextEncoder` exist in Node\n * and browsers, so — unlike `Buffer` — this never blanks a browser page that\n * transitively imports the module.\n */\nfunction base64UrlEncode(data: string | Uint8Array): string {\n const bytes = typeof data === \"string\" ? textEncoder.encode(data) : data;\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\n/**\n * Base64URL decode (RFC 7515) to raw bytes.\n */\nfunction base64UrlToBytes(data: string): Uint8Array {\n const padded = data + \"=\".repeat((4 - (data.length % 4)) % 4);\n const binary = atob(padded.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\n/**\n * Base64URL decode to a UTF-8 string.\n */\nfunction base64UrlDecode(data: string): string {\n return textDecoder.decode(base64UrlToBytes(data));\n}\n\n/**\n * Create HMAC-SHA256 signature.\n */\nfunction createSignature(data: string, secret: string): string {\n return base64UrlEncode(\n nodeCrypto().createHmac(\"sha256\", secret).update(data).digest(),\n );\n}\n\n/**\n * Verify signature using timing-safe comparison on raw HMAC bytes.\n *\n * Decoding both sides to raw bytes before the compare strips the\n * length-of-base64url-string oracle: every legitimate signature\n * decodes to exactly 32 bytes (HMAC-SHA256 digest size), so a\n * length mismatch on the decoded buffer means malformed input,\n * not a partial match. Equivalent to the pattern used in\n * `products/sandbox/sdk/src/auth/tokens.ts`.\n */\nfunction verifySignature(\n data: string,\n signature: string,\n secret: string,\n): boolean {\n const { createHmac, timingSafeEqual } = nodeCrypto();\n const expectedBuf = createHmac(\"sha256\", secret).update(data).digest();\n let providedBuf: Uint8Array;\n try {\n providedBuf = base64UrlToBytes(signature);\n } catch {\n return false;\n }\n if (providedBuf.length !== expectedBuf.length) return false;\n try {\n return timingSafeEqual(providedBuf, expectedBuf);\n } catch {\n return false;\n }\n}\n\n/**\n * Base64URL-encoded JWT header `{\"alg\":\"HS256\",\"typ\":\"JWT\"}`. Computed on first\n * use (not at module load) and memoized.\n */\nlet jwtHeaderCache: string | undefined;\nfunction jwtHeader(): string {\n if (jwtHeaderCache === undefined) {\n jwtHeaderCache = base64UrlEncode(\n JSON.stringify({ alg: \"HS256\", typ: \"JWT\" }),\n );\n }\n return jwtHeaderCache;\n}\n\n/**\n * Issue a read token (JWT) for WebSocket authentication.\n *\n * @param signingSecret - The product's signing secret\n * @param payload - Token payload (without iat/exp, those are added)\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueReadToken(\n signingSecret: string,\n payload: Omit<ReadTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes: number,\n): string {\n const now = Math.floor(Date.now() / 1000);\n\n // Type assertion is safe here: payload already satisfies one branch of the\n // ReadTokenPayload union (minus iat/exp/typ), and we're adding those fields.\n // TypeScript can't infer which union branch we're creating from a spread.\n const fullPayload = {\n ...payload,\n typ: \"read\" as const,\n iat: now,\n exp: now + ttlMinutes * 60,\n } as ReadTokenPayload;\n\n const encodedPayload = base64UrlEncode(JSON.stringify(fullPayload));\n const data = `${jwtHeader()}.${encodedPayload}`;\n const signature = createSignature(data, signingSecret);\n\n return `${data}.${signature}`;\n}\n\n/**\n * Issue a session-scoped token (JWT) for WebSocket authentication.\n * Grants access to a single session's events.\n *\n * @param signingSecret - The product's signing secret\n * @param payload - Token payload with session ID\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueSessionScopedToken(\n signingSecret: string,\n payload: Omit<SessionScopedTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes: number,\n): string {\n return issueReadToken(signingSecret, payload, ttlMinutes);\n}\n\n/**\n * Issue a project-scoped token (JWT) for WebSocket authentication.\n * Grants access to all sessions within a single project.\n *\n * @param signingSecret - The product's signing secret\n * @param payload - Token payload with project ID\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueProjectScopedToken(\n signingSecret: string,\n payload: Omit<ProjectScopedTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes: number,\n): string {\n return issueReadToken(\n signingSecret,\n payload as Omit<ReadTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes,\n );\n}\n\n/**\n * Issue a batch-scoped token (JWT) for WebSocket authentication.\n * Grants access to multiple projects (organization-level access).\n *\n * @param signingSecret - The product's signing secret\n * @param payload - Token payload with project IDs array\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueBatchScopedToken(\n signingSecret: string,\n payload: Omit<BatchScopedTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes: number,\n): string {\n return issueReadToken(\n signingSecret,\n payload as Omit<ReadTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes,\n );\n}\n\n// Ed25519 JWT header (asymmetric — sidecar cannot forge tokens); computed on\n// first use, not at module load, so the module stays browser-importable.\nlet jwtHeaderEddsaCache: string | undefined;\nfunction jwtHeaderEddsa(): string {\n if (jwtHeaderEddsaCache === undefined) {\n jwtHeaderEddsaCache = base64UrlEncode(\n JSON.stringify({ alg: \"EdDSA\", typ: \"JWT\" }),\n );\n }\n return jwtHeaderEddsaCache;\n}\n\n/**\n * The accepted values of the `cap` claim. The exported union is derived from\n * this list so the type a caller programs against and the set the verifier\n * enforces cannot drift apart.\n */\nconst SIDECAR_CAPABILITIES = [\n \"computer_use\",\n \"read\",\n \"debug\",\n \"terminal\",\n \"workspace\",\n \"control\",\n \"raw_input\",\n] as const;\n\n/**\n * Capability strings carried in a sidecar token's `cap` claim. This is the\n * shared vocabulary the crypto layer stamps into the JWT (issueSidecarAccessToken)\n * and reads back out (verifySidecarToken). The route→capability authorization\n * policy that enforces these claims lives in the sidecar layer, not here.\n * Tokens with a `cap` claim are strictly limited to those capabilities; tokens\n * without a `cap` claim are full-scope (legacy orchestrator-internal use).\n *\n * `cap` is the claim that decides authorization, so both halves of the round\n * trip check it: the issuer throws and the verifier rejects unless the claim is\n * an array whose every entry is listed above. An empty array is valid and means\n * \"scoped to no capability\" — consumers read that as deny-everything, which is\n * the fail-closed direction. What must never happen is a malformed value\n * reaching a consumer as an *absent* claim, since absent means full scope.\n *\n * Adding a new capability: extend `SIDECAR_CAPABILITIES` above, then update the\n * enforcement policy (route maps + authorization check) in the sidecar-auth\n * package. That is a breaking change for verifiers — `verifySidecarToken`\n * rejects a capability it does not recognise, so every verifier must ship the\n * new value before any issuer stamps it. Sidecars are baked container images\n * that outlive an orchestrator roll, so \"ship\" means deployed, not merged.\n */\nexport type SidecarCapability = (typeof SIDECAR_CAPABILITIES)[number];\n\n/**\n * Whether `value` is a usable `cap` claim. Shared by the issuer and the\n * verifier so one definition decides what the claim may hold.\n *\n * `includes` compares by SameValueZero against a `readonly unknown[]` view, so\n * this rejects every non-string a JSON claim can hold (`null`, `42`, a nested\n * object) as well as an unrecognised string. A non-array — including the\n * `cap: \"read\"` case, where a consumer's `cap.includes(\"read\")` degrades into a\n * substring test that passes the read gate — fails the `Array.isArray` guard.\n */\nfunction isSidecarCapabilityList(\n value: unknown,\n): value is SidecarCapability[] {\n return (\n Array.isArray(value) &&\n value.every((entry) =>\n (SIDECAR_CAPABILITIES as readonly unknown[]).includes(entry),\n )\n );\n}\n\n/**\n * The accepted values of the `sst` claim. The exported union is derived from\n * this list so the type a caller programs against and the set the verifier\n * enforces cannot drift apart.\n */\nconst SIDECAR_SID_SCOPES = [\"session\", \"project\"] as const;\n\n/**\n * What a sidecar token's `sid` claim holds, carried in the `sst` claim.\n *\n * `sid` is minted with two different meanings: for a session-runtime token it\n * is a session id, and for a project-scoped read-only token it is a project\n * ref. Nothing on the token distinguished them, so a consumer could not tell\n * whether comparing `sid` against a session id in a request path was an\n * authorization check or a guaranteed mismatch.\n *\n * How a consumer reads the claim on a session-addressed route:\n *\n * - `\"session\"` — `sid` is a session id. Compare it against the session the\n * request names and deny a mismatch. This is the check the claim exists for.\n * - `\"project\"` — `sid` is a project ref, so no session comparison is\n * possible. The route's own capability policy decides, unchanged: a\n * project-scoped read-only token legitimately reaches every session in its\n * project.\n * - absent — the meaning of `sid` is unknown, so enforce no session\n * comparison. Tokens minted before this claim existed carry no `sst`.\n * - a value this build does not recognise — deny. `verifySidecarToken`\n * rejects those already, so this is a second line for a consumer that\n * reads the claim from somewhere else.\n *\n * Do not collapse that to \"anything but `\"session\"` is not a session token,\n * so deny it on a session route\". `\"project\"` is a legitimate scope there,\n * and denying it 403s every read-only and terminal token on every session\n * route — the behaviour that got the first version of this guard reverted.\n *\n * The verifier does not check that `sid` looks like what `sst` declares. That\n * is a semantic judgement about ids this layer cannot make; it belongs to the\n * consumer that knows the id namespaces.\n *\n * Adding a value here is a breaking change for verifiers: `verifySidecarToken`\n * rejects an `sst` it does not recognise, so every verifier must ship the new\n * value before any issuer stamps it.\n */\nexport type SidecarSidScope = (typeof SIDECAR_SID_SCOPES)[number];\n\n/**\n * Generate an Ed25519 key pair for per-session JWT signing.\n * Private key stays in orchestrator memory. Public key is injected into\n * the sidecar container. Even if the sidecar is fully compromised,\n * the attacker cannot forge JWTs for any container.\n *\n * @returns { privateKey, publicKey } as PEM strings\n */\nexport function generateSidecarKeyPair(): {\n privateKey: string;\n publicKey: string;\n} {\n const { privateKey, publicKey } = nodeCrypto().generateKeyPairSync(\n \"ed25519\",\n {\n publicKeyEncoding: { type: \"spki\", format: \"pem\" },\n privateKeyEncoding: { type: \"pkcs8\", format: \"pem\" },\n },\n );\n return { privateKey, publicKey };\n}\n\n/**\n * Issue a sidecar access token (JWT) using Ed25519 signing.\n * Scoped to a specific container via the required `cid` claim.\n *\n * @param privateKey - Ed25519 private key (PEM). If not provided, falls back to HMAC.\n * @param hmacFallbackSecret - HMAC secret for backward compat (used when no Ed25519 key)\n * @param payload - Token payload with container ID\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueSidecarAccessToken(\n privateKey: string,\n payload: {\n sub: string;\n pid: string;\n cid: string;\n sid?: string;\n /**\n * What `sid` holds. Stamp it whenever `sid` is set, so a consumer can tell\n * a session-bound token from a project-bound one instead of guessing from\n * the capability list. Omitting it leaves consumers unable to enforce a\n * session comparison.\n */\n sst?: SidecarSidScope;\n /**\n * Capability allowlist. When present, the token is restricted to\n * routes whose required capability is in this list, as enforced by the\n * sidecar-auth policy layer. Absent = full scope (legacy\n * orchestrator-internal tokens), so pass a list or omit the key — never a\n * placeholder like `null`, which reads as full scope one layer down.\n */\n cap?: SidecarCapability[];\n },\n ttlMinutes: number,\n): string {\n if (!privateKey.includes(\"PRIVATE KEY\")) {\n throw new Error(\n \"issueSidecarAccessToken requires an Ed25519 private key (PEM). \" +\n \"HMAC fallback has been removed — generate a key pair with generateSidecarKeyPair().\",\n );\n }\n\n // Reject at mint time what no verifier accepts. Either mistake otherwise\n // produces a token that fails everywhere it is presented, debugged inside a\n // sidecar rather than in the issuer that made it. A caller reaching this\n // function from JavaScript, or through a value typed `string`, gets no help\n // from the parameter type.\n if (payload.cap !== undefined && !isSidecarCapabilityList(payload.cap)) {\n throw new Error(\n `issueSidecarAccessToken: cap must be an array whose entries are one of ${SIDECAR_CAPABILITIES.join(\", \")}. Omit the claim entirely for a full-scope token.`,\n );\n }\n if (payload.sst !== undefined) {\n if (!SIDECAR_SID_SCOPES.includes(payload.sst)) {\n throw new Error(\n `issueSidecarAccessToken: sst must be one of ${SIDECAR_SID_SCOPES.join(\", \")}.`,\n );\n }\n // `sst` names what `sid` holds, so it is meaningless without one. A\n // whitespace-only sid is the same mistake wearing a disguise: it passes a\n // truthiness test, then a consumer enforces a session match against a\n // degenerate value.\n if (!payload.sid || payload.sid.trim().length === 0) {\n throw new Error(\n \"issueSidecarAccessToken: sst describes the sid claim and cannot be set without a non-blank sid.\",\n );\n }\n }\n\n const now = Math.floor(Date.now() / 1000);\n // jti (JWT ID) enables token revocation — orchestrator adds jti to blocklist\n // on sandbox delete, sidecar checks blocklist on every request.\n const { randomBytes, sign } = nodeCrypto();\n const jti = `${payload.cid}:${now}:${randomBytes(8).toString(\"hex\")}`;\n const fullPayload = {\n ...payload,\n typ: \"sidecar\" as const,\n jti,\n iat: now,\n exp: now + ttlMinutes * 60,\n };\n const encodedPayload = base64UrlEncode(JSON.stringify(fullPayload));\n const data = `${jwtHeaderEddsa()}.${encodedPayload}`;\n const signature = base64UrlEncode(\n sign(null, textEncoder.encode(data), privateKey),\n );\n return `${data}.${signature}`;\n}\n\n/**\n * Verify a sidecar JWT using Ed25519 public key. Rejects non-EdDSA tokens.\n * Returns decoded payload on success, null on failure.\n */\nexport function verifySidecarToken(\n token: string,\n publicKey: string,\n containerId: string,\n): {\n sub: string;\n pid: string;\n cid: string;\n sid?: string;\n /**\n * What `sid` holds. Absent on tokens minted before the claim existed —\n * treat that as \"unknown\", never as a licence to skip a check.\n */\n sst?: SidecarSidScope;\n typ: string;\n jti?: string;\n exp: number;\n /**\n * Capability allowlist. Guaranteed to be an array of recognised\n * capabilities when present — a token carrying anything else is rejected, so\n * a consumer may call array methods on it without a shape check of its own.\n * Absent means full scope, so never substitute a placeholder for a missing\n * claim on the way to a policy check.\n */\n cap?: SidecarCapability[];\n} | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n\n const headerJson = base64UrlDecode(parts[0]);\n const header = JSON.parse(headerJson);\n if (header.alg !== \"EdDSA\") return null;\n\n // SECURITY: verify signature BEFORE inspecting claims.\n // Parsing claims before verification creates a timing oracle that\n // leaks valid container IDs via early-return timing differences.\n const data = `${parts[0]}.${parts[1]}`;\n const signatureBuffer = base64UrlToBytes(parts[2]);\n const valid = nodeCrypto().verify(\n null,\n textEncoder.encode(data),\n publicKey,\n signatureBuffer,\n );\n if (!valid) return null;\n\n // Signature verified — now safe to parse and inspect claims\n const payload = JSON.parse(base64UrlDecode(parts[1]));\n\n if (payload.typ !== \"sidecar\") return null;\n if (typeof payload.jti !== \"string\" || payload.jti.length === 0) {\n return null;\n }\n const exactContainerMatch = payload.cid === containerId;\n const shortDockerIdMatch = isDockerShortIdMatch(payload.cid, containerId);\n if (!exactContainerMatch && !shortDockerIdMatch) return null;\n\n // Require exp — tokens without expiration are rejected (no immortal tokens)\n const now = Math.floor(Date.now() / 1000);\n if (typeof payload.exp !== \"number\" || payload.exp < now) return null;\n\n // Require non-empty sub and cid\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0)\n return null;\n\n // `sst` decides whether a consumer runs an authorization check, so an\n // unrecognised or unusable value must reject the token rather than pass\n // through. Returning it would leave the consumer's `sst === \"session\"`\n // test false and silently skip the check the issuer asked for.\n if (payload.sst !== undefined) {\n // `includes` compares by SameValueZero, so this rejects every non-string\n // a JSON claim can hold as well as an unrecognised string.\n if (!SIDECAR_SID_SCOPES.includes(payload.sst)) return null;\n // Blank counts as absent. The trim applies only on this branch, so it\n // cannot reject a token minted before the claim existed.\n if (typeof payload.sid !== \"string\" || payload.sid.trim().length === 0) {\n return null;\n }\n }\n\n // `cap` is the claim authorization is decided on, so a value a consumer\n // cannot evaluate must reject the token rather than pass through. Returning\n // it hands the consumer a claim whose `includes` either throws out of the\n // auth middleware (a 500 where a 403 was meant) or, for a bare string,\n // silently becomes a substring test that satisfies the gate it names.\n //\n // Rejecting is the loud direction and the one every other claim here takes.\n // Coercing to `[]` would also deny, but quietly; coercing to *absent* is\n // the one option that must never be chosen, since absent means full scope.\n if (payload.cap !== undefined && !isSidecarCapabilityList(payload.cap)) {\n return null;\n }\n\n return payload;\n } catch {\n return null;\n }\n}\n\n/**\n * Compile-time assertions that keep the `sst` and `cap` claims narrow on both\n * halves of the round trip. They are load-bearing: this package type-checks\n * `src` only, so widening either field, or dropping the `as const` that derives\n * `SidecarSidScope` / `SidecarCapability`, otherwise collapses the union to\n * `string` and passes every gate here — leaving a consumer free to test the\n * claim against a value the verifier rejects.\n */\ntype AssertTrue<T extends true> = T;\ntype _IssuerTakesNarrowSst = AssertTrue<\n string extends NonNullable<Parameters<typeof issueSidecarAccessToken>[1][\"sst\"]>\n ? false\n : true\n>;\ntype _VerifierReturnsNarrowSst = AssertTrue<\n string extends NonNullable<\n NonNullable<ReturnType<typeof verifySidecarToken>>[\"sst\"]\n >\n ? false\n : true\n>;\ntype _IssuerTakesNarrowCap = AssertTrue<\n string extends NonNullable<\n Parameters<typeof issueSidecarAccessToken>[1][\"cap\"]\n >[number]\n ? false\n : true\n>;\ntype _VerifierReturnsNarrowCap = AssertTrue<\n string extends NonNullable<\n NonNullable<ReturnType<typeof verifySidecarToken>>[\"cap\"]\n >[number]\n ? false\n : true\n>;\n\nfunction isDockerShortIdMatch(\n payloadCid: unknown,\n containerId: unknown,\n): boolean {\n if (typeof payloadCid !== \"string\" || typeof containerId !== \"string\") {\n return false;\n }\n if (!/^[a-f0-9]{64}$/i.test(payloadCid)) {\n return false;\n }\n if (!/^[a-f0-9]{12}$/i.test(containerId)) {\n return false;\n }\n return payloadCid.startsWith(containerId);\n}\n\n/**\n * Check if a token payload is session-scoped.\n * Session-scoped tokens have a sid claim and no projectId/projectIds.\n */\nexport function isSessionScopedToken(\n payload: ReadTokenPayload,\n): payload is SessionScopedTokenPayload {\n return (\n \"sid\" in payload &&\n typeof payload.sid === \"string\" &&\n payload.sid.length > 0 &&\n !(\"projectId\" in payload && payload.projectId) &&\n !(\"projectIds\" in payload && payload.projectIds)\n );\n}\n\n/**\n * Check if a token payload is project-scoped.\n * Project-scoped tokens have a projectId claim and no sid/projectIds.\n */\nexport function isProjectScopedToken(\n payload: ReadTokenPayload,\n): payload is ProjectScopedTokenPayload {\n return (\n \"projectId\" in payload &&\n typeof payload.projectId === \"string\" &&\n payload.projectId.length > 0 &&\n !(\"sid\" in payload && payload.sid) &&\n !(\"projectIds\" in payload && payload.projectIds)\n );\n}\n\n/**\n * Check if a token payload is batch-scoped.\n * Batch-scoped tokens have a projectIds claim and no sid/projectId.\n */\nexport function isBatchScopedToken(\n payload: ReadTokenPayload,\n): payload is BatchScopedTokenPayload {\n return (\n \"projectIds\" in payload &&\n Array.isArray(payload.projectIds) &&\n payload.projectIds.length > 0 &&\n !(\"sid\" in payload && payload.sid) &&\n !(\"projectId\" in payload && payload.projectId)\n );\n}\n\n/**\n * Get the scope of a token payload.\n * Returns null if the token has an invalid/ambiguous scope.\n */\nexport function getTokenScope(payload: ReadTokenPayload): TokenScope | null {\n if (isSessionScopedToken(payload)) return \"session\";\n if (isProjectScopedToken(payload)) return \"project\";\n if (isBatchScopedToken(payload)) return \"batch\";\n return null;\n}\n\n/**\n * Validate that a token payload has exactly one valid scope.\n * Returns an error message if invalid, null if valid.\n */\nexport function validateTokenScope(payload: ReadTokenPayload): string | null {\n const hasSid = \"sid\" in payload && payload.sid;\n const hasProjectId = \"projectId\" in payload && payload.projectId;\n const hasProjectIds =\n \"projectIds\" in payload &&\n Array.isArray(payload.projectIds) &&\n payload.projectIds.length > 0;\n\n const scopeCount = [hasSid, hasProjectId, hasProjectIds].filter(\n Boolean,\n ).length;\n\n if (scopeCount === 0) {\n return \"Token must have one of: sid (session), projectId (project), or projectIds (batch)\";\n }\n\n if (scopeCount > 1) {\n return \"Token cannot have multiple scope claims (sid, projectId, projectIds are mutually exclusive)\";\n }\n\n return null;\n}\n\n/**\n * Decode a JWT without verification (to extract claims for lookup).\n * Returns null if the token is malformed.\n */\nexport function decodeToken(token: string): ReadTokenPayload | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n return null;\n }\n\n const payload = JSON.parse(base64UrlDecode(parts[1]));\n return payload as ReadTokenPayload;\n } catch {\n return null;\n }\n}\n\n/**\n * Verify a read token against a product's signing secrets.\n *\n * @param token - The JWT token to verify\n * @param product - Product auth info containing secrets\n */\nexport function verifyReadToken(\n token: string,\n product: ProductAuthInfo,\n): TokenValidationResult {\n // Check product status\n if (product.status !== \"active\") {\n return {\n valid: false,\n error: \"Product is suspended\",\n errorCode: \"PRODUCT_SUSPENDED\",\n };\n }\n\n // Parse token\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n return {\n valid: false,\n error: \"Invalid token format\",\n errorCode: \"TOKEN_INVALID\",\n };\n }\n\n const [header, encodedPayload, signature] = parts;\n const data = `${header}.${encodedPayload}`;\n\n // Pin algorithm to HS256 — defense-in-depth against alg-confusion\n // attacks that swap a forged header (e.g. `alg: \"none\"`) onto an\n // otherwise valid token. Fast path: when the header bytes match the\n // canonical `JWT_HEADER` verbatim, we skip the parse. Slow path:\n // decode the header and reject anything whose `alg` claim is not\n // `\"HS256\"`. Non-canonical-but-valid headers (e.g. `alg: \"HS256\"`\n // with an extra `kid` claim) fall through to signature verification,\n // which covers the actual header bytes — so any tampering with the\n // header still fails the signature check.\n if (header !== jwtHeader()) {\n let parsedAlg: string | null = null;\n try {\n const decoded = JSON.parse(base64UrlDecode(header)) as Record<\n string,\n unknown\n >;\n if (typeof decoded.alg === \"string\") parsedAlg = decoded.alg;\n } catch {\n // Header didn't decode cleanly; treat as TOKEN_INVALID below.\n }\n if (parsedAlg !== \"HS256\") {\n return {\n valid: false,\n error: \"Unsupported token algorithm\",\n errorCode: \"TOKEN_INVALID\",\n };\n }\n }\n\n // Verify signature with current secret\n let signatureValid = verifySignature(\n data,\n signature,\n product.secrets.current.secret,\n );\n\n // If failed and we have a previous secret in grace period, try it\n if (!signatureValid && product.secrets.previous) {\n if (Date.now() < product.secrets.previous.expires_at) {\n signatureValid = verifySignature(\n data,\n signature,\n product.secrets.previous.secret,\n );\n }\n }\n\n if (!signatureValid) {\n return {\n valid: false,\n error: \"Invalid signature\",\n errorCode: \"SIGNATURE_INVALID\",\n };\n }\n\n // Parse payload\n let payload: ReadTokenPayload;\n try {\n payload = JSON.parse(base64UrlDecode(encodedPayload));\n } catch {\n return {\n valid: false,\n error: \"Invalid token payload\",\n errorCode: \"TOKEN_INVALID\",\n };\n }\n\n // Check token type\n if (payload.typ !== \"read\") {\n return {\n valid: false,\n error: \"Not a read token\",\n errorCode: \"TOKEN_WRONG_TYPE\",\n };\n }\n\n // Check expiration\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp < now) {\n return {\n valid: false,\n error: \"Token expired\",\n errorCode: \"TOKEN_EXPIRED\",\n };\n }\n\n // Check product ID matches\n if (payload.pid !== product.product_id) {\n return {\n valid: false,\n error: \"Product ID mismatch\",\n errorCode: \"TOKEN_INVALID\",\n };\n }\n\n // Validate token scope (must have exactly one of sid, projectId, projectIds)\n const scopeError = validateTokenScope(payload);\n if (scopeError) {\n return {\n valid: false,\n error: scopeError,\n errorCode: \"TOKEN_INVALID\",\n };\n }\n\n return {\n valid: true,\n payload,\n scope: getTokenScope(payload) ?? undefined,\n };\n}\n\n/**\n * Get time until token expires (in seconds).\n * Returns negative if expired.\n */\nexport function getTokenTTL(payload: ReadTokenPayload): number {\n const now = Math.floor(Date.now() / 1000);\n return payload.exp - now;\n}\n\n/**\n * Check if token is expiring soon (within buffer seconds).\n */\nexport function isTokenExpiringSoon(\n payload: ReadTokenPayload,\n bufferSeconds = 60,\n): boolean {\n return getTokenTTL(payload) <= bufferSeconds;\n}\n\n/**\n * Default salt for API key hashing (development only).\n * Production MUST set API_KEY_HASH_SALT environment variable.\n * WARNING: Changing the salt will invalidate all existing API key hashes.\n */\nconst DEFAULT_API_KEY_SALT = \"api_key_hash_salt\";\n\nlet _saltWarned = false;\n\n/**\n * Get the API key hash salt from environment.\n * Logs a warning if falling back to the weak default (development only).\n */\nfunction getApiKeyHashSalt(): string {\n if (typeof process !== \"undefined\" && process.env?.API_KEY_HASH_SALT) {\n return process.env.API_KEY_HASH_SALT;\n }\n if (\n !_saltWarned &&\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"production\"\n ) {\n _saltWarned = true;\n console.error(\n \"[SECURITY] API_KEY_HASH_SALT not set in production. \" +\n \"Using weak default salt. Set API_KEY_HASH_SALT to a cryptographically random value.\",\n );\n }\n return DEFAULT_API_KEY_SALT;\n}\n\n/**\n * Hash an API key for storage/lookup.\n * Uses HMAC-SHA256 for consistent, fast hashing.\n *\n * The salt can be configured via API_KEY_HASH_SALT environment variable.\n * WARNING: Changing the salt will invalidate all existing API key hashes.\n */\nexport function hashApiKey(apiKey: string): string {\n return nodeCrypto()\n .createHmac(\"sha256\", getApiKeyHashSalt())\n .update(apiKey)\n .digest(\"hex\");\n}\n\n/**\n * Hash an API key with an explicit salt.\n * Use this when you need to verify against a specific salt.\n */\nexport function hashApiKeyWithSalt(apiKey: string, salt: string): string {\n return nodeCrypto().createHmac(\"sha256\", salt).update(apiKey).digest(\"hex\");\n}\n\n/**\n * Verify an API key using timing-safe comparison.\n */\nexport function verifyApiKey(provided: string, stored: string): boolean {\n if (provided.length !== stored.length) {\n return false;\n }\n try {\n return nodeCrypto().timingSafeEqual(\n textEncoder.encode(provided),\n textEncoder.encode(stored),\n );\n } catch {\n return false;\n }\n}\n","/**\n * Channel Access Control\n *\n * Determines what channels a token can access based on its scope.\n * Used by SessionGateway to validate subscriptions and event delivery.\n *\n * Channel patterns:\n * - session:{sessionId} - Events for a specific session\n * - agent:{sessionId} - Agent events for a specific session\n * - project:{projectId} - All events for a project\n * - system - System-wide events (heartbeat, etc.)\n */\n\nimport {\n isBatchScopedToken,\n isProjectScopedToken,\n isSessionScopedToken,\n} from \"./tokens.js\";\nimport type {\n BatchScopedTokenPayload,\n ProjectScopedTokenPayload,\n ReadTokenPayload,\n SessionScopedTokenPayload,\n} from \"./types.js\";\n\n/**\n * Result of channel access check.\n */\nexport interface ChannelAccessResult {\n /** Whether access is allowed */\n allowed: boolean;\n /** Reason for denial (if not allowed) */\n reason?: string;\n}\n\n/**\n * Extract session ID from a session channel pattern.\n * Returns null if not a session channel.\n */\nexport function extractSessionFromChannel(channel: string): string | null {\n if (channel.startsWith(\"session:\")) {\n return channel.slice(8);\n }\n if (channel.startsWith(\"agent:\")) {\n return channel.slice(6);\n }\n return null;\n}\n\n/**\n * Extract project ID from a project channel pattern.\n * Returns null if not a project channel.\n */\nexport function extractProjectFromChannel(channel: string): string | null {\n if (channel.startsWith(\"project:\")) {\n return channel.slice(8);\n }\n return null;\n}\n\n/**\n * Check if a session-scoped token can access a channel.\n *\n * Session tokens can access:\n * - session:{their-sid}\n * - agent:{their-sid}\n * - system (always allowed)\n * - Wildcard patterns matching their session\n */\nexport function canSessionTokenAccessChannel(\n payload: SessionScopedTokenPayload,\n channel: string,\n): ChannelAccessResult {\n // System channels are always allowed\n if (channel === \"system\" || channel.startsWith(\"system.\")) {\n return { allowed: true };\n }\n\n // Global wildcard requires higher scope\n if (channel === \"*\") {\n return {\n allowed: false,\n reason: \"Session-scoped tokens cannot subscribe to all channels\",\n };\n }\n\n // Check session channel match\n const sessionId = extractSessionFromChannel(channel);\n if (sessionId !== null) {\n if (sessionId === payload.sid) {\n return { allowed: true };\n }\n // Wildcard session channels not allowed for session tokens\n if (sessionId === \"*\") {\n return {\n allowed: false,\n reason: \"Session-scoped tokens cannot subscribe to all sessions\",\n };\n }\n return {\n allowed: false,\n reason: `Token only has access to session ${payload.sid}`,\n };\n }\n\n // Project channels require project scope\n if (channel.startsWith(\"project:\")) {\n return {\n allowed: false,\n reason: \"Session-scoped tokens cannot access project channels\",\n };\n }\n\n // Default: check if channel matches session pattern\n // Allow channels that don't have explicit scope (backward compatibility)\n return { allowed: true };\n}\n\n/**\n * Check if a project-scoped token can access a channel.\n *\n * Project tokens can access:\n * - project:{their-projectId}\n * - session:* (all sessions within project - requires validation by caller)\n * - agent:* (all agent events within project)\n * - system (always allowed)\n */\nexport function canProjectTokenAccessChannel(\n payload: ProjectScopedTokenPayload,\n channel: string,\n): ChannelAccessResult {\n // System channels are always allowed\n if (channel === \"system\" || channel.startsWith(\"system.\")) {\n return { allowed: true };\n }\n\n // Global wildcard requires batch scope or higher\n if (channel === \"*\") {\n return {\n allowed: false,\n reason:\n \"Project-scoped tokens cannot subscribe to all channels across projects\",\n };\n }\n\n // Project channel access\n const projectId = extractProjectFromChannel(channel);\n if (projectId !== null) {\n if (projectId === payload.projectId) {\n return { allowed: true };\n }\n return {\n allowed: false,\n reason: `Token only has access to project ${payload.projectId}`,\n };\n }\n\n // Session channels - project tokens can access any session in their project\n // The actual validation that session belongs to project must be done by the caller\n // who has access to the session-to-project mapping\n if (channel.startsWith(\"session:\") || channel.startsWith(\"agent:\")) {\n // Wildcard session channels allowed for project tokens\n const sessionId = extractSessionFromChannel(channel);\n if (sessionId === \"*\") {\n return { allowed: true };\n }\n // Specific session - allowed but caller must verify session is in project\n return { allowed: true };\n }\n\n // Default: allow general channels (backward compatibility)\n return { allowed: true };\n}\n\n/**\n * Check if a batch-scoped token can access a channel.\n *\n * Batch tokens can access:\n * - project:{any of their projectIds}\n * - session:* (all sessions within their projects)\n * - agent:* (all agent events within their projects)\n * - system (always allowed)\n * - * (global wildcard - for organization-wide subscriptions)\n */\nexport function canBatchTokenAccessChannel(\n payload: BatchScopedTokenPayload,\n channel: string,\n): ChannelAccessResult {\n // System channels are always allowed\n if (channel === \"system\" || channel.startsWith(\"system.\")) {\n return { allowed: true };\n }\n\n // Global wildcard allowed for batch tokens\n if (channel === \"*\") {\n return { allowed: true };\n }\n\n // Project channel access\n const projectId = extractProjectFromChannel(channel);\n if (projectId !== null) {\n // Wildcard project access allowed\n if (projectId === \"*\") {\n return { allowed: true };\n }\n // Specific project must be in the list\n if (payload.projectIds.includes(projectId)) {\n return { allowed: true };\n }\n return {\n allowed: false,\n reason: `Token does not have access to project ${projectId}`,\n };\n }\n\n // Session/agent channels - batch tokens can access any session in their projects\n // Caller must verify session belongs to one of the token's projects\n if (channel.startsWith(\"session:\") || channel.startsWith(\"agent:\")) {\n return { allowed: true };\n }\n\n // Default: allow general channels (backward compatibility)\n return { allowed: true };\n}\n\n/**\n * Check if a token can access a channel.\n * Dispatches to scope-specific functions based on token type.\n *\n * @param payload - The token payload\n * @param channel - The channel to check access for\n * @returns Access result with allowed boolean and optional reason\n */\nexport function canTokenAccessChannel(\n payload: ReadTokenPayload,\n channel: string,\n): ChannelAccessResult {\n // Use type guards for clean scope detection and type narrowing\n if (isSessionScopedToken(payload)) {\n return canSessionTokenAccessChannel(payload, channel);\n }\n\n if (isProjectScopedToken(payload)) {\n return canProjectTokenAccessChannel(payload, channel);\n }\n\n if (isBatchScopedToken(payload)) {\n return canBatchTokenAccessChannel(payload, channel);\n }\n\n // Invalid token scope\n return {\n allowed: false,\n reason: \"Token has no valid scope (missing sid, projectId, or projectIds)\",\n };\n}\n\n/**\n * Get the list of allowed channel patterns for a token.\n * Useful for informing clients what they can subscribe to.\n *\n * @param payload - The token payload\n * @returns Array of allowed channel patterns\n */\nexport function getAllowedChannelPatterns(payload: ReadTokenPayload): string[] {\n const patterns: string[] = [\"system\", \"system.*\"];\n\n // Use type guards for clean scope detection\n if (isSessionScopedToken(payload)) {\n patterns.push(`session:${payload.sid}`, `agent:${payload.sid}`);\n return patterns;\n }\n\n if (isProjectScopedToken(payload)) {\n patterns.push(\n `project:${payload.projectId}`,\n \"session:*\", // Within project\n \"agent:*\", // Within project\n );\n return patterns;\n }\n\n if (isBatchScopedToken(payload)) {\n patterns.push(\n \"*\", // Global wildcard\n \"project:*\", // All projects in batch\n ...payload.projectIds.map((id) => `project:${id}`),\n \"session:*\",\n \"agent:*\",\n );\n return patterns;\n }\n\n return patterns;\n}\n\n/**\n * Validate channel subscription request.\n * Checks multiple channels and returns aggregated result.\n *\n * @param payload - The token payload\n * @param channels - Channels to validate\n * @returns Object with allowed channels and denied channels with reasons\n */\nexport function validateChannelSubscription(\n payload: ReadTokenPayload,\n channels: string[],\n): {\n allowed: string[];\n denied: Array<{ channel: string; reason: string }>;\n} {\n const allowed: string[] = [];\n const denied: Array<{ channel: string; reason: string }> = [];\n\n for (const channel of channels) {\n const result = canTokenAccessChannel(payload, channel);\n if (result.allowed) {\n allowed.push(channel);\n } else {\n denied.push({ channel, reason: result.reason ?? \"Access denied\" });\n }\n }\n\n return { allowed, denied };\n}\n","/**\n * Authentication Configuration\n *\n * Token management with refresh hooks and expiration handling.\n */\n\nimport { issueReadToken } from \"./tokens.js\";\n\n// Re-export channel access utilities\nexport * from \"./channel-access.js\";\n// Re-export shared auth providers (eliminates 185 lines of duplication with browser.ts)\nexport type { AuthProvider } from \"./shared-providers.js\";\nexport {\n CallbackAuth,\n NoAuth,\n ReadTokenAuth,\n RefreshableTokenAuth,\n StaticTokenAuth,\n} from \"./shared-providers.js\";\nexport * from \"./tokens.js\";\n// Re-export types and token utilities\nexport * from \"./types.js\";\n\n/**\n * Environment-based auth that reads from env vars.\n * Matches the server-side patterns used in sidecar/orchestrator.\n */\nexport class EnvTokenAuth {\n private envVar: string;\n private fallback?: string;\n\n constructor(options: { envVar: string; fallback?: string }) {\n this.envVar = options.envVar;\n this.fallback = options.fallback;\n }\n\n async getToken(): Promise<string | null> {\n // Works in Node.js; in browser, fallback would be used\n if (typeof process !== \"undefined\" && process.env) {\n return process.env[this.envVar] ?? this.fallback ?? null;\n }\n return this.fallback ?? null;\n }\n}\n\n/**\n * Pre-configured auth for sidecar connections.\n * Uses SIDECAR_AUTH_TOKEN env var (matches server expectation).\n */\nexport class SidecarAuth extends EnvTokenAuth {\n constructor(token?: string) {\n super({ envVar: \"SIDECAR_AUTH_TOKEN\", fallback: token });\n }\n}\n\n/**\n * Product-based auth for multi-tenant orchestrator connections.\n * Uses ORCHESTRATOR_API_SECRET_KEY env var.\n */\nexport class ProductAuth extends EnvTokenAuth {\n constructor(apiKey?: string) {\n super({ envVar: \"ORCHESTRATOR_API_SECRET_KEY\", fallback: apiKey });\n }\n}\n\n/**\n * Product token issuer for backend services.\n *\n * Use this in product backends to issue read tokens for WebSocket connections.\n *\n * @example\n * ```typescript\n * import { ProductTokenIssuer } from \"@tangle-network/agent-core\";\n *\n * const issuer = new ProductTokenIssuer({\n * productId: \"vibecode\",\n * signingSecret: process.env.ORCHESTRATOR_SIGNING_SECRET!,\n * ttlMinutes: { free: 15, pro: 240 },\n * });\n *\n * // Issue a token for a user session\n * const token = issuer.issue({\n * userId: \"user_123\",\n * sessionId: \"sess_abc\",\n * tier: \"pro\",\n * });\n *\n * // Return token to frontend for WebSocket connection\n * res.json({\n * readToken: token.token,\n * expiresAt: token.expiresAt,\n * websocketUrl: `wss://orchestrator.example.com/session?token=${token.token}`,\n * });\n * ```\n */\nexport class ProductTokenIssuer {\n private readonly productId: string;\n private readonly signingSecret: string;\n private readonly ttlMinutes: {\n free: number;\n pro: number;\n enterprise?: number;\n };\n\n constructor(config: {\n productId: string;\n signingSecret: string;\n /** TTL in minutes for each tier (default: { free: 15, pro: 240 }) */\n ttlMinutes?: { free?: number; pro?: number; enterprise?: number };\n }) {\n this.productId = config.productId;\n this.signingSecret = config.signingSecret;\n this.ttlMinutes = {\n free: config.ttlMinutes?.free ?? 15,\n pro: config.ttlMinutes?.pro ?? 240,\n enterprise: config.ttlMinutes?.enterprise ?? 480,\n };\n }\n\n /**\n * Issue a read token for a user session.\n */\n issue(params: {\n userId: string;\n sessionId: string;\n tier?: \"free\" | \"pro\" | \"enterprise\";\n sidecarId?: string;\n }): { token: string; expiresAt: number } {\n const tier = params.tier ?? \"free\";\n const ttl = this.ttlMinutes[tier] ?? this.ttlMinutes.free;\n\n const token = issueReadToken(\n this.signingSecret,\n {\n sub: params.userId,\n sid: params.sessionId,\n pid: this.productId,\n cid: params.sidecarId,\n },\n ttl,\n );\n\n const expiresAt = Math.floor(Date.now() / 1000) + ttl * 60;\n\n return { token, expiresAt };\n }\n\n /**\n * Get the TTL in minutes for a tier.\n */\n getTtlMinutes(tier: \"free\" | \"pro\" | \"enterprise\" = \"free\"): number {\n return this.ttlMinutes[tier] ?? this.ttlMinutes.free;\n }\n}\n"],"mappings":";;AAkBA,MAAM,cAAc,IAAI,aAAa;AACrC,MAAM,cAAc,IAAI,aAAa;AAGrC,IAAI;;;;;;;;;AAUJ,SAAS,aAAyB;AAChC,KAAI,gBAAiB,QAAO;CAC5B,MAAM,WACJ,WAGA,SAAS,mBAAmB,cAAc;AAC5C,KAAI,CAAC,SACH,OAAM,IAAI,MACR,iJACD;AAEH,mBAAkB;AAClB,QAAO;;;;;;;AAQT,SAAgB,oBAAoB,QAAgB,QAAQ,IAAY;AACtE,QAAO,GAAG,SAAS,gBAAgB,YAAY,CAAC,YAAY,MAAM,CAAC;;;;;AAMrE,SAAgB,iBAAyB;AACvC,QAAO,oBAAoB,aAAa;;;;;AAM1C,SAAgB,wBAAgC;AAC9C,QAAO,oBAAoB,aAAa;;;;;;;AAQ1C,SAAS,gBAAgB,MAAmC;CAC1D,MAAM,QAAQ,OAAO,SAAS,WAAW,YAAY,OAAO,KAAK,GAAG;CACpE,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,MAAO,WAAU,OAAO,aAAa,KAAK;AAC7D,QAAO,KAAK,OAAO,CAChB,QAAQ,OAAO,IAAI,CACnB,QAAQ,OAAO,IAAI,CACnB,QAAQ,OAAO,GAAG;;;;;AAMvB,SAAS,iBAAiB,MAA0B;CAClD,MAAM,SAAS,OAAO,IAAI,QAAQ,IAAK,KAAK,SAAS,KAAM,EAAE;CAC7D,MAAM,SAAS,KAAK,OAAO,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI,CAAC;CACjE,MAAM,QAAQ,IAAI,WAAW,OAAO,OAAO;AAC3C,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,IAAK,OAAM,KAAK,OAAO,WAAW,EAAE;AACvE,QAAO;;;;;AAMT,SAAS,gBAAgB,MAAsB;AAC7C,QAAO,YAAY,OAAO,iBAAiB,KAAK,CAAC;;;;;AAMnD,SAAS,gBAAgB,MAAc,QAAwB;AAC7D,QAAO,gBACL,YAAY,CAAC,WAAW,UAAU,OAAO,CAAC,OAAO,KAAK,CAAC,QAAQ,CAChE;;;;;;;;;;;;AAaH,SAAS,gBACP,MACA,WACA,QACS;CACT,MAAM,EAAE,YAAY,oBAAoB,YAAY;CACpD,MAAM,cAAc,WAAW,UAAU,OAAO,CAAC,OAAO,KAAK,CAAC,QAAQ;CACtE,IAAI;AACJ,KAAI;AACF,gBAAc,iBAAiB,UAAU;SACnC;AACN,SAAO;;AAET,KAAI,YAAY,WAAW,YAAY,OAAQ,QAAO;AACtD,KAAI;AACF,SAAO,gBAAgB,aAAa,YAAY;SAC1C;AACN,SAAO;;;;;;;AAQX,IAAI;AACJ,SAAS,YAAoB;AAC3B,KAAI,mBAAmB,KAAA,EACrB,kBAAiB,gBACf,KAAK,UAAU;EAAE,KAAK;EAAS,KAAK;EAAO,CAAC,CAC7C;AAEH,QAAO;;;;;;;;;AAUT,SAAgB,eACd,eACA,SACA,YACQ;CACR,MAAM,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;CAKzC,MAAM,cAAc;EAClB,GAAG;EACH,KAAK;EACL,KAAK;EACL,KAAK,MAAM,aAAa;EACzB;CAED,MAAM,iBAAiB,gBAAgB,KAAK,UAAU,YAAY,CAAC;CACnE,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG;AAG/B,QAAO,GAAG,KAAK,GAFG,gBAAgB,MAAM,cAEb;;;;;;;;;;AAW7B,SAAgB,wBACd,eACA,SACA,YACQ;AACR,QAAO,eAAe,eAAe,SAAS,WAAW;;;;;;;;;;AAW3D,SAAgB,wBACd,eACA,SACA,YACQ;AACR,QAAO,eACL,eACA,SACA,WACD;;;;;;;;;;AAWH,SAAgB,sBACd,eACA,SACA,YACQ;AACR,QAAO,eACL,eACA,SACA,WACD;;AAKH,IAAI;AACJ,SAAS,iBAAyB;AAChC,KAAI,wBAAwB,KAAA,EAC1B,uBAAsB,gBACpB,KAAK,UAAU;EAAE,KAAK;EAAS,KAAK;EAAO,CAAC,CAC7C;AAEH,QAAO;;;;;;;AAQT,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;;;;AAoCD,SAAS,wBACP,OAC8B;AAC9B,QACE,MAAM,QAAQ,MAAM,IACpB,MAAM,OAAO,UACV,qBAA4C,SAAS,MAAM,CAC7D;;;;;;;AASL,MAAM,qBAAqB,CAAC,WAAW,UAAU;;;;;;;;;AAgDjD,SAAgB,yBAGd;CACA,MAAM,EAAE,YAAY,cAAc,YAAY,CAAC,oBAC7C,WACA;EACE,mBAAmB;GAAE,MAAM;GAAQ,QAAQ;GAAO;EAClD,oBAAoB;GAAE,MAAM;GAAS,QAAQ;GAAO;EACrD,CACF;AACD,QAAO;EAAE;EAAY;EAAW;;;;;;;;;;;AAYlC,SAAgB,wBACd,YACA,SAqBA,YACQ;AACR,KAAI,CAAC,WAAW,SAAS,cAAc,CACrC,OAAM,IAAI,MACR,qJAED;AAQH,KAAI,QAAQ,QAAQ,KAAA,KAAa,CAAC,wBAAwB,QAAQ,IAAI,CACpE,OAAM,IAAI,MACR,0EAA0E,qBAAqB,KAAK,KAAK,CAAC,mDAC3G;AAEH,KAAI,QAAQ,QAAQ,KAAA,GAAW;AAC7B,MAAI,CAAC,mBAAmB,SAAS,QAAQ,IAAI,CAC3C,OAAM,IAAI,MACR,+CAA+C,mBAAmB,KAAK,KAAK,CAAC,GAC9E;AAMH,MAAI,CAAC,QAAQ,OAAO,QAAQ,IAAI,MAAM,CAAC,WAAW,EAChD,OAAM,IAAI,MACR,kGACD;;CAIL,MAAM,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;CAGzC,MAAM,EAAE,aAAa,SAAS,YAAY;CAC1C,MAAM,MAAM,GAAG,QAAQ,IAAI,GAAG,IAAI,GAAG,YAAY,EAAE,CAAC,SAAS,MAAM;CACnE,MAAM,cAAc;EAClB,GAAG;EACH,KAAK;EACL;EACA,KAAK;EACL,KAAK,MAAM,aAAa;EACzB;CACD,MAAM,iBAAiB,gBAAgB,KAAK,UAAU,YAAY,CAAC;CACnE,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG;AAIpC,QAAO,GAAG,KAAK,GAHG,gBAChB,KAAK,MAAM,YAAY,OAAO,KAAK,EAAE,WAAW,CAEvB;;;;;;AAO7B,SAAgB,mBACd,OACA,WACA,aAsBO;AACP,KAAI;EACF,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,MAAI,MAAM,WAAW,EAAG,QAAO;EAE/B,MAAM,aAAa,gBAAgB,MAAM,GAAG;AAE5C,MADe,KAAK,MAAM,WAChB,CAAC,QAAQ,QAAS,QAAO;EAKnC,MAAM,OAAO,GAAG,MAAM,GAAG,GAAG,MAAM;EAClC,MAAM,kBAAkB,iBAAiB,MAAM,GAAG;AAOlD,MAAI,CANU,YAAY,CAAC,OACzB,MACA,YAAY,OAAO,KAAK,EACxB,WACA,gBAEQ,CAAE,QAAO;EAGnB,MAAM,UAAU,KAAK,MAAM,gBAAgB,MAAM,GAAG,CAAC;AAErD,MAAI,QAAQ,QAAQ,UAAW,QAAO;AACtC,MAAI,OAAO,QAAQ,QAAQ,YAAY,QAAQ,IAAI,WAAW,EAC5D,QAAO;EAET,MAAM,sBAAsB,QAAQ,QAAQ;EAC5C,MAAM,qBAAqB,qBAAqB,QAAQ,KAAK,YAAY;AACzE,MAAI,CAAC,uBAAuB,CAAC,mBAAoB,QAAO;EAGxD,MAAM,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AACzC,MAAI,OAAO,QAAQ,QAAQ,YAAY,QAAQ,MAAM,IAAK,QAAO;AAGjE,MAAI,OAAO,QAAQ,QAAQ,YAAY,QAAQ,IAAI,WAAW,EAC5D,QAAO;AAMT,MAAI,QAAQ,QAAQ,KAAA,GAAW;AAG7B,OAAI,CAAC,mBAAmB,SAAS,QAAQ,IAAI,CAAE,QAAO;AAGtD,OAAI,OAAO,QAAQ,QAAQ,YAAY,QAAQ,IAAI,MAAM,CAAC,WAAW,EACnE,QAAO;;AAaX,MAAI,QAAQ,QAAQ,KAAA,KAAa,CAAC,wBAAwB,QAAQ,IAAI,CACpE,QAAO;AAGT,SAAO;SACD;AACN,SAAO;;;AAwCX,SAAS,qBACP,YACA,aACS;AACT,KAAI,OAAO,eAAe,YAAY,OAAO,gBAAgB,SAC3D,QAAO;AAET,KAAI,CAAC,kBAAkB,KAAK,WAAW,CACrC,QAAO;AAET,KAAI,CAAC,kBAAkB,KAAK,YAAY,CACtC,QAAO;AAET,QAAO,WAAW,WAAW,YAAY;;;;;;AAO3C,SAAgB,qBACd,SACsC;AACtC,QACE,SAAS,WACT,OAAO,QAAQ,QAAQ,YACvB,QAAQ,IAAI,SAAS,KACrB,EAAE,eAAe,WAAW,QAAQ,cACpC,EAAE,gBAAgB,WAAW,QAAQ;;;;;;AAQzC,SAAgB,qBACd,SACsC;AACtC,QACE,eAAe,WACf,OAAO,QAAQ,cAAc,YAC7B,QAAQ,UAAU,SAAS,KAC3B,EAAE,SAAS,WAAW,QAAQ,QAC9B,EAAE,gBAAgB,WAAW,QAAQ;;;;;;AAQzC,SAAgB,mBACd,SACoC;AACpC,QACE,gBAAgB,WAChB,MAAM,QAAQ,QAAQ,WAAW,IACjC,QAAQ,WAAW,SAAS,KAC5B,EAAE,SAAS,WAAW,QAAQ,QAC9B,EAAE,eAAe,WAAW,QAAQ;;;;;;AAQxC,SAAgB,cAAc,SAA8C;AAC1E,KAAI,qBAAqB,QAAQ,CAAE,QAAO;AAC1C,KAAI,qBAAqB,QAAQ,CAAE,QAAO;AAC1C,KAAI,mBAAmB,QAAQ,CAAE,QAAO;AACxC,QAAO;;;;;;AAOT,SAAgB,mBAAmB,SAA0C;CAQ3E,MAAM,aAAa;EAPJ,SAAS,WAAW,QAAQ;EACtB,eAAe,WAAW,QAAQ;EAErD,gBAAgB,WAChB,MAAM,QAAQ,QAAQ,WAAW,IACjC,QAAQ,WAAW,SAAS;EAE0B,CAAC,OACvD,QACD,CAAC;AAEF,KAAI,eAAe,EACjB,QAAO;AAGT,KAAI,aAAa,EACf,QAAO;AAGT,QAAO;;;;;;AAOT,SAAgB,YAAY,OAAwC;AAClE,KAAI;EACF,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,MAAI,MAAM,WAAW,EACnB,QAAO;AAIT,SADgB,KAAK,MAAM,gBAAgB,MAAM,GAAG,CACtC;SACR;AACN,SAAO;;;;;;;;;AAUX,SAAgB,gBACd,OACA,SACuB;AAEvB,KAAI,QAAQ,WAAW,SACrB,QAAO;EACL,OAAO;EACP,OAAO;EACP,WAAW;EACZ;CAIH,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,KAAI,MAAM,WAAW,EACnB,QAAO;EACL,OAAO;EACP,OAAO;EACP,WAAW;EACZ;CAGH,MAAM,CAAC,QAAQ,gBAAgB,aAAa;CAC5C,MAAM,OAAO,GAAG,OAAO,GAAG;AAW1B,KAAI,WAAW,WAAW,EAAE;EAC1B,IAAI,YAA2B;AAC/B,MAAI;GACF,MAAM,UAAU,KAAK,MAAM,gBAAgB,OAAO,CAAC;AAInD,OAAI,OAAO,QAAQ,QAAQ,SAAU,aAAY,QAAQ;UACnD;AAGR,MAAI,cAAc,QAChB,QAAO;GACL,OAAO;GACP,OAAO;GACP,WAAW;GACZ;;CAKL,IAAI,iBAAiB,gBACnB,MACA,WACA,QAAQ,QAAQ,QAAQ,OACzB;AAGD,KAAI,CAAC,kBAAkB,QAAQ,QAAQ;MACjC,KAAK,KAAK,GAAG,QAAQ,QAAQ,SAAS,WACxC,kBAAiB,gBACf,MACA,WACA,QAAQ,QAAQ,SAAS,OAC1B;;AAIL,KAAI,CAAC,eACH,QAAO;EACL,OAAO;EACP,OAAO;EACP,WAAW;EACZ;CAIH,IAAI;AACJ,KAAI;AACF,YAAU,KAAK,MAAM,gBAAgB,eAAe,CAAC;SAC/C;AACN,SAAO;GACL,OAAO;GACP,OAAO;GACP,WAAW;GACZ;;AAIH,KAAI,QAAQ,QAAQ,OAClB,QAAO;EACL,OAAO;EACP,OAAO;EACP,WAAW;EACZ;CAIH,MAAM,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AACzC,KAAI,QAAQ,MAAM,IAChB,QAAO;EACL,OAAO;EACP,OAAO;EACP,WAAW;EACZ;AAIH,KAAI,QAAQ,QAAQ,QAAQ,WAC1B,QAAO;EACL,OAAO;EACP,OAAO;EACP,WAAW;EACZ;CAIH,MAAM,aAAa,mBAAmB,QAAQ;AAC9C,KAAI,WACF,QAAO;EACL,OAAO;EACP,OAAO;EACP,WAAW;EACZ;AAGH,QAAO;EACL,OAAO;EACP;EACA,OAAO,cAAc,QAAQ,IAAI,KAAA;EAClC;;;;;;AAOH,SAAgB,YAAY,SAAmC;CAC7D,MAAM,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AACzC,QAAO,QAAQ,MAAM;;;;;AAMvB,SAAgB,oBACd,SACA,gBAAgB,IACP;AACT,QAAO,YAAY,QAAQ,IAAI;;;;;;;AAQjC,MAAM,uBAAuB;AAE7B,IAAI,cAAc;;;;;AAMlB,SAAS,oBAA4B;AACnC,KAAI,OAAO,YAAY,eAAe,QAAQ,KAAK,kBACjD,QAAO,QAAQ,IAAI;AAErB,KACE,CAAC,eACD,OAAO,YAAY,eACnB,QAAQ,KAAK,aAAa,cAC1B;AACA,gBAAc;AACd,UAAQ,MACN,0IAED;;AAEH,QAAO;;;;;;;;;AAUT,SAAgB,WAAW,QAAwB;AACjD,QAAO,YAAY,CAChB,WAAW,UAAU,mBAAmB,CAAC,CACzC,OAAO,OAAO,CACd,OAAO,MAAM;;;;;;AAOlB,SAAgB,mBAAmB,QAAgB,MAAsB;AACvE,QAAO,YAAY,CAAC,WAAW,UAAU,KAAK,CAAC,OAAO,OAAO,CAAC,OAAO,MAAM;;;;;AAM7E,SAAgB,aAAa,UAAkB,QAAyB;AACtE,KAAI,SAAS,WAAW,OAAO,OAC7B,QAAO;AAET,KAAI;AACF,SAAO,YAAY,CAAC,gBAClB,YAAY,OAAO,SAAS,EAC5B,YAAY,OAAO,OAAO,CAC3B;SACK;AACN,SAAO;;;;;;;;;;;;;;;;;;;;;AC15BX,SAAgB,0BAA0B,SAAgC;AACxE,KAAI,QAAQ,WAAW,WAAW,CAChC,QAAO,QAAQ,MAAM,EAAE;AAEzB,KAAI,QAAQ,WAAW,SAAS,CAC9B,QAAO,QAAQ,MAAM,EAAE;AAEzB,QAAO;;;;;;AAOT,SAAgB,0BAA0B,SAAgC;AACxE,KAAI,QAAQ,WAAW,WAAW,CAChC,QAAO,QAAQ,MAAM,EAAE;AAEzB,QAAO;;;;;;;;;;;AAYT,SAAgB,6BACd,SACA,SACqB;AAErB,KAAI,YAAY,YAAY,QAAQ,WAAW,UAAU,CACvD,QAAO,EAAE,SAAS,MAAM;AAI1B,KAAI,YAAY,IACd,QAAO;EACL,SAAS;EACT,QAAQ;EACT;CAIH,MAAM,YAAY,0BAA0B,QAAQ;AACpD,KAAI,cAAc,MAAM;AACtB,MAAI,cAAc,QAAQ,IACxB,QAAO,EAAE,SAAS,MAAM;AAG1B,MAAI,cAAc,IAChB,QAAO;GACL,SAAS;GACT,QAAQ;GACT;AAEH,SAAO;GACL,SAAS;GACT,QAAQ,oCAAoC,QAAQ;GACrD;;AAIH,KAAI,QAAQ,WAAW,WAAW,CAChC,QAAO;EACL,SAAS;EACT,QAAQ;EACT;AAKH,QAAO,EAAE,SAAS,MAAM;;;;;;;;;;;AAY1B,SAAgB,6BACd,SACA,SACqB;AAErB,KAAI,YAAY,YAAY,QAAQ,WAAW,UAAU,CACvD,QAAO,EAAE,SAAS,MAAM;AAI1B,KAAI,YAAY,IACd,QAAO;EACL,SAAS;EACT,QACE;EACH;CAIH,MAAM,YAAY,0BAA0B,QAAQ;AACpD,KAAI,cAAc,MAAM;AACtB,MAAI,cAAc,QAAQ,UACxB,QAAO,EAAE,SAAS,MAAM;AAE1B,SAAO;GACL,SAAS;GACT,QAAQ,oCAAoC,QAAQ;GACrD;;AAMH,KAAI,QAAQ,WAAW,WAAW,IAAI,QAAQ,WAAW,SAAS,EAAE;AAGlE,MADkB,0BAA0B,QAC/B,KAAK,IAChB,QAAO,EAAE,SAAS,MAAM;AAG1B,SAAO,EAAE,SAAS,MAAM;;AAI1B,QAAO,EAAE,SAAS,MAAM;;;;;;;;;;;;AAa1B,SAAgB,2BACd,SACA,SACqB;AAErB,KAAI,YAAY,YAAY,QAAQ,WAAW,UAAU,CACvD,QAAO,EAAE,SAAS,MAAM;AAI1B,KAAI,YAAY,IACd,QAAO,EAAE,SAAS,MAAM;CAI1B,MAAM,YAAY,0BAA0B,QAAQ;AACpD,KAAI,cAAc,MAAM;AAEtB,MAAI,cAAc,IAChB,QAAO,EAAE,SAAS,MAAM;AAG1B,MAAI,QAAQ,WAAW,SAAS,UAAU,CACxC,QAAO,EAAE,SAAS,MAAM;AAE1B,SAAO;GACL,SAAS;GACT,QAAQ,yCAAyC;GAClD;;AAKH,KAAI,QAAQ,WAAW,WAAW,IAAI,QAAQ,WAAW,SAAS,CAChE,QAAO,EAAE,SAAS,MAAM;AAI1B,QAAO,EAAE,SAAS,MAAM;;;;;;;;;;AAW1B,SAAgB,sBACd,SACA,SACqB;AAErB,KAAI,qBAAqB,QAAQ,CAC/B,QAAO,6BAA6B,SAAS,QAAQ;AAGvD,KAAI,qBAAqB,QAAQ,CAC/B,QAAO,6BAA6B,SAAS,QAAQ;AAGvD,KAAI,mBAAmB,QAAQ,CAC7B,QAAO,2BAA2B,SAAS,QAAQ;AAIrD,QAAO;EACL,SAAS;EACT,QAAQ;EACT;;;;;;;;;AAUH,SAAgB,0BAA0B,SAAqC;CAC7E,MAAM,WAAqB,CAAC,UAAU,WAAW;AAGjD,KAAI,qBAAqB,QAAQ,EAAE;AACjC,WAAS,KAAK,WAAW,QAAQ,OAAO,SAAS,QAAQ,MAAM;AAC/D,SAAO;;AAGT,KAAI,qBAAqB,QAAQ,EAAE;AACjC,WAAS,KACP,WAAW,QAAQ,aACnB,aACA,UACD;AACD,SAAO;;AAGT,KAAI,mBAAmB,QAAQ,EAAE;AAC/B,WAAS,KACP,KACA,aACA,GAAG,QAAQ,WAAW,KAAK,OAAO,WAAW,KAAK,EAClD,aACA,UACD;AACD,SAAO;;AAGT,QAAO;;;;;;;;;;AAWT,SAAgB,4BACd,SACA,UAIA;CACA,MAAM,UAAoB,EAAE;CAC5B,MAAM,SAAqD,EAAE;AAE7D,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,SAAS,sBAAsB,SAAS,QAAQ;AACtD,MAAI,OAAO,QACT,SAAQ,KAAK,QAAQ;MAErB,QAAO,KAAK;GAAE;GAAS,QAAQ,OAAO,UAAU;GAAiB,CAAC;;AAItE,QAAO;EAAE;EAAS;EAAQ;;;;;;;;;;;;;ACxS5B,IAAa,eAAb,MAA0B;CACxB;CACA;CAEA,YAAY,SAAgD;AAC1D,OAAK,SAAS,QAAQ;AACtB,OAAK,WAAW,QAAQ;;CAG1B,MAAM,WAAmC;AAEvC,MAAI,OAAO,YAAY,eAAe,QAAQ,IAC5C,QAAO,QAAQ,IAAI,KAAK,WAAW,KAAK,YAAY;AAEtD,SAAO,KAAK,YAAY;;;;;;;AAQ5B,IAAa,cAAb,cAAiC,aAAa;CAC5C,YAAY,OAAgB;AAC1B,QAAM;GAAE,QAAQ;GAAsB,UAAU;GAAO,CAAC;;;;;;;AAQ5D,IAAa,cAAb,cAAiC,aAAa;CAC5C,YAAY,QAAiB;AAC3B,QAAM;GAAE,QAAQ;GAA+B,UAAU;GAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCtE,IAAa,qBAAb,MAAgC;CAC9B;CACA;CACA;CAMA,YAAY,QAKT;AACD,OAAK,YAAY,OAAO;AACxB,OAAK,gBAAgB,OAAO;AAC5B,OAAK,aAAa;GAChB,MAAM,OAAO,YAAY,QAAQ;GACjC,KAAK,OAAO,YAAY,OAAO;GAC/B,YAAY,OAAO,YAAY,cAAc;GAC9C;;;;;CAMH,MAAM,QAKmC;EACvC,MAAM,OAAO,OAAO,QAAQ;EAC5B,MAAM,MAAM,KAAK,WAAW,SAAS,KAAK,WAAW;AAerD,SAAO;GAAE,OAbK,eACZ,KAAK,eACL;IACE,KAAK,OAAO;IACZ,KAAK,OAAO;IACZ,KAAK,KAAK;IACV,KAAK,OAAO;IACb,EACD,IAKY;GAAE,WAFE,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,GAAG,MAAM;GAE7B;;;;;CAM7B,cAAc,OAAsC,QAAgB;AAClE,SAAO,KAAK,WAAW,SAAS,KAAK,WAAW"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/auth/tokens.ts","../../src/auth/channel-access.ts","../../src/auth/index.ts"],"sourcesContent":["/**\n * JWT Token Utilities\n *\n * Token generation and verification for multi-tenant auth.\n * - Read/product tokens: HMAC-SHA256 (symmetric, fast)\n * - Sidecar tokens: Ed25519 (asymmetric — sidecar cannot forge tokens)\n */\n\nimport {\n base64UrlDecode,\n base64UrlToBytes,\n decodeToken,\n getTokenTTL,\n isTokenExpiringSoon,\n} from \"./tokens-browser.js\";\nimport type {\n BatchScopedTokenPayload,\n ProductAuthInfo,\n ProjectScopedTokenPayload,\n ReadTokenPayload,\n SessionScopedTokenPayload,\n TokenScope,\n TokenValidationResult,\n} from \"./types.js\";\n\nconst textEncoder = new TextEncoder();\n\nexport { decodeToken, getTokenTTL, isTokenExpiringSoon };\n\ntype NodeCrypto = typeof import(\"node:crypto\");\nlet nodeCryptoCache: NodeCrypto | undefined;\n\n/**\n * Resolve Node's `crypto` lazily and synchronously. Signing, verification, and\n * key generation are server-only, but this module must stay importable in a\n * browser bundle — a static `import \"node:crypto\"` forces bundlers to resolve\n * the builtin at load and blanks the page. `process.getBuiltinModule` exists\n * only on a Node runtime (Node >= 22.3); in a browser `globalThis.process` is\n * undefined, so callers get a clear server-only error instead of a crash.\n */\nfunction nodeCrypto(): NodeCrypto {\n if (nodeCryptoCache) return nodeCryptoCache;\n const resolved = (\n globalThis as {\n process?: { getBuiltinModule?: (id: string) => unknown };\n }\n ).process?.getBuiltinModule?.(\"node:crypto\") as NodeCrypto | undefined;\n if (!resolved) {\n throw new Error(\n \"@tangle-network/agent-core/auth: token signing, verification, and key generation are server-only (Node.js crypto) and cannot run in a browser.\",\n );\n }\n nodeCryptoCache = resolved;\n return resolved;\n}\n\n/**\n * Generate a cryptographically secure random string.\n * @param prefix - Prefix for the generated string (e.g., \"orch_prod_\")\n * @param bytes - Number of random bytes (default: 32 = 256 bits)\n */\nexport function generateSecureToken(prefix: string, bytes = 32): string {\n return `${prefix}${base64UrlEncode(nodeCrypto().randomBytes(bytes))}`;\n}\n\n/**\n * Generate a product API key.\n */\nexport function generateApiKey(): string {\n return generateSecureToken(\"orch_prod_\");\n}\n\n/**\n * Generate a signing secret.\n */\nexport function generateSigningSecret(): string {\n return generateSecureToken(\"orch_sign_\");\n}\n\n/**\n * Base64URL encode (RFC 7515). Isomorphic: `btoa` + `TextEncoder` exist in Node\n * and browsers, so — unlike `Buffer` — this never blanks a browser page that\n * transitively imports the module.\n */\nfunction base64UrlEncode(data: string | Uint8Array): string {\n const bytes = typeof data === \"string\" ? textEncoder.encode(data) : data;\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\n/**\n * Create HMAC-SHA256 signature.\n */\nfunction createSignature(data: string, secret: string): string {\n return base64UrlEncode(\n nodeCrypto().createHmac(\"sha256\", secret).update(data).digest(),\n );\n}\n\n/**\n * Verify signature using timing-safe comparison on raw HMAC bytes.\n *\n * Decoding both sides to raw bytes before the compare strips the\n * length-of-base64url-string oracle: every legitimate signature\n * decodes to exactly 32 bytes (HMAC-SHA256 digest size), so a\n * length mismatch on the decoded buffer means malformed input,\n * not a partial match. Equivalent to the pattern used in\n * `products/sandbox/sdk/src/auth/tokens.ts`.\n */\nfunction verifySignature(\n data: string,\n signature: string,\n secret: string,\n): boolean {\n const { createHmac, timingSafeEqual } = nodeCrypto();\n const expectedBuf = createHmac(\"sha256\", secret).update(data).digest();\n let providedBuf: Uint8Array;\n try {\n providedBuf = base64UrlToBytes(signature);\n } catch {\n return false;\n }\n if (providedBuf.length !== expectedBuf.length) return false;\n try {\n return timingSafeEqual(providedBuf, expectedBuf);\n } catch {\n return false;\n }\n}\n\n/**\n * Base64URL-encoded JWT header `{\"alg\":\"HS256\",\"typ\":\"JWT\"}`. Computed on first\n * use (not at module load) and memoized.\n */\nlet jwtHeaderCache: string | undefined;\nfunction jwtHeader(): string {\n if (jwtHeaderCache === undefined) {\n jwtHeaderCache = base64UrlEncode(\n JSON.stringify({ alg: \"HS256\", typ: \"JWT\" }),\n );\n }\n return jwtHeaderCache;\n}\n\n/**\n * Issue a read token (JWT) for WebSocket authentication.\n *\n * @param signingSecret - The product's signing secret\n * @param payload - Token payload (without iat/exp, those are added)\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueReadToken(\n signingSecret: string,\n payload: Omit<ReadTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes: number,\n): string {\n const now = Math.floor(Date.now() / 1000);\n\n // Type assertion is safe here: payload already satisfies one branch of the\n // ReadTokenPayload union (minus iat/exp/typ), and we're adding those fields.\n // TypeScript can't infer which union branch we're creating from a spread.\n const fullPayload = {\n ...payload,\n typ: \"read\" as const,\n iat: now,\n exp: now + ttlMinutes * 60,\n } as ReadTokenPayload;\n\n const encodedPayload = base64UrlEncode(JSON.stringify(fullPayload));\n const data = `${jwtHeader()}.${encodedPayload}`;\n const signature = createSignature(data, signingSecret);\n\n return `${data}.${signature}`;\n}\n\n/**\n * Issue a session-scoped token (JWT) for WebSocket authentication.\n * Grants access to a single session's events.\n *\n * @param signingSecret - The product's signing secret\n * @param payload - Token payload with session ID\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueSessionScopedToken(\n signingSecret: string,\n payload: Omit<SessionScopedTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes: number,\n): string {\n return issueReadToken(signingSecret, payload, ttlMinutes);\n}\n\n/**\n * Issue a project-scoped token (JWT) for WebSocket authentication.\n * Grants access to all sessions within a single project.\n *\n * @param signingSecret - The product's signing secret\n * @param payload - Token payload with project ID\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueProjectScopedToken(\n signingSecret: string,\n payload: Omit<ProjectScopedTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes: number,\n): string {\n return issueReadToken(\n signingSecret,\n payload as Omit<ReadTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes,\n );\n}\n\n/**\n * Issue a batch-scoped token (JWT) for WebSocket authentication.\n * Grants access to multiple projects (organization-level access).\n *\n * @param signingSecret - The product's signing secret\n * @param payload - Token payload with project IDs array\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueBatchScopedToken(\n signingSecret: string,\n payload: Omit<BatchScopedTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes: number,\n): string {\n return issueReadToken(\n signingSecret,\n payload as Omit<ReadTokenPayload, \"iat\" | \"exp\" | \"typ\">,\n ttlMinutes,\n );\n}\n\n// Ed25519 JWT header (asymmetric — sidecar cannot forge tokens); computed on\n// first use, not at module load, so the module stays browser-importable.\nlet jwtHeaderEddsaCache: string | undefined;\nfunction jwtHeaderEddsa(): string {\n if (jwtHeaderEddsaCache === undefined) {\n jwtHeaderEddsaCache = base64UrlEncode(\n JSON.stringify({ alg: \"EdDSA\", typ: \"JWT\" }),\n );\n }\n return jwtHeaderEddsaCache;\n}\n\n/**\n * The accepted values of the `cap` claim. The exported union is derived from\n * this list so the type a caller programs against and the set the verifier\n * enforces cannot drift apart.\n */\nconst SIDECAR_CAPABILITIES = [\n \"computer_use\",\n \"read\",\n \"debug\",\n \"terminal\",\n \"workspace\",\n \"control\",\n \"raw_input\",\n] as const;\n\n/**\n * Capability strings carried in a sidecar token's `cap` claim. This is the\n * shared vocabulary the crypto layer stamps into the JWT (issueSidecarAccessToken)\n * and reads back out (verifySidecarToken). The route→capability authorization\n * policy that enforces these claims lives in the sidecar layer, not here.\n * Tokens with a `cap` claim are strictly limited to those capabilities; tokens\n * without a `cap` claim are full-scope (legacy orchestrator-internal use).\n *\n * `cap` is the claim that decides authorization, so both halves of the round\n * trip check it: the issuer throws and the verifier rejects unless the claim is\n * an array whose every entry is listed above. An empty array is valid and means\n * \"scoped to no capability\" — consumers read that as deny-everything, which is\n * the fail-closed direction. What must never happen is a malformed value\n * reaching a consumer as an *absent* claim, since absent means full scope.\n *\n * Adding a new capability: extend `SIDECAR_CAPABILITIES` above, then update the\n * enforcement policy (route maps + authorization check) in the sidecar-auth\n * package. That is a breaking change for verifiers — `verifySidecarToken`\n * rejects a capability it does not recognise, so every verifier must ship the\n * new value before any issuer stamps it. Sidecars are baked container images\n * that outlive an orchestrator roll, so \"ship\" means deployed, not merged.\n */\nexport type SidecarCapability = (typeof SIDECAR_CAPABILITIES)[number];\n\n/**\n * Whether `value` is a usable `cap` claim. Shared by the issuer and the\n * verifier so one definition decides what the claim may hold.\n *\n * `includes` compares by SameValueZero against a `readonly unknown[]` view, so\n * this rejects every non-string a JSON claim can hold (`null`, `42`, a nested\n * object) as well as an unrecognised string. A non-array — including the\n * `cap: \"read\"` case, where a consumer's `cap.includes(\"read\")` degrades into a\n * substring test that passes the read gate — fails the `Array.isArray` guard.\n */\nfunction isSidecarCapabilityList(\n value: unknown,\n): value is SidecarCapability[] {\n return (\n Array.isArray(value) &&\n value.every((entry) =>\n (SIDECAR_CAPABILITIES as readonly unknown[]).includes(entry),\n )\n );\n}\n\n/**\n * The accepted values of the `sst` claim. The exported union is derived from\n * this list so the type a caller programs against and the set the verifier\n * enforces cannot drift apart.\n */\nconst SIDECAR_SID_SCOPES = [\"session\", \"project\"] as const;\n\n/**\n * What a sidecar token's `sid` claim holds, carried in the `sst` claim.\n *\n * `sid` is minted with two different meanings: for a session-runtime token it\n * is a session id, and for a project-scoped read-only token it is a project\n * ref. Nothing on the token distinguished them, so a consumer could not tell\n * whether comparing `sid` against a session id in a request path was an\n * authorization check or a guaranteed mismatch.\n *\n * How a consumer reads the claim on a session-addressed route:\n *\n * - `\"session\"` — `sid` is a session id. Compare it against the session the\n * request names and deny a mismatch. This is the check the claim exists for.\n * - `\"project\"` — `sid` is a project ref, so no session comparison is\n * possible. The route's own capability policy decides, unchanged: a\n * project-scoped read-only token legitimately reaches every session in its\n * project.\n * - absent — the meaning of `sid` is unknown, so enforce no session\n * comparison. Tokens minted before this claim existed carry no `sst`.\n * - a value this build does not recognise — deny. `verifySidecarToken`\n * rejects those already, so this is a second line for a consumer that\n * reads the claim from somewhere else.\n *\n * Do not collapse that to \"anything but `\"session\"` is not a session token,\n * so deny it on a session route\". `\"project\"` is a legitimate scope there,\n * and denying it 403s every read-only and terminal token on every session\n * route — the behaviour that got the first version of this guard reverted.\n *\n * The verifier does not check that `sid` looks like what `sst` declares. That\n * is a semantic judgement about ids this layer cannot make; it belongs to the\n * consumer that knows the id namespaces.\n *\n * Adding a value here is a breaking change for verifiers: `verifySidecarToken`\n * rejects an `sst` it does not recognise, so every verifier must ship the new\n * value before any issuer stamps it.\n */\nexport type SidecarSidScope = (typeof SIDECAR_SID_SCOPES)[number];\n\n/**\n * Generate an Ed25519 key pair for per-session JWT signing.\n * Private key stays in orchestrator memory. Public key is injected into\n * the sidecar container. Even if the sidecar is fully compromised,\n * the attacker cannot forge JWTs for any container.\n *\n * @returns { privateKey, publicKey } as PEM strings\n */\nexport function generateSidecarKeyPair(): {\n privateKey: string;\n publicKey: string;\n} {\n const { privateKey, publicKey } = nodeCrypto().generateKeyPairSync(\n \"ed25519\",\n {\n publicKeyEncoding: { type: \"spki\", format: \"pem\" },\n privateKeyEncoding: { type: \"pkcs8\", format: \"pem\" },\n },\n );\n return { privateKey, publicKey };\n}\n\n/**\n * Issue a sidecar access token (JWT) using Ed25519 signing.\n * Scoped to a specific container via the required `cid` claim.\n *\n * @param privateKey - Ed25519 private key (PEM). If not provided, falls back to HMAC.\n * @param hmacFallbackSecret - HMAC secret for backward compat (used when no Ed25519 key)\n * @param payload - Token payload with container ID\n * @param ttlMinutes - Token TTL in minutes\n */\nexport function issueSidecarAccessToken(\n privateKey: string,\n payload: {\n sub: string;\n pid: string;\n cid: string;\n sid?: string;\n /**\n * What `sid` holds. Stamp it whenever `sid` is set, so a consumer can tell\n * a session-bound token from a project-bound one instead of guessing from\n * the capability list. Omitting it leaves consumers unable to enforce a\n * session comparison.\n */\n sst?: SidecarSidScope;\n /**\n * Capability allowlist. When present, the token is restricted to\n * routes whose required capability is in this list, as enforced by the\n * sidecar-auth policy layer. Absent = full scope (legacy\n * orchestrator-internal tokens), so pass a list or omit the key — never a\n * placeholder like `null`, which reads as full scope one layer down.\n */\n cap?: SidecarCapability[];\n },\n ttlMinutes: number,\n): string {\n if (!privateKey.includes(\"PRIVATE KEY\")) {\n throw new Error(\n \"issueSidecarAccessToken requires an Ed25519 private key (PEM). \" +\n \"HMAC fallback has been removed — generate a key pair with generateSidecarKeyPair().\",\n );\n }\n\n // Reject at mint time what no verifier accepts. Either mistake otherwise\n // produces a token that fails everywhere it is presented, debugged inside a\n // sidecar rather than in the issuer that made it. A caller reaching this\n // function from JavaScript, or through a value typed `string`, gets no help\n // from the parameter type.\n if (payload.cap !== undefined && !isSidecarCapabilityList(payload.cap)) {\n throw new Error(\n `issueSidecarAccessToken: cap must be an array whose entries are one of ${SIDECAR_CAPABILITIES.join(\", \")}. Omit the claim entirely for a full-scope token.`,\n );\n }\n if (payload.sst !== undefined) {\n if (!SIDECAR_SID_SCOPES.includes(payload.sst)) {\n throw new Error(\n `issueSidecarAccessToken: sst must be one of ${SIDECAR_SID_SCOPES.join(\", \")}.`,\n );\n }\n // `sst` names what `sid` holds, so it is meaningless without one. A\n // whitespace-only sid is the same mistake wearing a disguise: it passes a\n // truthiness test, then a consumer enforces a session match against a\n // degenerate value.\n if (!payload.sid || payload.sid.trim().length === 0) {\n throw new Error(\n \"issueSidecarAccessToken: sst describes the sid claim and cannot be set without a non-blank sid.\",\n );\n }\n }\n\n const now = Math.floor(Date.now() / 1000);\n // jti (JWT ID) enables token revocation — orchestrator adds jti to blocklist\n // on sandbox delete, sidecar checks blocklist on every request.\n const { randomBytes, sign } = nodeCrypto();\n const jti = `${payload.cid}:${now}:${randomBytes(8).toString(\"hex\")}`;\n const fullPayload = {\n ...payload,\n typ: \"sidecar\" as const,\n jti,\n iat: now,\n exp: now + ttlMinutes * 60,\n };\n const encodedPayload = base64UrlEncode(JSON.stringify(fullPayload));\n const data = `${jwtHeaderEddsa()}.${encodedPayload}`;\n const signature = base64UrlEncode(\n sign(null, textEncoder.encode(data), privateKey),\n );\n return `${data}.${signature}`;\n}\n\n/**\n * Verify a sidecar JWT using Ed25519 public key. Rejects non-EdDSA tokens.\n * Returns decoded payload on success, null on failure.\n */\nexport function verifySidecarToken(\n token: string,\n publicKey: string,\n containerId: string,\n): {\n sub: string;\n pid: string;\n cid: string;\n sid?: string;\n /**\n * What `sid` holds. Absent on tokens minted before the claim existed —\n * treat that as \"unknown\", never as a licence to skip a check.\n */\n sst?: SidecarSidScope;\n typ: string;\n jti?: string;\n exp: number;\n /**\n * Capability allowlist. Guaranteed to be an array of recognised\n * capabilities when present — a token carrying anything else is rejected, so\n * a consumer may call array methods on it without a shape check of its own.\n * Absent means full scope, so never substitute a placeholder for a missing\n * claim on the way to a policy check.\n */\n cap?: SidecarCapability[];\n} | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n\n const headerJson = base64UrlDecode(parts[0]);\n const header = JSON.parse(headerJson);\n if (header.alg !== \"EdDSA\") return null;\n\n // SECURITY: verify signature BEFORE inspecting claims.\n // Parsing claims before verification creates a timing oracle that\n // leaks valid container IDs via early-return timing differences.\n const data = `${parts[0]}.${parts[1]}`;\n const signatureBuffer = base64UrlToBytes(parts[2]);\n const valid = nodeCrypto().verify(\n null,\n textEncoder.encode(data),\n publicKey,\n signatureBuffer,\n );\n if (!valid) return null;\n\n // Signature verified — now safe to parse and inspect claims\n const payload = JSON.parse(base64UrlDecode(parts[1]));\n\n if (payload.typ !== \"sidecar\") return null;\n if (typeof payload.jti !== \"string\" || payload.jti.length === 0) {\n return null;\n }\n const exactContainerMatch = payload.cid === containerId;\n const shortDockerIdMatch = isDockerShortIdMatch(payload.cid, containerId);\n if (!exactContainerMatch && !shortDockerIdMatch) return null;\n\n // Require exp — tokens without expiration are rejected (no immortal tokens)\n const now = Math.floor(Date.now() / 1000);\n if (typeof payload.exp !== \"number\" || payload.exp < now) return null;\n\n // Require non-empty sub and cid\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0)\n return null;\n\n // `sst` decides whether a consumer runs an authorization check, so an\n // unrecognised or unusable value must reject the token rather than pass\n // through. Returning it would leave the consumer's `sst === \"session\"`\n // test false and silently skip the check the issuer asked for.\n if (payload.sst !== undefined) {\n // `includes` compares by SameValueZero, so this rejects every non-string\n // a JSON claim can hold as well as an unrecognised string.\n if (!SIDECAR_SID_SCOPES.includes(payload.sst)) return null;\n // Blank counts as absent. The trim applies only on this branch, so it\n // cannot reject a token minted before the claim existed.\n if (typeof payload.sid !== \"string\" || payload.sid.trim().length === 0) {\n return null;\n }\n }\n\n // `cap` is the claim authorization is decided on, so a value a consumer\n // cannot evaluate must reject the token rather than pass through. Returning\n // it hands the consumer a claim whose `includes` either throws out of the\n // auth middleware (a 500 where a 403 was meant) or, for a bare string,\n // silently becomes a substring test that satisfies the gate it names.\n //\n // Rejecting is the loud direction and the one every other claim here takes.\n // Coercing to `[]` would also deny, but quietly; coercing to *absent* is\n // the one option that must never be chosen, since absent means full scope.\n if (payload.cap !== undefined && !isSidecarCapabilityList(payload.cap)) {\n return null;\n }\n\n return payload;\n } catch {\n return null;\n }\n}\n\n/**\n * Compile-time assertions that keep the `sst` and `cap` claims narrow on both\n * halves of the round trip. They are load-bearing: this package type-checks\n * `src` only, so widening either field, or dropping the `as const` that derives\n * `SidecarSidScope` / `SidecarCapability`, otherwise collapses the union to\n * `string` and passes every gate here — leaving a consumer free to test the\n * claim against a value the verifier rejects.\n */\ntype AssertTrue<T extends true> = T;\ntype _IssuerTakesNarrowSst = AssertTrue<\n string extends NonNullable<Parameters<typeof issueSidecarAccessToken>[1][\"sst\"]>\n ? false\n : true\n>;\ntype _VerifierReturnsNarrowSst = AssertTrue<\n string extends NonNullable<\n NonNullable<ReturnType<typeof verifySidecarToken>>[\"sst\"]\n >\n ? false\n : true\n>;\ntype _IssuerTakesNarrowCap = AssertTrue<\n string extends NonNullable<\n Parameters<typeof issueSidecarAccessToken>[1][\"cap\"]\n >[number]\n ? false\n : true\n>;\ntype _VerifierReturnsNarrowCap = AssertTrue<\n string extends NonNullable<\n NonNullable<ReturnType<typeof verifySidecarToken>>[\"cap\"]\n >[number]\n ? false\n : true\n>;\n\nfunction isDockerShortIdMatch(\n payloadCid: unknown,\n containerId: unknown,\n): boolean {\n if (typeof payloadCid !== \"string\" || typeof containerId !== \"string\") {\n return false;\n }\n if (!/^[a-f0-9]{64}$/i.test(payloadCid)) {\n return false;\n }\n if (!/^[a-f0-9]{12}$/i.test(containerId)) {\n return false;\n }\n return payloadCid.startsWith(containerId);\n}\n\n/**\n * Check if a token payload is session-scoped.\n * Session-scoped tokens have a sid claim and no projectId/projectIds.\n */\nexport function isSessionScopedToken(\n payload: ReadTokenPayload,\n): payload is SessionScopedTokenPayload {\n return (\n \"sid\" in payload &&\n typeof payload.sid === \"string\" &&\n payload.sid.length > 0 &&\n !(\"projectId\" in payload && payload.projectId) &&\n !(\"projectIds\" in payload && payload.projectIds)\n );\n}\n\n/**\n * Check if a token payload is project-scoped.\n * Project-scoped tokens have a projectId claim and no sid/projectIds.\n */\nexport function isProjectScopedToken(\n payload: ReadTokenPayload,\n): payload is ProjectScopedTokenPayload {\n return (\n \"projectId\" in payload &&\n typeof payload.projectId === \"string\" &&\n payload.projectId.length > 0 &&\n !(\"sid\" in payload && payload.sid) &&\n !(\"projectIds\" in payload && payload.projectIds)\n );\n}\n\n/**\n * Check if a token payload is batch-scoped.\n * Batch-scoped tokens have a projectIds claim and no sid/projectId.\n */\nexport function isBatchScopedToken(\n payload: ReadTokenPayload,\n): payload is BatchScopedTokenPayload {\n return (\n \"projectIds\" in payload &&\n Array.isArray(payload.projectIds) &&\n payload.projectIds.length > 0 &&\n !(\"sid\" in payload && payload.sid) &&\n !(\"projectId\" in payload && payload.projectId)\n );\n}\n\n/**\n * Get the scope of a token payload.\n * Returns null if the token has an invalid/ambiguous scope.\n */\nexport function getTokenScope(payload: ReadTokenPayload): TokenScope | null {\n if (isSessionScopedToken(payload)) return \"session\";\n if (isProjectScopedToken(payload)) return \"project\";\n if (isBatchScopedToken(payload)) return \"batch\";\n return null;\n}\n\n/**\n * Validate that a token payload has exactly one valid scope.\n * Returns an error message if invalid, null if valid.\n */\nexport function validateTokenScope(payload: ReadTokenPayload): string | null {\n const hasSid = \"sid\" in payload && payload.sid;\n const hasProjectId = \"projectId\" in payload && payload.projectId;\n const hasProjectIds =\n \"projectIds\" in payload &&\n Array.isArray(payload.projectIds) &&\n payload.projectIds.length > 0;\n\n const scopeCount = [hasSid, hasProjectId, hasProjectIds].filter(\n Boolean,\n ).length;\n\n if (scopeCount === 0) {\n return \"Token must have one of: sid (session), projectId (project), or projectIds (batch)\";\n }\n\n if (scopeCount > 1) {\n return \"Token cannot have multiple scope claims (sid, projectId, projectIds are mutually exclusive)\";\n }\n\n return null;\n}\n\n/**\n * Verify a read token against a product's signing secrets.\n *\n * @param token - The JWT token to verify\n * @param product - Product auth info containing secrets\n */\nexport function verifyReadToken(\n token: string,\n product: ProductAuthInfo,\n): TokenValidationResult {\n // Check product status\n if (product.status !== \"active\") {\n return {\n valid: false,\n error: \"Product is suspended\",\n errorCode: \"PRODUCT_SUSPENDED\",\n };\n }\n\n // Parse token\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n return {\n valid: false,\n error: \"Invalid token format\",\n errorCode: \"TOKEN_INVALID\",\n };\n }\n\n const [header, encodedPayload, signature] = parts;\n const data = `${header}.${encodedPayload}`;\n\n // Pin algorithm to HS256 — defense-in-depth against alg-confusion\n // attacks that swap a forged header (e.g. `alg: \"none\"`) onto an\n // otherwise valid token. Fast path: when the header bytes match the\n // canonical `JWT_HEADER` verbatim, we skip the parse. Slow path:\n // decode the header and reject anything whose `alg` claim is not\n // `\"HS256\"`. Non-canonical-but-valid headers (e.g. `alg: \"HS256\"`\n // with an extra `kid` claim) fall through to signature verification,\n // which covers the actual header bytes — so any tampering with the\n // header still fails the signature check.\n if (header !== jwtHeader()) {\n let parsedAlg: string | null = null;\n try {\n const decoded = JSON.parse(base64UrlDecode(header)) as Record<\n string,\n unknown\n >;\n if (typeof decoded.alg === \"string\") parsedAlg = decoded.alg;\n } catch {\n // Header didn't decode cleanly; treat as TOKEN_INVALID below.\n }\n if (parsedAlg !== \"HS256\") {\n return {\n valid: false,\n error: \"Unsupported token algorithm\",\n errorCode: \"TOKEN_INVALID\",\n };\n }\n }\n\n // Verify signature with current secret\n let signatureValid = verifySignature(\n data,\n signature,\n product.secrets.current.secret,\n );\n\n // If failed and we have a previous secret in grace period, try it\n if (!signatureValid && product.secrets.previous) {\n if (Date.now() < product.secrets.previous.expires_at) {\n signatureValid = verifySignature(\n data,\n signature,\n product.secrets.previous.secret,\n );\n }\n }\n\n if (!signatureValid) {\n return {\n valid: false,\n error: \"Invalid signature\",\n errorCode: \"SIGNATURE_INVALID\",\n };\n }\n\n // Parse payload\n let payload: ReadTokenPayload;\n try {\n payload = JSON.parse(base64UrlDecode(encodedPayload));\n } catch {\n return {\n valid: false,\n error: \"Invalid token payload\",\n errorCode: \"TOKEN_INVALID\",\n };\n }\n\n // Check token type\n if (payload.typ !== \"read\") {\n return {\n valid: false,\n error: \"Not a read token\",\n errorCode: \"TOKEN_WRONG_TYPE\",\n };\n }\n\n // Check expiration\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp < now) {\n return {\n valid: false,\n error: \"Token expired\",\n errorCode: \"TOKEN_EXPIRED\",\n };\n }\n\n // Check product ID matches\n if (payload.pid !== product.product_id) {\n return {\n valid: false,\n error: \"Product ID mismatch\",\n errorCode: \"TOKEN_INVALID\",\n };\n }\n\n // Validate token scope (must have exactly one of sid, projectId, projectIds)\n const scopeError = validateTokenScope(payload);\n if (scopeError) {\n return {\n valid: false,\n error: scopeError,\n errorCode: \"TOKEN_INVALID\",\n };\n }\n\n return {\n valid: true,\n payload,\n scope: getTokenScope(payload) ?? undefined,\n };\n}\n\n/**\n * Default salt for API key hashing (development only).\n * Production MUST set API_KEY_HASH_SALT environment variable.\n * WARNING: Changing the salt will invalidate all existing API key hashes.\n */\nconst DEFAULT_API_KEY_SALT = \"api_key_hash_salt\";\n\nlet _saltWarned = false;\n\n/**\n * Get the API key hash salt from environment.\n * Logs a warning if falling back to the weak default (development only).\n */\nfunction getApiKeyHashSalt(): string {\n if (typeof process !== \"undefined\" && process.env?.API_KEY_HASH_SALT) {\n return process.env.API_KEY_HASH_SALT;\n }\n if (\n !_saltWarned &&\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"production\"\n ) {\n _saltWarned = true;\n console.error(\n \"[SECURITY] API_KEY_HASH_SALT not set in production. \" +\n \"Using weak default salt. Set API_KEY_HASH_SALT to a cryptographically random value.\",\n );\n }\n return DEFAULT_API_KEY_SALT;\n}\n\n/**\n * Hash an API key for storage/lookup.\n * Uses HMAC-SHA256 for consistent, fast hashing.\n *\n * The salt can be configured via API_KEY_HASH_SALT environment variable.\n * WARNING: Changing the salt will invalidate all existing API key hashes.\n */\nexport function hashApiKey(apiKey: string): string {\n return nodeCrypto()\n .createHmac(\"sha256\", getApiKeyHashSalt())\n .update(apiKey)\n .digest(\"hex\");\n}\n\n/**\n * Hash an API key with an explicit salt.\n * Use this when you need to verify against a specific salt.\n */\nexport function hashApiKeyWithSalt(apiKey: string, salt: string): string {\n return nodeCrypto().createHmac(\"sha256\", salt).update(apiKey).digest(\"hex\");\n}\n\n/**\n * Verify an API key using timing-safe comparison.\n */\nexport function verifyApiKey(provided: string, stored: string): boolean {\n if (provided.length !== stored.length) {\n return false;\n }\n try {\n return nodeCrypto().timingSafeEqual(\n textEncoder.encode(provided),\n textEncoder.encode(stored),\n );\n } catch {\n return false;\n }\n}\n","/**\n * Channel Access Control\n *\n * Determines what channels a token can access based on its scope.\n * Used by SessionGateway to validate subscriptions and event delivery.\n *\n * Channel patterns:\n * - session:{sessionId} - Events for a specific session\n * - agent:{sessionId} - Agent events for a specific session\n * - project:{projectId} - All events for a project\n * - system - System-wide events (heartbeat, etc.)\n */\n\nimport {\n isBatchScopedToken,\n isProjectScopedToken,\n isSessionScopedToken,\n} from \"./tokens.js\";\nimport type {\n BatchScopedTokenPayload,\n ProjectScopedTokenPayload,\n ReadTokenPayload,\n SessionScopedTokenPayload,\n} from \"./types.js\";\n\n/**\n * Result of channel access check.\n */\nexport interface ChannelAccessResult {\n /** Whether access is allowed */\n allowed: boolean;\n /** Reason for denial (if not allowed) */\n reason?: string;\n}\n\n/**\n * Extract session ID from a session channel pattern.\n * Returns null if not a session channel.\n */\nexport function extractSessionFromChannel(channel: string): string | null {\n if (channel.startsWith(\"session:\")) {\n return channel.slice(8);\n }\n if (channel.startsWith(\"agent:\")) {\n return channel.slice(6);\n }\n return null;\n}\n\n/**\n * Extract project ID from a project channel pattern.\n * Returns null if not a project channel.\n */\nexport function extractProjectFromChannel(channel: string): string | null {\n if (channel.startsWith(\"project:\")) {\n return channel.slice(8);\n }\n return null;\n}\n\n/**\n * Check if a session-scoped token can access a channel.\n *\n * Session tokens can access:\n * - session:{their-sid}\n * - agent:{their-sid}\n * - system (always allowed)\n * - Wildcard patterns matching their session\n */\nexport function canSessionTokenAccessChannel(\n payload: SessionScopedTokenPayload,\n channel: string,\n): ChannelAccessResult {\n // System channels are always allowed\n if (channel === \"system\" || channel.startsWith(\"system.\")) {\n return { allowed: true };\n }\n\n // Global wildcard requires higher scope\n if (channel === \"*\") {\n return {\n allowed: false,\n reason: \"Session-scoped tokens cannot subscribe to all channels\",\n };\n }\n\n // Check session channel match\n const sessionId = extractSessionFromChannel(channel);\n if (sessionId !== null) {\n if (sessionId === payload.sid) {\n return { allowed: true };\n }\n // Wildcard session channels not allowed for session tokens\n if (sessionId === \"*\") {\n return {\n allowed: false,\n reason: \"Session-scoped tokens cannot subscribe to all sessions\",\n };\n }\n return {\n allowed: false,\n reason: `Token only has access to session ${payload.sid}`,\n };\n }\n\n // Project channels require project scope\n if (channel.startsWith(\"project:\")) {\n return {\n allowed: false,\n reason: \"Session-scoped tokens cannot access project channels\",\n };\n }\n\n // Default: check if channel matches session pattern\n // Allow channels that don't have explicit scope (backward compatibility)\n return { allowed: true };\n}\n\n/**\n * Check if a project-scoped token can access a channel.\n *\n * Project tokens can access:\n * - project:{their-projectId}\n * - session:* (all sessions within project - requires validation by caller)\n * - agent:* (all agent events within project)\n * - system (always allowed)\n */\nexport function canProjectTokenAccessChannel(\n payload: ProjectScopedTokenPayload,\n channel: string,\n): ChannelAccessResult {\n // System channels are always allowed\n if (channel === \"system\" || channel.startsWith(\"system.\")) {\n return { allowed: true };\n }\n\n // Global wildcard requires batch scope or higher\n if (channel === \"*\") {\n return {\n allowed: false,\n reason:\n \"Project-scoped tokens cannot subscribe to all channels across projects\",\n };\n }\n\n // Project channel access\n const projectId = extractProjectFromChannel(channel);\n if (projectId !== null) {\n if (projectId === payload.projectId) {\n return { allowed: true };\n }\n return {\n allowed: false,\n reason: `Token only has access to project ${payload.projectId}`,\n };\n }\n\n // Session channels - project tokens can access any session in their project\n // The actual validation that session belongs to project must be done by the caller\n // who has access to the session-to-project mapping\n if (channel.startsWith(\"session:\") || channel.startsWith(\"agent:\")) {\n // Wildcard session channels allowed for project tokens\n const sessionId = extractSessionFromChannel(channel);\n if (sessionId === \"*\") {\n return { allowed: true };\n }\n // Specific session - allowed but caller must verify session is in project\n return { allowed: true };\n }\n\n // Default: allow general channels (backward compatibility)\n return { allowed: true };\n}\n\n/**\n * Check if a batch-scoped token can access a channel.\n *\n * Batch tokens can access:\n * - project:{any of their projectIds}\n * - session:* (all sessions within their projects)\n * - agent:* (all agent events within their projects)\n * - system (always allowed)\n * - * (global wildcard - for organization-wide subscriptions)\n */\nexport function canBatchTokenAccessChannel(\n payload: BatchScopedTokenPayload,\n channel: string,\n): ChannelAccessResult {\n // System channels are always allowed\n if (channel === \"system\" || channel.startsWith(\"system.\")) {\n return { allowed: true };\n }\n\n // Global wildcard allowed for batch tokens\n if (channel === \"*\") {\n return { allowed: true };\n }\n\n // Project channel access\n const projectId = extractProjectFromChannel(channel);\n if (projectId !== null) {\n // Wildcard project access allowed\n if (projectId === \"*\") {\n return { allowed: true };\n }\n // Specific project must be in the list\n if (payload.projectIds.includes(projectId)) {\n return { allowed: true };\n }\n return {\n allowed: false,\n reason: `Token does not have access to project ${projectId}`,\n };\n }\n\n // Session/agent channels - batch tokens can access any session in their projects\n // Caller must verify session belongs to one of the token's projects\n if (channel.startsWith(\"session:\") || channel.startsWith(\"agent:\")) {\n return { allowed: true };\n }\n\n // Default: allow general channels (backward compatibility)\n return { allowed: true };\n}\n\n/**\n * Check if a token can access a channel.\n * Dispatches to scope-specific functions based on token type.\n *\n * @param payload - The token payload\n * @param channel - The channel to check access for\n * @returns Access result with allowed boolean and optional reason\n */\nexport function canTokenAccessChannel(\n payload: ReadTokenPayload,\n channel: string,\n): ChannelAccessResult {\n // Use type guards for clean scope detection and type narrowing\n if (isSessionScopedToken(payload)) {\n return canSessionTokenAccessChannel(payload, channel);\n }\n\n if (isProjectScopedToken(payload)) {\n return canProjectTokenAccessChannel(payload, channel);\n }\n\n if (isBatchScopedToken(payload)) {\n return canBatchTokenAccessChannel(payload, channel);\n }\n\n // Invalid token scope\n return {\n allowed: false,\n reason: \"Token has no valid scope (missing sid, projectId, or projectIds)\",\n };\n}\n\n/**\n * Get the list of allowed channel patterns for a token.\n * Useful for informing clients what they can subscribe to.\n *\n * @param payload - The token payload\n * @returns Array of allowed channel patterns\n */\nexport function getAllowedChannelPatterns(payload: ReadTokenPayload): string[] {\n const patterns: string[] = [\"system\", \"system.*\"];\n\n // Use type guards for clean scope detection\n if (isSessionScopedToken(payload)) {\n patterns.push(`session:${payload.sid}`, `agent:${payload.sid}`);\n return patterns;\n }\n\n if (isProjectScopedToken(payload)) {\n patterns.push(\n `project:${payload.projectId}`,\n \"session:*\", // Within project\n \"agent:*\", // Within project\n );\n return patterns;\n }\n\n if (isBatchScopedToken(payload)) {\n patterns.push(\n \"*\", // Global wildcard\n \"project:*\", // All projects in batch\n ...payload.projectIds.map((id) => `project:${id}`),\n \"session:*\",\n \"agent:*\",\n );\n return patterns;\n }\n\n return patterns;\n}\n\n/**\n * Validate channel subscription request.\n * Checks multiple channels and returns aggregated result.\n *\n * @param payload - The token payload\n * @param channels - Channels to validate\n * @returns Object with allowed channels and denied channels with reasons\n */\nexport function validateChannelSubscription(\n payload: ReadTokenPayload,\n channels: string[],\n): {\n allowed: string[];\n denied: Array<{ channel: string; reason: string }>;\n} {\n const allowed: string[] = [];\n const denied: Array<{ channel: string; reason: string }> = [];\n\n for (const channel of channels) {\n const result = canTokenAccessChannel(payload, channel);\n if (result.allowed) {\n allowed.push(channel);\n } else {\n denied.push({ channel, reason: result.reason ?? \"Access denied\" });\n }\n }\n\n return { allowed, denied };\n}\n","/**\n * Authentication Configuration\n *\n * Token management with refresh hooks and expiration handling.\n */\n\nimport { issueReadToken } from \"./tokens.js\";\n\n// Re-export channel access utilities\nexport * from \"./channel-access.js\";\n// Re-export shared auth providers (eliminates 185 lines of duplication with browser.ts)\nexport type { AuthProvider } from \"./shared-providers.js\";\nexport {\n CallbackAuth,\n NoAuth,\n ReadTokenAuth,\n RefreshableTokenAuth,\n StaticTokenAuth,\n} from \"./shared-providers.js\";\nexport * from \"./tokens.js\";\n// Re-export types and token utilities\nexport * from \"./types.js\";\n\n/**\n * Environment-based auth that reads from env vars.\n * Matches the server-side patterns used in sidecar/orchestrator.\n */\nexport class EnvTokenAuth {\n private envVar: string;\n private fallback?: string;\n\n constructor(options: { envVar: string; fallback?: string }) {\n this.envVar = options.envVar;\n this.fallback = options.fallback;\n }\n\n async getToken(): Promise<string | null> {\n // Works in Node.js; in browser, fallback would be used\n if (typeof process !== \"undefined\" && process.env) {\n return process.env[this.envVar] ?? this.fallback ?? null;\n }\n return this.fallback ?? null;\n }\n}\n\n/**\n * Pre-configured auth for sidecar connections.\n * Uses SIDECAR_AUTH_TOKEN env var (matches server expectation).\n */\nexport class SidecarAuth extends EnvTokenAuth {\n constructor(token?: string) {\n super({ envVar: \"SIDECAR_AUTH_TOKEN\", fallback: token });\n }\n}\n\n/**\n * Product-based auth for multi-tenant orchestrator connections.\n * Uses ORCHESTRATOR_API_SECRET_KEY env var.\n */\nexport class ProductAuth extends EnvTokenAuth {\n constructor(apiKey?: string) {\n super({ envVar: \"ORCHESTRATOR_API_SECRET_KEY\", fallback: apiKey });\n }\n}\n\n/**\n * Product token issuer for backend services.\n *\n * Use this in product backends to issue read tokens for WebSocket connections.\n *\n * @example\n * ```typescript\n * import { ProductTokenIssuer } from \"@tangle-network/agent-core\";\n *\n * const issuer = new ProductTokenIssuer({\n * productId: \"vibecode\",\n * signingSecret: process.env.ORCHESTRATOR_SIGNING_SECRET!,\n * ttlMinutes: { free: 15, pro: 240 },\n * });\n *\n * // Issue a token for a user session\n * const token = issuer.issue({\n * userId: \"user_123\",\n * sessionId: \"sess_abc\",\n * tier: \"pro\",\n * });\n *\n * // Return token to frontend for WebSocket connection\n * res.json({\n * readToken: token.token,\n * expiresAt: token.expiresAt,\n * websocketUrl: `wss://orchestrator.example.com/session?token=${token.token}`,\n * });\n * ```\n */\nexport class ProductTokenIssuer {\n private readonly productId: string;\n private readonly signingSecret: string;\n private readonly ttlMinutes: {\n free: number;\n pro: number;\n enterprise?: number;\n };\n\n constructor(config: {\n productId: string;\n signingSecret: string;\n /** TTL in minutes for each tier (default: { free: 15, pro: 240 }) */\n ttlMinutes?: { free?: number; pro?: number; enterprise?: number };\n }) {\n this.productId = config.productId;\n this.signingSecret = config.signingSecret;\n this.ttlMinutes = {\n free: config.ttlMinutes?.free ?? 15,\n pro: config.ttlMinutes?.pro ?? 240,\n enterprise: config.ttlMinutes?.enterprise ?? 480,\n };\n }\n\n /**\n * Issue a read token for a user session.\n */\n issue(params: {\n userId: string;\n sessionId: string;\n tier?: \"free\" | \"pro\" | \"enterprise\";\n sidecarId?: string;\n }): { token: string; expiresAt: number } {\n const tier = params.tier ?? \"free\";\n const ttl = this.ttlMinutes[tier] ?? this.ttlMinutes.free;\n\n const token = issueReadToken(\n this.signingSecret,\n {\n sub: params.userId,\n sid: params.sessionId,\n pid: this.productId,\n cid: params.sidecarId,\n },\n ttl,\n );\n\n const expiresAt = Math.floor(Date.now() / 1000) + ttl * 60;\n\n return { token, expiresAt };\n }\n\n /**\n * Get the TTL in minutes for a tier.\n */\n getTtlMinutes(tier: \"free\" | \"pro\" | \"enterprise\" = \"free\"): number {\n return this.ttlMinutes[tier] ?? this.ttlMinutes.free;\n }\n}\n"],"mappings":"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"}
@@ -143,4 +143,4 @@ function isRetryable(error) {
143
143
  //#endregion
144
144
  export { isRetryable as n, isSDKError as r, SDKError as t };
145
145
 
146
- //# sourceMappingURL=errors-DJ6U6cHY.js.map
146
+ //# sourceMappingURL=errors-RaBbEzQu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors-DJ6U6cHY.js","names":[],"sources":["../src/errors/index.ts"],"sourcesContent":["/**\n * SDK Error Types\n *\n * Structured errors with codes, retry hints, and context.\n */\n\n/** Error categories for routing and handling */\nexport type ErrorCode =\n | \"NETWORK\"\n | \"TIMEOUT\"\n | \"AUTH\"\n | \"AUTH_EXPIRED\"\n | \"VALIDATION\"\n | \"NOT_FOUND\"\n | \"RATE_LIMITED\"\n | \"SERVER\"\n | \"CANCELLED\"\n | \"UNKNOWN\";\n\n/** SDK error with structured metadata */\nexport class SDKError extends Error {\n readonly code: ErrorCode;\n readonly status?: number;\n readonly retryable: boolean;\n readonly retryAfterMs?: number;\n readonly context?: Record<string, unknown>;\n\n constructor(\n message: string,\n options: {\n code: ErrorCode;\n status?: number;\n retryable?: boolean;\n retryAfterMs?: number;\n context?: Record<string, unknown>;\n cause?: Error;\n },\n ) {\n super(message, { cause: options.cause });\n this.name = \"SDKError\";\n this.code = options.code;\n this.status = options.status;\n this.retryable =\n options.retryable ?? this.inferRetryable(options.code, options.status);\n this.retryAfterMs = options.retryAfterMs;\n this.context = options.context;\n }\n\n private inferRetryable(code: ErrorCode, status?: number): boolean {\n // Network issues and server errors are typically retryable\n if (code === \"NETWORK\" || code === \"TIMEOUT\") return true;\n if (code === \"RATE_LIMITED\") return true;\n if (code === \"SERVER\" && status && status >= 500) return true;\n // Auth expired can be retried after refresh\n if (code === \"AUTH_EXPIRED\") return true;\n // Client errors are not retryable\n return false;\n }\n\n /** Create from HTTP response */\n static fromResponse(status: number, body?: unknown): SDKError {\n const message = SDKError.extractMessageFromBody(body) ?? `HTTP ${status}`;\n\n if (status === 401) {\n return new SDKError(message, {\n code: \"AUTH\",\n status,\n retryable: false,\n context: { body },\n });\n }\n if (status === 403) {\n return new SDKError(message, {\n code: \"AUTH\",\n status,\n retryable: false,\n context: { body },\n });\n }\n if (status === 404) {\n return new SDKError(message, {\n code: \"NOT_FOUND\",\n status,\n retryable: false,\n context: { body },\n });\n }\n if (status === 422 || status === 400) {\n return new SDKError(message, {\n code: \"VALIDATION\",\n status,\n retryable: false,\n context: { body },\n });\n }\n if (status === 429) {\n const retryAfter =\n typeof body === \"object\" && body !== null && \"retryAfter\" in body\n ? Number((body as { retryAfter: unknown }).retryAfter) * 1000\n : 1000;\n return new SDKError(message, {\n code: \"RATE_LIMITED\",\n status,\n retryable: true,\n retryAfterMs: retryAfter,\n context: { body },\n });\n }\n if (status >= 500) {\n return new SDKError(message, {\n code: \"SERVER\",\n status,\n retryable: true,\n context: { body },\n });\n }\n return new SDKError(message, {\n code: \"UNKNOWN\",\n status,\n context: { body },\n });\n }\n\n /** Create from caught error */\n static fromError(error: unknown): SDKError {\n if (error instanceof SDKError) return error;\n\n if (error instanceof Error) {\n // Detect network errors\n if (error.name === \"TypeError\" && error.message.includes(\"fetch\")) {\n return new SDKError(\"Network request failed\", {\n code: \"NETWORK\",\n cause: error,\n });\n }\n if (error.name === \"AbortError\") {\n return new SDKError(\"Request cancelled\", {\n code: \"CANCELLED\",\n retryable: false,\n cause: error,\n });\n }\n if (error.message.includes(\"timeout\") || error.name === \"TimeoutError\") {\n return new SDKError(\"Request timeout\", {\n code: \"TIMEOUT\",\n cause: error,\n });\n }\n return new SDKError(error.message, { code: \"UNKNOWN\", cause: error });\n }\n\n return new SDKError(String(error), { code: \"UNKNOWN\" });\n }\n\n /**\n * Extract a human-readable message from a parsed error response body.\n *\n * Probes shapes seen across our upstreams:\n * - `{ message: \"...\" }` — most internal services\n * - `{ error: \"...\" }` — flat-error shape\n * - `{ error: { message: \"...\" } }` — Hetzner Cloud API\n * - `{ error: { details: \"...\" } }` — Hetzner field-level details\n * - `string` — non-JSON text/plain errors\n *\n * Returns null if no message-like field is present so callers can\n * fall back to \"HTTP <status>\". Without this, Hetzner 422s logged\n * as opaque \"HTTP 422\" because the message lives at\n * `body.error.message`, blocking diagnosis of every autoscaler\n * failure (issue #785).\n */\n private static extractMessageFromBody(body: unknown): string | null {\n if (typeof body === \"string\" && body.trim().length > 0) {\n return body.length > 500 ? `${body.slice(0, 500)}…` : body;\n }\n if (typeof body !== \"object\" || body === null) return null;\n\n const top = body as { message?: unknown; error?: unknown };\n if (typeof top.message === \"string\" && top.message.length > 0) {\n return top.message;\n }\n if (typeof top.error === \"string\" && top.error.length > 0) {\n return top.error;\n }\n if (typeof top.error === \"object\" && top.error !== null) {\n const inner = top.error as {\n message?: unknown;\n details?: unknown;\n code?: unknown;\n };\n const message =\n typeof inner.message === \"string\" && inner.message.length > 0\n ? inner.message\n : null;\n const details =\n typeof inner.details === \"string\" && inner.details.length > 0\n ? inner.details\n : null;\n const code =\n typeof inner.code === \"string\" && inner.code.length > 0\n ? inner.code\n : null;\n const parts = [code, message, details].filter(\n (part): part is string => part !== null,\n );\n if (parts.length > 0) return parts.join(\": \");\n }\n return null;\n }\n\n /** Check if error is retryable after token refresh */\n isAuthExpired(): boolean {\n return (\n this.code === \"AUTH_EXPIRED\" ||\n (this.code === \"AUTH\" && this.status === 401)\n );\n }\n}\n\n/** Type guard for SDKError */\nexport function isSDKError(error: unknown): error is SDKError {\n return error instanceof SDKError;\n}\n\n/** Type guard for retryable errors */\nexport function isRetryable(error: unknown): boolean {\n return isSDKError(error) && error.retryable;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"errors-RaBbEzQu.js","names":[],"sources":["../src/errors/index.ts"],"sourcesContent":["/**\n * SDK Error Types\n *\n * Structured errors with codes, retry hints, and context.\n */\n\n/** Error categories for routing and handling */\nexport type ErrorCode =\n | \"NETWORK\"\n | \"TIMEOUT\"\n | \"AUTH\"\n | \"AUTH_EXPIRED\"\n | \"VALIDATION\"\n | \"NOT_FOUND\"\n | \"RATE_LIMITED\"\n | \"SERVER\"\n | \"CANCELLED\"\n | \"UNKNOWN\";\n\n/** SDK error with structured metadata */\nexport class SDKError extends Error {\n readonly code: ErrorCode;\n readonly status?: number;\n readonly retryable: boolean;\n readonly retryAfterMs?: number;\n readonly context?: Record<string, unknown>;\n\n constructor(\n message: string,\n options: {\n code: ErrorCode;\n status?: number;\n retryable?: boolean;\n retryAfterMs?: number;\n context?: Record<string, unknown>;\n cause?: Error;\n },\n ) {\n super(message, { cause: options.cause });\n this.name = \"SDKError\";\n this.code = options.code;\n this.status = options.status;\n this.retryable =\n options.retryable ?? this.inferRetryable(options.code, options.status);\n this.retryAfterMs = options.retryAfterMs;\n this.context = options.context;\n }\n\n private inferRetryable(code: ErrorCode, status?: number): boolean {\n // Network issues and server errors are typically retryable\n if (code === \"NETWORK\" || code === \"TIMEOUT\") return true;\n if (code === \"RATE_LIMITED\") return true;\n if (code === \"SERVER\" && status && status >= 500) return true;\n // Auth expired can be retried after refresh\n if (code === \"AUTH_EXPIRED\") return true;\n // Client errors are not retryable\n return false;\n }\n\n /** Create from HTTP response */\n static fromResponse(status: number, body?: unknown): SDKError {\n const message = SDKError.extractMessageFromBody(body) ?? `HTTP ${status}`;\n\n if (status === 401) {\n return new SDKError(message, {\n code: \"AUTH\",\n status,\n retryable: false,\n context: { body },\n });\n }\n if (status === 403) {\n return new SDKError(message, {\n code: \"AUTH\",\n status,\n retryable: false,\n context: { body },\n });\n }\n if (status === 404) {\n return new SDKError(message, {\n code: \"NOT_FOUND\",\n status,\n retryable: false,\n context: { body },\n });\n }\n if (status === 422 || status === 400) {\n return new SDKError(message, {\n code: \"VALIDATION\",\n status,\n retryable: false,\n context: { body },\n });\n }\n if (status === 429) {\n const retryAfter =\n typeof body === \"object\" && body !== null && \"retryAfter\" in body\n ? Number((body as { retryAfter: unknown }).retryAfter) * 1000\n : 1000;\n return new SDKError(message, {\n code: \"RATE_LIMITED\",\n status,\n retryable: true,\n retryAfterMs: retryAfter,\n context: { body },\n });\n }\n if (status >= 500) {\n return new SDKError(message, {\n code: \"SERVER\",\n status,\n retryable: true,\n context: { body },\n });\n }\n return new SDKError(message, {\n code: \"UNKNOWN\",\n status,\n context: { body },\n });\n }\n\n /** Create from caught error */\n static fromError(error: unknown): SDKError {\n if (error instanceof SDKError) return error;\n\n if (error instanceof Error) {\n // Detect network errors\n if (error.name === \"TypeError\" && error.message.includes(\"fetch\")) {\n return new SDKError(\"Network request failed\", {\n code: \"NETWORK\",\n cause: error,\n });\n }\n if (error.name === \"AbortError\") {\n return new SDKError(\"Request cancelled\", {\n code: \"CANCELLED\",\n retryable: false,\n cause: error,\n });\n }\n if (error.message.includes(\"timeout\") || error.name === \"TimeoutError\") {\n return new SDKError(\"Request timeout\", {\n code: \"TIMEOUT\",\n cause: error,\n });\n }\n return new SDKError(error.message, { code: \"UNKNOWN\", cause: error });\n }\n\n return new SDKError(String(error), { code: \"UNKNOWN\" });\n }\n\n /**\n * Extract a human-readable message from a parsed error response body.\n *\n * Probes shapes seen across our upstreams:\n * - `{ message: \"...\" }` — most internal services\n * - `{ error: \"...\" }` — flat-error shape\n * - `{ error: { message: \"...\" } }` — Hetzner Cloud API\n * - `{ error: { details: \"...\" } }` — Hetzner field-level details\n * - `string` — non-JSON text/plain errors\n *\n * Returns null if no message-like field is present so callers can\n * fall back to \"HTTP <status>\". Without this, Hetzner 422s logged\n * as opaque \"HTTP 422\" because the message lives at\n * `body.error.message`, blocking diagnosis of every autoscaler\n * failure (issue #785).\n */\n private static extractMessageFromBody(body: unknown): string | null {\n if (typeof body === \"string\" && body.trim().length > 0) {\n return body.length > 500 ? `${body.slice(0, 500)}…` : body;\n }\n if (typeof body !== \"object\" || body === null) return null;\n\n const top = body as { message?: unknown; error?: unknown };\n if (typeof top.message === \"string\" && top.message.length > 0) {\n return top.message;\n }\n if (typeof top.error === \"string\" && top.error.length > 0) {\n return top.error;\n }\n if (typeof top.error === \"object\" && top.error !== null) {\n const inner = top.error as {\n message?: unknown;\n details?: unknown;\n code?: unknown;\n };\n const message =\n typeof inner.message === \"string\" && inner.message.length > 0\n ? inner.message\n : null;\n const details =\n typeof inner.details === \"string\" && inner.details.length > 0\n ? inner.details\n : null;\n const code =\n typeof inner.code === \"string\" && inner.code.length > 0\n ? inner.code\n : null;\n const parts = [code, message, details].filter(\n (part): part is string => part !== null,\n );\n if (parts.length > 0) return parts.join(\": \");\n }\n return null;\n }\n\n /** Check if error is retryable after token refresh */\n isAuthExpired(): boolean {\n return (\n this.code === \"AUTH_EXPIRED\" ||\n (this.code === \"AUTH\" && this.status === 401)\n );\n }\n}\n\n/** Type guard for SDKError */\nexport function isSDKError(error: unknown): error is SDKError {\n return error instanceof SDKError;\n}\n\n/** Type guard for retryable errors */\nexport function isRetryable(error: unknown): boolean {\n return isSDKError(error) && error.retryable;\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { i as isSDKError, n as SDKError, r as isRetryable, t as ErrorCode } from "./index-D59fZ7n3.js";
2
- import { C as ReadTokenAuth, S as NoAuth, T as StaticTokenAuth, _ as SessionScopedTokenPayloadSchema, a as CreateSessionResponse, b as AuthProvider, c as ProductSchema, d as ProjectScopedTokenPayloadSchema, f as ReadTokenPayload, g as SessionScopedTokenPayload, h as SendMessageRequestSchema, i as CreateSessionRequestSchema, l as ProductSecrets, m as SendMessageRequest, n as BatchScopedTokenPayloadSchema, o as Product, p as ReadTokenPayloadSchema, r as CreateSessionRequest, s as ProductAuthInfo, t as BatchScopedTokenPayload, u as ProjectScopedTokenPayload, v as TokenScope, w as RefreshableTokenAuth, x as CallbackAuth, y as TokenValidationResult } from "./types-Cd5jUzcr.js";
3
- import { ChannelAccessResult, EnvTokenAuth, ProductAuth, ProductTokenIssuer, SidecarAuth, canBatchTokenAccessChannel, canProjectTokenAccessChannel, canSessionTokenAccessChannel, canTokenAccessChannel, decodeToken, extractProjectFromChannel, extractSessionFromChannel, generateApiKey, generateSecureToken, generateSigningSecret, getAllowedChannelPatterns, getTokenScope, getTokenTTL, hashApiKey, isBatchScopedToken, isProjectScopedToken, isSessionScopedToken, isTokenExpiringSoon, issueBatchScopedToken, issueProjectScopedToken, issueReadToken, issueSessionScopedToken, validateChannelSubscription, validateTokenScope, verifyApiKey, verifyReadToken } from "./auth/index.js";
2
+ import { C as AuthProvider, D as RefreshableTokenAuth, E as ReadTokenAuth, O as StaticTokenAuth, S as TokenValidationResult, T as NoAuth, _ as SendMessageRequest, a as BatchScopedTokenPayloadSchema, b as SessionScopedTokenPayloadSchema, c as CreateSessionResponse, d as ProductSchema, f as ProductSecrets, g as ReadTokenPayloadSchema, h as ReadTokenPayload, i as BatchScopedTokenPayload, l as Product, m as ProjectScopedTokenPayloadSchema, n as getTokenTTL, o as CreateSessionRequest, p as ProjectScopedTokenPayload, r as isTokenExpiringSoon, s as CreateSessionRequestSchema, t as decodeToken, u as ProductAuthInfo, v as SendMessageRequestSchema, w as CallbackAuth, x as TokenScope, y as SessionScopedTokenPayload } from "./tokens-browser-EiSc2dah.js";
3
+ import { ChannelAccessResult, EnvTokenAuth, ProductAuth, ProductTokenIssuer, SidecarAuth, canBatchTokenAccessChannel, canProjectTokenAccessChannel, canSessionTokenAccessChannel, canTokenAccessChannel, extractProjectFromChannel, extractSessionFromChannel, generateApiKey, generateSecureToken, generateSigningSecret, getAllowedChannelPatterns, getTokenScope, hashApiKey, isBatchScopedToken, isProjectScopedToken, isSessionScopedToken, issueBatchScopedToken, issueProjectScopedToken, issueReadToken, issueSessionScopedToken, validateChannelSubscription, validateTokenScope, verifyApiKey, verifyReadToken } from "./auth/index.js";
4
4
  import { a as EventChannel, c as EventBufferConfig, i as ChannelHandler, n as EventDeduplicator, o as BufferedEvent, r as ChannelConfig, s as EventBuffer, t as DeduplicatorConfig } from "./index-C_1MnL24.js";
5
5
  import { S as TransportMode, _ as StreamOptions, a as RecordedRequest, b as TransportEvents, c as TransportAdapter, d as ConnectionQuality, f as ConnectionState, g as SSEEvent, h as RequestOptions, i as MockTransportConfig, l as ConnectionManager, m as ReconnectionConfig, n as MockStreamEvent, o as BaseTransportAdapter, p as DEFAULT_RECONNECTION_CONFIG, r as MockTransportAdapter, s as Subscription, t as MockResponse, u as ConnectionManagerConfig, v as SubscriptionState, x as TransportMetrics, y as TransportConfig } from "./index-B4yy8ghH.js";
6
6
  import { a as ProtectionOptions, c as CircuitBreaker, d as CircuitOpenError, f as CircuitState, i as withTimeout, l as CircuitBreakerConfig, n as createTimeoutController, o as createProtectedFn, r as sleep, s as withProtection, t as TimeoutError, u as CircuitBreakerStats } from "./index-HMlPCgTI.js";
@@ -486,5 +486,5 @@ declare function createSessionTranslator(translation: SessionTranslation): <T>(e
486
486
  */
487
487
  declare function extractSessionId(event: unknown): string | null;
488
488
  //#endregion
489
- export { type AgentEvent, type AgentEventType, AgentEventTypeSchema, type AuthProvider, BaseTransportAdapter, type BatchScopedTokenPayload, BatchScopedTokenPayloadSchema, BrowserNetworkInfo, BrowserPersistence, type BufferedEvent, CONTENT_COMPLETION_KEY, CONTENT_KEYS, CONTENT_KEY_FIELD, CONTENT_PROMPT_KEY, CONTENT_TOOL_ARGS_KEY, CONTENT_TOOL_NAME_KEY, CONTENT_TOOL_RESULT_KEY, type CacheConfig, type CacheStorage, CallbackAuth, type ChannelAccessResult, type ChannelConfig, type ChannelHandler, CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerStats, CircuitOpenError, type CircuitState, type ConnectionInitEvent, ConnectionManager, type ConnectionManagerConfig, type ConnectionQuality, type ConnectionState, type ContentField, type CreateSessionRequest, CreateSessionRequestSchema, type CreateSessionResponse, DEFAULT_RECONNECTION_CONFIG, type DeclaredIntentMatch, type DeduplicatorConfig, type DoneEvent, EnvTokenAuth, type ErrorCode, type ErrorContext, type ErrorEvent, EventBuffer, type EventBufferConfig, EventChannel, EventDeduplicator, type EventMetadata, type ExecutionCompletedEvent, type ExecutionFailedEvent, type ExecutionStartedEvent, type FilePart, GEN_AI_CONVERSATION_ID, GEN_AI_INPUT_TOKEN_KEYS, GEN_AI_MODEL_KEYS, GEN_AI_OPERATION_NAME, GEN_AI_OUTPUT_TOKEN_KEYS, GEN_AI_REQUEST_MODEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, type GenAiUsage, type HeartbeatEvent, INDEXED_CONTENT_KEY_LIKE_PATTERNS, type Interceptor, InterceptorChain, LocalStorage, MemoryCache, MemorySecureStorage, MemoryStorage, type MessagePartUpdatedEvent, type MockResponse, type MockStreamEvent, MockTransportAdapter, type MockTransportConfig, type NetworkInfo, NoAuth, NodeNetworkInfo, type NormalizedContent, OfflineQueue, PARENT_RUN_KEY_ATTRS, type ParsedSSEEvent, type Part, type PartBase, type PersistenceAdapter, type PlatformAdapter, type PlatformCapabilities, type Product, ProductAuth, type ProductAuthInfo, ProductSchema, type ProductSecrets, ProductTokenIssuer, type ProjectScopedTokenPayload, ProjectScopedTokenPayloadSchema, type ProtectionOptions, type QueuedRequest, RUN_KEY_ATTRS, RUN_KIND_ATTRS, ReadTokenAuth, type ReadTokenPayload, ReadTokenPayloadSchema, type ReasoningPart, type ReconnectionConfig, type RecordedRequest, RefreshableTokenAuth, type ReplayEndEvent, type ReplayStartEvent, type RequestContext, type RequestOptions, ResponseCache, type ResponseContext, type ResultEvent, type RetryConfig, Retryable, SDKError, SSEChunkParser, type SSEEvent, type SSEEventData, type SSEParserOptions, SUBJECT_KEY_ATTRS, type SecureStorage, type SendMessageRequest, SendMessageRequestSchema, type SessionScopedTokenPayload, SessionScopedTokenPayloadSchema, type SessionTranslation, type SessionUpdatedEvent, SidecarAuth, StaticTokenAuth, type StatusEvent, type Storage, StoragePersistence, type StreamOptions, type StreamTokenUsage, type StreamUsageAccumulator, StreamUsageExtractor, type Subscription, type SubscriptionState, type SubtaskPart, TANGLE_RUN_ID_CAMEL_KEY, TANGLE_RUN_ID_KEY, TANGLE_RUN_KIND_KEY, TANGLE_RUN_PARENT_ID_KEY, TANGLE_SUBJECT_KEY, TANGLE_TRIGGER_ID_KEY, TANGLE_TRIGGER_KIND_KEY, TANGLE_TRIGGER_SOURCE_KEY, TANGLE_WORKFLOW_ID_KEY, TOKEN_USAGE_CACHE_BAG_KEYS, TOKEN_USAGE_CACHE_BAG_READ_KEYS, TOKEN_USAGE_CACHE_BAG_WRITE_KEYS, TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS, TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS, TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS, TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS, TOKEN_USAGE_COST_KEYS, TOKEN_USAGE_DETAIL_KEYS, TOKEN_USAGE_INPUT_KEYS, TOKEN_USAGE_OUTPUT_KEYS, TRIGGER_ID_ATTRS, TRIGGER_SOURCE_ATTRS, type TerminalDataEvent, type TerminalEvent, type TerminalExitEvent, type TerminalResizeEvent, type TextPart, TimeoutError, type TokenScope, type TokenUsageCounts, type TokenValidationResult, type ToolPart, type ToolState, type ToolStateCompleted, type ToolStateError, type ToolStatePending, type ToolStateRunning, type TransportAdapter, type TransportConfig, type TransportEvents, type TransportMetrics, type TransportMode, WORKFLOW_MARKER_ATTRS, addTokenUsage, asContentField, asContentString, cacheHitRate, calculateDelay, canBatchTokenAccessChannel, canProjectTokenAccessChannel, canSessionTokenAccessChannel, canTokenAccessChannel, classifyAgent, consumeSSEStream, createDefaultPlatformAdapter, createHeaderInterceptor, createLoggingInterceptor, createMetricsInterceptor, createPlatformAdapter, createProtectedFn, createSessionTranslator, createStreamUsageExtractor, createTimeoutController, createUsageCallback, declaredTaskText, decodeToken, detectPlatform, detectStorage, extractContent, extractProjectFromChannel, extractSessionFromChannel, extractSessionId, firstTokenCount, firstUsageCostUsd, genAiUsageAttributes, generateApiKey, generateIdempotencyKey, generateRequestId, generateSecureToken, generateSigningSecret, getAllowedChannelPatterns, getTokenScope, getTokenTTL, hasContent, hashApiKey, isBatchScopedToken, isFilePart, isProjectScopedToken, isReasoningPart, isRetryable, isSDKError, isSessionScopedToken, isSubtaskPart, isTextPart, isTokenExpiringSoon, isToolPart, issueBatchScopedToken, issueProjectScopedToken, issueReadToken, issueSessionScopedToken, logRecordContentBag, matchesAnyChannel, matchesChannel, normalizeContentAttributes, parseSSEData, parseSSEStream, promptTokens, readTokenCostUsd, readTokenUsage, resolveDeclaredIntent, sleep, tokenCount, tokenUsageSource, translateSessionId, validateChannelSubscription, validateTokenScope, verifyApiKey, verifyReadToken, withProtection, withRetry, withTimeout };
489
+ export { AgentEvent, AgentEventType, AgentEventTypeSchema, type AuthProvider, BaseTransportAdapter, type BatchScopedTokenPayload, BatchScopedTokenPayloadSchema, BrowserNetworkInfo, BrowserPersistence, type BufferedEvent, CONTENT_COMPLETION_KEY, CONTENT_KEYS, CONTENT_KEY_FIELD, CONTENT_PROMPT_KEY, CONTENT_TOOL_ARGS_KEY, CONTENT_TOOL_NAME_KEY, CONTENT_TOOL_RESULT_KEY, type CacheConfig, type CacheStorage, CallbackAuth, type ChannelAccessResult, type ChannelConfig, type ChannelHandler, CircuitBreaker, CircuitBreakerConfig, CircuitBreakerStats, CircuitOpenError, CircuitState, ConnectionInitEvent, ConnectionManager, ConnectionManagerConfig, ConnectionQuality, ConnectionState, ContentField, type CreateSessionRequest, CreateSessionRequestSchema, type CreateSessionResponse, DEFAULT_RECONNECTION_CONFIG, DeclaredIntentMatch, type DeduplicatorConfig, DoneEvent, EnvTokenAuth, type ErrorCode, type ErrorContext, ErrorEvent, EventBuffer, type EventBufferConfig, EventChannel, EventDeduplicator, EventMetadata, ExecutionCompletedEvent, ExecutionFailedEvent, ExecutionStartedEvent, FilePart, GEN_AI_CONVERSATION_ID, GEN_AI_INPUT_TOKEN_KEYS, GEN_AI_MODEL_KEYS, GEN_AI_OPERATION_NAME, GEN_AI_OUTPUT_TOKEN_KEYS, GEN_AI_REQUEST_MODEL, GEN_AI_RESPONSE_MODEL, GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GenAiUsage, HeartbeatEvent, INDEXED_CONTENT_KEY_LIKE_PATTERNS, type Interceptor, InterceptorChain, LocalStorage, MemoryCache, MemorySecureStorage, MemoryStorage, MessagePartUpdatedEvent, MockResponse, MockStreamEvent, MockTransportAdapter, MockTransportConfig, type NetworkInfo, NoAuth, NodeNetworkInfo, NormalizedContent, OfflineQueue, PARENT_RUN_KEY_ATTRS, type ParsedSSEEvent, Part, PartBase, type PersistenceAdapter, type PlatformAdapter, type PlatformCapabilities, type Product, ProductAuth, type ProductAuthInfo, ProductSchema, type ProductSecrets, ProductTokenIssuer, type ProjectScopedTokenPayload, ProjectScopedTokenPayloadSchema, ProtectionOptions, type QueuedRequest, RUN_KEY_ATTRS, RUN_KIND_ATTRS, ReadTokenAuth, type ReadTokenPayload, ReadTokenPayloadSchema, ReasoningPart, ReconnectionConfig, RecordedRequest, RefreshableTokenAuth, ReplayEndEvent, ReplayStartEvent, type RequestContext, RequestOptions, ResponseCache, type ResponseContext, ResultEvent, type RetryConfig, Retryable, SDKError, SSEChunkParser, SSEEvent, type SSEEventData, type SSEParserOptions, SUBJECT_KEY_ATTRS, type SecureStorage, type SendMessageRequest, SendMessageRequestSchema, type SessionScopedTokenPayload, SessionScopedTokenPayloadSchema, SessionTranslation, SessionUpdatedEvent, SidecarAuth, StaticTokenAuth, StatusEvent, type Storage, StoragePersistence, StreamOptions, type StreamTokenUsage, type StreamUsageAccumulator, StreamUsageExtractor, Subscription, SubscriptionState, SubtaskPart, TANGLE_RUN_ID_CAMEL_KEY, TANGLE_RUN_ID_KEY, TANGLE_RUN_KIND_KEY, TANGLE_RUN_PARENT_ID_KEY, TANGLE_SUBJECT_KEY, TANGLE_TRIGGER_ID_KEY, TANGLE_TRIGGER_KIND_KEY, TANGLE_TRIGGER_SOURCE_KEY, TANGLE_WORKFLOW_ID_KEY, TOKEN_USAGE_CACHE_BAG_KEYS, TOKEN_USAGE_CACHE_BAG_READ_KEYS, TOKEN_USAGE_CACHE_BAG_WRITE_KEYS, TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS, TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS, TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS, TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS, TOKEN_USAGE_COST_KEYS, TOKEN_USAGE_DETAIL_KEYS, TOKEN_USAGE_INPUT_KEYS, TOKEN_USAGE_OUTPUT_KEYS, TRIGGER_ID_ATTRS, TRIGGER_SOURCE_ATTRS, TerminalDataEvent, TerminalEvent, TerminalExitEvent, TerminalResizeEvent, TextPart, TimeoutError, type TokenScope, TokenUsageCounts, type TokenValidationResult, ToolPart, ToolState, ToolStateCompleted, ToolStateError, ToolStatePending, ToolStateRunning, TransportAdapter, TransportConfig, TransportEvents, TransportMetrics, TransportMode, WORKFLOW_MARKER_ATTRS, addTokenUsage, asContentField, asContentString, cacheHitRate, calculateDelay, canBatchTokenAccessChannel, canProjectTokenAccessChannel, canSessionTokenAccessChannel, canTokenAccessChannel, classifyAgent, consumeSSEStream, createDefaultPlatformAdapter, createHeaderInterceptor, createLoggingInterceptor, createMetricsInterceptor, createPlatformAdapter, createProtectedFn, createSessionTranslator, createStreamUsageExtractor, createTimeoutController, createUsageCallback, declaredTaskText, decodeToken, detectPlatform, detectStorage, extractContent, extractProjectFromChannel, extractSessionFromChannel, extractSessionId, firstTokenCount, firstUsageCostUsd, genAiUsageAttributes, generateApiKey, generateIdempotencyKey, generateRequestId, generateSecureToken, generateSigningSecret, getAllowedChannelPatterns, getTokenScope, getTokenTTL, hasContent, hashApiKey, isBatchScopedToken, isFilePart, isProjectScopedToken, isReasoningPart, isRetryable, isSDKError, isSessionScopedToken, isSubtaskPart, isTextPart, isTokenExpiringSoon, isToolPart, issueBatchScopedToken, issueProjectScopedToken, issueReadToken, issueSessionScopedToken, logRecordContentBag, matchesAnyChannel, matchesChannel, normalizeContentAttributes, parseSSEData, parseSSEStream, promptTokens, readTokenCostUsd, readTokenUsage, resolveDeclaredIntent, sleep, tokenCount, tokenUsageSource, translateSessionId, validateChannelSubscription, validateTokenScope, verifyApiKey, verifyReadToken, withProtection, withRetry, withTimeout };
490
490
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { n as isRetryable, r as isSDKError, t as SDKError } from "./errors-DJ6U6cHY.js";
2
- import { a as ReadTokenPayloadSchema, c as CallbackAuth, d as RefreshableTokenAuth, f as StaticTokenAuth, i as ProjectScopedTokenPayloadSchema, l as NoAuth, n as CreateSessionRequestSchema, o as SendMessageRequestSchema, r as ProductSchema, s as SessionScopedTokenPayloadSchema, t as BatchScopedTokenPayloadSchema, u as ReadTokenAuth } from "./types-DC6OJ1um.js";
3
- import { EnvTokenAuth, ProductAuth, ProductTokenIssuer, SidecarAuth, canBatchTokenAccessChannel, canProjectTokenAccessChannel, canSessionTokenAccessChannel, canTokenAccessChannel, decodeToken, extractProjectFromChannel, extractSessionFromChannel, generateApiKey, generateSecureToken, generateSigningSecret, getAllowedChannelPatterns, getTokenScope, getTokenTTL, hashApiKey, isBatchScopedToken, isProjectScopedToken, isSessionScopedToken, isTokenExpiringSoon, issueBatchScopedToken, issueProjectScopedToken, issueReadToken, issueSessionScopedToken, validateChannelSubscription, validateTokenScope, verifyApiKey, verifyReadToken } from "./auth/index.js";
1
+ import { _ as isTokenExpiringSoon, a as ReadTokenPayloadSchema, c as CallbackAuth, d as RefreshableTokenAuth, f as StaticTokenAuth, g as getTokenTTL, h as decodeToken, i as ProjectScopedTokenPayloadSchema, l as NoAuth, n as CreateSessionRequestSchema, o as SendMessageRequestSchema, r as ProductSchema, s as SessionScopedTokenPayloadSchema, t as BatchScopedTokenPayloadSchema, u as ReadTokenAuth } from "./types-Boft7Ohf.js";
2
+ import { n as isRetryable, r as isSDKError, t as SDKError } from "./errors-RaBbEzQu.js";
3
+ import { EnvTokenAuth, ProductAuth, ProductTokenIssuer, SidecarAuth, canBatchTokenAccessChannel, canProjectTokenAccessChannel, canSessionTokenAccessChannel, canTokenAccessChannel, extractProjectFromChannel, extractSessionFromChannel, generateApiKey, generateSecureToken, generateSigningSecret, getAllowedChannelPatterns, getTokenScope, hashApiKey, isBatchScopedToken, isProjectScopedToken, isSessionScopedToken, issueBatchScopedToken, issueProjectScopedToken, issueReadToken, issueSessionScopedToken, validateChannelSubscription, validateTokenScope, verifyApiKey, verifyReadToken } from "./auth/index.js";
4
4
  import { n as EventChannel, r as EventBuffer, t as EventDeduplicator } from "./events-CUWh9qrv.js";
5
- import { a as sleep, c as CircuitOpenError, i as createTimeoutController, n as withProtection, o as withTimeout, r as TimeoutError, s as CircuitBreaker, t as createProtectedFn } from "./resilience-BQzDfw_C.js";
5
+ import { a as sleep, c as CircuitOpenError, i as createTimeoutController, n as withProtection, o as withTimeout, r as TimeoutError, s as CircuitBreaker, t as createProtectedFn } from "./resilience-v3NFIdDo.js";
6
6
  import { SSEChunkParser, StreamUsageExtractor, consumeSSEStream, createStreamUsageExtractor, createUsageCallback, parseSSEData, parseSSEStream } from "./sse/index.js";
7
7
  import { $ as CONTENT_TOOL_ARGS_KEY, A as TANGLE_TRIGGER_ID_KEY, B as GEN_AI_OPERATION_NAME, C as RUN_KIND_ATTRS, D as TANGLE_RUN_KIND_KEY, E as TANGLE_RUN_ID_KEY, F as TRIGGER_SOURCE_ATTRS, G as GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS, H as GEN_AI_REQUEST_MODEL, I as WORKFLOW_MARKER_ATTRS, J as genAiUsageAttributes, K as GEN_AI_USAGE_INPUT_TOKENS, L as GEN_AI_CONVERSATION_ID, M as TANGLE_TRIGGER_SOURCE_KEY, N as TANGLE_WORKFLOW_ID_KEY, O as TANGLE_RUN_PARENT_ID_KEY, P as TRIGGER_ID_ATTRS, Q as CONTENT_PROMPT_KEY, R as GEN_AI_INPUT_TOKEN_KEYS, S as RUN_KEY_ATTRS, T as TANGLE_RUN_ID_CAMEL_KEY, U as GEN_AI_RESPONSE_MODEL, V as GEN_AI_OUTPUT_TOKEN_KEYS, W as GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS, X as CONTENT_KEYS, Y as CONTENT_COMPLETION_KEY, Z as CONTENT_KEY_FIELD, _ as readTokenCostUsd, a as TOKEN_USAGE_CACHE_READ_INCLUSIVE_KEYS, at as classifyAgent, b as tokenUsageSource, c as TOKEN_USAGE_COST_KEYS, ct as hasContent, d as TOKEN_USAGE_OUTPUT_KEYS, dt as resolveDeclaredIntent, et as CONTENT_TOOL_NAME_KEY, f as addTokenUsage, g as promptTokens, h as firstUsageCostUsd, i as TOKEN_USAGE_CACHE_READ_EXCLUSIVE_KEYS, it as asContentString, j as TANGLE_TRIGGER_KIND_KEY, k as TANGLE_SUBJECT_KEY, l as TOKEN_USAGE_DETAIL_KEYS, lt as logRecordContentBag, m as firstTokenCount, n as TOKEN_USAGE_CACHE_BAG_READ_KEYS, nt as INDEXED_CONTENT_KEY_LIKE_PATTERNS, o as TOKEN_USAGE_CACHE_WRITE_EXCLUSIVE_KEYS, ot as declaredTaskText, p as cacheHitRate, q as GEN_AI_USAGE_OUTPUT_TOKENS, r as TOKEN_USAGE_CACHE_BAG_WRITE_KEYS, rt as asContentField, s as TOKEN_USAGE_CACHE_WRITE_INCLUSIVE_KEYS, st as extractContent, t as TOKEN_USAGE_CACHE_BAG_KEYS, tt as CONTENT_TOOL_RESULT_KEY, u as TOKEN_USAGE_INPUT_KEYS, ut as normalizeContentAttributes, v as readTokenUsage, w as SUBJECT_KEY_ATTRS, x as PARENT_RUN_KEY_ATTRS, y as tokenCount, z as GEN_AI_MODEL_KEYS } from "./telemetry-x1wNw1P4.js";
8
8
  import { i as DEFAULT_RECONNECTION_CONFIG, n as BaseTransportAdapter, r as ConnectionManager, t as MockTransportAdapter } from "./transport-XLxpQmUW.js";
@@ -1,2 +1,2 @@
1
- import { a as sleep, c as CircuitOpenError, i as createTimeoutController, n as withProtection, o as withTimeout, r as TimeoutError, s as CircuitBreaker, t as createProtectedFn } from "../resilience-BQzDfw_C.js";
1
+ import { a as sleep, c as CircuitOpenError, i as createTimeoutController, n as withProtection, o as withTimeout, r as TimeoutError, s as CircuitBreaker, t as createProtectedFn } from "../resilience-v3NFIdDo.js";
2
2
  export { CircuitBreaker, CircuitOpenError, TimeoutError, createProtectedFn, createTimeoutController, sleep, withProtection, withTimeout };
@@ -1,4 +1,4 @@
1
- import { t as SDKError } from "./errors-DJ6U6cHY.js";
1
+ import { t as SDKError } from "./errors-RaBbEzQu.js";
2
2
  //#region src/resilience/circuit-breaker.ts
3
3
  /**
4
4
  * Circuit Breaker
@@ -218,4 +218,4 @@ function createProtectedFn(fn, options) {
218
218
  //#endregion
219
219
  export { sleep as a, CircuitOpenError as c, createTimeoutController as i, withProtection as n, withTimeout as o, TimeoutError as r, CircuitBreaker as s, createProtectedFn as t };
220
220
 
221
- //# sourceMappingURL=resilience-BQzDfw_C.js.map
221
+ //# sourceMappingURL=resilience-v3NFIdDo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resilience-BQzDfw_C.js","names":[],"sources":["../src/resilience/circuit-breaker.ts","../src/resilience/timeout.ts","../src/resilience/protection.ts"],"sourcesContent":["/**\n * Circuit Breaker\n *\n * Implements the circuit breaker pattern for fail-fast behavior when\n * downstream services are unhealthy. Prevents cascade failures by\n * stopping requests to failing services.\n *\n * States:\n * - CLOSED: Normal operation, requests pass through\n * - OPEN: Service unhealthy, requests fail immediately\n * - HALF_OPEN: Testing if service recovered\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\nexport type CircuitState = \"closed\" | \"open\" | \"half-open\";\n\nexport interface CircuitBreakerConfig {\n /** Number of failures before opening circuit */\n failureThreshold: number;\n /** Time in ms to wait before testing recovery (half-open) */\n resetTimeoutMs: number;\n /** Number of successes in half-open state to close circuit */\n successThreshold: number;\n /** Optional: Only count these error types as failures */\n failureFilter?: (error: unknown) => boolean;\n}\n\nexport interface CircuitBreakerStats {\n state: CircuitState;\n failures: number;\n successes: number;\n lastFailureTime: number | null;\n totalFailures: number;\n totalSuccesses: number;\n totalRejected: number;\n}\n\nexport class CircuitOpenError extends SDKError {\n readonly circuitName: string;\n readonly resetAfterMs: number;\n\n constructor(name: string, resetAfterMs: number) {\n super(`Circuit breaker '${name}' is open`, {\n code: \"SERVER\",\n retryable: true,\n retryAfterMs: resetAfterMs,\n context: { circuitName: name },\n });\n this.name = \"CircuitOpenError\";\n this.circuitName = name;\n this.resetAfterMs = resetAfterMs;\n }\n}\n\nexport class CircuitBreaker {\n private state: CircuitState = \"closed\";\n private failures = 0;\n private successes = 0;\n private lastFailureTime: number | null = null;\n\n // Lifetime stats\n private totalFailures = 0;\n private totalSuccesses = 0;\n private totalRejected = 0;\n\n constructor(\n private readonly name: string,\n private readonly config: CircuitBreakerConfig,\n ) {\n if (config.failureThreshold < 1) {\n throw new Error(\"failureThreshold must be >= 1\");\n }\n if (config.resetTimeoutMs < 0) {\n throw new Error(\"resetTimeoutMs must be >= 0\");\n }\n if (config.successThreshold < 1) {\n throw new Error(\"successThreshold must be >= 1\");\n }\n }\n\n /**\n * Execute a function through the circuit breaker.\n * Throws CircuitOpenError if circuit is open.\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n // Check if we should transition from open to half-open\n if (this.state === \"open\") {\n const timeSinceFailure = this.lastFailureTime\n ? Date.now() - this.lastFailureTime\n : Number.POSITIVE_INFINITY;\n\n if (timeSinceFailure >= this.config.resetTimeoutMs) {\n this.state = \"half-open\";\n this.successes = 0;\n } else {\n this.totalRejected++;\n throw new CircuitOpenError(\n this.name,\n this.config.resetTimeoutMs - timeSinceFailure,\n );\n }\n }\n\n try {\n const result = await fn();\n this.onSuccess();\n return result;\n } catch (error) {\n this.onFailure(error);\n throw error;\n }\n }\n\n private onSuccess(): void {\n this.totalSuccesses++;\n\n if (this.state === \"half-open\") {\n this.successes++;\n if (this.successes >= this.config.successThreshold) {\n // Service recovered, close circuit\n this.state = \"closed\";\n this.failures = 0;\n this.successes = 0;\n }\n } else if (this.state === \"closed\") {\n // Reset failure count on success\n this.failures = 0;\n }\n }\n\n private onFailure(error: unknown): void {\n // Check if this error should count as a failure\n if (this.config.failureFilter && !this.config.failureFilter(error)) {\n return;\n }\n\n this.totalFailures++;\n this.failures++;\n this.lastFailureTime = Date.now();\n this.successes = 0;\n\n if (this.state === \"half-open\") {\n // Any failure in half-open returns to open\n this.state = \"open\";\n } else if (\n this.state === \"closed\" &&\n this.failures >= this.config.failureThreshold\n ) {\n // Threshold exceeded, open circuit\n this.state = \"open\";\n }\n }\n\n /** Get current circuit state */\n getState(): CircuitState {\n return this.state;\n }\n\n /** Get circuit statistics */\n getStats(): CircuitBreakerStats {\n return {\n state: this.state,\n failures: this.failures,\n successes: this.successes,\n lastFailureTime: this.lastFailureTime,\n totalFailures: this.totalFailures,\n totalSuccesses: this.totalSuccesses,\n totalRejected: this.totalRejected,\n };\n }\n\n /** Check if circuit is allowing requests */\n isAllowingRequests(): boolean {\n if (this.state === \"closed\" || this.state === \"half-open\") {\n return true;\n }\n\n // Check if reset timeout has passed\n const timeSinceFailure = this.lastFailureTime\n ? Date.now() - this.lastFailureTime\n : Number.POSITIVE_INFINITY;\n\n return timeSinceFailure >= this.config.resetTimeoutMs;\n }\n\n /** Manually reset the circuit to closed state */\n reset(): void {\n this.state = \"closed\";\n this.failures = 0;\n this.successes = 0;\n this.lastFailureTime = null;\n }\n\n /** Manually open the circuit */\n open(): void {\n this.state = \"open\";\n this.lastFailureTime = Date.now();\n }\n}\n","/**\n * Timeout Utilities\n *\n * Promise-based timeout handling with proper cleanup.\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\nexport class TimeoutError extends SDKError {\n readonly timeoutMs: number;\n\n constructor(message: string, timeoutMs: number) {\n super(message, {\n code: \"TIMEOUT\",\n retryable: true,\n context: { timeoutMs },\n });\n this.name = \"TimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Wrap a promise with a timeout.\n * Rejects with TimeoutError if the promise doesn't resolve in time.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message?: string,\n): Promise<T> {\n if (timeoutMs <= 0) {\n return promise;\n }\n\n let timeoutId: ReturnType<typeof setTimeout>;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(\n new TimeoutError(\n message || `Operation timed out after ${timeoutMs}ms`,\n timeoutMs,\n ),\n );\n }, timeoutMs);\n });\n\n return Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimeout(timeoutId);\n });\n}\n\n/**\n * Create an AbortController that auto-aborts after a timeout.\n * Returns the controller and a cleanup function.\n */\nexport function createTimeoutController(timeoutMs: number): {\n controller: AbortController;\n cleanup: () => void;\n} {\n const controller = new AbortController();\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n if (timeoutMs > 0) {\n timeoutId = setTimeout(() => {\n controller.abort(new TimeoutError(\"Request timeout\", timeoutMs));\n }, timeoutMs);\n }\n\n return {\n controller,\n cleanup: () => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n }\n },\n };\n}\n\n/**\n * Sleep for a specified duration.\n * Optionally accepts an AbortSignal for cancellation.\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n\n const timeoutId = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timeoutId);\n reject(signal.reason);\n },\n { once: true },\n );\n });\n}\n","/**\n * Combines timeout + circuit breaker into a single request guard.\n */\n\nimport type { CircuitBreaker } from \"./circuit-breaker.js\";\nimport { withTimeout } from \"./timeout.js\";\n\nexport interface ProtectionOptions {\n /** Timeout in milliseconds (0 or undefined = no timeout) */\n timeoutMs?: number;\n /** Circuit breaker instance (optional) */\n breaker?: CircuitBreaker;\n /** Operation name for error messages */\n operationName?: string;\n}\n\n/**\n * Execute a function with timeout and circuit breaker protection.\n *\n * Order of execution:\n * 1. Circuit breaker check (fail fast if open)\n * 2. Timeout wrapper\n * 3. Execute function\n * 4. Update circuit breaker state\n */\nexport async function withProtection<T>(\n fn: () => Promise<T>,\n options: ProtectionOptions = {},\n): Promise<T> {\n const { timeoutMs, breaker, operationName = \"operation\" } = options;\n\n const execute = async (): Promise<T> => {\n if (timeoutMs && timeoutMs > 0) {\n return withTimeout(fn(), timeoutMs, `${operationName} timed out`);\n }\n return fn();\n };\n\n if (breaker) {\n return breaker.execute(execute);\n }\n\n return execute();\n}\n\n/**\n * Create a protected function that always applies the same protection options.\n */\nexport function createProtectedFn<TArgs extends unknown[], TResult>(\n fn: (...args: TArgs) => Promise<TResult>,\n options: ProtectionOptions,\n): (...args: TArgs) => Promise<TResult> {\n return (...args: TArgs) => withProtection(() => fn(...args), options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resilience-v3NFIdDo.js","names":[],"sources":["../src/resilience/circuit-breaker.ts","../src/resilience/timeout.ts","../src/resilience/protection.ts"],"sourcesContent":["/**\n * Circuit Breaker\n *\n * Implements the circuit breaker pattern for fail-fast behavior when\n * downstream services are unhealthy. Prevents cascade failures by\n * stopping requests to failing services.\n *\n * States:\n * - CLOSED: Normal operation, requests pass through\n * - OPEN: Service unhealthy, requests fail immediately\n * - HALF_OPEN: Testing if service recovered\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\nexport type CircuitState = \"closed\" | \"open\" | \"half-open\";\n\nexport interface CircuitBreakerConfig {\n /** Number of failures before opening circuit */\n failureThreshold: number;\n /** Time in ms to wait before testing recovery (half-open) */\n resetTimeoutMs: number;\n /** Number of successes in half-open state to close circuit */\n successThreshold: number;\n /** Optional: Only count these error types as failures */\n failureFilter?: (error: unknown) => boolean;\n}\n\nexport interface CircuitBreakerStats {\n state: CircuitState;\n failures: number;\n successes: number;\n lastFailureTime: number | null;\n totalFailures: number;\n totalSuccesses: number;\n totalRejected: number;\n}\n\nexport class CircuitOpenError extends SDKError {\n readonly circuitName: string;\n readonly resetAfterMs: number;\n\n constructor(name: string, resetAfterMs: number) {\n super(`Circuit breaker '${name}' is open`, {\n code: \"SERVER\",\n retryable: true,\n retryAfterMs: resetAfterMs,\n context: { circuitName: name },\n });\n this.name = \"CircuitOpenError\";\n this.circuitName = name;\n this.resetAfterMs = resetAfterMs;\n }\n}\n\nexport class CircuitBreaker {\n private state: CircuitState = \"closed\";\n private failures = 0;\n private successes = 0;\n private lastFailureTime: number | null = null;\n\n // Lifetime stats\n private totalFailures = 0;\n private totalSuccesses = 0;\n private totalRejected = 0;\n\n constructor(\n private readonly name: string,\n private readonly config: CircuitBreakerConfig,\n ) {\n if (config.failureThreshold < 1) {\n throw new Error(\"failureThreshold must be >= 1\");\n }\n if (config.resetTimeoutMs < 0) {\n throw new Error(\"resetTimeoutMs must be >= 0\");\n }\n if (config.successThreshold < 1) {\n throw new Error(\"successThreshold must be >= 1\");\n }\n }\n\n /**\n * Execute a function through the circuit breaker.\n * Throws CircuitOpenError if circuit is open.\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n // Check if we should transition from open to half-open\n if (this.state === \"open\") {\n const timeSinceFailure = this.lastFailureTime\n ? Date.now() - this.lastFailureTime\n : Number.POSITIVE_INFINITY;\n\n if (timeSinceFailure >= this.config.resetTimeoutMs) {\n this.state = \"half-open\";\n this.successes = 0;\n } else {\n this.totalRejected++;\n throw new CircuitOpenError(\n this.name,\n this.config.resetTimeoutMs - timeSinceFailure,\n );\n }\n }\n\n try {\n const result = await fn();\n this.onSuccess();\n return result;\n } catch (error) {\n this.onFailure(error);\n throw error;\n }\n }\n\n private onSuccess(): void {\n this.totalSuccesses++;\n\n if (this.state === \"half-open\") {\n this.successes++;\n if (this.successes >= this.config.successThreshold) {\n // Service recovered, close circuit\n this.state = \"closed\";\n this.failures = 0;\n this.successes = 0;\n }\n } else if (this.state === \"closed\") {\n // Reset failure count on success\n this.failures = 0;\n }\n }\n\n private onFailure(error: unknown): void {\n // Check if this error should count as a failure\n if (this.config.failureFilter && !this.config.failureFilter(error)) {\n return;\n }\n\n this.totalFailures++;\n this.failures++;\n this.lastFailureTime = Date.now();\n this.successes = 0;\n\n if (this.state === \"half-open\") {\n // Any failure in half-open returns to open\n this.state = \"open\";\n } else if (\n this.state === \"closed\" &&\n this.failures >= this.config.failureThreshold\n ) {\n // Threshold exceeded, open circuit\n this.state = \"open\";\n }\n }\n\n /** Get current circuit state */\n getState(): CircuitState {\n return this.state;\n }\n\n /** Get circuit statistics */\n getStats(): CircuitBreakerStats {\n return {\n state: this.state,\n failures: this.failures,\n successes: this.successes,\n lastFailureTime: this.lastFailureTime,\n totalFailures: this.totalFailures,\n totalSuccesses: this.totalSuccesses,\n totalRejected: this.totalRejected,\n };\n }\n\n /** Check if circuit is allowing requests */\n isAllowingRequests(): boolean {\n if (this.state === \"closed\" || this.state === \"half-open\") {\n return true;\n }\n\n // Check if reset timeout has passed\n const timeSinceFailure = this.lastFailureTime\n ? Date.now() - this.lastFailureTime\n : Number.POSITIVE_INFINITY;\n\n return timeSinceFailure >= this.config.resetTimeoutMs;\n }\n\n /** Manually reset the circuit to closed state */\n reset(): void {\n this.state = \"closed\";\n this.failures = 0;\n this.successes = 0;\n this.lastFailureTime = null;\n }\n\n /** Manually open the circuit */\n open(): void {\n this.state = \"open\";\n this.lastFailureTime = Date.now();\n }\n}\n","/**\n * Timeout Utilities\n *\n * Promise-based timeout handling with proper cleanup.\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\nexport class TimeoutError extends SDKError {\n readonly timeoutMs: number;\n\n constructor(message: string, timeoutMs: number) {\n super(message, {\n code: \"TIMEOUT\",\n retryable: true,\n context: { timeoutMs },\n });\n this.name = \"TimeoutError\";\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Wrap a promise with a timeout.\n * Rejects with TimeoutError if the promise doesn't resolve in time.\n */\nexport function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n message?: string,\n): Promise<T> {\n if (timeoutMs <= 0) {\n return promise;\n }\n\n let timeoutId: ReturnType<typeof setTimeout>;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(\n new TimeoutError(\n message || `Operation timed out after ${timeoutMs}ms`,\n timeoutMs,\n ),\n );\n }, timeoutMs);\n });\n\n return Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimeout(timeoutId);\n });\n}\n\n/**\n * Create an AbortController that auto-aborts after a timeout.\n * Returns the controller and a cleanup function.\n */\nexport function createTimeoutController(timeoutMs: number): {\n controller: AbortController;\n cleanup: () => void;\n} {\n const controller = new AbortController();\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n if (timeoutMs > 0) {\n timeoutId = setTimeout(() => {\n controller.abort(new TimeoutError(\"Request timeout\", timeoutMs));\n }, timeoutMs);\n }\n\n return {\n controller,\n cleanup: () => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n }\n },\n };\n}\n\n/**\n * Sleep for a specified duration.\n * Optionally accepts an AbortSignal for cancellation.\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n\n const timeoutId = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timeoutId);\n reject(signal.reason);\n },\n { once: true },\n );\n });\n}\n","/**\n * Combines timeout + circuit breaker into a single request guard.\n */\n\nimport type { CircuitBreaker } from \"./circuit-breaker.js\";\nimport { withTimeout } from \"./timeout.js\";\n\nexport interface ProtectionOptions {\n /** Timeout in milliseconds (0 or undefined = no timeout) */\n timeoutMs?: number;\n /** Circuit breaker instance (optional) */\n breaker?: CircuitBreaker;\n /** Operation name for error messages */\n operationName?: string;\n}\n\n/**\n * Execute a function with timeout and circuit breaker protection.\n *\n * Order of execution:\n * 1. Circuit breaker check (fail fast if open)\n * 2. Timeout wrapper\n * 3. Execute function\n * 4. Update circuit breaker state\n */\nexport async function withProtection<T>(\n fn: () => Promise<T>,\n options: ProtectionOptions = {},\n): Promise<T> {\n const { timeoutMs, breaker, operationName = \"operation\" } = options;\n\n const execute = async (): Promise<T> => {\n if (timeoutMs && timeoutMs > 0) {\n return withTimeout(fn(), timeoutMs, `${operationName} timed out`);\n }\n return fn();\n };\n\n if (breaker) {\n return breaker.execute(execute);\n }\n\n return execute();\n}\n\n/**\n * Create a protected function that always applies the same protection options.\n */\nexport function createProtectedFn<TArgs extends unknown[], TResult>(\n fn: (...args: TArgs) => Promise<TResult>,\n options: ProtectionOptions,\n): (...args: TArgs) => Promise<TResult> {\n return (...args: TArgs) => withProtection(() => fn(...args), options);\n}\n"],"mappings":"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"}
@@ -316,5 +316,21 @@ declare const SendMessageRequestSchema: z.ZodObject<{
316
316
  }, z.core.$strip>;
317
317
  type SendMessageRequest = z.infer<typeof SendMessageRequestSchema>;
318
318
  //#endregion
319
- export { ReadTokenAuth as C, NoAuth as S, StaticTokenAuth as T, SessionScopedTokenPayloadSchema as _, CreateSessionResponse as a, AuthProvider as b, ProductSchema as c, ProjectScopedTokenPayloadSchema as d, ReadTokenPayload as f, SessionScopedTokenPayload as g, SendMessageRequestSchema as h, CreateSessionRequestSchema as i, ProductSecrets as l, SendMessageRequest as m, BatchScopedTokenPayloadSchema as n, Product as o, ReadTokenPayloadSchema as p, CreateSessionRequest as r, ProductAuthInfo as s, BatchScopedTokenPayload as t, ProjectScopedTokenPayload as u, TokenScope as v, RefreshableTokenAuth as w, CallbackAuth as x, TokenValidationResult as y };
320
- //# sourceMappingURL=types-Cd5jUzcr.d.ts.map
319
+ //#region src/auth/tokens-browser.d.ts
320
+ /**
321
+ * Decode a JWT without verifying it, to read claims for lookup.
322
+ * Returns null if the token is malformed.
323
+ */
324
+ declare function decodeToken(token: string): ReadTokenPayload | null;
325
+ /**
326
+ * Get time until token expires (in seconds).
327
+ * Returns negative if expired.
328
+ */
329
+ declare function getTokenTTL(payload: ReadTokenPayload): number;
330
+ /**
331
+ * Check if token is expiring soon (within buffer seconds).
332
+ */
333
+ declare function isTokenExpiringSoon(payload: ReadTokenPayload, bufferSeconds?: number): boolean;
334
+ //#endregion
335
+ export { AuthProvider as C, RefreshableTokenAuth as D, ReadTokenAuth as E, StaticTokenAuth as O, TokenValidationResult as S, NoAuth as T, SendMessageRequest as _, BatchScopedTokenPayloadSchema as a, SessionScopedTokenPayloadSchema as b, CreateSessionResponse as c, ProductSchema as d, ProductSecrets as f, ReadTokenPayloadSchema as g, ReadTokenPayload as h, BatchScopedTokenPayload as i, Product as l, ProjectScopedTokenPayloadSchema as m, getTokenTTL as n, CreateSessionRequest as o, ProjectScopedTokenPayload as p, isTokenExpiringSoon as r, CreateSessionRequestSchema as s, decodeToken as t, ProductAuthInfo as u, SendMessageRequestSchema as v, CallbackAuth as w, TokenScope as x, SessionScopedTokenPayload as y };
336
+ //# sourceMappingURL=tokens-browser-EiSc2dah.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens-browser-EiSc2dah.d.ts","names":[],"sources":["../src/auth/shared-providers.ts","../src/auth/types.ts","../src/auth/tokens-browser.ts"],"mappings":"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"}
@@ -1,5 +1,54 @@
1
- import { t as SDKError } from "./errors-DJ6U6cHY.js";
1
+ import { t as SDKError } from "./errors-RaBbEzQu.js";
2
2
  import { z } from "zod";
3
+ //#region src/auth/tokens-browser.ts
4
+ const textDecoder = new TextDecoder();
5
+ /** Base64URL decode (RFC 7515) to raw bytes. */
6
+ function base64UrlToBytes(data) {
7
+ const padded = data + "=".repeat((4 - data.length % 4) % 4);
8
+ const binary = atob(padded.replace(/-/g, "+").replace(/_/g, "/"));
9
+ const bytes = new Uint8Array(binary.length);
10
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
11
+ return bytes;
12
+ }
13
+ /**
14
+ * Base64URL decode to a UTF-8 string.
15
+ *
16
+ * `atob` answers one byte per code unit, so its result is the raw bytes and
17
+ * not yet text. A claim carrying any non-ASCII character — an account name, a
18
+ * project name, an email display name — is only recovered by decoding those
19
+ * bytes as UTF-8.
20
+ */
21
+ function base64UrlDecode(data) {
22
+ return textDecoder.decode(base64UrlToBytes(data));
23
+ }
24
+ /**
25
+ * Decode a JWT without verifying it, to read claims for lookup.
26
+ * Returns null if the token is malformed.
27
+ */
28
+ function decodeToken(token) {
29
+ try {
30
+ const parts = token.split(".");
31
+ if (parts.length !== 3) return null;
32
+ return JSON.parse(base64UrlDecode(parts[1]));
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+ /**
38
+ * Get time until token expires (in seconds).
39
+ * Returns negative if expired.
40
+ */
41
+ function getTokenTTL(payload) {
42
+ const now = Math.floor(Date.now() / 1e3);
43
+ return payload.exp - now;
44
+ }
45
+ /**
46
+ * Check if token is expiring soon (within buffer seconds).
47
+ */
48
+ function isTokenExpiringSoon(payload, bufferSeconds = 60) {
49
+ return getTokenTTL(payload) <= bufferSeconds;
50
+ }
51
+ //#endregion
3
52
  //#region src/auth/shared-providers.ts
4
53
  /**
5
54
  * Shared Auth Providers
@@ -326,6 +375,6 @@ const SendMessageRequestSchema = z.object({
326
375
  metadata: z.object({ model: z.string().optional() }).passthrough().optional()
327
376
  });
328
377
  //#endregion
329
- export { ReadTokenPayloadSchema as a, CallbackAuth as c, RefreshableTokenAuth as d, StaticTokenAuth as f, ProjectScopedTokenPayloadSchema as i, NoAuth as l, CreateSessionRequestSchema as n, SendMessageRequestSchema as o, ProductSchema as r, SessionScopedTokenPayloadSchema as s, BatchScopedTokenPayloadSchema as t, ReadTokenAuth as u };
378
+ export { isTokenExpiringSoon as _, ReadTokenPayloadSchema as a, CallbackAuth as c, RefreshableTokenAuth as d, StaticTokenAuth as f, getTokenTTL as g, decodeToken as h, ProjectScopedTokenPayloadSchema as i, NoAuth as l, base64UrlToBytes as m, CreateSessionRequestSchema as n, SendMessageRequestSchema as o, base64UrlDecode as p, ProductSchema as r, SessionScopedTokenPayloadSchema as s, BatchScopedTokenPayloadSchema as t, ReadTokenAuth as u };
330
379
 
331
- //# sourceMappingURL=types-DC6OJ1um.js.map
380
+ //# sourceMappingURL=types-Boft7Ohf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-Boft7Ohf.js","names":[],"sources":["../src/auth/tokens-browser.ts","../src/auth/shared-providers.ts","../src/auth/types.ts"],"sourcesContent":["/**\n * Reading a token, on any runtime.\n *\n * Inspecting a token needs no secret, so this half of the token surface is\n * portable: it uses only `atob`, `TextDecoder`, and `Date`, all of which a\n * browser and Node both provide. The server module builds on it rather than\n * keeping a second copy, so `@tangle-network/agent-core/auth` and\n * `@tangle-network/agent-core/auth/browser` cannot read one token two ways.\n */\n\nimport type { ReadTokenPayload } from \"./types.js\";\n\nconst textDecoder = new TextDecoder();\n\n/** Base64URL decode (RFC 7515) to raw bytes. */\nexport function base64UrlToBytes(data: string): Uint8Array {\n const padded = data + \"=\".repeat((4 - (data.length % 4)) % 4);\n const binary = atob(padded.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\n/**\n * Base64URL decode to a UTF-8 string.\n *\n * `atob` answers one byte per code unit, so its result is the raw bytes and\n * not yet text. A claim carrying any non-ASCII character — an account name, a\n * project name, an email display name — is only recovered by decoding those\n * bytes as UTF-8.\n */\nexport function base64UrlDecode(data: string): string {\n return textDecoder.decode(base64UrlToBytes(data));\n}\n\n/**\n * Decode a JWT without verifying it, to read claims for lookup.\n * Returns null if the token is malformed.\n */\nexport function decodeToken(token: string): ReadTokenPayload | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n return null;\n }\n\n const payload = JSON.parse(base64UrlDecode(parts[1]));\n return payload as ReadTokenPayload;\n } catch {\n return null;\n }\n}\n\n/**\n * Get time until token expires (in seconds).\n * Returns negative if expired.\n */\nexport function getTokenTTL(payload: ReadTokenPayload): number {\n const now = Math.floor(Date.now() / 1000);\n return payload.exp - now;\n}\n\n/**\n * Check if token is expiring soon (within buffer seconds).\n */\nexport function isTokenExpiringSoon(\n payload: ReadTokenPayload,\n bufferSeconds = 60,\n): boolean {\n return getTokenTTL(payload) <= bufferSeconds;\n}\n","/**\n * Shared Auth Providers\n *\n * Browser-safe auth classes shared between index.ts and browser.ts.\n * These classes have no node:crypto dependency and work in all environments.\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\n/** Auth provider interface */\nexport interface AuthProvider {\n /** Get current token (called on each request) */\n getToken(): Promise<string | null>;\n /** Refresh expired token */\n refreshToken?(): Promise<string>;\n /** Called when token is rejected by server */\n onTokenRejected?(error: SDKError): Promise<void>;\n}\n\n/** Simple static token auth */\nexport class StaticTokenAuth implements AuthProvider {\n constructor(private token: string) {}\n\n async getToken(): Promise<string> {\n return this.token;\n }\n\n setToken(token: string): void {\n this.token = token;\n }\n}\n\n/** Token with refresh capability */\nexport class RefreshableTokenAuth implements AuthProvider {\n private accessToken: string;\n private refreshTokenValue: string;\n private expiresAt: number;\n private refreshPromise: Promise<string> | null = null;\n\n constructor(\n private config: {\n accessToken: string;\n refreshToken: string;\n expiresIn: number; // seconds\n onRefresh: (refreshToken: string) => Promise<{\n accessToken: string;\n refreshToken?: string;\n expiresIn: number;\n }>;\n onRefreshFailed?: (error: Error) => void;\n /** Buffer before expiry to trigger refresh (default: 60s) */\n refreshBuffer?: number;\n },\n ) {\n this.accessToken = config.accessToken;\n this.refreshTokenValue = config.refreshToken;\n this.expiresAt = Date.now() + config.expiresIn * 1000;\n }\n\n async getToken(): Promise<string> {\n const buffer = (this.config.refreshBuffer ?? 60) * 1000;\n\n // Proactively refresh if close to expiry\n if (Date.now() > this.expiresAt - buffer) {\n return this.refreshToken();\n }\n\n return this.accessToken;\n }\n\n async refreshToken(): Promise<string> {\n // Deduplicate concurrent refresh calls\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.refreshPromise = this.doRefresh();\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n private async doRefresh(): Promise<string> {\n try {\n const result = await this.config.onRefresh(this.refreshTokenValue);\n this.accessToken = result.accessToken;\n if (result.refreshToken) {\n this.refreshTokenValue = result.refreshToken;\n }\n this.expiresAt = Date.now() + result.expiresIn * 1000;\n return this.accessToken;\n } catch (error) {\n this.config.onRefreshFailed?.(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new SDKError(\"Token refresh failed\", {\n code: \"AUTH\",\n retryable: false,\n cause: error instanceof Error ? error : undefined,\n });\n }\n }\n\n async onTokenRejected(error: SDKError): Promise<void> {\n // Force refresh on 401\n if (error.status === 401) {\n this.expiresAt = 0; // Mark as expired\n }\n }\n\n /** Check if token needs refresh */\n isExpired(): boolean {\n return Date.now() > this.expiresAt;\n }\n}\n\n/** No auth (for public endpoints) */\nexport class NoAuth implements AuthProvider {\n async getToken(): Promise<null> {\n return null;\n }\n}\n\n/**\n * Callback-based auth for dynamic token retrieval.\n * Useful when token comes from external source (user session, secure storage).\n */\nexport class CallbackAuth implements AuthProvider {\n constructor(\n private getTokenFn: () => string | null | Promise<string | null>,\n ) {}\n\n async getToken(): Promise<string | null> {\n return this.getTokenFn();\n }\n}\n\n/**\n * Read token auth for WebSocket connections.\n * Wraps a read token with refresh capability.\n */\nexport class ReadTokenAuth implements AuthProvider {\n private token: string;\n private expiresAt: number;\n private refreshPromise: Promise<string> | null = null;\n private expiryWarned = false;\n\n constructor(\n private config: {\n token: string;\n expiresAt: number; // Unix timestamp (seconds)\n onRefresh: () => Promise<{ token: string; expiresAt: number }>;\n onExpiring?: (secondsRemaining: number) => void;\n onRefreshFailed?: (error: Error) => void;\n /** Buffer before expiry to trigger refresh (default: 60s) */\n refreshBuffer?: number;\n /** Buffer before expiry to trigger warning (default: 60s) */\n warningBuffer?: number;\n },\n ) {\n this.token = config.token;\n this.expiresAt = config.expiresAt * 1000; // Convert to ms\n }\n\n async getToken(): Promise<string> {\n const now = Date.now();\n const refreshBuffer = (this.config.refreshBuffer ?? 60) * 1000;\n const warningBuffer = (this.config.warningBuffer ?? 60) * 1000;\n\n // Warn if expiring soon\n if (!this.expiryWarned && now > this.expiresAt - warningBuffer) {\n this.expiryWarned = true;\n const secondsRemaining = Math.floor((this.expiresAt - now) / 1000);\n this.config.onExpiring?.(secondsRemaining);\n }\n\n // Proactively refresh if close to expiry\n if (now > this.expiresAt - refreshBuffer) {\n return this.refreshToken();\n }\n\n return this.token;\n }\n\n async refreshToken(): Promise<string> {\n // Deduplicate concurrent refresh calls\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.refreshPromise = this.doRefresh();\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n private async doRefresh(): Promise<string> {\n try {\n const result = await this.config.onRefresh();\n this.token = result.token;\n this.expiresAt = result.expiresAt * 1000;\n this.expiryWarned = false;\n return this.token;\n } catch (error) {\n this.config.onRefreshFailed?.(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n }\n }\n\n /** Check if token is expired */\n isExpired(): boolean {\n return Date.now() > this.expiresAt;\n }\n\n /** Get seconds until expiry */\n getSecondsRemaining(): number {\n return Math.floor((this.expiresAt - Date.now()) / 1000);\n }\n}\n","/**\n * Auth Types\n *\n * Type definitions for multi-tenant authentication.\n */\n\nimport { z } from \"zod\";\n\n/**\n * Product configuration for multi-tenant orchestrator.\n */\nexport const ProductSchema = z\n .object({\n /** Unique product identifier (e.g., \"vibecode\", \"bgagents\") */\n product_id: z.string().min(1).max(64),\n\n /** Human-readable name */\n name: z.string().min(1).max(255),\n\n /** API key for backend → orchestrator auth (format: orch_prod_{random}) */\n api_key: z.string().min(32),\n\n /** Signing secrets for JWT tokens */\n secrets: z.object({\n current: z.object({\n secret: z.string().min(32),\n created_at: z.number(),\n }),\n previous: z\n .object({\n secret: z.string().min(32),\n created_at: z.number(),\n expires_at: z.number(),\n })\n .optional(),\n }),\n\n /** Token TTL configuration (minutes) */\n token_config: z.object({\n free_ttl_minutes: z.number().min(1).max(2880).default(15),\n paid_ttl_minutes: z.number().min(1).max(2880).default(240),\n max_ttl_minutes: z.number().min(1).max(2880).default(480),\n }),\n\n /** Per-product rate limits */\n rate_limits: z.object({\n max_concurrent_sessions: z.number().min(1).default(10000),\n max_sessions_per_minute: z.number().min(1).default(100),\n max_requests_per_minute: z.number().min(1).default(1000),\n }),\n\n /**\n * When true, skip all quota checks and usage metering.\n * Rate limits still enforced (DoS protection).\n * Use for internal/company accounts that don't go through billing.\n */\n unlimited: z.boolean().default(false),\n\n /**\n * Admin-only notes for internal tracking.\n * Examples: \"Engineering team\", \"Partner: Acme Corp\", \"Trial until 2024-03\"\n */\n internal_notes: z.string().max(1000).optional(),\n\n /** Optional webhook URL for notifications */\n webhook_url: z.string().url().optional(),\n\n /** Product status */\n status: z.enum([\"active\", \"suspended\", \"deleted\"]).default(\"active\"),\n\n /** Deployment type: shared (multi-tenant) or customer-hosted (on-prem) */\n deployment_type: z.enum([\"shared\", \"customer-hosted\"]).default(\"shared\"),\n\n /** Customer orchestrator configuration (for on-prem deployments) */\n customer_orchestrator: z\n .object({\n /** Customer's orchestrator URL (e.g., https://orch.customer.internal) */\n url: z.string().url().startsWith(\"https://\", {\n message: \"Customer orchestrator URL must use HTTPS\",\n }),\n /** Customer's orchestrator API key */\n api_key: z.string().min(32),\n })\n .optional(),\n\n /** Timestamps */\n created_at: z.string().datetime(),\n updated_at: z.string().datetime(),\n })\n .refine(\n (data) => {\n if (\n data.customer_orchestrator &&\n data.deployment_type !== \"customer-hosted\"\n ) {\n return false;\n }\n return true;\n },\n {\n message:\n \"customer_orchestrator can only be set when deployment_type is 'customer-hosted'\",\n path: [\"customer_orchestrator\"],\n },\n );\n\nexport type Product = z.infer<typeof ProductSchema>;\n\n/**\n * Token scope types for multi-tenant access control.\n *\n * - \"session\": Access to a single session (requires sid)\n * - \"project\": Access to all sessions within a project (requires projectId)\n * - \"batch\": Access to multiple projects (requires projectIds array)\n */\nexport type TokenScope = \"session\" | \"project\" | \"batch\";\n\n/**\n * Base token payload (common fields for all scopes).\n */\nconst BaseTokenPayloadSchema = z.object({\n /** Subject: User ID */\n sub: z.string().min(1),\n\n /** Product ID (used to look up signing secret) */\n pid: z.string().min(1),\n\n /** Sidecar/Container ID (optional, for direct routing) */\n cid: z.string().optional(),\n\n /** Token type (always \"read\" for read tokens) */\n typ: z.literal(\"read\"),\n\n /** Issued at (Unix timestamp, seconds) */\n iat: z.number(),\n\n /** Expires at (Unix timestamp, seconds) */\n exp: z.number(),\n});\n\n/**\n * Session-scoped token payload.\n * Grants access to a single session's events.\n */\nexport const SessionScopedTokenPayloadSchema = BaseTokenPayloadSchema.extend({\n /** Session ID - required for session-scoped tokens */\n sid: z.string().min(1),\n /** Project ID - must not be present for session-scoped tokens */\n projectId: z.undefined().optional(),\n /** Project IDs - must not be present for session-scoped tokens */\n projectIds: z.undefined().optional(),\n});\n\n/**\n * Project-scoped token payload.\n * Grants access to all sessions within a single project.\n */\nexport const ProjectScopedTokenPayloadSchema = BaseTokenPayloadSchema.extend({\n /** Session ID - must not be present for project-scoped tokens */\n sid: z.undefined().optional(),\n /** Project ID - required for project-scoped tokens */\n projectId: z.string().min(1),\n /** Project IDs - must not be present for project-scoped tokens */\n projectIds: z.undefined().optional(),\n});\n\n/**\n * Batch-scoped token payload.\n * Grants access to multiple projects (organization-level access).\n */\nexport const BatchScopedTokenPayloadSchema = BaseTokenPayloadSchema.extend({\n /** Session ID - must not be present for batch-scoped tokens */\n sid: z.undefined().optional(),\n /** Project ID - must not be present for batch-scoped tokens */\n projectId: z.undefined().optional(),\n /** Project IDs - required for batch-scoped tokens */\n projectIds: z.array(z.string().min(1)).min(1),\n});\n\n/**\n * Read token payload (JWT claims for WebSocket auth).\n *\n * Supports three scopes:\n * - Session: sid present (single session)\n * - Project: projectId present (all sessions in project)\n * - Batch: projectIds present (multiple projects)\n *\n * For backwards compatibility, tokens with sid are session-scoped.\n */\nexport const ReadTokenPayloadSchema = z.union([\n SessionScopedTokenPayloadSchema,\n ProjectScopedTokenPayloadSchema,\n BatchScopedTokenPayloadSchema,\n]);\n\nexport type ReadTokenPayload = z.infer<typeof ReadTokenPayloadSchema>;\nexport type SessionScopedTokenPayload = z.infer<\n typeof SessionScopedTokenPayloadSchema\n>;\nexport type ProjectScopedTokenPayload = z.infer<\n typeof ProjectScopedTokenPayloadSchema\n>;\nexport type BatchScopedTokenPayload = z.infer<\n typeof BatchScopedTokenPayloadSchema\n>;\n\n/**\n * Token validation result.\n */\nexport interface TokenValidationResult {\n valid: boolean;\n payload?: ReadTokenPayload;\n /** Token scope (session, project, or batch) */\n scope?: TokenScope;\n error?: string;\n errorCode?:\n | \"TOKEN_MISSING\"\n | \"TOKEN_INVALID\"\n | \"TOKEN_EXPIRED\"\n | \"TOKEN_WRONG_TYPE\"\n | \"PRODUCT_NOT_FOUND\"\n | \"PRODUCT_SUSPENDED\"\n | \"SIGNATURE_INVALID\";\n}\n\n/**\n * Product secrets structure.\n */\nexport interface ProductSecrets {\n current: {\n secret: string;\n created_at: number;\n };\n previous?: {\n secret: string;\n created_at: number;\n expires_at: number;\n };\n}\n\n/**\n * Minimal product info for token validation (avoids full product lookup).\n */\nexport interface ProductAuthInfo {\n product_id: string;\n secrets: ProductSecrets;\n status: \"active\" | \"suspended\" | \"deleted\";\n}\n\n/**\n * Session creation request from product backend.\n */\nexport const CreateSessionRequestSchema = z.object({\n user_id: z.string().min(1),\n project_ref: z.string().optional(),\n tier: z\n .enum([\"free\", \"pro\", \"enterprise\"])\n .or(z.literal(\"paid\").transform(() => \"pro\" as const))\n .default(\"free\"),\n config: z\n .object({\n image: z.string().optional(),\n env: z.record(z.string(), z.string()).optional(),\n resources: z\n .object({\n cpu: z.number().optional(),\n memory: z.string().optional(),\n })\n .optional(),\n })\n .optional(),\n});\n\nexport type CreateSessionRequest = z.infer<typeof CreateSessionRequestSchema>;\n\n/**\n * Session creation response to product backend.\n */\nexport interface CreateSessionResponse {\n session_id: string;\n sidecar_id: string;\n websocket_url: string;\n}\n\n/**\n * Message send request from product backend.\n *\n * `metadata` is intentionally free-form (.passthrough): the wire format\n * accepts arbitrary product-specific keys without requiring a schema bump.\n * Only `model` is named today; legacy gateway-credential fields previously\n * declared here were never read internally and have been dropped — any\n * such fields sent by older callers continue to flow through .passthrough.\n */\nexport const SendMessageRequestSchema = z.object({\n content: z.string(),\n user_id: z.string().min(1),\n execution_id: z.string().optional(),\n metadata: z\n .object({\n model: z.string().optional(),\n })\n .passthrough()\n .optional(),\n});\n\nexport type SendMessageRequest = z.infer<typeof SendMessageRequestSchema>;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-core",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Core transport interfaces, event utilities, auth, SSE parsing, and resilience primitives for talking to Tangle agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -138,7 +138,7 @@
138
138
  "LICENSE"
139
139
  ],
140
140
  "dependencies": {
141
- "@tangle-network/agent-interface": "0.56.0",
141
+ "@tangle-network/agent-interface": "^1.5.0",
142
142
  "zod": "4.4.3"
143
143
  },
144
144
  "peerDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser.d.ts","names":[],"sources":["../../src/auth/tokens-browser.ts"],"mappings":";;;;AAkDA;;;;;iBAlBgB,WAAA,CAAY,KAAA,WAAgB,gBAAA;;;;;iBAkB5B,WAAA,CAAY,OAAA,EAAS,gBAAA;;;;iBAQrB,mBAAA,CACd,OAAA,EAAS,gBAAA,EACT,aAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser.js","names":[],"sources":["../../src/auth/tokens-browser.ts"],"sourcesContent":["/**\n * Browser-safe Token Utilities\n *\n * Token decoding and inspection that works in both browser and Node.js.\n * Uses only standard APIs (atob/btoa or TextEncoder/TextDecoder).\n */\n\nimport type { ReadTokenPayload } from \"./types.js\";\n\n/**\n * Base64URL decode (browser-safe).\n */\nfunction base64UrlDecode(data: string): string {\n // Add padding if needed\n const padded = data + \"=\".repeat((4 - (data.length % 4)) % 4);\n // Convert base64url to base64\n const base64 = padded.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\n // Use atob in browser, Buffer in Node\n if (typeof atob === \"function\") {\n return atob(base64);\n }\n // Node.js fallback\n return Buffer.from(base64, \"base64\").toString();\n}\n\n/**\n * Decode a JWT without verification (to extract claims for lookup).\n * Returns null if the token is malformed.\n *\n * This is safe to use in browsers - it only parses the token, doesn't verify.\n */\nexport function decodeToken(token: string): ReadTokenPayload | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n return null;\n }\n\n const payload = JSON.parse(base64UrlDecode(parts[1]));\n return payload as ReadTokenPayload;\n } catch {\n return null;\n }\n}\n\n/**\n * Get time until token expires (in seconds).\n * Returns negative if expired.\n */\nexport function getTokenTTL(payload: ReadTokenPayload): number {\n const now = Math.floor(Date.now() / 1000);\n return payload.exp - now;\n}\n\n/**\n * Check if token is expiring soon (within buffer seconds).\n */\nexport function isTokenExpiringSoon(\n payload: ReadTokenPayload,\n bufferSeconds = 60,\n): boolean {\n return getTokenTTL(payload) <= bufferSeconds;\n}\n"],"mappings":";;;;;AAYA,SAAS,gBAAgB,MAAsB;CAI7C,MAAM,UAFS,OAAO,IAAI,QAAQ,IAAK,KAAK,SAAS,KAAM,EAAE,EAEvC,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI;AAG3D,KAAI,OAAO,SAAS,WAClB,QAAO,KAAK,OAAO;AAGrB,QAAO,OAAO,KAAK,QAAQ,SAAS,CAAC,UAAU;;;;;;;;AASjD,SAAgB,YAAY,OAAwC;AAClE,KAAI;EACF,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,MAAI,MAAM,WAAW,EACnB,QAAO;AAIT,SADgB,KAAK,MAAM,gBAAgB,MAAM,GAAG,CACtC;SACR;AACN,SAAO;;;;;;;AAQX,SAAgB,YAAY,SAAmC;CAC7D,MAAM,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AACzC,QAAO,QAAQ,MAAM;;;;;AAMvB,SAAgB,oBACd,SACA,gBAAgB,IACP;AACT,QAAO,YAAY,QAAQ,IAAI"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-Cd5jUzcr.d.ts","names":[],"sources":["../src/auth/shared-providers.ts","../src/auth/types.ts"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-DC6OJ1um.js","names":[],"sources":["../src/auth/shared-providers.ts","../src/auth/types.ts"],"sourcesContent":["/**\n * Shared Auth Providers\n *\n * Browser-safe auth classes shared between index.ts and browser.ts.\n * These classes have no node:crypto dependency and work in all environments.\n */\n\nimport { SDKError } from \"../errors/index.js\";\n\n/** Auth provider interface */\nexport interface AuthProvider {\n /** Get current token (called on each request) */\n getToken(): Promise<string | null>;\n /** Refresh expired token */\n refreshToken?(): Promise<string>;\n /** Called when token is rejected by server */\n onTokenRejected?(error: SDKError): Promise<void>;\n}\n\n/** Simple static token auth */\nexport class StaticTokenAuth implements AuthProvider {\n constructor(private token: string) {}\n\n async getToken(): Promise<string> {\n return this.token;\n }\n\n setToken(token: string): void {\n this.token = token;\n }\n}\n\n/** Token with refresh capability */\nexport class RefreshableTokenAuth implements AuthProvider {\n private accessToken: string;\n private refreshTokenValue: string;\n private expiresAt: number;\n private refreshPromise: Promise<string> | null = null;\n\n constructor(\n private config: {\n accessToken: string;\n refreshToken: string;\n expiresIn: number; // seconds\n onRefresh: (refreshToken: string) => Promise<{\n accessToken: string;\n refreshToken?: string;\n expiresIn: number;\n }>;\n onRefreshFailed?: (error: Error) => void;\n /** Buffer before expiry to trigger refresh (default: 60s) */\n refreshBuffer?: number;\n },\n ) {\n this.accessToken = config.accessToken;\n this.refreshTokenValue = config.refreshToken;\n this.expiresAt = Date.now() + config.expiresIn * 1000;\n }\n\n async getToken(): Promise<string> {\n const buffer = (this.config.refreshBuffer ?? 60) * 1000;\n\n // Proactively refresh if close to expiry\n if (Date.now() > this.expiresAt - buffer) {\n return this.refreshToken();\n }\n\n return this.accessToken;\n }\n\n async refreshToken(): Promise<string> {\n // Deduplicate concurrent refresh calls\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.refreshPromise = this.doRefresh();\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n private async doRefresh(): Promise<string> {\n try {\n const result = await this.config.onRefresh(this.refreshTokenValue);\n this.accessToken = result.accessToken;\n if (result.refreshToken) {\n this.refreshTokenValue = result.refreshToken;\n }\n this.expiresAt = Date.now() + result.expiresIn * 1000;\n return this.accessToken;\n } catch (error) {\n this.config.onRefreshFailed?.(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new SDKError(\"Token refresh failed\", {\n code: \"AUTH\",\n retryable: false,\n cause: error instanceof Error ? error : undefined,\n });\n }\n }\n\n async onTokenRejected(error: SDKError): Promise<void> {\n // Force refresh on 401\n if (error.status === 401) {\n this.expiresAt = 0; // Mark as expired\n }\n }\n\n /** Check if token needs refresh */\n isExpired(): boolean {\n return Date.now() > this.expiresAt;\n }\n}\n\n/** No auth (for public endpoints) */\nexport class NoAuth implements AuthProvider {\n async getToken(): Promise<null> {\n return null;\n }\n}\n\n/**\n * Callback-based auth for dynamic token retrieval.\n * Useful when token comes from external source (user session, secure storage).\n */\nexport class CallbackAuth implements AuthProvider {\n constructor(\n private getTokenFn: () => string | null | Promise<string | null>,\n ) {}\n\n async getToken(): Promise<string | null> {\n return this.getTokenFn();\n }\n}\n\n/**\n * Read token auth for WebSocket connections.\n * Wraps a read token with refresh capability.\n */\nexport class ReadTokenAuth implements AuthProvider {\n private token: string;\n private expiresAt: number;\n private refreshPromise: Promise<string> | null = null;\n private expiryWarned = false;\n\n constructor(\n private config: {\n token: string;\n expiresAt: number; // Unix timestamp (seconds)\n onRefresh: () => Promise<{ token: string; expiresAt: number }>;\n onExpiring?: (secondsRemaining: number) => void;\n onRefreshFailed?: (error: Error) => void;\n /** Buffer before expiry to trigger refresh (default: 60s) */\n refreshBuffer?: number;\n /** Buffer before expiry to trigger warning (default: 60s) */\n warningBuffer?: number;\n },\n ) {\n this.token = config.token;\n this.expiresAt = config.expiresAt * 1000; // Convert to ms\n }\n\n async getToken(): Promise<string> {\n const now = Date.now();\n const refreshBuffer = (this.config.refreshBuffer ?? 60) * 1000;\n const warningBuffer = (this.config.warningBuffer ?? 60) * 1000;\n\n // Warn if expiring soon\n if (!this.expiryWarned && now > this.expiresAt - warningBuffer) {\n this.expiryWarned = true;\n const secondsRemaining = Math.floor((this.expiresAt - now) / 1000);\n this.config.onExpiring?.(secondsRemaining);\n }\n\n // Proactively refresh if close to expiry\n if (now > this.expiresAt - refreshBuffer) {\n return this.refreshToken();\n }\n\n return this.token;\n }\n\n async refreshToken(): Promise<string> {\n // Deduplicate concurrent refresh calls\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.refreshPromise = this.doRefresh();\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n }\n }\n\n private async doRefresh(): Promise<string> {\n try {\n const result = await this.config.onRefresh();\n this.token = result.token;\n this.expiresAt = result.expiresAt * 1000;\n this.expiryWarned = false;\n return this.token;\n } catch (error) {\n this.config.onRefreshFailed?.(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n }\n }\n\n /** Check if token is expired */\n isExpired(): boolean {\n return Date.now() > this.expiresAt;\n }\n\n /** Get seconds until expiry */\n getSecondsRemaining(): number {\n return Math.floor((this.expiresAt - Date.now()) / 1000);\n }\n}\n","/**\n * Auth Types\n *\n * Type definitions for multi-tenant authentication.\n */\n\nimport { z } from \"zod\";\n\n/**\n * Product configuration for multi-tenant orchestrator.\n */\nexport const ProductSchema = z\n .object({\n /** Unique product identifier (e.g., \"vibecode\", \"bgagents\") */\n product_id: z.string().min(1).max(64),\n\n /** Human-readable name */\n name: z.string().min(1).max(255),\n\n /** API key for backend → orchestrator auth (format: orch_prod_{random}) */\n api_key: z.string().min(32),\n\n /** Signing secrets for JWT tokens */\n secrets: z.object({\n current: z.object({\n secret: z.string().min(32),\n created_at: z.number(),\n }),\n previous: z\n .object({\n secret: z.string().min(32),\n created_at: z.number(),\n expires_at: z.number(),\n })\n .optional(),\n }),\n\n /** Token TTL configuration (minutes) */\n token_config: z.object({\n free_ttl_minutes: z.number().min(1).max(2880).default(15),\n paid_ttl_minutes: z.number().min(1).max(2880).default(240),\n max_ttl_minutes: z.number().min(1).max(2880).default(480),\n }),\n\n /** Per-product rate limits */\n rate_limits: z.object({\n max_concurrent_sessions: z.number().min(1).default(10000),\n max_sessions_per_minute: z.number().min(1).default(100),\n max_requests_per_minute: z.number().min(1).default(1000),\n }),\n\n /**\n * When true, skip all quota checks and usage metering.\n * Rate limits still enforced (DoS protection).\n * Use for internal/company accounts that don't go through billing.\n */\n unlimited: z.boolean().default(false),\n\n /**\n * Admin-only notes for internal tracking.\n * Examples: \"Engineering team\", \"Partner: Acme Corp\", \"Trial until 2024-03\"\n */\n internal_notes: z.string().max(1000).optional(),\n\n /** Optional webhook URL for notifications */\n webhook_url: z.string().url().optional(),\n\n /** Product status */\n status: z.enum([\"active\", \"suspended\", \"deleted\"]).default(\"active\"),\n\n /** Deployment type: shared (multi-tenant) or customer-hosted (on-prem) */\n deployment_type: z.enum([\"shared\", \"customer-hosted\"]).default(\"shared\"),\n\n /** Customer orchestrator configuration (for on-prem deployments) */\n customer_orchestrator: z\n .object({\n /** Customer's orchestrator URL (e.g., https://orch.customer.internal) */\n url: z.string().url().startsWith(\"https://\", {\n message: \"Customer orchestrator URL must use HTTPS\",\n }),\n /** Customer's orchestrator API key */\n api_key: z.string().min(32),\n })\n .optional(),\n\n /** Timestamps */\n created_at: z.string().datetime(),\n updated_at: z.string().datetime(),\n })\n .refine(\n (data) => {\n if (\n data.customer_orchestrator &&\n data.deployment_type !== \"customer-hosted\"\n ) {\n return false;\n }\n return true;\n },\n {\n message:\n \"customer_orchestrator can only be set when deployment_type is 'customer-hosted'\",\n path: [\"customer_orchestrator\"],\n },\n );\n\nexport type Product = z.infer<typeof ProductSchema>;\n\n/**\n * Token scope types for multi-tenant access control.\n *\n * - \"session\": Access to a single session (requires sid)\n * - \"project\": Access to all sessions within a project (requires projectId)\n * - \"batch\": Access to multiple projects (requires projectIds array)\n */\nexport type TokenScope = \"session\" | \"project\" | \"batch\";\n\n/**\n * Base token payload (common fields for all scopes).\n */\nconst BaseTokenPayloadSchema = z.object({\n /** Subject: User ID */\n sub: z.string().min(1),\n\n /** Product ID (used to look up signing secret) */\n pid: z.string().min(1),\n\n /** Sidecar/Container ID (optional, for direct routing) */\n cid: z.string().optional(),\n\n /** Token type (always \"read\" for read tokens) */\n typ: z.literal(\"read\"),\n\n /** Issued at (Unix timestamp, seconds) */\n iat: z.number(),\n\n /** Expires at (Unix timestamp, seconds) */\n exp: z.number(),\n});\n\n/**\n * Session-scoped token payload.\n * Grants access to a single session's events.\n */\nexport const SessionScopedTokenPayloadSchema = BaseTokenPayloadSchema.extend({\n /** Session ID - required for session-scoped tokens */\n sid: z.string().min(1),\n /** Project ID - must not be present for session-scoped tokens */\n projectId: z.undefined().optional(),\n /** Project IDs - must not be present for session-scoped tokens */\n projectIds: z.undefined().optional(),\n});\n\n/**\n * Project-scoped token payload.\n * Grants access to all sessions within a single project.\n */\nexport const ProjectScopedTokenPayloadSchema = BaseTokenPayloadSchema.extend({\n /** Session ID - must not be present for project-scoped tokens */\n sid: z.undefined().optional(),\n /** Project ID - required for project-scoped tokens */\n projectId: z.string().min(1),\n /** Project IDs - must not be present for project-scoped tokens */\n projectIds: z.undefined().optional(),\n});\n\n/**\n * Batch-scoped token payload.\n * Grants access to multiple projects (organization-level access).\n */\nexport const BatchScopedTokenPayloadSchema = BaseTokenPayloadSchema.extend({\n /** Session ID - must not be present for batch-scoped tokens */\n sid: z.undefined().optional(),\n /** Project ID - must not be present for batch-scoped tokens */\n projectId: z.undefined().optional(),\n /** Project IDs - required for batch-scoped tokens */\n projectIds: z.array(z.string().min(1)).min(1),\n});\n\n/**\n * Read token payload (JWT claims for WebSocket auth).\n *\n * Supports three scopes:\n * - Session: sid present (single session)\n * - Project: projectId present (all sessions in project)\n * - Batch: projectIds present (multiple projects)\n *\n * For backwards compatibility, tokens with sid are session-scoped.\n */\nexport const ReadTokenPayloadSchema = z.union([\n SessionScopedTokenPayloadSchema,\n ProjectScopedTokenPayloadSchema,\n BatchScopedTokenPayloadSchema,\n]);\n\nexport type ReadTokenPayload = z.infer<typeof ReadTokenPayloadSchema>;\nexport type SessionScopedTokenPayload = z.infer<\n typeof SessionScopedTokenPayloadSchema\n>;\nexport type ProjectScopedTokenPayload = z.infer<\n typeof ProjectScopedTokenPayloadSchema\n>;\nexport type BatchScopedTokenPayload = z.infer<\n typeof BatchScopedTokenPayloadSchema\n>;\n\n/**\n * Token validation result.\n */\nexport interface TokenValidationResult {\n valid: boolean;\n payload?: ReadTokenPayload;\n /** Token scope (session, project, or batch) */\n scope?: TokenScope;\n error?: string;\n errorCode?:\n | \"TOKEN_MISSING\"\n | \"TOKEN_INVALID\"\n | \"TOKEN_EXPIRED\"\n | \"TOKEN_WRONG_TYPE\"\n | \"PRODUCT_NOT_FOUND\"\n | \"PRODUCT_SUSPENDED\"\n | \"SIGNATURE_INVALID\";\n}\n\n/**\n * Product secrets structure.\n */\nexport interface ProductSecrets {\n current: {\n secret: string;\n created_at: number;\n };\n previous?: {\n secret: string;\n created_at: number;\n expires_at: number;\n };\n}\n\n/**\n * Minimal product info for token validation (avoids full product lookup).\n */\nexport interface ProductAuthInfo {\n product_id: string;\n secrets: ProductSecrets;\n status: \"active\" | \"suspended\" | \"deleted\";\n}\n\n/**\n * Session creation request from product backend.\n */\nexport const CreateSessionRequestSchema = z.object({\n user_id: z.string().min(1),\n project_ref: z.string().optional(),\n tier: z\n .enum([\"free\", \"pro\", \"enterprise\"])\n .or(z.literal(\"paid\").transform(() => \"pro\" as const))\n .default(\"free\"),\n config: z\n .object({\n image: z.string().optional(),\n env: z.record(z.string(), z.string()).optional(),\n resources: z\n .object({\n cpu: z.number().optional(),\n memory: z.string().optional(),\n })\n .optional(),\n })\n .optional(),\n});\n\nexport type CreateSessionRequest = z.infer<typeof CreateSessionRequestSchema>;\n\n/**\n * Session creation response to product backend.\n */\nexport interface CreateSessionResponse {\n session_id: string;\n sidecar_id: string;\n websocket_url: string;\n}\n\n/**\n * Message send request from product backend.\n *\n * `metadata` is intentionally free-form (.passthrough): the wire format\n * accepts arbitrary product-specific keys without requiring a schema bump.\n * Only `model` is named today; legacy gateway-credential fields previously\n * declared here were never read internally and have been dropped — any\n * such fields sent by older callers continue to flow through .passthrough.\n */\nexport const SendMessageRequestSchema = z.object({\n content: z.string(),\n user_id: z.string().min(1),\n execution_id: z.string().optional(),\n metadata: z\n .object({\n model: z.string().optional(),\n })\n .passthrough()\n .optional(),\n});\n\nexport type SendMessageRequest = z.infer<typeof SendMessageRequestSchema>;\n"],"mappings":";;;;;;;;;;AAoBA,IAAa,kBAAb,MAAqD;CACnD,YAAY,OAAuB;AAAf,OAAA,QAAA;;CAEpB,MAAM,WAA4B;AAChC,SAAO,KAAK;;CAGd,SAAS,OAAqB;AAC5B,OAAK,QAAQ;;;;AAKjB,IAAa,uBAAb,MAA0D;CACxD;CACA;CACA;CACA,iBAAiD;CAEjD,YACE,QAaA;AAbQ,OAAA,SAAA;AAcR,OAAK,cAAc,OAAO;AAC1B,OAAK,oBAAoB,OAAO;AAChC,OAAK,YAAY,KAAK,KAAK,GAAG,OAAO,YAAY;;CAGnD,MAAM,WAA4B;EAChC,MAAM,UAAU,KAAK,OAAO,iBAAiB,MAAM;AAGnD,MAAI,KAAK,KAAK,GAAG,KAAK,YAAY,OAChC,QAAO,KAAK,cAAc;AAG5B,SAAO,KAAK;;CAGd,MAAM,eAAgC;AAEpC,MAAI,KAAK,eACP,QAAO,KAAK;AAGd,OAAK,iBAAiB,KAAK,WAAW;AAEtC,MAAI;AACF,UAAO,MAAM,KAAK;YACV;AACR,QAAK,iBAAiB;;;CAI1B,MAAc,YAA6B;AACzC,MAAI;GACF,MAAM,SAAS,MAAM,KAAK,OAAO,UAAU,KAAK,kBAAkB;AAClE,QAAK,cAAc,OAAO;AAC1B,OAAI,OAAO,aACT,MAAK,oBAAoB,OAAO;AAElC,QAAK,YAAY,KAAK,KAAK,GAAG,OAAO,YAAY;AACjD,UAAO,KAAK;WACL,OAAO;AACd,QAAK,OAAO,kBACV,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAC1D;AACD,SAAM,IAAI,SAAS,wBAAwB;IACzC,MAAM;IACN,WAAW;IACX,OAAO,iBAAiB,QAAQ,QAAQ,KAAA;IACzC,CAAC;;;CAIN,MAAM,gBAAgB,OAAgC;AAEpD,MAAI,MAAM,WAAW,IACnB,MAAK,YAAY;;;CAKrB,YAAqB;AACnB,SAAO,KAAK,KAAK,GAAG,KAAK;;;;AAK7B,IAAa,SAAb,MAA4C;CAC1C,MAAM,WAA0B;AAC9B,SAAO;;;;;;;AAQX,IAAa,eAAb,MAAkD;CAChD,YACE,YACA;AADQ,OAAA,aAAA;;CAGV,MAAM,WAAmC;AACvC,SAAO,KAAK,YAAY;;;;;;;AAQ5B,IAAa,gBAAb,MAAmD;CACjD;CACA;CACA,iBAAiD;CACjD,eAAuB;CAEvB,YACE,QAWA;AAXQ,OAAA,SAAA;AAYR,OAAK,QAAQ,OAAO;AACpB,OAAK,YAAY,OAAO,YAAY;;CAGtC,MAAM,WAA4B;EAChC,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,iBAAiB,KAAK,OAAO,iBAAiB,MAAM;EAC1D,MAAM,iBAAiB,KAAK,OAAO,iBAAiB,MAAM;AAG1D,MAAI,CAAC,KAAK,gBAAgB,MAAM,KAAK,YAAY,eAAe;AAC9D,QAAK,eAAe;GACpB,MAAM,mBAAmB,KAAK,OAAO,KAAK,YAAY,OAAO,IAAK;AAClE,QAAK,OAAO,aAAa,iBAAiB;;AAI5C,MAAI,MAAM,KAAK,YAAY,cACzB,QAAO,KAAK,cAAc;AAG5B,SAAO,KAAK;;CAGd,MAAM,eAAgC;AAEpC,MAAI,KAAK,eACP,QAAO,KAAK;AAGd,OAAK,iBAAiB,KAAK,WAAW;AAEtC,MAAI;AACF,UAAO,MAAM,KAAK;YACV;AACR,QAAK,iBAAiB;;;CAI1B,MAAc,YAA6B;AACzC,MAAI;GACF,MAAM,SAAS,MAAM,KAAK,OAAO,WAAW;AAC5C,QAAK,QAAQ,OAAO;AACpB,QAAK,YAAY,OAAO,YAAY;AACpC,QAAK,eAAe;AACpB,UAAO,KAAK;WACL,OAAO;AACd,QAAK,OAAO,kBACV,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAC1D;AACD,SAAM;;;;CAKV,YAAqB;AACnB,SAAO,KAAK,KAAK,GAAG,KAAK;;;CAI3B,sBAA8B;AAC5B,SAAO,KAAK,OAAO,KAAK,YAAY,KAAK,KAAK,IAAI,IAAK;;;;;;;;;;;;;ACrN3D,MAAa,gBAAgB,EAC1B,OAAO;;CAEN,YAAY,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,GAAG;;CAGrC,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI;;CAGhC,SAAS,EAAE,QAAQ,CAAC,IAAI,GAAG;;CAG3B,SAAS,EAAE,OAAO;EAChB,SAAS,EAAE,OAAO;GAChB,QAAQ,EAAE,QAAQ,CAAC,IAAI,GAAG;GAC1B,YAAY,EAAE,QAAQ;GACvB,CAAC;EACF,UAAU,EACP,OAAO;GACN,QAAQ,EAAE,QAAQ,CAAC,IAAI,GAAG;GAC1B,YAAY,EAAE,QAAQ;GACtB,YAAY,EAAE,QAAQ;GACvB,CAAC,CACD,UAAU;EACd,CAAC;;CAGF,cAAc,EAAE,OAAO;EACrB,kBAAkB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,CAAC,QAAQ,GAAG;EACzD,kBAAkB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,CAAC,QAAQ,IAAI;EAC1D,iBAAiB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,CAAC,QAAQ,IAAI;EAC1D,CAAC;;CAGF,aAAa,EAAE,OAAO;EACpB,yBAAyB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,IAAM;EACzD,yBAAyB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,IAAI;EACvD,yBAAyB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,IAAK;EACzD,CAAC;;;;;;CAOF,WAAW,EAAE,SAAS,CAAC,QAAQ,MAAM;;;;;CAMrC,gBAAgB,EAAE,QAAQ,CAAC,IAAI,IAAK,CAAC,UAAU;;CAG/C,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU;;CAGxC,QAAQ,EAAE,KAAK;EAAC;EAAU;EAAa;EAAU,CAAC,CAAC,QAAQ,SAAS;;CAGpE,iBAAiB,EAAE,KAAK,CAAC,UAAU,kBAAkB,CAAC,CAAC,QAAQ,SAAS;;CAGxE,uBAAuB,EACpB,OAAO;;EAEN,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,YAAY,EAC3C,SAAS,4CACV,CAAC;;EAEF,SAAS,EAAE,QAAQ,CAAC,IAAI,GAAG;EAC5B,CAAC,CACD,UAAU;;CAGb,YAAY,EAAE,QAAQ,CAAC,UAAU;CACjC,YAAY,EAAE,QAAQ,CAAC,UAAU;CAClC,CAAC,CACD,QACE,SAAS;AACR,KACE,KAAK,yBACL,KAAK,oBAAoB,kBAEzB,QAAO;AAET,QAAO;GAET;CACE,SACE;CACF,MAAM,CAAC,wBAAwB;CAChC,CACF;;;;AAgBH,MAAM,yBAAyB,EAAE,OAAO;;CAEtC,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE;;CAGtB,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE;;CAGtB,KAAK,EAAE,QAAQ,CAAC,UAAU;;CAG1B,KAAK,EAAE,QAAQ,OAAO;;CAGtB,KAAK,EAAE,QAAQ;;CAGf,KAAK,EAAE,QAAQ;CAChB,CAAC;;;;;AAMF,MAAa,kCAAkC,uBAAuB,OAAO;;CAE3E,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE;;CAEtB,WAAW,EAAE,WAAW,CAAC,UAAU;;CAEnC,YAAY,EAAE,WAAW,CAAC,UAAU;CACrC,CAAC;;;;;AAMF,MAAa,kCAAkC,uBAAuB,OAAO;;CAE3E,KAAK,EAAE,WAAW,CAAC,UAAU;;CAE7B,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE;;CAE5B,YAAY,EAAE,WAAW,CAAC,UAAU;CACrC,CAAC;;;;;AAMF,MAAa,gCAAgC,uBAAuB,OAAO;;CAEzE,KAAK,EAAE,WAAW,CAAC,UAAU;;CAE7B,WAAW,EAAE,WAAW,CAAC,UAAU;;CAEnC,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;CAC9C,CAAC;;;;;;;;;;;AAYF,MAAa,yBAAyB,EAAE,MAAM;CAC5C;CACA;CACA;CACD,CAAC;;;;AA2DF,MAAa,6BAA6B,EAAE,OAAO;CACjD,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC1B,aAAa,EAAE,QAAQ,CAAC,UAAU;CAClC,MAAM,EACH,KAAK;EAAC;EAAQ;EAAO;EAAa,CAAC,CACnC,GAAG,EAAE,QAAQ,OAAO,CAAC,gBAAgB,MAAe,CAAC,CACrD,QAAQ,OAAO;CAClB,QAAQ,EACL,OAAO;EACN,OAAO,EAAE,QAAQ,CAAC,UAAU;EAC5B,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC,UAAU;EAChD,WAAW,EACR,OAAO;GACN,KAAK,EAAE,QAAQ,CAAC,UAAU;GAC1B,QAAQ,EAAE,QAAQ,CAAC,UAAU;GAC9B,CAAC,CACD,UAAU;EACd,CAAC,CACD,UAAU;CACd,CAAC;;;;;;;;;;AAsBF,MAAa,2BAA2B,EAAE,OAAO;CAC/C,SAAS,EAAE,QAAQ;CACnB,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE;CAC1B,cAAc,EAAE,QAAQ,CAAC,UAAU;CACnC,UAAU,EACP,OAAO,EACN,OAAO,EAAE,QAAQ,CAAC,UAAU,EAC7B,CAAC,CACD,aAAa,CACb,UAAU;CACd,CAAC"}