@hotcell/daemon 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/agent-check.d.ts +1 -0
  2. package/dist/agent-check.js +105 -0
  3. package/dist/agent-check.js.map +1 -0
  4. package/dist/api/server.d.ts +64 -0
  5. package/dist/api/server.js +1368 -0
  6. package/dist/api/server.js.map +1 -0
  7. package/dist/api/ws.d.ts +14 -0
  8. package/dist/api/ws.js +214 -0
  9. package/dist/api/ws.js.map +1 -0
  10. package/dist/applevz-check.d.ts +1 -0
  11. package/dist/applevz-check.js +280 -0
  12. package/dist/applevz-check.js.map +1 -0
  13. package/dist/backups.d.ts +29 -0
  14. package/dist/backups.js +76 -0
  15. package/dist/backups.js.map +1 -0
  16. package/dist/capacity.d.ts +63 -0
  17. package/dist/capacity.js +95 -0
  18. package/dist/capacity.js.map +1 -0
  19. package/dist/config.d.ts +241 -0
  20. package/dist/config.js +152 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/cost.d.ts +9 -0
  23. package/dist/cost.js +19 -0
  24. package/dist/cost.js.map +1 -0
  25. package/dist/density-bench.d.ts +1 -0
  26. package/dist/density-bench.js +97 -0
  27. package/dist/density-bench.js.map +1 -0
  28. package/dist/driver/agent-driver.d.ts +67 -0
  29. package/dist/driver/agent-driver.js +197 -0
  30. package/dist/driver/agent-driver.js.map +1 -0
  31. package/dist/driver/agent.d.ts +94 -0
  32. package/dist/driver/agent.js +319 -0
  33. package/dist/driver/agent.js.map +1 -0
  34. package/dist/driver/applevz.d.ts +95 -0
  35. package/dist/driver/applevz.js +527 -0
  36. package/dist/driver/applevz.js.map +1 -0
  37. package/dist/driver/container.d.ts +99 -0
  38. package/dist/driver/container.js +733 -0
  39. package/dist/driver/container.js.map +1 -0
  40. package/dist/driver/fc-api.d.ts +85 -0
  41. package/dist/driver/fc-api.js +173 -0
  42. package/dist/driver/fc-api.js.map +1 -0
  43. package/dist/driver/firecracker.d.ts +126 -0
  44. package/dist/driver/firecracker.js +560 -0
  45. package/dist/driver/firecracker.js.map +1 -0
  46. package/dist/driver/index.d.ts +17 -0
  47. package/dist/driver/index.js +61 -0
  48. package/dist/driver/index.js.map +1 -0
  49. package/dist/driver/router.d.ts +82 -0
  50. package/dist/driver/router.js +159 -0
  51. package/dist/driver/router.js.map +1 -0
  52. package/dist/driver/types.d.ts +234 -0
  53. package/dist/driver/types.js +2 -0
  54. package/dist/driver/types.js.map +1 -0
  55. package/dist/driver/unsupported.d.ts +50 -0
  56. package/dist/driver/unsupported.js +81 -0
  57. package/dist/driver/unsupported.js.map +1 -0
  58. package/dist/driver/vz-image.d.ts +35 -0
  59. package/dist/driver/vz-image.js +75 -0
  60. package/dist/driver/vz-image.js.map +1 -0
  61. package/dist/e2e-harness.d.ts +1 -0
  62. package/dist/e2e-harness.js +99 -0
  63. package/dist/e2e-harness.js.map +1 -0
  64. package/dist/egress-check.d.ts +8 -0
  65. package/dist/egress-check.js +317 -0
  66. package/dist/egress-check.js.map +1 -0
  67. package/dist/fc-check.d.ts +1 -0
  68. package/dist/fc-check.js +113 -0
  69. package/dist/fc-check.js.map +1 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +114 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/kernels.d.ts +22 -0
  74. package/dist/kernels.js +134 -0
  75. package/dist/kernels.js.map +1 -0
  76. package/dist/lifecycle.d.ts +49 -0
  77. package/dist/lifecycle.js +111 -0
  78. package/dist/lifecycle.js.map +1 -0
  79. package/dist/logger.d.ts +16 -0
  80. package/dist/logger.js +67 -0
  81. package/dist/logger.js.map +1 -0
  82. package/dist/metrics.d.ts +46 -0
  83. package/dist/metrics.js +92 -0
  84. package/dist/metrics.js.map +1 -0
  85. package/dist/net/firewall.d.ts +4 -0
  86. package/dist/net/firewall.js +80 -0
  87. package/dist/net/firewall.js.map +1 -0
  88. package/dist/pricing.d.ts +36 -0
  89. package/dist/pricing.js +81 -0
  90. package/dist/pricing.js.map +1 -0
  91. package/dist/proxy/allowlist-default.d.ts +23 -0
  92. package/dist/proxy/allowlist-default.js +53 -0
  93. package/dist/proxy/allowlist-default.js.map +1 -0
  94. package/dist/proxy/allowlist.d.ts +43 -0
  95. package/dist/proxy/allowlist.js +87 -0
  96. package/dist/proxy/allowlist.js.map +1 -0
  97. package/dist/proxy/egress.d.ts +49 -0
  98. package/dist/proxy/egress.js +599 -0
  99. package/dist/proxy/egress.js.map +1 -0
  100. package/dist/proxy/ratelimit.d.ts +49 -0
  101. package/dist/proxy/ratelimit.js +74 -0
  102. package/dist/proxy/ratelimit.js.map +1 -0
  103. package/dist/proxy/server.d.ts +26 -0
  104. package/dist/proxy/server.js +202 -0
  105. package/dist/proxy/server.js.map +1 -0
  106. package/dist/router-check.d.ts +1 -0
  107. package/dist/router-check.js +97 -0
  108. package/dist/router-check.js.map +1 -0
  109. package/dist/smoke-fc.d.ts +1 -0
  110. package/dist/smoke-fc.js +183 -0
  111. package/dist/smoke-fc.js.map +1 -0
  112. package/dist/smoke-microvm.d.ts +1 -0
  113. package/dist/smoke-microvm.js +187 -0
  114. package/dist/smoke-microvm.js.map +1 -0
  115. package/dist/smoke-remote.d.ts +20 -0
  116. package/dist/smoke-remote.js +155 -0
  117. package/dist/smoke-remote.js.map +1 -0
  118. package/dist/smoke-vz.d.ts +1 -0
  119. package/dist/smoke-vz.js +161 -0
  120. package/dist/smoke-vz.js.map +1 -0
  121. package/dist/smoke.d.ts +7 -0
  122. package/dist/smoke.js +962 -0
  123. package/dist/smoke.js.map +1 -0
  124. package/dist/store.d.ts +124 -0
  125. package/dist/store.js +578 -0
  126. package/dist/store.js.map +1 -0
  127. package/dist/tracing.d.ts +45 -0
  128. package/dist/tracing.js +144 -0
  129. package/dist/tracing.js.map +1 -0
  130. package/dist/types.d.ts +274 -0
  131. package/dist/types.js +3 -0
  132. package/dist/types.js.map +1 -0
  133. package/dist/util.d.ts +27 -0
  134. package/dist/util.js +63 -0
  135. package/dist/util.js.map +1 -0
  136. package/dist/warmpool-check.d.ts +1 -0
  137. package/dist/warmpool-check.js +83 -0
  138. package/dist/warmpool-check.js.map +1 -0
  139. package/dist/web/dashboard.d.ts +1 -0
  140. package/dist/web/dashboard.js +456 -0
  141. package/dist/web/dashboard.js.map +1 -0
  142. package/package.json +53 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * On-demand OCI→ext4 image cache for the Apple VZ driver. Converts an OCI image
