hyperbrane 0.1.1 → 0.2.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 (287) hide show
  1. package/README.md +81 -11
  2. package/dist/agent/agentClient.d.ts +5 -0
  3. package/dist/agent/agentClient.js +4 -0
  4. package/dist/agent/agentService.d.ts +9 -30
  5. package/dist/agent/agentService.js +62 -293
  6. package/dist/agent/agentStorage.d.ts +1 -20
  7. package/dist/agent/agentStorage.js +0 -30
  8. package/dist/agent/api.d.ts +89 -133
  9. package/dist/agent/api.js +4 -4
  10. package/dist/agent/client.d.ts +3 -1
  11. package/dist/agent/client.js +2 -3
  12. package/dist/agent/control.d.ts +23 -0
  13. package/dist/agent/control.js +479 -0
  14. package/dist/agent/hostTranscript.d.ts +0 -6
  15. package/dist/agent/hostTranscript.js +3 -14
  16. package/dist/agent/session/backend.d.ts +17 -30
  17. package/dist/agent/session/claude/home.d.ts +0 -1
  18. package/dist/agent/session/claude/home.js +0 -1
  19. package/dist/agent/session/claude/provision.d.ts +10 -0
  20. package/dist/agent/session/claude/provision.js +17 -0
  21. package/dist/agent/session/claude/transcript.d.ts +2 -7
  22. package/dist/agent/session/claude/transcript.js +68 -14
  23. package/dist/agent/session/client.d.ts +1 -10
  24. package/dist/agent/session/client.js +0 -3
  25. package/dist/agent/session/codex/provision.d.ts +14 -0
  26. package/dist/agent/session/codex/provision.js +25 -0
  27. package/dist/agent/session/fileTranscript.d.ts +6 -49
  28. package/dist/agent/session/fileTranscript.js +26 -82
  29. package/dist/agent/session/install.d.ts +5 -0
  30. package/dist/agent/session/install.js +35 -0
  31. package/dist/agent/session/sessionStorage.d.ts +4 -35
  32. package/dist/agent/session/sessionStorage.js +10 -16
  33. package/dist/agent/session/sharedConnection.d.ts +0 -8
  34. package/dist/agent/session/sharedConnection.js +0 -7
  35. package/dist/agent/session/storedTranscripts.d.ts +1 -6
  36. package/dist/agent/session/storedTranscripts.js +0 -3
  37. package/dist/agent/session/worker/jsonRpc.d.ts +2 -13
  38. package/dist/agent/session/worker/jsonRpc.js +2 -11
  39. package/dist/agent/session/worker/protocol.d.ts +27 -18
  40. package/dist/agent/session/worker/protocol.js +32 -16
  41. package/dist/agent/session/worker/workerParams.d.ts +0 -5
  42. package/dist/agent/session/worker/workerParams.js +15 -14
  43. package/dist/agent/session/workerConnection.d.ts +2 -13
  44. package/dist/agent/session/workerConnection.js +7 -12
  45. package/dist/agent/session/workerGatewayConnection.d.ts +18 -0
  46. package/dist/agent/session/workerGatewayConnection.js +86 -0
  47. package/dist/agent/vendor-types/manifest.json +87 -0
  48. package/dist/agent/workerBundle.d.ts +4 -19
  49. package/dist/agent/workerBundle.generated.js +1 -1
  50. package/dist/agent/workerBundle.js +3 -30
  51. package/dist/agent/workerReplacement.d.ts +3 -0
  52. package/dist/agent/workerReplacement.js +49 -0
  53. package/dist/cli/agent.js +44 -16
  54. package/dist/cli/command.d.ts +10 -11
  55. package/dist/cli/command.js +0 -4
  56. package/dist/cli/dimension.js +6 -6
  57. package/dist/cli/gateway.js +3 -6
  58. package/dist/cli/main.js +14 -31
  59. package/dist/cli/streamPrinter.d.ts +2 -10
  60. package/dist/cli/streamPrinter.js +16 -46
  61. package/dist/cli/table.d.ts +0 -3
  62. package/dist/cli/table.js +0 -4
  63. package/dist/dimension/api.d.ts +39 -27
  64. package/dist/dimension/api.js +28 -47
  65. package/dist/dimension/client.d.ts +5 -0
  66. package/dist/dimension/client.js +4 -0
  67. package/dist/dimension/control.d.ts +21 -0
  68. package/dist/dimension/control.js +328 -0
  69. package/dist/dimension/image.d.ts +0 -5
  70. package/dist/dimension/image.js +0 -8
  71. package/dist/dimension/imageFiles.generated.js +1 -1
  72. package/dist/dimension/paths.d.ts +5 -4
  73. package/dist/dimension/paths.js +15 -6
  74. package/dist/dimension/service.d.ts +8 -21
  75. package/dist/dimension/service.js +60 -224
  76. package/dist/dimension/transport-worker.d.ts +1 -0
  77. package/dist/dimension/transport-worker.js +151 -0
  78. package/dist/dimension/transportBundle.d.ts +2 -0
  79. package/dist/dimension/transportBundle.generated.d.ts +1 -0
  80. package/dist/dimension/transportBundle.generated.js +2 -0
  81. package/dist/dimension/transportBundle.js +4 -0
  82. package/dist/dimension/transportClient.d.ts +10 -0
  83. package/dist/dimension/transportClient.js +115 -0
  84. package/dist/dimension/transportProtocol.d.ts +53 -0
  85. package/dist/dimension/transportProtocol.js +8 -0
  86. package/dist/index.d.ts +5 -8
  87. package/dist/index.js +5 -16
  88. package/dist/lib/control.d.ts +8 -0
  89. package/dist/lib/control.js +62 -0
  90. package/dist/lib/docker.d.ts +0 -22
  91. package/dist/lib/docker.js +0 -10
  92. package/dist/lib/image.d.ts +0 -3
  93. package/dist/lib/image.js +0 -2
  94. package/dist/lib/jsonDirectory.d.ts +0 -13
  95. package/dist/lib/jsonDirectory.js +10 -15
  96. package/dist/lib/scriptBundle.d.ts +6 -0
  97. package/dist/lib/scriptBundle.js +28 -0
  98. package/dist/lib/sh.d.ts +2 -0
  99. package/dist/lib/sh.js +8 -0
  100. package/dist/lib/sleep.d.ts +0 -1
  101. package/dist/lib/sleep.js +0 -1
  102. package/dist/lib/streamFetch.d.ts +15 -0
  103. package/dist/lib/streamFetch.js +150 -0
  104. package/dist/lib/tar.d.ts +0 -1
  105. package/dist/lib/tar.js +5 -11
  106. package/dist/portal-gateway/api.d.ts +13 -44
  107. package/dist/portal-gateway/api.js +1 -3
  108. package/dist/portal-gateway/control.d.ts +42 -0
  109. package/dist/portal-gateway/control.js +657 -0
  110. package/dist/portal-gateway/gatewayClient.d.ts +5 -0
  111. package/dist/portal-gateway/gatewayClient.js +4 -0
  112. package/dist/portal-gateway/gatewayService.d.ts +8 -35
  113. package/dist/portal-gateway/gatewayService.js +79 -253
  114. package/dist/portal-gateway/image.d.ts +1 -2
  115. package/dist/portal-gateway/image.js +1 -3
  116. package/dist/portal-gateway/imageFiles.generated.js +3 -3
  117. package/dist/portal-gateway/portalClient.d.ts +5 -0
  118. package/dist/portal-gateway/portalClient.js +4 -0
  119. package/dist/portal-gateway/portalService.d.ts +5 -47
  120. package/dist/portal-gateway/portalService.js +19 -317
  121. package/dist/portal-gateway/transport-worker.d.ts +1 -0
  122. package/dist/portal-gateway/transport-worker.js +205 -0
  123. package/dist/portal-gateway/transportBundle.d.ts +2 -0
  124. package/dist/portal-gateway/transportBundle.generated.d.ts +1 -0
  125. package/dist/portal-gateway/transportBundle.generated.js +2 -0
  126. package/dist/portal-gateway/transportBundle.js +4 -0
  127. package/dist/portal-gateway/upstream.d.ts +12 -82
  128. package/dist/portal-gateway/upstream.js +0 -1
  129. package/dist/sandbox/api.d.ts +121 -178
  130. package/dist/sandbox/api.js +5 -7
  131. package/dist/sandbox/client.d.ts +2 -0
  132. package/dist/sandbox/client.js +4 -0
  133. package/dist/sandbox/data-endpoints/dataEndpoints.d.ts +13 -0
  134. package/dist/sandbox/data-endpoints/dataEndpoints.js +100 -0
  135. package/dist/sandbox/data-endpoints/firewalledPorts.d.ts +4 -0
  136. package/dist/sandbox/data-endpoints/firewalledPorts.js +28 -0
  137. package/dist/sandbox/lib/httpOverStream.d.ts +3 -0
  138. package/dist/sandbox/lib/httpOverStream.js +21 -0
  139. package/dist/sandbox/lib/remoteFileOperations.d.ts +10 -0
  140. package/dist/sandbox/lib/remoteFileOperations.js +70 -0
  141. package/dist/sandbox/lib/shellCommand.d.ts +3 -0
  142. package/dist/sandbox/lib/shellCommand.js +7 -0
  143. package/dist/sandbox/providers/boat/boatService.d.ts +2 -0
  144. package/dist/sandbox/providers/boat/boatService.js +321 -0
  145. package/dist/sandbox/providers/exedev/exeDevService.d.ts +2 -0
  146. package/dist/sandbox/providers/exedev/exeDevService.js +127 -0
  147. package/dist/sandbox/providers/exedev/exeDevToken.d.ts +1 -0
  148. package/dist/sandbox/providers/exedev/exeDevToken.js +54 -0
  149. package/dist/sandbox/providers/local/localService.d.ts +2 -0
  150. package/dist/sandbox/providers/local/localService.js +219 -0
  151. package/dist/sandbox/providers/ssh/sshService.d.ts +2 -0
  152. package/dist/sandbox/providers/ssh/sshService.js +82 -0
  153. package/dist/sandbox/providers/ssh/sshTransport.d.ts +12 -0
  154. package/dist/sandbox/providers/ssh/sshTransport.js +216 -0
  155. package/dist/sandbox/service.d.ts +2 -0
  156. package/dist/sandbox/service.js +18 -0
  157. package/dist/sandbox/storage/descriptors.d.ts +6 -0
  158. package/dist/sandbox/storage/descriptors.js +25 -0
  159. package/dist/sandbox/storage/sandboxStorage.d.ts +11 -0
  160. package/dist/sandbox/storage/sandboxStorage.js +42 -0
  161. package/dist/upstreams/anthropic.d.ts +0 -5
  162. package/dist/upstreams/anthropic.js +1 -8
  163. package/dist/upstreams/index.d.ts +2 -6
  164. package/dist/upstreams/index.js +0 -4
  165. package/dist/upstreams/openai.d.ts +0 -5
  166. package/dist/upstreams/openai.js +1 -13
  167. package/package.json +12 -4
  168. package/dist/agent/gatewaySignIns.d.ts +0 -5
  169. package/dist/agent/gatewaySignIns.js +0 -11
  170. package/dist/agent/session/codex/home.d.ts +0 -2
  171. package/dist/agent/session/codex/home.js +0 -4
  172. package/dist/agent/session/codex/messages.d.ts +0 -30
  173. package/dist/agent/session/codex/messages.js +0 -135
  174. package/dist/agent/session/codex/transcript.d.ts +0 -12
  175. package/dist/agent/session/codex/transcript.js +0 -22
  176. package/dist/agent/session/sdkMessages.d.ts +0 -15
  177. package/dist/agent/session/sdkMessages.js +0 -28
  178. package/dist/agent/session/workerSseConnection.d.ts +0 -15
  179. package/dist/agent/session/workerSseConnection.js +0 -26
  180. package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.d.ts +0 -10
  181. package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.js +0 -2
  182. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.d.ts +0 -2
  183. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.js +0 -2
  184. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.d.ts +0 -22
  185. package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.js +0 -2
  186. package/dist/agent/vendor-types/codex/app-server/ImageDetail.d.ts +0 -1
  187. package/dist/agent/vendor-types/codex/app-server/ImageDetail.js +0 -2
  188. package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.d.ts +0 -5
  189. package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.js +0 -2
  190. package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.d.ts +0 -11
  191. package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.js +0 -2
  192. package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.d.ts +0 -23
  193. package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.js +0 -2
  194. package/dist/agent/vendor-types/codex/app-server/MessagePhase.d.ts +0 -7
  195. package/dist/agent/vendor-types/codex/app-server/MessagePhase.js +0 -2
  196. package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.d.ts +0 -4
  197. package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.js +0 -2
  198. package/dist/agent/vendor-types/codex/app-server/SleepItem.d.ts +0 -7
  199. package/dist/agent/vendor-types/codex/app-server/SleepItem.js +0 -2
  200. package/dist/agent/vendor-types/codex/app-server/WebSearchItem.d.ts +0 -14
  201. package/dist/agent/vendor-types/codex/app-server/WebSearchItem.js +0 -2
  202. package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.d.ts +0 -3
  203. package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.js +0 -2
  204. package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.d.ts +0 -1
  205. package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.js +0 -2
  206. package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.d.ts +0 -4
  207. package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.js +0 -2
  208. package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.d.ts +0 -4
  209. package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.js +0 -2
  210. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.d.ts +0 -5
  211. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.js +0 -2
  212. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.d.ts +0 -1
  213. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.js +0 -2
  214. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.d.ts +0 -1
  215. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.js +0 -2
  216. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.d.ts +0 -1
  217. package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.js +0 -2
  218. package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.d.ts +0 -19
  219. package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.js +0 -2
  220. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.d.ts +0 -1
  221. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.js +0 -2
  222. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.d.ts +0 -1
  223. package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.js +0 -2
  224. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.d.ts +0 -10
  225. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.js +0 -2
  226. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.d.ts +0 -1
  227. package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.js +0 -2
  228. package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.d.ts +0 -6
  229. package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.js +0 -2
  230. package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.d.ts +0 -4
  231. package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.js +0 -2
  232. package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.d.ts +0 -1
  233. package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.js +0 -2
  234. package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.d.ts +0 -8
  235. package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.js +0 -2
  236. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.d.ts +0 -7
  237. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.js +0 -2
  238. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.d.ts +0 -3
  239. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.js +0 -2
  240. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.d.ts +0 -6
  241. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.js +0 -2
  242. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.d.ts +0 -1
  243. package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.js +0 -2
  244. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.d.ts +0 -5
  245. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.js +0 -2
  246. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.d.ts +0 -6
  247. package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.js +0 -2
  248. package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.d.ts +0 -1
  249. package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.js +0 -2
  250. package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.d.ts +0 -8
  251. package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.js +0 -2
  252. package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.d.ts +0 -1
  253. package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.js +0 -2
  254. package/dist/agent/vendor-types/codex/app-server/v2/TextElement.d.ts +0 -11
  255. package/dist/agent/vendor-types/codex/app-server/v2/TextElement.js +0 -2
  256. package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.d.ts +0 -215
  257. package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.js +0 -2
  258. package/dist/agent/vendor-types/codex/app-server/v2/UserInput.d.ts +0 -35
  259. package/dist/agent/vendor-types/codex/app-server/v2/UserInput.js +0 -2
  260. package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.d.ts +0 -14
  261. package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.js +0 -2
  262. package/dist/cli/services.d.ts +0 -19
  263. package/dist/cli/services.js +0 -33
  264. package/dist/dimension/http.d.ts +0 -7
  265. package/dist/dimension/http.js +0 -77
  266. package/dist/dimension/storage.d.ts +0 -39
  267. package/dist/dimension/storage.js +0 -79
  268. package/dist/portal-gateway/fakes.d.ts +0 -3
  269. package/dist/portal-gateway/fakes.js +0 -11
  270. package/dist/portal-gateway/gatewayStorage.d.ts +0 -66
  271. package/dist/portal-gateway/gatewayStorage.js +0 -125
  272. package/dist/portal-gateway/portalStorage.d.ts +0 -34
  273. package/dist/portal-gateway/portalStorage.js +0 -44
  274. package/dist/sandbox/boatService.d.ts +0 -52
  275. package/dist/sandbox/boatService.js +0 -343
  276. package/dist/sandbox/containerService.d.ts +0 -15
  277. package/dist/sandbox/containerService.js +0 -184
  278. package/dist/sandbox/endpointProxy.d.ts +0 -90
  279. package/dist/sandbox/endpointProxy.js +0 -701
  280. package/dist/sandbox/exeDevAuth.d.ts +0 -16
  281. package/dist/sandbox/exeDevAuth.js +0 -49
  282. package/dist/sandbox/localService.d.ts +0 -19
  283. package/dist/sandbox/localService.js +0 -200
  284. package/dist/sandbox/sseConnection.d.ts +0 -41
  285. package/dist/sandbox/sseConnection.js +0 -166
  286. package/dist/sandbox/sshService.d.ts +0 -30
  287. package/dist/sandbox/sshService.js +0 -680