3
+ * (e.g. `python:3.11-slim`) into a VZ-bootable read-only rootfs — with the
4
+ * in-sandbox agent injected as PID 1 — and caches it keyed by image name, so a
5
+ * VZ sandbox runs the same image as the container driver. Also builds the blank
6
+ * pre-formatted workspace template the driver clones per sandbox.
7
+ *
8
+ * Both artifacts are produced by helper shell scripts that use Docker's
9
+ * `mkfs.ext4 -d` (populate-from-dir) — no privileges or loop-mounts, so it works
10
+ * on macOS where there is no native `mke2fs`.
11
+ */
12
+ export interface VzImageCacheOpts {
13
+ /** The `helpers/hotcell-vz` dir holding the converter scripts + staged guest files. */
14
+ vzDir: string;
15
+ /** Where converted rootfs images + the blank workspace template are cached. */
16
+ cacheDir: string;
17
+ /** Prebuilt Alpine base rootfs used for the sentinel image names (no conversion). */
18
+ prebuiltRootfs: string;
19
+ /** Target platform for the guest (default linux/arm64 — Apple silicon + VZ). */
20
+ platform?: string;
21
+ }
22
+ export declare class VzImageCache {
23
+ private readonly o;
24
+ private inflight;
25
+ constructor(o: VzImageCacheOpts);
26
+ /** Resolve an OCI image to a VZ-bootable rootfs path, converting on cache miss. */
27
+ ensureRootfs(image: string): Promise<string>;
28
+ /** Path to a blank pre-formatted workspace image of `diskGb`, building on miss. */
29
+ ensureBlankWorkspace(diskGb: number): Promise<string>;
30
+ private get platform();
31
+ /** Dedupe concurrent builds of the same artifact (key = output path). */
32
+ private once;
33
+ private sanitize;
34
+ private run;
35
+ }
@@ -0,0 +1,75 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, mkdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { log } from "../logger.js";
5
+ // Image names that map to the prebuilt Alpine base rootfs instead of converting:
6
+ // the lightweight default for tests/dev that needs no Docker pull.
7
+ const SENTINELS = new Set(["base", "alpine", "hotcell/base", "hotcell/base:latest", "sbx/base", "sbx/base:latest", "default"]);
8
+ export class VzImageCache {
9
+ o;
10
+ inflight = new Map();
11
+ constructor(o) {
12
+ this.o = o;
13
+ }
14
+ /** Resolve an OCI image to a VZ-bootable rootfs path, converting on cache miss. */
15
+ async ensureRootfs(image) {
16
+ if (SENTINELS.has(image))
17
+ return this.o.prebuiltRootfs;
18
+ const out = join(this.o.cacheDir, `${this.sanitize(image)}.img`);
19
+ if (existsSync(out))
20
+ return out;
21
+ return this.once(out, async () => {
22
+ mkdirSync(this.o.cacheDir, { recursive: true });
23
+ log.info("converting OCI image to a VZ rootfs (first use; cached after)", { image, out });
24
+ await this.run(join(this.o.vzDir, "convert-image.sh"), [
25
+ image,
26
+ out,
27
+ join(this.o.vzDir, "guest", "hotcell-agent"),
28
+ join(this.o.vzDir, "guest", "init.sh"),
29
+ this.platform,
30
+ ]);
31
+ return out;
32
+ });
33
+ }
34
+ /** Path to a blank pre-formatted workspace image of `diskGb`, building on miss. */
35
+ async ensureBlankWorkspace(diskGb) {
36
+ const sizeMb = Math.max(64, Math.round(diskGb * 1024));
37
+ const out = join(this.o.cacheDir, `blank-${sizeMb}m.img`);
38
+ if (existsSync(out))
39
+ return out;
40
+ return this.once(out, async () => {
41
+ mkdirSync(this.o.cacheDir, { recursive: true });
42
+ log.info("building the blank workspace template (first use; cached after)", { sizeMb, out });
43
+ await this.run(join(this.o.vzDir, "build-blank-workspace.sh"), [out, String(sizeMb), this.platform]);
44
+ return out;
45
+ });
46
+ }
47
+ get platform() {
48
+ return this.o.platform ?? "linux/arm64";
49
+ }
50
+ /** Dedupe concurrent builds of the same artifact (key = output path). */
51
+ once(key, fn) {
52
+ const existing = this.inflight.get(key);
53
+ if (existing)
54
+ return existing;
55
+ const p = fn().finally(() => this.inflight.delete(key));
56
+ this.inflight.set(key, p);
57
+ return p;
58
+ }
59
+ sanitize(image) {
60
+ return image.replace(/[^a-zA-Z0-9._-]+/g, "_");
61
+ }
62
+ run(script, args) {
63
+ return new Promise((resolve, reject) => {
64
+ const child = spawn("bash", [script, ...args], { stdio: ["ignore", "pipe", "pipe"] });
65
+ let err = "";
66
+ child.stderr.on("data", (d) => (err += d.toString()));
67
+ child.stdout.resume();
68
+ child.on("error", reject);
69
+ child.on("close", (code) => code === 0
70
+ ? resolve()
71
+ : reject(new Error(`${script} exited ${code}: ${err.trim().slice(-600)}`)));
72
+ });
73
+ }
74
+ }
75
+ //# sourceMappingURL=vz-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vz-image.js","sourceRoot":"","sources":["../../src/driver/vz-image.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Real-harness e2e (plan §Verification 5): run **headless Claude Code** inside a
3
+ * sandbox with provider keys injected by the egress gateway — the sandbox only
4
+ * ever sees a revocable per-sandbox token; the daemon swaps it for the real key
5
+ * and meters the call.
6
+ *
7
+ * The gateway's `openrouter` provider serves Anthropic's Messages API
8
+ * (openrouter.ai/api is Anthropic-compatible), so Claude Code speaks its native
9
+ * protocol through the gateway with the OpenRouter key held on the daemon host.
10
+ *
11
+ * Prereqs: a daemon whose environment has SBX_PROVIDER_KEY_OPENROUTER, Docker
12
+ * (container driver), and internet egress for the gateway itself.
13
+ * Env: SBX_ENDPOINT (default http://127.0.0.1:4750), SBX_API_KEY (if auth on),
14
+ * SBX_E2E_MODEL (OpenRouter slug; default anthropic/claude-3.5-haiku),
15
+ * SBX_E2E_EXPECT — "completion" (default; needs a real provider key) or
16
+ * "auth-fail": run with a dummy key and accept the provider's auth error
17
+ * as proof the whole chain works (sandbox → token env → headless Claude
18
+ * Code → gateway → key injection → real provider), minus the billing.
19
+ * Run: npm run e2e:harness
20
+ */
21
+ import assert from "node:assert/strict";
22
+ import { HotcellClient } from "@hotcell/sdk";
23
+ async function main() {
24
+ const endpoint = process.env.SBX_ENDPOINT ?? "http://127.0.0.1:4750";
25
+ const model = process.env.SBX_E2E_MODEL ?? "anthropic/claude-3.5-haiku";
26
+ const client = new HotcellClient({ endpoint, apiKey: process.env.SBX_API_KEY });
27
+ let passed = 0;
28
+ const ok = (l) => {
29
+ passed++;
30
+ console.log(` ✓ ${l}`);
31
+ };
32
+ const info = await client.info();
33
+ assert.ok(info.egressProviders?.includes("openrouter"), `daemon has no openrouter provider key (providers: ${info.egressProviders?.join(", ") || "none"})`);
34
+ ok(`daemon up (${endpoint}); gateway providers: ${info.egressProviders.join(", ")}`);
35
+ let sandbox;
36
+ try {
37
+ // Node image: Claude Code is an npm package. `egress: true` auto-wires the
38
+ // provider env (BASE_URLs at the gateway + the token as key).
39
+ sandbox = await client.getSandbox(undefined, { image: "node:22-slim", egress: true });
40
+ ok(`sandbox ${sandbox.id} created (node:22-slim, egress wired)`);
41
+ const wiring = await sandbox.exec("echo url=$OPENROUTER_BASE_URL; echo tok=${OPENROUTER_API_KEY:+set}");
42
+ assert.match(wiring.stdout, /url=http.+\/openrouter/, "OPENROUTER_BASE_URL not injected");
43
+ assert.match(wiring.stdout, /tok=set/, "egress token not injected");
44
+ assert.ok(!/sk-or-/.test(wiring.stdout), "a real provider key leaked into the sandbox env");
45
+ ok("egress env wired: gateway base URL + token (no real key in the sandbox)");
46
+ console.error("[e2e] installing @anthropic-ai/claude-code in the sandbox (~1-2 min)…");
47
+ const install = await sandbox.exec("npm install -g @anthropic-ai/claude-code --no-fund --no-audit 2>&1 | tail -2 && claude --version");
48
+ assert.equal(install.exitCode, 0, `install failed:\n${install.stdout}\n${install.stderr}`);
49
+ ok(`headless harness installed: claude ${install.stdout.trim().split("\n").pop()}`);
50
+ console.error("[e2e] running headless Claude Code through the gateway…");
51
+ const run = await sandbox.exec([
52
+ // Claude Code → gateway → openrouter.ai/api/v1/messages, per OpenRouter's
53
+ // Claude Code integration guide: base URL swap, token as auth, blank the
54
+ // real-key var so nothing conflicts, model pinned to a cheap Anthropic slug.
55
+ `export ANTHROPIC_BASE_URL="$OPENROUTER_BASE_URL"`,
56
+ `export ANTHROPIC_AUTH_TOKEN="$OPENROUTER_API_KEY"`,
57
+ `export ANTHROPIC_API_KEY=""`,
58
+ `export ANTHROPIC_DEFAULT_HAIKU_MODEL="${model}"`,
59
+ `export DISABLE_TELEMETRY=1 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`,
60
+ `export HOME=/root IS_SANDBOX=1`,
61
+ `claude -p "Reply with exactly: SBX_E2E_OK" --model haiku 2>&1`,
62
+ ].join(" && "));
63
+ const expect = process.env.SBX_E2E_EXPECT ?? "completion";
64
+ if (expect === "auth-fail") {
65
+ // Dummy-key mode: the provider rejecting our injected key IS the proof —
66
+ // the request left headless Claude Code, crossed the gateway, had the
67
+ // (dummy) real key injected, and reached the provider over the internet.
68
+ assert.match(run.stdout, /401|unauthori[sz]ed|invalid.*(key|token)|authentication/i, `expected a provider auth error, got:\n${run.stdout.slice(-800)}`);
69
+ ok("★ headless Claude Code → gateway → real provider (auth error as expected with a dummy key)");
70
+ }
71
+ else {
72
+ assert.match(run.stdout, /SBX_E2E_OK/, `no completion came back through the gateway:\n${run.stdout.slice(-800)}`);
73
+ ok("★ headless Claude Code completed a real prompt through the egress gateway");
74
+ }
75
+ // The gateway metered the traffic: provider calls on the record.
76
+ const metrics = await sandbox.metrics();
77
+ if (expect === "auth-fail") {
78
+ ok(`gateway saw ${metrics.usage?.providerCalls ?? 0} provider call(s) (auth-fail mode)`);
79
+ }
80
+ else {
81
+ assert.ok((metrics.usage?.providerCalls ?? 0) > 0, "gateway did not meter any provider call");
82
+ ok(`gateway metered ${metrics.usage.providerCalls} provider call(s), ` +
83
+ `${metrics.usage.providerTokensIn}/${metrics.usage.providerTokensOut} tokens in/out, ` +
84
+ `provider cost $${metrics.usage.providerCost.toFixed(4)}`);
85
+ }
86
+ console.log(`\ne2e-harness: ${passed} checks passed (real agent, real completion, key never in sandbox)`);
87
+ return 0;
88
+ }
89
+ catch (err) {
90
+ console.error(`e2e-harness FAILED: ${err instanceof Error ? err.message : String(err)}`);
91
+ return 1;
92
+ }
93
+ finally {
94
+ if (sandbox)
95
+ await sandbox.destroy().catch(() => { });
96
+ }
97
+ }
98
+ main().then((code) => process.exit(code));
99
+ //# sourceMappingURL=e2e-harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2e-harness.js","sourceRoot":"","sources":["../src/e2e-harness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Docker-free egress control-plane check. Exercises the gateway's policy
3
+ * enforcement + cost metering entirely in-process (mock upstream + in-memory
4
+ * store), so the per-token policy logic can be verified anywhere — no container
5
+ * runtime required (the full container-backed flow is asserted by `smoke.ts` /
6
+ * `smoke-remote.ts` against a real daemon). Run: `npm run check:egress`.
7
+ */
8
+ export {};
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Docker-free egress control-plane check. Exercises the gateway's policy
3
+ * enforcement + cost metering entirely in-process (mock upstream + in-memory
4
+ * store), so the per-token policy logic can be verified anywhere — no container
5
+ * runtime required (the full container-backed flow is asserted by `smoke.ts` /
6
+ * `smoke-remote.ts` against a real daemon). Run: `npm run check:egress`.
7
+ */
8
+ import { createServer, request as httpRequest } from "node:http";
9
+ import { createServer as netCreateServer } from "node:net";
10
+ import assert from "node:assert/strict";
11
+ import { buildProviders, createEgressProxy } from "./proxy/egress.js";
12
+ import { Allowlist } from "./proxy/allowlist.js";
13
+ import { DEFAULT_MODEL_PRICES } from "./pricing.js";
14
+ import { loadConfig } from "./config.js";
15
+ import { emptyUsage, SandboxStore } from "./store.js";
16
+ const REAL_KEY = "REAL-KEY-123";
17
+ async function main() {
18
+ const config = loadConfig();
19
+ config.dbPath = ":memory:";
20
+ const store = new SandboxStore(":memory:");
21
+ const sandboxId = "sbxtest01";
22
+ const rec = {
23
+ id: sandboxId,
24
+ image: "test",
25
+ status: "running",
26
+ createdAt: new Date().toISOString(),
27
+ labels: {},
28
+ env: {},
29
+ persist: false,
30
+ lastActivityAt: new Date().toISOString(),
31
+ sleepAfterMs: 0,
32
+ limits: {},
33
+ usage: emptyUsage(),
34
+ };
35
+ store.add(rec);
36
+ // Mock provider upstream: echoes the auth header it received, echoes the
37
+ // request's `model`, returns 11/7 tokens, and includes `usage.cost` only when
38
+ // asked via `x-mock-cost` — so we can exercise both the reported-cost and the
39
+ // computed-from-table paths.
40
+ const upstream = createServer((req, res) => {
41
+ let body = "";
42
+ req.on("data", (c) => (body += c));
43
+ req.on("end", () => {
44
+ let model = "gpt-4o";
45
+ try {
46
+ const o = JSON.parse(body || "{}");
47
+ if (typeof o.model === "string")
48
+ model = o.model;
49
+ }
50
+ catch {
51
+ /* ignore */
52
+ }
53
+ const usage = { prompt_tokens: 11, completion_tokens: 7 };
54
+ if (req.headers["x-mock-cost"])
55
+ usage.cost = 0.0123;
56
+ res.writeHead(200, { "content-type": "application/json" });
57
+ res.end(JSON.stringify({ model, usage, receivedAuth: req.headers["authorization"] }));
58
+ });
59
+ });
60
+ const upstreamPort = await listen(upstream);
61
+ const egress = createEgressProxy({
62
+ config,
63
+ store,
64
+ prices: DEFAULT_MODEL_PRICES,
65
+ providers: {
66
+ mock: {
67
+ baseUrl: `http://127.0.0.1:${upstreamPort}`,
68
+ authHeader: "authorization",
69
+ format: (k) => `Bearer ${k}`,
70
+ apiKey: REAL_KEY,
71
+ },
72
+ },
73
+ });
74
+ const egressPort = await listen(egress);
75
+ const base = `http://127.0.0.1:${egressPort}`;
76
+ const mint = (policy = {}) => {
77
+ const token = SandboxStore.newEgressToken();
78
+ store.addEgressToken(token, sandboxId, policy);
79
+ return token;
80
+ };
81
+ const call = async (token, body = { model: "gpt-4o" }, headers = {}) => {
82
+ const res = await fetch(`${base}/mock/v1/chat/completions`, {
83
+ method: "POST",
84
+ headers: { "content-type": "application/json", "x-hotcell-egress": token, ...headers },
85
+ body: JSON.stringify(body),
86
+ });
87
+ const text = await res.text();
88
+ let json;
89
+ try {
90
+ json = JSON.parse(text);
91
+ }
92
+ catch {
93
+ json = text;
94
+ }
95
+ return { status: res.status, json };
96
+ };
97
+ let passed = 0;
98
+ const ok = (label) => {
99
+ passed++;
100
+ console.log(` ✓ ${label}`);
101
+ };
102
+ // 1) Key injection: upstream sees the REAL key, never the token.
103
+ {
104
+ const token = mint();
105
+ const r = await call(token);
106
+ assert.equal(r.status, 200, "valid call should 200");
107
+ assert.equal(r.json.receivedAuth, `Bearer ${REAL_KEY}`, "upstream must see injected real key");
108
+ ok("key injection: upstream receives real key, not the token");
109
+ }
110
+ // 2) Computed cost from the price table (no provider-reported cost).
111
+ {
112
+ const token = mint();
113
+ await call(token, { model: "gpt-4o" });
114
+ const u = store.get(sandboxId).usage;
115
+ // gpt-4o = $2.5/1M in, $10/1M out → 11*2.5e-6 + 7*10e-6 = 9.75e-5
116
+ const expected = (11 / 1e6) * 2.5 + (7 / 1e6) * 10;
117
+ const spend = store.resolveEgressTokenFull(token).spendUsd;
118
+ assert.ok(Math.abs(spend - expected) < 1e-12, `computed cost ${spend} != ${expected}`);
119
+ assert.ok(u.providerCalls >= 1 && u.providerTokensIn >= 11, "tokens metered");
120
+ ok(`computed cost from table: $${spend.toFixed(8)} for gpt-4o 11/7`);
121
+ }
122
+ // 3) Reported cost is authoritative (not recomputed) when present.
123
+ {
124
+ const token = mint();
125
+ await call(token, { model: "gpt-4o" }, { "x-mock-cost": "1" });
126
+ const spend = store.resolveEgressTokenFull(token).spendUsd;
127
+ assert.ok(Math.abs(spend - 0.0123) < 1e-9, `reported cost should win, got ${spend}`);
128
+ ok("reported cost authoritative ($0.0123)");
129
+ }
130
+ // 4) Invalid token → 403.
131
+ {
132
+ const r = await call("sbx-nope");
133
+ assert.equal(r.status, 403, "invalid token should 403");
134
+ ok("invalid token → 403");
135
+ }
136
+ // 5) Expired token → 403.
137
+ {
138
+ const token = mint({ expiresAt: new Date(Date.now() - 1000).toISOString() });
139
+ const r = await call(token);
140
+ assert.equal(r.status, 403, "expired token should 403");
141
+ ok("expired token → 403");
142
+ }
143
+ // 6) Provider not in scope → 403.
144
+ {
145
+ const token = mint({ providers: ["openai"] }); // calling /mock
146
+ const r = await call(token);
147
+ assert.equal(r.status, 403, "out-of-scope provider should 403");
148
+ ok("provider out of scope → 403");
149
+ }
150
+ // 7) Model not in allowlist → 403; allowed model → 200.
151
+ {
152
+ const token = mint({ models: ["gpt-4o"] });
153
+ const bad = await call(token, { model: "gpt-3.5-turbo" });
154
+ assert.equal(bad.status, 403, "disallowed model should 403");
155
+ const good = await call(token, { model: "gpt-4o" });
156
+ assert.equal(good.status, 200, "allowed model should 200");
157
+ ok("model allowlist: gpt-3.5 → 403, gpt-4o → 200");
158
+ }
159
+ // 8) Spend cap → 402 once accumulated spend reaches the cap.
160
+ {
161
+ const token = mint({ spendCapUsd: 0.01 });
162
+ const first = await call(token, { model: "gpt-4o" }, { "x-mock-cost": "1" }); // spends 0.0123
163
+ assert.equal(first.status, 200, "first call under cap should 200");
164
+ const second = await call(token, { model: "gpt-4o" }, { "x-mock-cost": "1" });
165
+ assert.equal(second.status, 402, "second call over cap should 402");
166
+ ok("spend cap → first 200, then 402");
167
+ }
168
+ // 9) Rate limit (calls) → 429 on the 2nd call within the window.
169
+ {
170
+ const token = mint({ rateLimit: { calls: 1, windowMs: 60_000 } });
171
+ const first = await call(token);
172
+ assert.equal(first.status, 200, "first call should 200");
173
+ const second = await call(token);
174
+ assert.equal(second.status, 429, "second call should 429");
175
+ ok("rate limit (calls=1) → first 200, then 429");
176
+ }
177
+ // 10) Config-driven providers: built-ins, custom shapes, and CF-gateway repoint.
178
+ {
179
+ const cfg = loadConfig();
180
+ cfg.providerKeys = { openai: "k1", google: "k2", cfopenai: "k3", noshape: "k4" };
181
+ cfg.providerConfigs = {
182
+ // Cloudflare AI Gateway prefix in front of OpenAI, expressed as a custom provider.
183
+ cfopenai: {
184
+ baseUrl: "https://gateway.ai.cloudflare.com/v1/acct/gw/openai",
185
+ authHeader: "authorization",
186
+ formatTemplate: "Bearer {key}",
187
+ },
188
+ };
189
+ const built = buildProviders(cfg);
190
+ assert.ok(built.openai && built.openai.baseUrl === "https://api.openai.com", "built-in openai");
191
+ assert.ok(built.google && built.google.authHeader === "x-goog-api-key", "built-in google");
192
+ assert.ok(built.cfopenai, "custom cfopenai provider present");
193
+ assert.equal(built.cfopenai.format("XYZ"), "Bearer XYZ", "custom format template applied");
194
+ assert.ok(built.cfopenai.baseUrl.includes("cloudflare"), "custom base URL used");
195
+ assert.ok(!built.noshape, "keyed-but-shapeless provider is omitted");
196
+ ok("config-driven providers: built-ins + custom CF-gateway, shapeless omitted");
197
+ }
198
+ // 11) Allowlist matcher (pure): exact, wildcard, denylist, port-strip.
199
+ {
200
+ const al = new Allowlist(["pypi.org", "*.githubusercontent.com"], ["dns.google"]);
201
+ assert.equal(al.check("pypi.org").allow, true, "exact allow");
202
+ assert.equal(al.check("raw.githubusercontent.com").allow, true, "wildcard allow");
203
+ assert.equal(al.check("evil.example").allow, false, "default deny");
204
+ assert.equal(al.check("dns.google").allow, false, "denylist wins");
205
+ assert.equal(al.check("pypi.org:443").allow, true, "port stripped");
206
+ ok("allowlist matcher: exact, wildcard, denylist, port-strip");
207
+ }
208
+ // 12) Forward proxy + CONNECT tunnel + provider-domain guard (custom allowlist
209
+ // that permits 127.0.0.1 only). Exercises the security-critical path.
210
+ {
211
+ const fwAllow = new Allowlist(["127.0.0.1", "localhost"], []);
212
+ const fwProxy = createEgressProxy({
213
+ config,
214
+ store,
215
+ prices: DEFAULT_MODEL_PRICES,
216
+ allowlist: fwAllow,
217
+ providers: {}, // provider-host guard still derives from built-in defaults
218
+ });
219
+ const fwPort = await listen(fwProxy);
220
+ const token = mint();
221
+ // Absolute-form HTTP forward proxy: allowed host → 200, denied → 403,
222
+ // LLM-provider host → 403 (guard).
223
+ const fAllowed = await forward(fwPort, `http://127.0.0.1:${upstreamPort}/v1/x`, token);
224
+ assert.equal(fAllowed.status, 200, "forward to allowed host should 200");
225
+ const fDenied = await forward(fwPort, "http://evil.example/x", token);
226
+ assert.equal(fDenied.status, 403, "forward to denied host should 403");
227
+ const fProvider = await forward(fwPort, "http://api.openai.com/v1/chat", token);
228
+ assert.equal(fProvider.status, 403, "forward to LLM provider should 403 (guard)");
229
+ // CONNECT tunnel: allowed host round-trips bytes; denied + provider → 403.
230
+ const echo = netCreateServer((s) => s.pipe(s));
231
+ const echoPort = await listen(echo);
232
+ const cOk = await connectProxy(fwPort, `127.0.0.1:${echoPort}`, token);
233
+ assert.equal(cOk.status, 200, "CONNECT to allowed host should 200");
234
+ const echoed = await new Promise((resolve) => {
235
+ cOk.socket.once("data", (d) => resolve(d.toString()));
236
+ cOk.socket.write("ping");
237
+ });
238
+ assert.equal(echoed, "ping", "CONNECT tunnel round-trips bytes");
239
+ cOk.socket.destroy();
240
+ const cDenied = await connectProxy(fwPort, "evil.example:443", token);
241
+ assert.equal(cDenied.status, 403, "CONNECT to denied host should 403");
242
+ const cProvider = await connectProxy(fwPort, "api.openai.com:443", token);
243
+ assert.equal(cProvider.status, 403, "CONNECT to LLM provider should 403 (guard)");
244
+ fwProxy.close();
245
+ echo.close();
246
+ ok("forward proxy + CONNECT: allow round-trips, deny → 403, provider guard → 403");
247
+ }
248
+ // 13) Per-sandbox spend ceiling: a hard cap across all of a sandbox's tokens,
249
+ // independent of per-token policy.
250
+ {
251
+ const sb2 = { ...rec, id: "sbxcap0002", egressSpendCapUsd: 0.01, usage: emptyUsage() };
252
+ store.add(sb2);
253
+ const token = SandboxStore.newEgressToken();
254
+ store.addEgressToken(token, sb2.id); // no per-token policy
255
+ const first = await fetch(`${base}/mock/v1/chat`, {
256
+ method: "POST",
257
+ headers: { "content-type": "application/json", "x-hotcell-egress": token, "x-mock-cost": "1" },
258
+ body: JSON.stringify({ model: "gpt-4o" }),
259
+ });
260
+ assert.equal(first.status, 200, "first call under sandbox cap should 200");
261
+ const second = await fetch(`${base}/mock/v1/chat`, {
262
+ method: "POST",
263
+ headers: { "content-type": "application/json", "x-hotcell-egress": token, "x-mock-cost": "1" },
264
+ body: JSON.stringify({ model: "gpt-4o" }),
265
+ });
266
+ assert.equal(second.status, 402, "second call over sandbox cap should 402");
267
+ ok("per-sandbox spend ceiling → first 200, then 402");
268
+ }
269
+ console.log(`\negress-check: ${passed} checks passed`);
270
+ egress.close();
271
+ upstream.close();
272
+ store.close();
273
+ }
274
+ /** Send an absolute-form (forward-proxy) GET through the gateway. */
275
+ function forward(port, absUrl, token) {
276
+ return new Promise((resolve) => {
277
+ const req = httpRequest({ host: "127.0.0.1", port, method: "GET", path: absUrl, headers: { "proxy-authorization": `Bearer ${token}` } }, (res) => {
278
+ let b = "";
279
+ res.on("data", (c) => (b += c));
280
+ res.on("end", () => resolve({ status: res.statusCode ?? -1, body: b }));
281
+ });
282
+ req.on("error", () => resolve({ status: -1, body: "" }));
283
+ req.end();
284
+ });
285
+ }
286
+ /** Open a CONNECT tunnel through the gateway. Resolves on either outcome. */
287
+ function connectProxy(port, authority, token) {
288
+ return new Promise((resolve) => {
289
+ const req = httpRequest({
290
+ method: "CONNECT",
291
+ host: "127.0.0.1",
292
+ port,
293
+ path: authority,
294
+ headers: { "proxy-authorization": `Bearer ${token}` },
295
+ });
296
+ req.on("connect", (res, socket) => resolve({ status: res.statusCode ?? -1, socket }));
297
+ req.on("response", (res) => {
298
+ res.resume();
299
+ resolve({ status: res.statusCode ?? -1 });
300
+ });
301
+ req.on("error", () => resolve({ status: -1 }));
302
+ req.end();
303
+ });
304
+ }
305
+ function listen(server) {
306
+ return new Promise((resolve) => {
307
+ server.listen(0, "127.0.0.1", () => {
308
+ const addr = server.address();
309
+ resolve(typeof addr === "object" && addr ? addr.port : 0);
310
+ });
311
+ });
312
+ }
313
+ main().then(() => process.exit(0), (err) => {
314
+ console.error("egress-check FAILED:", err);
315
+ process.exit(1);
316
+ });
317
+ //# sourceMappingURL=egress-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"egress-check.js","sourceRoot":"","sources":["../src/egress-check.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,YAAY,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGtD,MAAM,QAAQ,GAAG,cAAc,CAAC;AAEhC,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,CAAC,MAAM,GAAG,UAAU,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,UAAU,CAAC,CAAC;IAE3C,MAAM,SAAS,GAAG,WAAW,CAAC;IAC9B,MAAM,GAAG,GAAkB;QACzB,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,MAAM;QACb,MAAM,EAAE,SAAS;QACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,MAAM,EAAE,EAAE;QACV,GAAG,EAAE,EAAE;QACP,OAAO,EAAE,KAAK;QACd,cAAc,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACxC,YAAY,EAAE,CAAC;QACf,MAAM,EAAE,EAAE;QACV,KAAK,EAAE,UAAU,EAAE;KACpB,CAAC;IACF,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAEf,yEAAyE;IACzE,8EAA8E;IAC9E,8EAA8E;IAC9E,6BAA6B;IAC7B,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACzC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;QACnC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACjB,IAAI,KAAK,GAAG,QAAQ,CAAC;YACrB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;gBACnC,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;oBAAE,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;YACD,MAAM,KAAK,GAA2B,EAAE,aAAa,EAAE,EAAE,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;YAClF,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;gBAAE,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC;YACpD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC;QACxF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAG,iBAAiB,CAAC;QAC/B,MAAM;QACN,KAAK;QACL,MAAM,EAAE,oBAAoB;QAC5B,SAAS,EAAE;YACT,IAAI,EAAE;gBACJ,OAAO,EAAE,oBAAoB,YAAY,EAAE;gBAC3C,UAAU,EAAE,eAAe;gBAC3B,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE;gBACpC,MAAM,EAAE,QAAQ;aACjB;SACF;KACF,CAAC,CAAC;IACH,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,oBAAoB,UAAU,EAAE,CAAC;IAE9C,MAAM,IAAI,GAAG,CAAC,SAAuB,EAAE,EAAU,EAAE;QACjD,MAAM,KAAK,GAAG,YAAY,CAAC,cAAc,EAAE,CAAC;QAC5C,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IACF,MAAM,IAAI,GAAG,KAAK,EAChB,KAAa,EACb,OAAgC,EAAE,KAAK,EAAE,QAAQ,EAAE,EACnD,UAAkC,EAAE,EACI,EAAE;QAC1C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,2BAA2B,EAAE;YAC1D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE;YACtF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,IAAS,CAAC;QACd,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;IACtC,CAAC,CAAC;IAEF,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,EAAE,GAAG,CAAC,KAAa,EAAE,EAAE;QAC3B,MAAM,EAAE,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,iEAAiE;IACjE,CAAC;QACC,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACrD,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,QAAQ,EAAE,EAAE,qCAAqC,CAAC,CAAC;QAC/F,EAAE,CAAC,0DAA0D,CAAC,CAAC;IACjE,CAAC;IAED,qEAAqE;IACrE,CAAC;QACC,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC,KAAK,CAAC;QACtC,kEAAkE;QAClE,MAAM,QAAQ,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;QACnD,MAAM,KAAK,GAAG,KAAK,CAAC,sBAAsB,CAAC,KAAK,CAAE,CAAC,QAAQ,CAAC;QAC5D,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,QAAQ,CAAC,GAAG,KAAK,EAAE,iBAAiB,KAAK,OAAO,QAAQ,EAAE,CAAC,CAAC;QACvF,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,CAAC,gBAAgB,IAAI,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAC9E,EAAE,CAAC,8BAA8B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;IACvE,CAAC;IAED,mEAAmE;IACnE,CAAC;QACC,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,sBAAsB,CAAC,KAAK,CAAE,CAAC,QAAQ,CAAC;QAC5D,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,iCAAiC,KAAK,EAAE,CAAC,CAAC;QACrF,EAAE,CAAC,uCAAuC,CAAC,CAAC;IAC9C,CAAC;IAED,0BAA0B;IAC1B,CAAC;QACC,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,0BAA0B,CAAC,CAAC;QACxD,EAAE,CAAC,qBAAqB,CAAC,CAAC;IAC5B,CAAC;IAED,0BAA0B;IAC1B,CAAC;QACC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC7E,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,0BAA0B,CAAC,CAAC;QACxD,EAAE,CAAC,qBAAqB,CAAC,CAAC;IAC5B,CAAC;IAED,kCAAkC;IAClC,CAAC;QACC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB;QAC/D,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,kCAAkC,CAAC,CAAC;QAChE,EAAE,CAAC,6BAA6B,CAAC,CAAC;IACpC,CAAC;IAED,wDAAwD;IACxD,CAAC;QACC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,6BAA6B,CAAC,CAAC;QAC7D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,0BAA0B,CAAC,CAAC;QAC3D,EAAE,CAAC,8CAA8C,CAAC,CAAC;IACrD,CAAC;IAED,6DAA6D;IAC7D,CAAC;QACC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,gBAAgB;QAC9F,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,iCAAiC,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9E,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,iCAAiC,CAAC,CAAC;QACpE,EAAE,CAAC,iCAAiC,CAAC,CAAC;IACxC,CAAC;IAED,iEAAiE;IACjE,CAAC;QACC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,wBAAwB,CAAC,CAAC;QAC3D,EAAE,CAAC,4CAA4C,CAAC,CAAC;IACnD,CAAC;IAED,iFAAiF;IACjF,CAAC;QACC,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;QACzB,GAAG,CAAC,YAAY,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACjF,GAAG,CAAC,eAAe,GAAG;YACpB,mFAAmF;YACnF,QAAQ,EAAE;gBACR,OAAO,EAAE,qDAAqD;gBAC9D,UAAU,EAAE,eAAe;gBAC3B,cAAc,EAAE,cAAc;aAC/B;SACF,CAAC;QACF,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,KAAK,wBAAwB,EAAE,iBAAiB,CAAC,CAAC;QAChG,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,UAAU,KAAK,gBAAgB,EAAE,iBAAiB,CAAC,CAAC;QAC3F,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,kCAAkC,CAAC,CAAC;QAC9D,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAS,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,gCAAgC,CAAC,CAAC;QAC5F,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,QAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,sBAAsB,CAAC,CAAC;QAClF,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,yCAAyC,CAAC,CAAC;QACrE,EAAE,CAAC,2EAA2E,CAAC,CAAC;IAClF,CAAC;IAED,uEAAuE;IACvE,CAAC;QACC,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,CAAC,UAAU,EAAE,yBAAyB,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;QAClF,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QAC9D,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC;QAClF,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;QACpE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC;QACnE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;QACpE,EAAE,CAAC,0DAA0D,CAAC,CAAC;IACjE,CAAC;IAED,+EAA+E;IAC/E,0EAA0E;IAC1E,CAAC;QACC,MAAM,OAAO,GAAG,IAAI,SAAS,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,iBAAiB,CAAC;YAChC,MAAM;YACN,KAAK;YACL,MAAM,EAAE,oBAAoB;YAC5B,SAAS,EAAE,OAAO;YAClB,SAAS,EAAE,EAAE,EAAE,2DAA2D;SAC3E,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC;QAErB,sEAAsE;QACtE,mCAAmC;QACnC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,oBAAoB,YAAY,OAAO,EAAE,KAAK,CAAC,CAAC;QACvF,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,oCAAoC,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,uBAAuB,EAAE,KAAK,CAAC,CAAC;QACtE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,mCAAmC,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,+BAA+B,EAAE,KAAK,CAAC,CAAC;QAChF,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,4CAA4C,CAAC,CAAC;QAElF,2EAA2E;QAC3E,MAAM,IAAI,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,aAAa,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,oCAAoC,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YACnD,GAAG,CAAC,MAAO,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;YAC/D,GAAG,CAAC,MAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,kCAAkC,CAAC,CAAC;QACjE,GAAG,CAAC,MAAO,CAAC,OAAO,EAAE,CAAC;QAEtB,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,CAAC,CAAC;QACtE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,mCAAmC,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,oBAAoB,EAAE,KAAK,CAAC,CAAC;QAC1E,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,4CAA4C,CAAC,CAAC;QAElF,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,EAAE,CAAC,8EAA8E,CAAC,CAAC;IACrF,CAAC;IAED,8EAA8E;IAC9E,uCAAuC;IACvC,CAAC;QACC,MAAM,GAAG,GAAkB,EAAE,GAAG,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,iBAAiB,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;QACtG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,MAAM,KAAK,GAAG,YAAY,CAAC,cAAc,EAAE,CAAC;QAC5C,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,sBAAsB;QAC3D,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,eAAe,EAAE;YAChD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE;YAC9F,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;SAC1C,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,yCAAyC,CAAC,CAAC;QAC3E,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,eAAe,EAAE;YACjD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE;YAC9F,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;SAC1C,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,yCAAyC,CAAC,CAAC;QAC5E,EAAE,CAAC,iDAAiD,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,mBAAmB,MAAM,gBAAgB,CAAC,CAAC;IACvD,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,QAAQ,CAAC,KAAK,EAAE,CAAC;IACjB,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED,qEAAqE;AACrE,SAAS,OAAO,CACd,IAAY,EACZ,MAAc,EACd,KAAa;IAEb,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,WAAW,CACrB,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,qBAAqB,EAAE,UAAU,KAAK,EAAE,EAAE,EAAE,EAC/G,CAAC,GAAG,EAAE,EAAE;YACN,IAAI,CAAC,GAAG,EAAE,CAAC;YACX,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1E,CAAC,CACF,CAAC;QACF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACzD,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,6EAA6E;AAC7E,SAAS,YAAY,CACnB,IAAY,EACZ,SAAiB,EACjB,KAAa;IAEb,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,WAAW,CAAC;YACtB,MAAM,EAAE,SAAS;YACjB,IAAI,EAAE,WAAW;YACjB,IAAI;YACJ,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,EAAE,qBAAqB,EAAE,UAAU,KAAK,EAAE,EAAE;SACtD,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QACtF,GAAG,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE;YACzB,GAAG,CAAC,MAAM,EAAE,CAAC;YACb,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/C,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,MAAM,CAAC,MAA4D;IAC1E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACjC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,OAAO,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,IAAI,CACT,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EACrB,CAAC,GAAG,EAAE,EAAE;IACN,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;IAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CACF,CAAC"}
@@ -0,0 +1 @@
1
+ export {};