@@ -0,0 +1,205 @@
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
+ import { readdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { jwtVerify } from "jose";
5
+ import { dimensionLayout, isDimensionName } from "../dimension/api.js";
6
+ import { createLocalService } from "../sandbox/providers/local/localService.js";
7
+ const TRANSPORT_PROTOCOL = 1;
8
+ const TOKEN_PARAM = "hyb_token";
9
+ const HEARTBEAT_MS = 15_000;
10
+ const connections = new Map();
11
+ async function wired(dimensionName) {
12
+ const files = await readdir(join(data, "portals")).catch(() => []);
13
+ for (const file of files.filter((name) => name.endsWith(".json"))) {
14
+ const portal = await readFile(join(data, "portals", file), "utf8").then((text) => JSON.parse(text), () => null);
15
+ if (portal?.gatewayName === gateway && portal.dimensionName === dimensionName && portal.state === "ready")
16
+ return true;
17
+ }
18
+ return false;
19
+ }
20
+ async function authorized(url) {
21
+ const token = url.searchParams.get(TOKEN_PARAM);
22
+ if (!token)
23
+ return false;
24
+ try {
25
+ await jwtVerify(token, key, { algorithms: ["HS256"], requiredClaims: ["exp"] });
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ function cors(request, response) {
33
+ const origin = request.headers.get("origin");
34
+ if (!origin)
35
+ return response;
36
+ response.headers.set("access-control-allow-origin", origin);
37
+ response.headers.append("vary", "Origin");
38
+ return response;
39
+ }
40
+ async function preflight(request) {
41
+ const headers = new Headers({
42
+ "access-control-allow-methods": "GET, POST, OPTIONS",
43
+ "access-control-max-age": "600",
44
+ });
45
+ const asked = request.headers.get("access-control-request-headers");
46
+ if (asked)
47
+ headers.set("access-control-allow-headers", asked);
48
+ return cors(request, new Response(null, { status: 204, headers }));
49
+ }
50
+ async function answer(handler, request, server) {
51
+ server.timeout(request, 0);
52
+ if (!(await authorized(new URL(request.url))))
53
+ return Response.json({ error: "a valid token is required" }, { status: 401 });
54
+ try {
55
+ return await handler(request, server);
56
+ }
57
+ catch (error) {
58
+ const { name, message } = error instanceof Error ? error : new Error(String(error));
59
+ return Response.json({ name, message }, { status: 500 });
60
+ }
61
+ }
62
+ function guarded(handler) {
63
+ return async (request, server) => {
64
+ const response = await answer(handler, request, server);
65
+ return response && cors(request, response);
66
+ };
67
+ }
68
+ function relay(connection, send, end) {
69
+ connection.onData(send);
70
+ void connection.closed.then(end);
71
+ }
72
+ const encoder = new TextEncoder();
73
+ function multipartFrames() {
74
+ const boundary = randomBytes(24).toString("hex");
75
+ const part = (type, data) => Buffer.concat([encoder.encode(`\r\ncontent-type: ${type}\r\n\r\n`), data, encoder.encode(`\r\n--${boundary}`)]);
76
+ return {
77
+ contentType: `multipart/mixed; boundary="${boundary}"`,
78
+ open: (id) => Buffer.concat([encoder.encode(`--${boundary}`), part("application/json", encoder.encode(JSON.stringify({ connection: id })))]),
79
+ data: (chunk) => part("application/octet-stream", chunk),
80
+ heartbeat: part("application/octet-stream", new Uint8Array()),
81
+ end: encoder.encode("--\r\n"),
82
+ };
83
+ }
84
+ function lineFrames() {
85
+ const decoder = new TextDecoder();
86
+ let pending = "";
87
+ return {
88
+ contentType: "text/event-stream",
89
+ open: (id) => encoder.encode(`event: open\ndata: ${JSON.stringify({ connection: id })}\n\n`),
90
+ data(chunk) {
91
+ const lines = (pending + decoder.decode(chunk, { stream: true })).split("\n");
92
+ pending = lines.pop();
93
+ return encoder.encode(lines.map((line) => `data: ${line.replace(/\r$/, "")}\n\n`).join(""));
94
+ },
95
+ heartbeat: encoder.encode(": ping\n\n"),
96
+ end: encoder.encode("event: close\ndata: {}\n\n"),
97
+ };
98
+ }
99
+ function stream(connection, frames) {
100
+ const id = randomUUID();
101
+ let heartbeat;
102
+ let open = true;
103
+ const finish = () => {
104
+ open = false;
105
+ clearInterval(heartbeat);
106
+ connections.delete(id);
107
+ connection.close();
108
+ };
109
+ connections.set(id, connection);
110
+ const body = new ReadableStream({
111
+ start(controller) {
112
+ const send = (bytes) => {
113
+ if (open && bytes.length > 0)
114
+ controller.enqueue(bytes);
115
+ };
116
+ send(frames.open(id));
117
+ heartbeat = setInterval(() => send(frames.heartbeat), HEARTBEAT_MS);
118
+ relay(connection, (data) => send(frames.data(data)), () => {
119
+ if (!open)
120
+ return;
121
+ send(frames.end);
122
+ finish();
123
+ controller.close();
124
+ });
125
+ },
126
+ cancel: finish,
127
+ });
128
+ return new Response(body, { headers: { "content-type": frames.contentType, "cache-control": "no-cache, no-transform", "x-accel-buffering": "no" } });
129
+ }
130
+ async function write(request) {
131
+ const connection = connections.get(request.params.id);
132
+ if (!connection)
133
+ return Response.json({ error: "no such connection" }, { status: 404 });
134
+ connection.write(new Uint8Array(await request.arrayBuffer()));
135
+ return new Response(null, { status: 204 });
136
+ }
137
+ async function connect(request, server) {
138
+ const params = new URL(request.url).searchParams;
139
+ const dimensionName = params.get("dimension") ?? "";
140
+ const agentName = params.get("agent");
141
+ if (!isDimensionName(dimensionName) || (agentName !== null && !isDimensionName(agentName))) {
142
+ return Response.json({ error: "dimension and agent are names" }, { status: 400 });
143
+ }
144
+ if (!(await wired(dimensionName)))
145
+ return Response.json({ error: `dimension ${dimensionName} has no portal to gateway ${gateway}` }, { status: 403 });
146
+ const socket = agentName === null ? dimensionLayout.transportSocketPath(data, dimensionName) : join(dimensionLayout.runDirectory(data, dimensionName), `${agentName}.sock`);
147
+ const connection = await machine.connect(socket).catch((error) => error);
148
+ if (connection instanceof Error)
149
+ return Response.json({ error: connection.message }, { status: 502 });
150
+ if (request.headers.get("upgrade")?.toLowerCase() !== "websocket")
151
+ return stream(connection, agentName === null ? multipartFrames() : lineFrames());
152
+ if (server.upgrade(request, { data: connection }))
153
+ return undefined;
154
+ connection.close();
155
+ return Response.json({ error: "the websocket upgrade failed" }, { status: 400 });
156
+ }
157
+ function serve(port) {
158
+ return Bun.serve({
159
+ port,
160
+ hostname: "0.0.0.0",
161
+ routes: {
162
+ "/connect": {
163
+ GET: guarded(connect),
164
+ OPTIONS: preflight,
165
+ },
166
+ "/connect/:id": {
167
+ POST: guarded(write),
168
+ OPTIONS: preflight,
169
+ },
170
+ },
171
+ websocket: {
172
+ open(socket) {
173
+ relay(socket.data, (data) => socket.send(data), () => socket.close());
174
+ },
175
+ message(socket, message) {
176
+ socket.data.write(typeof message === "string" ? Buffer.from(message) : message);
177
+ },
178
+ close(socket) {
179
+ socket.data.close();
180
+ },
181
+ },
182
+ });
183
+ }
184
+ function required(name) {
185
+ const value = process.env[name];
186
+ if (!value)
187
+ throw new Error(`${name} is not set`);
188
+ return value;
189
+ }
190
+ process.umask(0o077);
191
+ const dir = required("HYB_TRANSPORT_DIR");
192
+ const build = required("HYB_TRANSPORT_BUILD");
193
+ const port = Number(required("HYB_TRANSPORT_PORT"));
194
+ const gateway = required("HYB_GATEWAY");
195
+ const data = required("HYB_DATA");
196
+ const key = new TextEncoder().encode(await readFile(join(dir, "secret"), "utf8"));
197
+ const machine = createLocalService({ kind: "local" });
198
+ const server = serve(port);
199
+ for (const signal of ["SIGTERM", "SIGINT"]) {
200
+ process.once(signal, () => {
201
+ server.stop(true);
202
+ process.exit(0);
203
+ });
204
+ }
205
+ await writeFile(join(dir, "ready"), `${process.pid} ${build} ${TRANSPORT_PROTOCOL}\n`);
@@ -0,0 +1,2 @@
1
+ export declare const transportEntry: () => string;
2
+ export declare const transportBundle: () => Promise<import("../lib/scriptBundle.ts").ScriptBundle>;
@@ -0,0 +1 @@
1
+ export declare const TRANSPORT_SOURCE: string;
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/bundle-worker.ts — do not edit; run `bun run bundle:worker`.
2
+ export const TRANSPORT_SOURCE = "// @bun\n// portal-gateway/transport-worker.ts\nimport { randomBytes as randomBytes2, randomUUID as randomUUID2 } from \"crypto\";\nimport { readdir as readdir2, readFile as readFile3, writeFile as writeFile3 } from \"fs/promises\";\nimport { join as join2 } from \"path\";\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/lib/buffer_utils.js\nvar encoder = new TextEncoder;\nvar decoder = new TextDecoder;\nvar strictDecoder = new TextDecoder(\"utf-8\", { fatal: true });\nvar UINT32_RANGE = 2 ** 32;\nfunction concat(...buffers) {\n const size = buffers.reduce((acc, { length }) => acc + length, 0), buf = new Uint8Array(size);\n let i = 0;\n for (const buffer of buffers)\n buf.set(buffer, i), i += buffer.length;\n return buf;\n}\nvar NON_ASCII = /[^\\x00-\\x7f]/;\nfunction encode(string) {\n if (typeof string == \"string\" && string.length >= 128) {\n if (NON_ASCII.test(string))\n throw new TypeError(\"non-ASCII string encountered in encode()\");\n return encoder.encode(string);\n }\n const bytes = new Uint8Array(string.length);\n for (let i = 0;i < string.length; i++) {\n const code = string.charCodeAt(i);\n if (code > 127)\n throw new TypeError(\"non-ASCII string encountered in encode()\");\n bytes[i] = code;\n }\n return bytes;\n}\nfunction encodeBase64(input, url = false) {\n if (Uint8Array.prototype.toBase64)\n return input.toBase64({ alphabet: url ? \"base64url\" : \"base64\", omitPadding: url });\n const CHUNK_SIZE = 32768, arr = [];\n for (let i = 0;i < input.length; i += CHUNK_SIZE)\n arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));\n const encoded = btoa(arr.join(\"\"));\n return url ? encoded.replace(/=/g, \"\").replace(/\\+/g, \"-\").replace(/\\//g, \"_\") : encoded;\n}\nfunction decodeBase64(encoded, url = false) {\n if (Uint8Array.fromBase64)\n return Uint8Array.fromBase64(encoded, { alphabet: url ? \"base64url\" : \"base64\" });\n if (url) {\n if (encoded.includes(\"+\") || encoded.includes(\"/\"))\n throw new TypeError(\"Invalid base64url\");\n encoded = encoded.replace(/-/g, \"+\").replace(/_/g, \"/\");\n }\n const binary = atob(encoded), bytes = new Uint8Array(binary.length);\n for (let i = 0;i < binary.length; i++)\n bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/util/errors.js\nclass JOSEError extends Error {\n static code = \"ERR_JOSE_GENERIC\";\n code = \"ERR_JOSE_GENERIC\";\n constructor(message, options) {\n super(message, options), this.name = this.constructor.name, Error.captureStackTrace?.(this, this.constructor);\n }\n}\n\nclass JWTClaimValidationFailed extends JOSEError {\n static code = \"ERR_JWT_CLAIM_VALIDATION_FAILED\";\n code = \"ERR_JWT_CLAIM_VALIDATION_FAILED\";\n claim;\n reason;\n payload;\n constructor(message, payload, claim = \"unspecified\", reason = \"unspecified\") {\n super(message, { cause: { claim, reason, payload } }), this.claim = claim, this.reason = reason, this.payload = payload;\n }\n}\n\nclass JWTExpired extends JOSEError {\n static code = \"ERR_JWT_EXPIRED\";\n code = \"ERR_JWT_EXPIRED\";\n claim;\n reason;\n payload;\n constructor(message, payload, claim = \"unspecified\", reason = \"unspecified\") {\n super(message, { cause: { claim, reason, payload } }), this.claim = claim, this.reason = reason, this.payload = payload;\n }\n}\n\nclass JOSEAlgNotAllowed extends JOSEError {\n static code = \"ERR_JOSE_ALG_NOT_ALLOWED\";\n code = \"ERR_JOSE_ALG_NOT_ALLOWED\";\n}\n\nclass JOSENotSupported extends JOSEError {\n static code = \"ERR_JOSE_NOT_SUPPORTED\";\n code = \"ERR_JOSE_NOT_SUPPORTED\";\n}\nclass JWSInvalid extends JOSEError {\n static code = \"ERR_JWS_INVALID\";\n code = \"ERR_JWS_INVALID\";\n}\n\nclass JWTInvalid extends JOSEError {\n static code = \"ERR_JWT_INVALID\";\n code = \"ERR_JWT_INVALID\";\n}\nclass JWSSignatureVerificationFailed extends JOSEError {\n static code = \"ERR_JWS_SIGNATURE_VERIFICATION_FAILED\";\n code = \"ERR_JWS_SIGNATURE_VERIFICATION_FAILED\";\n constructor(message = \"signature verification failed\", options) {\n super(message, options);\n }\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/util/base64url.js\nvar invalid = \"The input to be decoded is not correctly encoded.\";\nfunction decode(input) {\n try {\n return decodeBase64(typeof input == \"string\" ? input : decoder.decode(input), true);\n } catch (cause) {\n throw new TypeError(invalid, { cause });\n }\n}\nfunction encode2(input) {\n return encodeBase64(typeof input == \"string\" ? encoder.encode(input) : input, true);\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/lib/validate.js\nfunction isObject(input) {\n if (typeof input != \"object\" || input === null || Object.prototype.toString.call(input) !== \"[object Object]\")\n return false;\n const prototype = Object.getPrototypeOf(input);\n return prototype === null || Object.getPrototypeOf(prototype) === null;\n}\nfunction isDisjoint(...headers) {\n const parameters = /* @__PURE__ */ new Set;\n for (const header of headers)\n if (header)\n for (const parameter of Object.keys(header)) {\n if (parameters.has(parameter))\n return false;\n parameters.add(parameter);\n }\n return true;\n}\nfunction assertNotSet(value, name) {\n if (value !== undefined)\n throw new TypeError(`${name} can only be called once`);\n}\nfunction decodeBase64url(value, label, ErrorClass) {\n try {\n return decode(value);\n } catch (cause) {\n throw new ErrorClass(`Failed to base64url decode the ${label}`, { cause });\n }\n}\nfunction encodeAsciiMember(value, label, ErrorClass) {\n try {\n return encode(value);\n } catch (cause) {\n throw new ErrorClass(`The ${label} is not a valid base64url string`, { cause });\n }\n}\nfunction parseJoseHeader(encodedProtectedHeader, ErrorClass, message) {\n let parsed;\n try {\n parsed = JSON.parse(strictDecoder.decode(decode(encodedProtectedHeader)));\n } catch (cause) {\n throw new ErrorClass(message, { cause });\n }\n if (!isObject(parsed))\n throw new ErrorClass(message);\n return parsed;\n}\nvar JWS_RECOGNIZED = { __proto__: null, b64: true };\nfunction validateAlgorithms(option, algorithms) {\n if (algorithms !== undefined && (!Array.isArray(algorithms) || algorithms.some((s) => typeof s != \"string\")))\n throw new TypeError(`\"${option}\" option must be an array of strings`);\n return algorithms === undefined ? undefined : new Set(algorithms);\n}\nfunction validateCritDuplicates(Err, protectedHeader) {\n const { crit } = protectedHeader ?? {};\n if (Array.isArray(crit) && new Set(crit).size !== crit.length)\n throw new Err('\"crit\" (Critical) Header Parameter MUST NOT contain duplicate values');\n}\nfunction validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader, joseHeader) {\n if (joseHeader.crit !== undefined && protectedHeader?.crit === undefined)\n throw new Err('\"crit\" (Critical) Header Parameter MUST be integrity protected');\n if (!protectedHeader || protectedHeader.crit === undefined)\n return [];\n if (!Array.isArray(protectedHeader.crit) || protectedHeader.crit.length === 0 || protectedHeader.crit.some((input) => typeof input != \"string\" || input.length === 0))\n throw new Err('\"crit\" (Critical) Header Parameter MUST be an array of non-empty strings when present');\n const recognized = recognizedOption === undefined ? recognizedDefault : { __proto__: null, ...recognizedOption, ...recognizedDefault };\n for (const parameter of protectedHeader.crit) {\n if (!(parameter in recognized))\n throw new JOSENotSupported(`Extension Header Parameter \"${parameter}\" is not recognized`);\n if (!Object.hasOwn(joseHeader, parameter) || joseHeader[parameter] === undefined)\n throw new Err(`Extension Header Parameter \"${parameter}\" is missing`);\n if (recognized[parameter] && (!Object.hasOwn(protectedHeader, parameter) || protectedHeader[parameter] === undefined))\n throw new Err(`Extension Header Parameter \"${parameter}\" MUST be integrity protected`);\n }\n return protectedHeader.crit;\n}\nfunction validateB64(protectedHeader, extensions) {\n if (extensions.includes(\"b64\")) {\n const b64 = protectedHeader.b64;\n if (typeof b64 != \"boolean\")\n throw new JWSInvalid('The \"b64\" (base64url-encode payload) Header Parameter must be a boolean');\n return b64;\n }\n return true;\n}\nfunction serializeJoseHeader(Err, header) {\n let serialized, parsed;\n try {\n serialized = JSON.stringify(header), parsed = JSON.parse(serialized);\n } catch (cause) {\n throw new Err(\"JOSE Header is not valid JSON\", { cause });\n }\n if (!isObject(parsed))\n throw new Err(\"JOSE Header is not a JSON object\");\n return [parsed, serialized];\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/lib/key.js\nvar tag = (key) => key[Symbol.toStringTag];\nvar jwkMatchesOp = (entry, key, usage) => {\n const { alg } = entry;\n if (key.use !== undefined) {\n const expected = usage === \"sign\" || usage === \"verify\" ? \"sig\" : \"enc\";\n if (key.use !== expected)\n throw new TypeError(`Invalid key for this operation, its \"use\" must be \"${expected}\" when present`);\n }\n if (key.alg !== undefined && key.alg !== alg)\n throw new TypeError(`Invalid key for this operation, its \"alg\" must be \"${alg}\" when present`);\n if (Array.isArray(key.key_ops)) {\n const expectedKeyOp = usage === \"encrypt\" || usage === \"decrypt\" ? entry.ops?.[usage === \"encrypt\" ? 0 : 1] : usage;\n if (expectedKeyOp && !key.key_ops.includes(expectedKeyOp))\n throw new TypeError(`Invalid key for this operation, its \"key_ops\" must include \"${expectedKeyOp}\" when present`);\n }\n};\nasync function prepareKey(entry, key, usage) {\n const { alg, secret } = entry, requiresPrivateKey = usage === \"decrypt\" || usage === \"sign\";\n if (secret && key instanceof Uint8Array)\n return key;\n let normalized, keyObject;\n if (isObject(key)) {\n if (normalized = normalizeJwk(key), typeof normalized.kty != \"string\")\n throw invalidKeyType(alg, key, secret);\n if (!(secret ? normalized.kty === \"oct\" && typeof normalized.k == \"string\" : normalized.kty !== \"oct\" && (requiresPrivateKey ? normalized.kty === \"AKP\" && typeof normalized.priv == \"string\" || typeof normalized.d == \"string\" : normalized.d === undefined && normalized.priv === undefined)))\n throw new TypeError(secret ? 'JSON Web Key for symmetric algorithms must have JWK \"kty\" (Key Type) equal to \"oct\" and the JWK \"k\" (Key Value) present' : `JSON Web Key for this operation must be a ${requiresPrivateKey ? \"private\" : \"public\"} JWK`);\n if (jwkMatchesOp(entry, normalized, usage), normalized.kty === \"oct\")\n return decode(normalized.k);\n if (!Object.isFrozen(key)) {\n const { key_ops } = key;\n Array.isArray(key_ops) && Object.freeze(key_ops), Object.freeze(key);\n }\n } else {\n if (!isKeyLike(key))\n throw invalidKeyType(alg, key, secret);\n const expectedType = secret ? \"secret\" : requiresPrivateKey ? \"private\" : \"public\";\n if (key.type !== expectedType && (secret || [\"secret\", \"public\", \"private\"].includes(key.type)))\n throw new TypeError(`${tag(key)} instances must be of type \"${expectedType}\" for the ${alg} algorithm`);\n if (isCryptoKey(key))\n return key;\n if (keyObject = key, keyObject.type === \"secret\")\n return keyObject.export();\n }\n cache ||= /* @__PURE__ */ new WeakMap;\n const cacheKey = key;\n let cached = cache.get(cacheKey);\n if (cached?.[alg])\n return cached[alg];\n if (cached || cache.set(cacheKey, cached = {}), keyObject && typeof keyObject.toCryptoKey == \"function\") {\n const isPublic = keyObject.type === \"public\", crv = nist[keyObject.asymmetricKeyDetails?.namedCurve], params = entry.resolve?.({ crv, asymmetricKeyType: keyObject.asymmetricKeyType }) ?? entry.subtle;\n return cached[alg] = keyObject.toCryptoKey(params, isPublic, entry.usages[isPublic ? 0 : 1]);\n }\n return normalized ??= keyObject.export({ format: \"jwk\" }), normalized.alg = alg, cached[alg] = await jwkToKey(entry, normalized);\n}\nvar cache;\nvar nist = {\n __proto__: null,\n prime256v1: \"P-256\",\n secp384r1: \"P-384\",\n secp521r1: \"P-521\"\n};\nvar isCryptoKey = (key) => {\n if (key?.[Symbol.toStringTag] === \"CryptoKey\")\n return true;\n try {\n return key instanceof CryptoKey;\n } catch {\n return false;\n }\n};\nvar isKeyObject = (key) => key?.[Symbol.toStringTag] === \"KeyObject\";\nvar isKeyLike = (key) => isCryptoKey(key) || isKeyObject(key);\nfunction message(msg, actual, ...types) {\n if (types.length > 2) {\n const last = types.pop();\n msg += `one of type ${types.join(\", \")}, or ${last}.`;\n } else\n types.length === 2 ? msg += `one of type ${types[0]} or ${types[1]}.` : msg += `of type ${types[0]}.`;\n return actual == null ? msg += ` Received ${actual}` : typeof actual == \"function\" && actual.name ? msg += ` Received function ${actual.name}` : typeof actual == \"object\" && actual != null && actual.constructor?.name && (msg += ` Received an instance of ${actual.constructor.name}`), msg;\n}\nfunction invalidKeyType(alg, actual, secret) {\n const types = [\"CryptoKey\", \"KeyObject\", \"JSON Web Key\"];\n return secret && types.push(\"Uint8Array\"), new TypeError(message(`Key for the ${alg} algorithm must be `, actual, ...types));\n}\nvar unusable = (name, prop = \"algorithm.name\") => new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);\nfunction checkUsage(key, usage) {\n if (usage && !key.usages.includes(usage))\n throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);\n}\nfunction checkModulusLength(alg, key) {\n const { modulusLength } = key.algorithm;\n if (typeof modulusLength != \"number\" || modulusLength < 2048)\n throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);\n}\nfunction checkCryptoKey(key, expected, usage) {\n const algorithm = key.algorithm;\n if (algorithm.name !== expected.name)\n throw unusable(expected.name);\n if (expected.hash && algorithm.hash?.name !== expected.hash)\n throw unusable(expected.hash, \"algorithm.hash\");\n if (expected.namedCurve && algorithm.namedCurve !== expected.namedCurve)\n throw unusable(expected.namedCurve, \"algorithm.namedCurve\");\n if (expected.length !== undefined && algorithm.length !== expected.length)\n throw unusable(expected.length, \"algorithm.length\");\n checkUsage(key, usage);\n}\nfunction snapshotJwk(jwk) {\n return { __proto__: null, ...jwk };\n}\nfunction normalizeJwk(jwk) {\n const normalized = snapshotJwk(jwk);\n if (normalized.ext !== undefined && typeof normalized.ext != \"boolean\")\n throw new TypeError('\"ext\" (Extractable) Parameter must be a boolean');\n if (normalized.key_ops !== undefined) {\n const value = normalized.key_ops, keyOps = Array.isArray(value) ? [...value] : undefined;\n if (!keyOps || keyOps.some((operation) => typeof operation != \"string\") || new Set(keyOps).size !== keyOps.length)\n throw new TypeError('\"key_ops\" (Key Operations) Parameter must be an array of unique strings');\n normalized.key_ops = keyOps;\n }\n return normalized;\n}\nasync function jwkToKey(entry, jwk, extractable) {\n if (!entry.kty.includes(jwk.kty))\n throw new JOSENotSupported('Invalid or unsupported JWK \"alg\" (Algorithm) Parameter value');\n const algorithm = entry.resolve?.({ kty: jwk.kty, crv: jwk.crv }) ?? entry.subtle, isPrivate = !!(jwk.d || jwk.priv), keyData = { ...jwk, ext: extractable ?? jwk.ext };\n return keyData.kty !== \"AKP\" && delete keyData.alg, delete keyData.use, crypto.subtle.importKey(\"jwk\", keyData, algorithm, keyData.ext ?? !isPrivate, jwk.key_ops ?? entry.usages[isPrivate ? 1 : 0]);\n}\nasync function rawKey(key, expected, usage, extractable = false) {\n return key instanceof Uint8Array && (key = await crypto.subtle.importKey(\"raw\", key, expected, extractable, [usage])), checkCryptoKey(key, expected, usage), key;\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/lib/key_descriptor.js\nfunction table(entries) {\n const out = { __proto__: null };\n for (const alg in entries)\n out[alg] = { ...entries[alg], alg };\n return out;\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/lib/jws_algorithms.js\nvar sig = [[\"verify\"], [\"sign\"]];\nfunction hmac(bits) {\n const subtle = { name: \"HMAC\", hash: `SHA-${bits}` };\n return { kty: [\"oct\"], secret: true, subtle, signing: subtle, usages: sig };\n}\nfunction rsa(bits, saltLength) {\n const subtle = { name: saltLength ? \"RSA-PSS\" : \"RSASSA-PKCS1-v1_5\", hash: `SHA-${bits}` };\n return {\n kty: [\"RSA\"],\n subtle,\n signing: saltLength ? { ...subtle, saltLength } : subtle,\n usages: sig,\n minRsaBits: 2048\n };\n}\nfunction ecdsa(crv, bits) {\n return {\n kty: [\"EC\"],\n crv,\n subtle: { name: \"ECDSA\", namedCurve: crv },\n signing: { name: \"ECDSA\", hash: `SHA-${bits}` },\n usages: sig\n };\n}\nfunction eddsa() {\n const subtle = { name: \"Ed25519\" };\n return {\n kty: [\"OKP\"],\n crv: \"Ed25519\",\n subtle,\n signing: subtle,\n usages: sig\n };\n}\nfunction mldsa(parameterSet) {\n const subtle = { name: `ML-DSA-${parameterSet}` };\n return {\n kty: [\"AKP\"],\n subtle,\n signing: subtle,\n usages: sig\n };\n}\nvar JWS = table({\n HS256: hmac(256),\n HS384: hmac(384),\n HS512: hmac(512),\n RS256: rsa(256),\n RS384: rsa(384),\n RS512: rsa(512),\n PS256: rsa(256, 32),\n PS384: rsa(384, 48),\n PS512: rsa(512, 64),\n ES256: ecdsa(\"P-256\", 256),\n ES384: ecdsa(\"P-384\", 384),\n ES512: ecdsa(\"P-521\", 512),\n EdDSA: eddsa(),\n Ed25519: eddsa(),\n \"ML-DSA-44\": mldsa(44),\n \"ML-DSA-65\": mldsa(65),\n \"ML-DSA-87\": mldsa(87)\n});\nfunction jwsAlgorithm(alg) {\n const entry = typeof alg == \"string\" ? JWS[alg] : undefined;\n if (!entry)\n throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);\n return entry;\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/lib/jws_verify.js\nfunction prepareVerify(options) {\n return [options && validateAlgorithms(\"algorithms\", options.algorithms), options?.crit];\n}\nfunction parseProtectedHeader(encodedProtectedHeader) {\n return encodedProtectedHeader === undefined ? {} : parseJoseHeader(encodedProtectedHeader, JWSInvalid, \"JWS Protected Header is invalid\");\n}\nfunction encodeCompactUnencodedPayload(payload) {\n try {\n return encode(payload);\n } catch (cause) {\n throw new JWSInvalid(\"JWS Compact Serialization payload must use only ASCII characters\", {\n cause\n });\n }\n}\nasync function verifySignature(jws, shared, key, encodeUnencodedPayload, parsedProtected) {\n const { protected: encodedProtectedHeader, header, payload: inputPayload } = jws, protectedHeader = parsedProtected ?? parseProtectedHeader(encodedProtectedHeader);\n if (!isDisjoint(protectedHeader, header))\n throw new JWSInvalid(\"JWS Protected and JWS Unprotected Header Parameter names must be disjoint\");\n const joseHeader = { ...protectedHeader, ...header }, b64 = validateB64(protectedHeader, validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], protectedHeader, joseHeader)), { alg } = joseHeader;\n if (typeof alg != \"string\" || !alg)\n throw new JWSInvalid('JWS \"alg\" (Algorithm) Header Parameter missing or invalid');\n if (shared[0] && !shared[0].has(alg))\n throw new JOSEAlgNotAllowed('\"alg\" (Algorithm) Header Parameter value not allowed');\n if (b64) {\n if (typeof inputPayload != \"string\")\n throw new JWSInvalid(\"JWS Payload must be a string\");\n } else if (typeof inputPayload != \"string\" && !(inputPayload instanceof Uint8Array))\n throw new JWSInvalid(\"JWS Payload must be a string or an Uint8Array instance\");\n const signingPayload = b64 || typeof inputPayload != \"string\" ? inputPayload : encodeUnencodedPayload(inputPayload);\n let resolvedKey = false;\n typeof key == \"function\" && (key = await key(protectedHeader, jws), resolvedKey = true);\n const entry = jwsAlgorithm(alg), signingInput = concat(encodedProtectedHeader !== undefined ? encode(encodedProtectedHeader) : new Uint8Array, encode(\".\"), typeof signingPayload == \"string\" ? shared[2] ??= encodeAsciiMember(signingPayload, \"payload\", JWSInvalid) : signingPayload), signature = decodeBase64url(jws.signature, \"signature\", JWSInvalid), verificationKey = await prepareKey(entry, key, \"verify\"), cryptoKey = await rawKey(verificationKey, entry.subtle, \"verify\");\n entry.minRsaBits && checkModulusLength(entry.alg, cryptoKey);\n let verified = false;\n try {\n verified = await crypto.subtle.verify(entry.signing, cryptoKey, signature, signingInput);\n } catch (cause) {\n throw new JWSSignatureVerificationFailed(undefined, { cause });\n }\n if (!verified)\n throw new JWSSignatureVerificationFailed;\n const result = { payload: typeof signingPayload == \"string\" ? decodeBase64url(signingPayload, \"payload\", JWSInvalid) : signingPayload };\n return encodedProtectedHeader !== undefined && (result.protectedHeader = protectedHeader), header !== undefined && (result.unprotectedHeader = header), resolvedKey ? [{ ...result, key: verificationKey }, b64] : [result, b64];\n}\nasync function verifyCompact(jws, shared, key) {\n if (jws instanceof Uint8Array && (jws = decoder.decode(jws)), typeof jws != \"string\")\n throw new JWSInvalid(\"Compact JWS must be a string or Uint8Array\");\n const { 0: protectedHeader, 1: payload, 2: signature, length } = jws.split(\".\");\n if (length !== 3)\n throw new JWSInvalid(\"Invalid Compact JWS\");\n return verifySignature({ payload, protected: protectedHeader, signature }, shared, key, encodeCompactUnencodedPayload);\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/lib/jwt_claims_set.js\nvar epoch = (date) => Math.floor(date.getTime() / 1000);\nvar multipliers = {\n s: 1,\n m: 60,\n h: 3600,\n d: 86400,\n w: 604800,\n y: 31557600\n};\nvar REGEX = /^(\\+|\\-)? ?(\\d+|\\d+\\.\\d+) ?(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)(?: (ago|from now))?$/i;\nvar checkFailed = \"check_failed\";\nfunction invalidDuration() {\n throw new TypeError(\"Invalid time period format\");\n}\nfunction secs(str) {\n typeof str != \"string\" && invalidDuration();\n const matched = REGEX.exec(str);\n (!matched || matched[4] && matched[1]) && invalidDuration();\n const value = parseFloat(matched[2]), durationSeconds = Math.round(value * multipliers[matched[3][0].toLowerCase()]);\n return Number.isFinite(durationSeconds) || invalidDuration(), matched[1] === \"-\" || matched[4] === \"ago\" ? -durationSeconds : durationSeconds;\n}\nfunction validateInput(label, input) {\n if (!Number.isFinite(input))\n throw new TypeError(`Invalid ${label} input`);\n return input;\n}\nfunction validateStringClaim(claim, value) {\n if (typeof value != \"string\")\n throw new TypeError(`\"${claim}\" claim must be a string`);\n}\nfunction validateAudienceClaim(value) {\n if (typeof value != \"string\" && (!Array.isArray(value) || Array.from(value).some((member) => typeof member != \"string\")))\n throw new TypeError('\"aud\" claim must be a string or an array of strings');\n}\nfunction numericDate(value, label) {\n return typeof value == \"number\" ? validateInput(label, value) : value instanceof Date ? validateInput(label, epoch(value)) : epoch(/* @__PURE__ */ new Date) + secs(value);\n}\nvar normalizeTyp = (value) => {\n const normalized = value.toLowerCase();\n return value.includes(\"/\") ? normalized : `application/${normalized}`;\n};\nvar checkAudiencePresence = (audPayload, audOption) => typeof audPayload == \"string\" ? audOption.includes(audPayload) : Array.isArray(audPayload) ? audOption.some((aud) => audPayload.includes(aud)) : false;\nfunction validateNumericDate(claimsSet, claim, required = false) {\n const value = claimsSet[claim];\n if (!(value === undefined && !required)) {\n if (typeof value != \"number\")\n throw new JWTClaimValidationFailed(`\"${claim}\" claim must be a number`, claimsSet, claim, \"invalid\");\n return value;\n }\n}\nfunction unexpectedClaim(claimsSet, claim) {\n throw new JWTClaimValidationFailed(`unexpected \"${claim}\" claim value`, claimsSet, claim, checkFailed);\n}\nfunction validateClaimsSet(protectedHeader, claimsSetBytes, options = {}) {\n let claimsSet;\n try {\n claimsSet = JSON.parse(strictDecoder.decode(claimsSetBytes));\n } catch (cause) {\n throw new JWTInvalid(\"JWT Claims Set must be a top-level JSON object\", { cause });\n }\n if (!isObject(claimsSet))\n throw new JWTInvalid(\"JWT Claims Set must be a top-level JSON object\");\n const { typ } = options;\n if (typ !== undefined && (typeof protectedHeader.typ != \"string\" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ)))\n throw new JWTClaimValidationFailed('unexpected \"typ\" JWT header value', claimsSet, \"typ\", checkFailed);\n const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options, presenceCheck = [...requiredClaims];\n maxTokenAge !== undefined && presenceCheck.push(\"iat\"), audience !== undefined && presenceCheck.push(\"aud\"), subject !== undefined && presenceCheck.push(\"sub\"), issuer !== undefined && presenceCheck.push(\"iss\");\n for (const claim of new Set(presenceCheck.reverse()))\n if (!Object.hasOwn(claimsSet, claim))\n throw new JWTClaimValidationFailed(`missing required \"${claim}\" claim`, claimsSet, claim, \"missing\");\n issuer !== undefined && !(Array.isArray(issuer) ? issuer : [issuer]).includes(claimsSet.iss) && unexpectedClaim(claimsSet, \"iss\"), subject !== undefined && claimsSet.sub !== subject && unexpectedClaim(claimsSet, \"sub\"), audience !== undefined && !checkAudiencePresence(claimsSet.aud, typeof audience == \"string\" ? [audience] : audience) && unexpectedClaim(claimsSet, \"aud\");\n const { clockTolerance } = options;\n let tolerance = 0;\n if (typeof clockTolerance == \"string\")\n tolerance = secs(clockTolerance);\n else if (clockTolerance !== undefined) {\n if (typeof clockTolerance != \"number\")\n throw new TypeError(\"Invalid clockTolerance option type\");\n tolerance = clockTolerance;\n }\n validateInput(\"clockTolerance option\", tolerance);\n const { currentDate } = options, now = validateInput(\"currentDate option\", epoch(currentDate === undefined ? /* @__PURE__ */ new Date : currentDate)), iat = validateNumericDate(claimsSet, \"iat\", maxTokenAge !== undefined), nbf = validateNumericDate(claimsSet, \"nbf\");\n if (nbf !== undefined && nbf > now + tolerance)\n throw new JWTClaimValidationFailed('\"nbf\" claim timestamp check failed', claimsSet, \"nbf\", checkFailed);\n const exp = validateNumericDate(claimsSet, \"exp\");\n if (exp !== undefined && exp <= now - tolerance)\n throw new JWTExpired('\"exp\" claim timestamp check failed', claimsSet, \"exp\", checkFailed);\n if (maxTokenAge !== undefined) {\n const age = now - iat, max = validateInput(\"maxTokenAge option\", typeof maxTokenAge == \"number\" ? maxTokenAge : secs(maxTokenAge));\n if (age - tolerance > max)\n throw new JWTExpired('\"iat\" claim timestamp check failed (too far in the past)', claimsSet, \"iat\", checkFailed);\n if (age < -tolerance)\n throw new JWTClaimValidationFailed('\"iat\" claim timestamp check failed (it should be in the past)', claimsSet, \"iat\", checkFailed);\n }\n return claimsSet;\n}\nvar producerClaimsSets;\nfunction producerClaimsSet(producer) {\n return producerClaimsSets.get(producer);\n}\nfunction jwtClaimsSetBytes(producer) {\n const claimsSet = producerClaimsSet(producer);\n for (const claim of [\"iat\", \"nbf\", \"exp\"]) {\n const value = claimsSet[claim];\n if (typeof value == \"number\" && !Number.isFinite(value))\n throw new TypeError(`\"${claim}\" claim must be a finite number`);\n }\n return encoder.encode(JSON.stringify(claimsSet));\n}\nclass JWTClaimsBuilder {\n constructor(claimsSet = {}) {\n if (!isObject(claimsSet))\n throw new TypeError(\"JWT Claims Set MUST be an object\");\n (producerClaimsSets ||= /* @__PURE__ */ new WeakMap).set(this, structuredClone(claimsSet));\n }\n setIssuer(value) {\n return validateStringClaim(\"iss\", value), producerClaimsSet(this).iss = value, this;\n }\n setSubject(value) {\n return validateStringClaim(\"sub\", value), producerClaimsSet(this).sub = value, this;\n }\n setAudience(value) {\n return validateAudienceClaim(value), producerClaimsSet(this).aud = value, this;\n }\n setJti(value) {\n return validateStringClaim(\"jti\", value), producerClaimsSet(this).jti = value, this;\n }\n setNotBefore(value) {\n return producerClaimsSet(this).nbf = numericDate(value, \"setNotBefore\"), this;\n }\n setExpirationTime(value) {\n return producerClaimsSet(this).exp = numericDate(value, \"setExpirationTime\"), this;\n }\n setIssuedAt(value) {\n const claimsSet = producerClaimsSet(this);\n return value === undefined ? claimsSet.iat = epoch(/* @__PURE__ */ new Date) : typeof value == \"string\" ? claimsSet.iat = validateInput(\"setIssuedAt\", epoch(/* @__PURE__ */ new Date) + secs(value)) : claimsSet.iat = numericDate(value, \"setIssuedAt\"), this;\n }\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/jwt/verify.js\nasync function jwtVerify(jwt, key, options) {\n const [verified, b64] = await verifyCompact(jwt, prepareVerify(options), key);\n if (!b64)\n throw new JWTInvalid(\"JWTs MUST NOT use unencoded payload\");\n const payload = validateClaimsSet(verified.protectedHeader, verified.payload, options);\n return { ...verified, payload };\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/lib/jws_sign.js\nasync function createSignature(input, key, rejectUnencoded) {\n let [payload, protectedHeader, unprotectedHeader, crit] = input, encodedProtectedHeader = \"\";\n if (protectedHeader !== undefined) {\n const normalized = serializeJoseHeader(JWSInvalid, protectedHeader);\n protectedHeader = normalized[0], encodedProtectedHeader = encode2(normalized[1]);\n }\n if (unprotectedHeader !== undefined && (unprotectedHeader = serializeJoseHeader(JWSInvalid, unprotectedHeader)[0]), !protectedHeader && !unprotectedHeader)\n throw new JWSInvalid(\"either setProtectedHeader or setUnprotectedHeader must be called before #sign()\");\n if (!isDisjoint(protectedHeader, unprotectedHeader))\n throw new JWSInvalid(\"JWS Protected and JWS Unprotected Header Parameter names must be disjoint\");\n const joseHeader = { ...protectedHeader, ...unprotectedHeader };\n validateCritDuplicates(JWSInvalid, protectedHeader);\n const b64 = validateB64(protectedHeader, validateCrit(JWSInvalid, JWS_RECOGNIZED, crit, protectedHeader, joseHeader));\n b64 || rejectUnencoded?.();\n const { alg } = joseHeader;\n if (typeof alg != \"string\" || !alg)\n throw new JWSInvalid('JWS \"alg\" (Algorithm) Header Parameter missing or invalid');\n const entry = jwsAlgorithm(alg);\n let encodedPayload = \"\", signingPayload = payload, signingInput;\n if (b64) {\n const encodedPayloadCache = input[4];\n encodedPayloadCache ? (encodedPayload = encodedPayloadCache[0] ??= encode2(payload), signingPayload = encodedPayloadCache[1] ??= encode(encodedPayload)) : (encodedPayload = encode2(payload), signingInput = encoder.encode(`${encodedProtectedHeader}.${encodedPayload}`));\n }\n signingInput ??= concat(encode(encodedProtectedHeader), encode(\".\"), signingPayload);\n const signingKey = await rawKey(await prepareKey(entry, key, \"sign\"), entry.subtle, \"sign\");\n entry.minRsaBits && checkModulusLength(entry.alg, signingKey);\n const jws = {\n signature: encode2(new Uint8Array(await crypto.subtle.sign(entry.signing, signingKey, signingInput))),\n payload: encodedPayload\n };\n return protectedHeader && (jws.protected = encodedProtectedHeader), unprotectedHeader && (jws.header = unprotectedHeader), [jws, b64];\n}\nasync function createCompactSignature(payload, protectedHeader, crit, key, rejectUnencoded) {\n const [jws] = await createSignature([payload, protectedHeader, undefined, crit], key, rejectUnencoded);\n return `${jws.protected}.${jws.payload}.${jws.signature}`;\n}\n\n// ../node_modules/.bun/jose@6.2.13/node_modules/jose/dist/webapi/jwt/sign.js\nvar SignJWT_base = JWTClaimsBuilder;\n\nclass SignJWT extends SignJWT_base {\n #protectedHeader;\n setProtectedHeader(protectedHeader) {\n return assertNotSet(this.#protectedHeader, \"setProtectedHeader\"), this.#protectedHeader = protectedHeader, this;\n }\n async sign(key, options) {\n return createCompactSignature(jwtClaimsSetBytes(this), this.#protectedHeader, options?.crit, key, () => {\n throw new JWTInvalid(\"JWTs MUST NOT use unencoded payload\");\n });\n }\n}\n\n// dimension/paths.ts\nfunction containerName(dimensionName) {\n return `hyb-${dimensionName}`;\n}\nvar WORKSPACE_PATH = \"/workspace\";\nvar RUN_PATH = \"/run/hyperbrane\";\nvar STATE_PATH = \"/var/lib/hyperbrane\";\nvar CA_BUNDLE_PATH = \"/etc/ssl/certs/ca-certificates.crt\";\nvar TRANSPORT_SOCKET = \".transport.sock\";\nfunction dimensionRunDirectory(dataDirectory, dimensionName) {\n return `${dataDirectory}/dimension-run/${dimensionName}`;\n}\nfunction dimensionStateDirectory(dataDirectory, dimensionName) {\n return `${dataDirectory}/dimension-state/${dimensionName}`;\n}\nvar dimensionLayout = {\n containerName,\n workspacePath: () => WORKSPACE_PATH,\n runPath: () => RUN_PATH,\n statePath: () => STATE_PATH,\n caBundlePath: () => CA_BUNDLE_PATH,\n runDirectory: dimensionRunDirectory,\n stateDirectory: dimensionStateDirectory,\n transportSocketPath: (dataDirectory, dimensionName) => `${dimensionRunDirectory(dataDirectory, dimensionName)}/${TRANSPORT_SOCKET}`\n};\n\n// dimension/api.ts\nvar MEMORY_UNITS = {\n \"\": 1,\n K: 1024,\n M: 1024 ** 2,\n G: 1024 ** 3,\n T: 1024 ** 4\n};\nvar DIMENSION_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,62}$/;\nfunction isDimensionName(name) {\n return DIMENSION_NAME.test(name);\n}\n\n// sandbox/providers/local/localService.ts\nimport { spawn } from \"child_process\";\nimport { randomUUID } from \"crypto\";\nimport * as fs from \"fs/promises\";\nimport { connect as netConnect, Socket } from \"net\";\nimport { homedir, userInfo } from \"os\";\nimport { basename, dirname, join, resolve } from \"path\";\n\n// sandbox/api.ts\nclass SandboxNotFoundError extends Error {\n name = \"SandboxNotFoundError\";\n}\nclass SandboxConnectionRefusedError extends Error {\n name = \"SandboxConnectionRefusedError\";\n}\n\n// sandbox/storage/descriptors.ts\nfunction makeDescriptor(ctx, id, spec) {\n return {\n id,\n kind: ctx.kind,\n name: spec.name,\n ...ctx.sshHost === undefined ? {} : { sshHost: ctx.sshHost },\n ...ctx.sshUser === undefined ? {} : { sshUser: ctx.sshUser },\n ...ctx.sshPort === undefined ? {} : { sshPort: ctx.sshPort },\n ...spec.dataEndpointOptions === undefined ? {} : { dataEndpointOptions: spec.dataEndpointOptions }\n };\n}\nasync function createDescriptor(ctx, storage, id, spec) {\n const descriptor = makeDescriptor(ctx, id, spec);\n await storage.saveDescriptor(descriptor);\n return [{ ...ctx, id }, descriptor];\n}\nasync function openDescriptor(ctx, storage, id) {\n return await storage.readDescriptor() ?? storage.insertDescriptor(makeDescriptor(ctx, id, { name: id }));\n}\nasync function getDescriptor(storage, where) {\n const descriptor = await storage.readDescriptor();\n if (!descriptor)\n throw new Error(`${where} has no descriptor: opening it saves one`);\n return descriptor;\n}\n\n// sandbox/data-endpoints/dataEndpoints.ts\nimport { randomBytes } from \"crypto\";\n\n// lib/sh.ts\nfunction sh(strings, ...values) {\n if (values.length > 0)\n throw new Error(\"sh`\\u2026` takes no interpolations: pass inputs as environment variables or arguments\");\n return scriptText(strings.raw[0]);\n}\nfunction scriptText(raw) {\n return raw.replace(/\\\\(\\$\\{|`)/g, \"$1\").replace(/^\\n/, \"\");\n}\n\n// sandbox/data-endpoints/dataEndpoints.ts\nvar DEFAULT_TTL_S = 12 * 3600;\nfunction signToken(secret, ttlSeconds) {\n return new SignJWT().setProtectedHeader({ alg: \"HS256\" }).setExpirationTime(`${ttlSeconds}s`).sign(new TextEncoder().encode(secret));\n}\nasync function listeningPorts(sandbox) {\n const listed = await sandbox.runCommand([\"sh\", \"-c\", LISTENING_PORTS]);\n if (listed.exitCode !== 0)\n throw new Error(`listing the ports in use failed: ${listed.stderr.trim()}`);\n return listed.stdout.split(`\n`).filter(Boolean).map(Number);\n}\nasync function withLock(sandbox, body) {\n const lock = `${await sandbox.homeDirectory()}/.local/state/hyperbrane/endpoints.lock`;\n const taken = await sandbox.runCommand([\"sh\", \"-c\", TAKE_LOCK, \"sh\", lock]);\n if (taken.exitCode !== 0)\n throw new Error(`${lock} stayed locked: ${taken.stderr.trim()}`);\n try {\n return await body();\n } finally {\n await sandbox.runCommand([\"rm\", \"-rf\", lock]);\n }\n}\nfunction under(base, endpoint) {\n if (base === undefined || new URL(base).pathname === \"/\")\n return endpoint;\n return { ...endpoint, url: `${base.replace(/\\/+$/, \"\")}/${endpoint.name}` };\n}\nfunction createEndpoints(sandbox, storage, ports) {\n const located = async (endpoints) => {\n const base = (await storage.readDescriptor())?.dataEndpointOptions?.url;\n return endpoints.map((endpoint) => under(base, endpoint));\n };\n async function openEndpoint(name) {\n const [endpoint] = await located([await openStored(name)]);\n return endpoint;\n }\n async function openStored(name) {\n return withLock(sandbox, async () => {\n const existing = await storage.readEndpoint(name);\n if (existing)\n return existing;\n const { from, to } = await ports.ports();\n const used = new Set([...(await storage.listEndpoints()).map((endpoint) => endpoint.port), ...await listeningPorts(sandbox)]);\n let port = from;\n while (port <= to && used.has(port))\n port++;\n if (port > to)\n throw new Error(`no data endpoint ports are left in ${from}-${to}`);\n const endpoint = { name, url: await ports.expose(port), port, secret: randomBytes(32).toString(\"base64url\") };\n await storage.saveEndpoint(endpoint);\n return endpoint;\n });\n }\n async function closeEndpoint(name) {\n await withLock(sandbox, async () => {\n const endpoint = await storage.readEndpoint(name);\n if (!endpoint)\n return;\n await ports.conceal(endpoint.port);\n await storage.removeEndpoint(name);\n });\n }\n async function credentials(name, {\n ttlSeconds = DEFAULT_TTL_S\n } = {}) {\n const endpoint = await storage.readEndpoint(name);\n if (!endpoint)\n throw new SandboxNotFoundError(`there is no data endpoint ${name}`);\n return { token: await signToken(endpoint.secret, ttlSeconds), ...await ports.credentials?.(endpoint, ttlSeconds) };\n }\n return {\n openEndpoint,\n closeEndpoint,\n listEndpoints: async () => located(await storage.listEndpoints()),\n credentials\n };\n}\nvar LISTENING_PORTS = sh`\nawk 'FNR > 1 && $4 == \"0A\" { n = split($2, local, \":\"); print local[n] }' /proc/net/tcp /proc/net/tcp6 2> /dev/null | while read -r hex; do\n printf '%d\\n' \"0x$hex\"\ndone\n`;\nvar TAKE_LOCK = sh`\nmkdir -p \"$(dirname \"$1\")\"\nwaited=0\nuntil mkdir \"$1\" 2> /dev/null; do\n if [ -n \"$(find \"$1\" -maxdepth 0 -mmin +2 2> /dev/null)\" ]; then\n rm -rf \"$1\"\n continue\n fi\n [ \"$waited\" -lt 1200 ] || exit 1\n sleep 0.1\n waited=$((waited + 1))\ndone\n`;\n\n// sandbox/data-endpoints/firewalledPorts.ts\nfunction firewalledPorts(sandbox, storage) {\n const options = async () => (await storage.readDescriptor())?.dataEndpointOptions;\n async function firewall(rule) {\n const result = await sandbox.runCommand([\"sudo\", \"-n\", \"ufw\", ...rule]);\n if (result.exitCode !== 0)\n throw new Error(`ufw ${rule.join(\" \")} failed: ${result.stderr.trim()}`);\n }\n return {\n async ports() {\n const ports = (await options())?.ports;\n if (!ports)\n throw new Error(\"this machine has no data endpoints: it's created with dataEndpointOptions.ports\");\n return ports;\n },\n async expose(port) {\n const found = await options();\n if (found?.needsFirewall)\n await firewall([\"allow\", `${port}/tcp`]);\n const url = new URL(found?.url ?? \"http://127.0.0.1\");\n url.port = String(port);\n return url.toString().replace(/\\/$/, \"\");\n },\n async conceal(port) {\n if ((await options())?.needsFirewall)\n await firewall([\"delete\", \"allow\", `${port}/tcp`]);\n }\n };\n}\n\n// sandbox/lib/httpOverStream.ts\nimport { request as httpRequest } from \"http\";\nimport { Readable } from \"stream\";\nfunction httpOver(url, connection, { method = \"GET\", headers = {}, body, signal, timeout }) {\n return new Promise((resolve, reject) => {\n const target = new URL(url);\n const request = httpRequest({ createConnection: connection, path: `${target.pathname}${target.search}`, method, headers: { host: target.host, ...headers }, signal }, (response) => {\n const responseHeaders = new Headers;\n for (let i = 0;i + 1 < response.rawHeaders.length; i += 2)\n responseHeaders.append(response.rawHeaders[i], response.rawHeaders[i + 1]);\n const status = response.statusCode ?? 502;\n const nullBody = status === 204 || status === 304 || method === \"HEAD\";\n if (nullBody)\n response.resume();\n resolve(new Response(nullBody ? null : Readable.toWeb(response), { status, headers: responseHeaders }));\n });\n request.once(\"error\", reject);\n if (typeof timeout === \"number\")\n request.setTimeout(timeout, () => request.destroy(new Error(`fetch ${url}: no data for ${timeout}ms`)));\n request.end(body === undefined ? undefined : typeof body === \"string\" ? body : Buffer.from(body));\n });\n}\n\n// lib/jsonDirectory.ts\nvar EXTENSION = \".json\";\nvar NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,62}$/;\n\nclass InvalidNameError extends Error {\n name = \"InvalidNameError\";\n}\nfunction checkName(name) {\n if (!NAME_PATTERN.test(name)) {\n throw new InvalidNameError(`Invalid name \"${name}\": use letters, digits, \"_\", \".\" and \"-\", up to 63 characters`);\n }\n return name;\n}\nfunction createJsonDirectory(sandbox, dir) {\n async function pathFor(name) {\n return `${await dir()}/${checkName(name)}${EXTENSION}`;\n }\n function serialize(record) {\n return JSON.stringify(record, null, 2) + `\n`;\n }\n async function readPath(path) {\n try {\n return JSON.parse(new TextDecoder().decode(await sandbox.readFile(path)));\n } catch (error) {\n if (error instanceof SandboxNotFoundError)\n return null;\n throw error;\n }\n }\n async function read(name) {\n return readPath(await pathFor(name));\n }\n async function insert(name, record) {\n const path = await pathFor(name);\n if (await sandbox.writeFileExclusive(path, serialize(record)))\n return record;\n return await readPath(path) ?? insert(name, record);\n }\n async function save(name, record) {\n await sandbox.writeFile(await pathFor(name), serialize(record));\n }\n async function remove(name) {\n await sandbox.removeFile(await pathFor(name));\n }\n async function entries() {\n const path = await dir();\n const all = await sandbox.runCommand([\"sh\", \"-c\", LIST_SCRIPT, \"sh\", path], { env: { MISSING: String(MISSING), EXTENSION } }).catch(() => null);\n if (all?.exitCode === MISSING)\n return [];\n if (all?.exitCode === 0) {\n return all.stdout.split(RECORD_END).filter((text) => text.trim()).flatMap((text) => {\n const newline = text.indexOf(`\n`);\n try {\n return [[text.slice(0, newline).slice(0, -EXTENSION.length), JSON.parse(text.slice(newline + 1))]];\n } catch {\n return [];\n }\n });\n }\n let files;\n try {\n files = await sandbox.listDirectory(path);\n } catch (error) {\n if (error instanceof SandboxNotFoundError)\n return [];\n throw error;\n }\n const records = await Promise.all(files.filter((file) => file.endsWith(EXTENSION)).map(async (file) => [file.slice(0, -EXTENSION.length), await readPath(`${path}/${file}`)]));\n return records.filter((entry) => entry[1] !== null);\n }\n async function list() {\n return (await entries()).map(([, record]) => record);\n }\n return { read, insert, save, remove, list, entries };\n}\nvar MISSING = 44;\nvar RECORD_END = `\n\\x1E\n`;\nvar LIST_SCRIPT = sh`\ncd \"$1\" 2>/dev/null || exit \"$MISSING\"\nfor file in *\"$EXTENSION\"; do\n [ -f \"$file\" ] || continue\n printf '%s\\n' \"$file\"\n cat \"$file\"\n printf '\\n\\036\\n'\ndone\n`;\n\n// sandbox/storage/sandboxStorage.ts\nvar DESCRIPTOR = \"sandbox\";\nfunction createSandboxStorage(sandbox) {\n const stateDirectory = async () => `${await sandbox.homeDirectory()}/.local/state/hyperbrane`;\n const endpointsDirectory = async () => `${await stateDirectory()}/endpoints`;\n const descriptors = createJsonDirectory(sandbox, stateDirectory);\n const endpoints = createJsonDirectory(sandbox, endpointsDirectory);\n async function readDescriptor() {\n return descriptors.read(DESCRIPTOR);\n }\n async function insertDescriptor(descriptor) {\n return descriptors.insert(DESCRIPTOR, descriptor);\n }\n async function saveDescriptor(descriptor) {\n await descriptors.save(DESCRIPTOR, descriptor);\n }\n async function readEndpoint(name) {\n return endpoints.read(name);\n }\n async function saveEndpoint(endpoint) {\n const directory = await endpointsDirectory();\n const made = await sandbox.runCommand([\"sh\", \"-c\", 'mkdir -p \"$1\" && chmod 700 \"$1\"', \"sh\", directory]);\n if (made.exitCode !== 0)\n throw new Error(`making ${directory} private failed: ${made.stderr.trim()}`);\n await endpoints.save(endpoint.name, endpoint);\n }\n async function removeEndpoint(name) {\n await endpoints.remove(name);\n }\n async function listEndpoints() {\n return endpoints.list();\n }\n return {\n readDescriptor,\n insertDescriptor,\n saveDescriptor,\n readEndpoint,\n saveEndpoint,\n removeEndpoint,\n listEndpoints\n };\n}\n\n// sandbox/providers/local/localService.ts\nfunction hostPath(path) {\n return resolve(\"/\", path);\n}\nasync function notFoundAs(path, operation) {\n try {\n return await operation;\n } catch (error) {\n if (error.code === \"ENOENT\")\n throw new SandboxNotFoundError(`Not found: \"${path}\"`);\n throw error;\n }\n}\nasync function writeTemp(full, data, mode = 438) {\n await fs.mkdir(dirname(full), { recursive: true });\n const tmp = join(dirname(full), `.${basename(full)}.${randomUUID()}.tmp`);\n await fs.writeFile(tmp, data, { mode });\n return tmp;\n}\nasync function readFile2(path) {\n return new Uint8Array(await notFoundAs(path, fs.readFile(hostPath(path))));\n}\nasync function readFileRange(path, start, end) {\n const file = await notFoundAs(path, fs.open(hostPath(path), \"r\"));\n try {\n const buffer = new Uint8Array(Math.max(0, end - start));\n let filled = 0;\n while (filled < buffer.length) {\n const { bytesRead } = await file.read(buffer, filled, buffer.length - filled, start + filled);\n if (bytesRead === 0)\n break;\n filled += bytesRead;\n }\n return buffer.subarray(0, filled);\n } finally {\n await file.close();\n }\n}\nasync function writeFile2(path, data) {\n const full = hostPath(path);\n await fs.rename(await writeTemp(full, data), full);\n}\nasync function writeFileExclusive(path, data) {\n const full = hostPath(path);\n const tmp = await writeTemp(full, data);\n try {\n await fs.link(tmp, full);\n return true;\n } catch (error) {\n if (error.code === \"EEXIST\")\n return false;\n throw error;\n } finally {\n await fs.unlink(tmp);\n }\n}\nasync function removeFile(path) {\n await fs.rm(hostPath(path), { force: true });\n}\nasync function createDirectory(path) {\n await fs.mkdir(hostPath(path), { recursive: true });\n}\nasync function removeDirectory(path) {\n await fs.rm(hostPath(path), { recursive: true, force: true });\n}\nasync function listDirectory(path) {\n return notFoundAs(path, fs.readdir(hostPath(path)));\n}\nasync function stat2(path) {\n const stats = await notFoundAs(path, fs.stat(hostPath(path)));\n const type = stats.isFile() ? \"file\" : stats.isDirectory() ? \"directory\" : \"other\";\n return { type, uid: stats.uid, gid: stats.gid, size: stats.size };\n}\nasync function homeDirectory() {\n return process.env.HOME || homedir();\n}\nfunction runCommand(cmd, { cwd, env = {}, stdin } = {}) {\n return new Promise((resolve, reject) => {\n const [command, ...args] = cmd;\n if (!command)\n return reject(new Error(\"no command to run\"));\n const child = spawn(command, args, {\n cwd: hostPath(cwd ?? \".\"),\n env: { ...process.env, ...env },\n stdio: [stdin === undefined ? \"ignore\" : \"pipe\", \"pipe\", \"pipe\"]\n });\n const stdout = [];\n const stderr = [];\n child.stdout.on(\"data\", (chunk) => stdout.push(chunk));\n child.stderr.on(\"data\", (chunk) => stderr.push(chunk));\n child.once(\"error\", reject);\n child.once(\"close\", (code) => resolve({ exitCode: code ?? -1, stdout: Buffer.concat(stdout).toString(), stderr: Buffer.concat(stderr).toString() }));\n if (stdin !== undefined)\n child.stdin.end(stdin);\n });\n}\nasync function state() {\n return \"running\";\n}\nasync function start() {}\nasync function destroy() {}\nfunction hasExpiration() {\n return false;\n}\nasync function getExpiration() {\n return null;\n}\nasync function setExpiration() {\n return null;\n}\nfunction hasShell(ctx) {\n return ctx.sshKey !== undefined;\n}\nasync function sshEndpoint(ctx) {\n if (ctx.sshKey === undefined)\n throw new Error(\"this machine has no shell: its context has no SSH key (sshKey)\");\n return {\n host: ctx.sshHost ?? \"127.0.0.1\",\n port: ctx.sshPort ?? 22,\n user: ctx.sshUser ?? userInfo().username,\n privateKey: ctx.sshKey\n };\n}\nasync function fetch(url, { unix, ...init } = {}) {\n if (unix === undefined || \"Bun\" in globalThis)\n return globalThis.fetch(url, { ...init, unix: unix === undefined ? undefined : hostPath(unix) });\n return httpOver(url, () => netConnect(hostPath(unix)), init);\n}\nfunction connect(path) {\n return new Promise((resolve, reject) => {\n const socket = new Socket;\n let markClosed;\n const closed = new Promise((done) => markClosed = done);\n const listeners = [];\n socket.on(\"data\", (chunk) => {\n for (const listener of listeners)\n listener(new Uint8Array(chunk));\n });\n socket.once(\"close\", () => markClosed());\n socket.once(\"end\", () => markClosed());\n socket.on(\"error\", (error) => {\n markClosed();\n const message = `connecting to ${path} failed: ${error.message}`;\n reject(error.code === \"ENOENT\" || error.code === \"ECONNREFUSED\" ? new SandboxConnectionRefusedError(message) : new Error(message));\n });\n socket.once(\"connect\", () => resolve({\n write: (data) => void socket.write(data),\n onData: (listener) => void listeners.push(listener),\n closed,\n close: () => socket.end()\n }));\n socket.connect({ path: hostPath(path) });\n });\n}\nfunction createLocalService(ctx) {\n const fileOps = {\n readFile: readFile2,\n readFileRange,\n writeFile: writeFile2,\n writeFileExclusive,\n removeFile,\n createDirectory,\n removeDirectory,\n listDirectory,\n stat: stat2,\n homeDirectory\n };\n const cmdOps = {\n runCommand\n };\n const netOps = {\n fetch,\n connect\n };\n const machine = { ...fileOps, ...cmdOps, ...netOps };\n const storage = createSandboxStorage(machine);\n const lifecycleOps = {\n create: (spec) => createDescriptor(ctx, storage, ctx.id ?? spec.name, spec),\n open: () => openDescriptor(ctx, storage, ctx.id ?? \"this machine\"),\n get: () => getDescriptor(storage, \"this machine\"),\n state,\n start,\n destroy\n };\n const dataOps = createEndpoints(machine, storage, firewalledPorts(machine, storage));\n const expirationOps = {\n hasExpiration,\n getExpiration,\n setExpiration\n };\n const shellOps = {\n hasShell: () => hasShell(ctx),\n sshEndpoint: () => sshEndpoint(ctx)\n };\n return {\n ...fileOps,\n ...lifecycleOps,\n ...cmdOps,\n ...dataOps,\n ...expirationOps,\n ...shellOps,\n ...netOps\n };\n}\n\n// portal-gateway/transport-worker.ts\nvar TRANSPORT_PROTOCOL = 1;\nvar TOKEN_PARAM = \"hyb_token\";\nvar HEARTBEAT_MS = 15000;\nvar connections = new Map;\nasync function wired(dimensionName) {\n const files = await readdir2(join2(data, \"portals\")).catch(() => []);\n for (const file of files.filter((name) => name.endsWith(\".json\"))) {\n const portal = await readFile3(join2(data, \"portals\", file), \"utf8\").then((text) => JSON.parse(text), () => null);\n if (portal?.gatewayName === gateway && portal.dimensionName === dimensionName && portal.state === \"ready\")\n return true;\n }\n return false;\n}\nasync function authorized(url) {\n const token = url.searchParams.get(TOKEN_PARAM);\n if (!token)\n return false;\n try {\n await jwtVerify(token, key, { algorithms: [\"HS256\"], requiredClaims: [\"exp\"] });\n return true;\n } catch {\n return false;\n }\n}\nfunction cors(request, response) {\n const origin = request.headers.get(\"origin\");\n if (!origin)\n return response;\n response.headers.set(\"access-control-allow-origin\", origin);\n response.headers.append(\"vary\", \"Origin\");\n return response;\n}\nasync function preflight(request) {\n const headers = new Headers({\n \"access-control-allow-methods\": \"GET, POST, OPTIONS\",\n \"access-control-max-age\": \"600\"\n });\n const asked = request.headers.get(\"access-control-request-headers\");\n if (asked)\n headers.set(\"access-control-allow-headers\", asked);\n return cors(request, new Response(null, { status: 204, headers }));\n}\nasync function answer(handler, request, server) {\n server.timeout(request, 0);\n if (!await authorized(new URL(request.url)))\n return Response.json({ error: \"a valid token is required\" }, { status: 401 });\n try {\n return await handler(request, server);\n } catch (error) {\n const { name, message } = error instanceof Error ? error : new Error(String(error));\n return Response.json({ name, message }, { status: 500 });\n }\n}\nfunction guarded(handler) {\n return async (request, server) => {\n const response = await answer(handler, request, server);\n return response && cors(request, response);\n };\n}\nfunction relay(connection, send, end) {\n connection.onData(send);\n connection.closed.then(end);\n}\nvar encoder2 = new TextEncoder;\nfunction multipartFrames() {\n const boundary = randomBytes2(24).toString(\"hex\");\n const part = (type, data) => Buffer.concat([encoder2.encode(`\\r\ncontent-type: ${type}\\r\n\\r\n`), data, encoder2.encode(`\\r\n--${boundary}`)]);\n return {\n contentType: `multipart/mixed; boundary=\"${boundary}\"`,\n open: (id) => Buffer.concat([encoder2.encode(`--${boundary}`), part(\"application/json\", encoder2.encode(JSON.stringify({ connection: id })))]),\n data: (chunk) => part(\"application/octet-stream\", chunk),\n heartbeat: part(\"application/octet-stream\", new Uint8Array),\n end: encoder2.encode(`--\\r\n`)\n };\n}\nfunction lineFrames() {\n const decoder = new TextDecoder;\n let pending = \"\";\n return {\n contentType: \"text/event-stream\",\n open: (id) => encoder2.encode(`event: open\ndata: ${JSON.stringify({ connection: id })}\n\n`),\n data(chunk) {\n const lines = (pending + decoder.decode(chunk, { stream: true })).split(`\n`);\n pending = lines.pop();\n return encoder2.encode(lines.map((line) => `data: ${line.replace(/\\r$/, \"\")}\n\n`).join(\"\"));\n },\n heartbeat: encoder2.encode(`: ping\n\n`),\n end: encoder2.encode(`event: close\ndata: {}\n\n`)\n };\n}\nfunction stream(connection, frames) {\n const id = randomUUID2();\n let heartbeat;\n let open = true;\n const finish = () => {\n open = false;\n clearInterval(heartbeat);\n connections.delete(id);\n connection.close();\n };\n connections.set(id, connection);\n const body = new ReadableStream({\n start(controller) {\n const send = (bytes) => {\n if (open && bytes.length > 0)\n controller.enqueue(bytes);\n };\n send(frames.open(id));\n heartbeat = setInterval(() => send(frames.heartbeat), HEARTBEAT_MS);\n relay(connection, (data) => send(frames.data(data)), () => {\n if (!open)\n return;\n send(frames.end);\n finish();\n controller.close();\n });\n },\n cancel: finish\n });\n return new Response(body, { headers: { \"content-type\": frames.contentType, \"cache-control\": \"no-cache, no-transform\", \"x-accel-buffering\": \"no\" } });\n}\nasync function write(request) {\n const connection = connections.get(request.params.id);\n if (!connection)\n return Response.json({ error: \"no such connection\" }, { status: 404 });\n connection.write(new Uint8Array(await request.arrayBuffer()));\n return new Response(null, { status: 204 });\n}\nasync function connect2(request, server) {\n const params = new URL(request.url).searchParams;\n const dimensionName = params.get(\"dimension\") ?? \"\";\n const agentName = params.get(\"agent\");\n if (!isDimensionName(dimensionName) || agentName !== null && !isDimensionName(agentName)) {\n return Response.json({ error: \"dimension and agent are names\" }, { status: 400 });\n }\n if (!await wired(dimensionName))\n return Response.json({ error: `dimension ${dimensionName} has no portal to gateway ${gateway}` }, { status: 403 });\n const socket = agentName === null ? dimensionLayout.transportSocketPath(data, dimensionName) : join2(dimensionLayout.runDirectory(data, dimensionName), `${agentName}.sock`);\n const connection = await machine.connect(socket).catch((error) => error);\n if (connection instanceof Error)\n return Response.json({ error: connection.message }, { status: 502 });\n if (request.headers.get(\"upgrade\")?.toLowerCase() !== \"websocket\")\n return stream(connection, agentName === null ? multipartFrames() : lineFrames());\n if (server.upgrade(request, { data: connection }))\n return;\n connection.close();\n return Response.json({ error: \"the websocket upgrade failed\" }, { status: 400 });\n}\nfunction serve(port) {\n return Bun.serve({\n port,\n hostname: \"0.0.0.0\",\n routes: {\n \"/connect\": {\n GET: guarded(connect2),\n OPTIONS: preflight\n },\n \"/connect/:id\": {\n POST: guarded(write),\n OPTIONS: preflight\n }\n },\n websocket: {\n open(socket) {\n relay(socket.data, (data) => socket.send(data), () => socket.close());\n },\n message(socket, message) {\n socket.data.write(typeof message === \"string\" ? Buffer.from(message) : message);\n },\n close(socket) {\n socket.data.close();\n }\n }\n });\n}\nfunction required(name) {\n const value = process.env[name];\n if (!value)\n throw new Error(`${name} is not set`);\n return value;\n}\nprocess.umask(63);\nvar dir = required(\"HYB_TRANSPORT_DIR\");\nvar build = required(\"HYB_TRANSPORT_BUILD\");\nvar port = Number(required(\"HYB_TRANSPORT_PORT\"));\nvar gateway = required(\"HYB_GATEWAY\");\nvar data = required(\"HYB_DATA\");\nvar key = new TextEncoder().encode(await readFile3(join2(dir, \"secret\"), \"utf8\"));\nvar machine = createLocalService({ kind: \"local\" });\nvar server = serve(port);\nfor (const signal of [\"SIGTERM\", \"SIGINT\"]) {\n process.once(signal, () => {\n server.stop(true);\n process.exit(0);\n });\n}\nawait writeFile3(join2(dir, \"ready\"), `${process.pid} ${build} ${TRANSPORT_PROTOCOL}\n`);\n";
@@ -0,0 +1,4 @@
1
+ import { join } from "node:path";
2
+ import { scriptBundle } from "../lib/scriptBundle.js";
3
+ export const transportEntry = () => join(import.meta.dirname, "transport-worker.ts");
4
+ export const transportBundle = scriptBundle(transportEntry, async () => (await import("./transportBundle.generated.js")).TRANSPORT_SOURCE);
@@ -1,137 +1,70 @@
1
- /**
2
- * An upstream: a service a gateway holds a credential for, described as data (adapted from v2's
3
- * UpstreamDef). SDK clients define upstreams in TypeScript with `defineUpstream`; the definition
4
- * is plain JSON, sent to the gateway and applied there by generic code that knows nothing about
5
- * any particular upstream.
6
- *
7
- * The secret never reaches a dimension. Dimensions hold fakes, shaped like the real tokens so their
8
- * clients accept them, and the gateway replaces them in transit. Clients sign in from dimensions as
9
- * they normally would: the first sign-in goes to the upstream and the gateway keeps the tokens; once
10
- * it holds them, it completes every later sign-in itself (see OAuthMode.tokenUrl and `answers`).
11
- *
12
- * Credentials are scoped to the gateway: one per upstream, used for every dimension with a portal
13
- * to it. Switching accounts means deleting the gateway's credential, so the next sign-in goes to
14
- * the upstream again.
15
- */
16
1
  export type UpstreamDefinition = {
17
2
  name: string;
18
- /** At least one. The gateway swaps the fakes of every mode that has a credential. */
19
3
  modes: {
20
4
  oauth?: OAuthMode;
21
5
  apiKey?: ApiKeyMode;
6
+ forced?: ForcedMode;
22
7
  };
23
8
  };
9
+ export type UpstreamForce = {
10
+ target?: string;
11
+ path?: string;
12
+ query?: Record<string, string>;
13
+ headers?: Record<string, string>;
14
+ };
24
15
  type ModeBase = {
25
- /**
26
- * Requests to these hosts get the credential. Matched on host alone: v1 also matched the
27
- * user-agent, and a client changing its user-agent silently stopped getting its token.
28
- */
29
16
  hosts: string[];
30
- /** Where the credential goes on a matching request, replacing any fake the client sent. */
31
17
  present: {
32
18
  header: string;
33
19
  scheme?: "Bearer";
34
20
  } | {
35
21
  query: string;
36
22
  };
37
- /** What to force onto every matching request, whatever the client sent. */
38
- force?: {
39
- /** Retarget the request, e.g. "https://api.internal:8443". */
40
- target?: string;
41
- /** Prefix prepended to the request path, so dimensions needn't know where the API is mounted. */
42
- path?: string;
43
- /** Query params the client doesn't get to choose. */
44
- query?: Record<string, string>;
45
- /** Headers the client never sees the values of. */
46
- headers?: Record<string, string>;
47
- };
23
+ force?: UpstreamForce;
24
+ };
25
+ export type ForcedMode = {
26
+ hosts: string[];
27
+ force: UpstreamForce;
48
28
  };
49
29
  export type OAuthMode = ModeBase & {
50
- /**
51
- * The token endpoint. Until the gateway holds a credential, requests go through and the response
52
- * is kept as the credential, reaching the dimension with fakes in place of the tokens. From then
53
- * on the gateway answers them itself from that credential: a sign-in (whatever code it carries)
54
- * and a refresh made with a fake refresh token. A client's refresh goes upstream, replayed with the
55
- * real refresh token (its encoding, client id, scope, User-Agent: nothing about refreshing needs
56
- * declaring); refresh tokens rotate and every dimension shares the credential, so refreshes are
57
- * made one at a time and those arriving together share the result. Every client holds the same
58
- * fakes, so all of them get the new tokens. A sign-in to an expired credential refreshes it first;
59
- * one the gateway can't refresh (no client has refreshed yet, or upstream refuses) counts as none,
60
- * and the sign-in goes to the upstream again.
61
- */
62
30
  tokenUrl: string;
63
- /** What the fakes look like, per token in the token response. */
64
31
  fakes: {
65
32
  access: FakeShape;
66
33
  refresh: FakeShape;
67
34
  id?: FakeShape;
68
35
  };
69
- /** Other secrets the token endpoint returns, dropped before the response reaches the dimension. */
70
36
  strip?: {
71
37
  fields?: string[];
72
38
  headers?: string[];
73
39
  };
74
- /**
75
- * Other sign-in steps the gateway answers itself once it holds a credential, so the client's
76
- * sign-in completes without a browser: e.g. the endpoint a device flow polls until approved.
77
- */
78
40
  answers?: {
79
41
  url: string;
80
42
  status?: number;
81
43
  json: unknown;
82
44
  }[];
83
45
  };
84
- /**
85
- * The gateway holds any number of named keys for the upstream and gives each its own fake. A request
86
- * goes out with the real key its fake maps to, so a dimension can use different keys side by side
87
- * (one per process, per config entry, ...). A value that isn't one of the fakes is left as it is.
88
- */
89
46
  export type ApiKeyMode = ModeBase & {
90
47
  fake: FakeShape;
91
48
  };
92
- /**
93
- * `prefix`: the fake is the prefix plus random characters, as long as the real value (clients check
94
- * both; claude blanks a credentials file whose token it doesn't recognise).
95
- * `jwt`: the fake carries the real token's header and claims with a signature that can't verify,
96
- * for clients that read claims (codex decides when to refresh from `exp`). It's re-issued whenever
97
- * the real token changes, so its claims keep tracking the real ones.
98
- */
99
49
  export type FakeShape = {
100
50
  prefix: string;
101
51
  } | {
102
52
  jwt: true;
103
53
  };
104
- /**
105
- * What the gateway keeps for an upstream, in its state directory, one file per writer:
106
- * credentials/<upstream>.oauth.json (the gateway, as it captures and refreshes tokens) and
107
- * credentials/<upstream>.api-keys.json (the service).
108
- */
109
54
  export type OAuthCredential = {
110
55
  access: string;
111
56
  refresh?: string;
112
57
  id?: string;
113
- /** Epoch ms. */
114
58
  expiresAt?: number;
115
59
  refreshExpiresAt?: number;
116
60
  scopes?: string[];
117
- /** Epoch ms of the last sign-in or refresh. */
118
61
  lastRefresh: number;
119
- /**
120
- * Issued once and kept across refreshes and sign-ins, so every dimension's copy stays valid. A
121
- * prefix fake is recognised by its value; a JWT fake by its signature, since its header and
122
- * claims follow the real token's, so a stale copy still maps to the current token.
123
- */
124
62
  fakes: {
125
63
  access: StoredFake;
126
64
  refresh?: StoredFake;
127
65
  id?: StoredFake;
128
66
  };
129
- /** The last token response minus stripped fields; the gateway answers sign-ins and refreshes from it. */
130
67
  response: Record<string, unknown>;
131
- /**
132
- * The last refresh request a client made, minus its refresh token: the gateway replays it with the
133
- * real one, so upstream sees the CLI's own request (User-Agent null when the client sent none).
134
- */
135
68
  refreshRequest?: {
136
69
  contentType: string;
137
70
  userAgent: string | null;
@@ -144,14 +77,11 @@ export type StoredFake = {
144
77
  jwtSignature: string;
145
78
  };
146
79
  export type ApiKeyCredential = {
147
- /** Key name -> key. */
148
80
  keys: Record<string, ApiKey>;
149
81
  };
150
- /** `fake` is issued once and kept, so a dimension's configuration stays valid when the key changes. */
151
82
  export type ApiKey = {
152
83
  key: string;
153
84
  fake: string;
154
85
  };
155
- /** Declares an upstream; checks the shape at compile time and returns it unchanged. */
156
86
  export declare function defineUpstream(definition: UpstreamDefinition): UpstreamDefinition;
157
87
  export {};
@@ -1,4 +1,3 @@
1
- /** Declares an upstream; checks the shape at compile time and returns it unchanged. */
2
1
  export function defineUpstream(definition) {
3
2
  return definition;
4
3
  }