@fungi.computer/computer 0.0.0-stage → 0.0.1

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 (91) hide show
  1. package/Dockerfile +85 -0
  2. package/LICENSE +21 -0
  3. package/README.md +228 -2
  4. package/container/computer-init.mjs +119 -0
  5. package/container/computer-start.sh +24 -0
  6. package/container/package.json +14 -0
  7. package/container/pnpm-lock.yaml +378 -0
  8. package/container/pnpm-workspace.yaml +5 -0
  9. package/container/resource-bounds.mjs +54 -0
  10. package/container/runtime-privileges.cc +37 -0
  11. package/container/runtime-privileges.mjs +11 -0
  12. package/container/terminal-assets/README.md +35 -0
  13. package/container/terminal-assets/bashrc +49 -0
  14. package/container/terminal-assets/git-prompt.sh +586 -0
  15. package/container/terminal-protocol.mjs +58 -0
  16. package/container/terminal-runtime.mjs +10 -0
  17. package/container/terminal-server.mjs +243 -0
  18. package/dist/apply-patch/LICENSE.codex +201 -0
  19. package/dist/apply-patch/build.mjs +98 -0
  20. package/dist/apply-patch/cache.mjs +168 -0
  21. package/dist/apply-patch/compile.mjs +199 -0
  22. package/dist/apply-patch/docker/darwin.Dockerfile +15 -0
  23. package/dist/apply-patch/docker/linux-arm64.Dockerfile +38 -0
  24. package/dist/apply-patch/docker/linux-x64.Dockerfile +23 -0
  25. package/dist/apply-patch/main.rs +320 -0
  26. package/dist/apply-patch/notices.mjs +44 -0
  27. package/dist/apply-patch/pin.mjs +8 -0
  28. package/dist/apply-patch/toolchain.mjs +39 -0
  29. package/dist/computer-backends.d.ts +94 -0
  30. package/dist/computer-backends.js +103 -0
  31. package/dist/computerd.cjs +18010 -0
  32. package/dist/container-policy.d.ts +7 -0
  33. package/dist/container-policy.js +24 -0
  34. package/dist/container-system-trust.d.ts +8 -0
  35. package/dist/container-system-trust.js +66 -0
  36. package/dist/egress-policy.d.ts +89 -0
  37. package/dist/egress-policy.js +240 -0
  38. package/dist/execution-result.d.ts +3 -0
  39. package/dist/execution-result.js +28 -0
  40. package/dist/files-patch.d.ts +20 -0
  41. package/dist/files-patch.js +108 -0
  42. package/dist/files.d.ts +75 -0
  43. package/dist/files.js +136 -0
  44. package/dist/fleet/admission.d.ts +14 -0
  45. package/dist/fleet/admission.js +10 -0
  46. package/dist/fleet/backing.d.ts +52 -0
  47. package/dist/fleet/backing.js +242 -0
  48. package/dist/fleet/computers.d.ts +62 -0
  49. package/dist/fleet/computers.js +35 -0
  50. package/dist/fleet/index.d.ts +11 -0
  51. package/dist/fleet/index.js +11 -0
  52. package/dist/fleet/inventory-wire.d.ts +29 -0
  53. package/dist/fleet/inventory-wire.js +15 -0
  54. package/dist/fleet/inventory.d.ts +24 -0
  55. package/dist/fleet/inventory.js +152 -0
  56. package/dist/fleet/lifecycle.d.ts +99 -0
  57. package/dist/fleet/lifecycle.js +423 -0
  58. package/dist/fleet/runtime-usage.d.ts +50 -0
  59. package/dist/fleet/runtime-usage.js +145 -0
  60. package/dist/fleet/slots.d.ts +30 -0
  61. package/dist/fleet/slots.js +46 -0
  62. package/dist/fleet/wire.d.ts +228 -0
  63. package/dist/fleet/wire.js +127 -0
  64. package/dist/host-config.d.ts +12 -0
  65. package/dist/host-config.js +10 -0
  66. package/dist/index.d.ts +282 -0
  67. package/dist/index.js +1237 -0
  68. package/dist/light-shell.d.ts +44 -0
  69. package/dist/light-shell.js +376 -0
  70. package/dist/local-backend.d.ts +52 -0
  71. package/dist/local-backend.js +143 -0
  72. package/dist/metered-container-host.d.ts +26 -0
  73. package/dist/metered-container-host.js +48 -0
  74. package/dist/on-box-vm.d.ts +119 -0
  75. package/dist/on-box-vm.js +294 -0
  76. package/dist/operation-contract.d.ts +456 -0
  77. package/dist/operation-contract.js +452 -0
  78. package/dist/operation-record.d.ts +50 -0
  79. package/dist/operation-record.js +162 -0
  80. package/dist/preview.d.ts +55 -0
  81. package/dist/preview.js +151 -0
  82. package/dist/process-inventory.d.ts +80 -0
  83. package/dist/process-inventory.js +427 -0
  84. package/dist/pull-pending.d.ts +26 -0
  85. package/dist/pull-pending.js +35 -0
  86. package/dist/team-candidate-builder.mjs +28170 -0
  87. package/dist/transport-backend.d.ts +3 -0
  88. package/dist/transport-backend.js +99 -0
  89. package/dist/workspace-stream.d.ts +2 -0
  90. package/dist/workspace-stream.js +31 -0
  91. package/package.json +126 -3
@@ -0,0 +1,119 @@
1
+ import type { IWorkspaceContainerAPI, WorkspaceRef } from "@fungi.computer/cloudflare-computer/backends/container";
2
+ import { z } from "zod";
3
+ /**
4
+ * The on-box VM backing of a Computer's container.
5
+ *
6
+ * The container backing runs the unmodified Computer image (computerd plus
7
+ * the terminal server) and speaks to it through the library's container host
8
+ * seam, `IWorkspaceContainerAPI`. On Cloudflare that host is the platform
9
+ * container. Here it is one Firecracker microVM that a host service
10
+ * (`tooling/celld-platform/computer-vm/vmd.mjs`) runs on one of our own
11
+ * machines. The Computer contract, its capabilities and the container
12
+ * backend are unchanged; only the host behind the seam differs.
13
+ *
14
+ * Two doors cross the machine boundary, both mutually authenticated with
15
+ * Ed25519 request signatures (`@fungi.computer/seam`):
16
+ * - this Durable Object asks the host service to start, stop, inspect and
17
+ * intercept its VM and reaches the VM's ports through it (signed with the
18
+ * platform seam key the host pins);
19
+ * - the host service forwards the VM's intercepted HTTP(S) to
20
+ * `/__computer-vm/<id>/…` on this Worker (signed with the host key this
21
+ * Worker pins in its host registry), which hands it to the same
22
+ * `WorkspaceProxy` entrypoint the platform container interception uses.
23
+ */
24
+ export type OnBoxVmEnv = Readonly<{
25
+ /** Public host registry: `{ hostId: { origin, publicKey } }` as JSON. */
26
+ COMPUTER_VM_HOSTS?: string;
27
+ /** Secret: `keyId:base64(pkcs8 Ed25519)`, the platform seam signing key. */
28
+ COMPUTER_VM_SIGNING_KEY?: string;
29
+ /** A standalone Computer Worker's host; Hub chooses per Computer instead. */
30
+ COMPUTER_VM_DEFAULT_HOST?: string;
31
+ /** Cloudflare Access service token for the host Tunnel. Both values are secrets. */
32
+ COMPUTER_VM_ACCESS_CLIENT_ID?: string;
33
+ COMPUTER_VM_ACCESS_CLIENT_SECRET?: string;
34
+ }>;
35
+ /** Where one Computer's VM runs: a host from the registry. Secret-free. */
36
+ export type OnBoxVmPlacement = Readonly<{
37
+ hostId: string;
38
+ origin: string;
39
+ }>;
40
+ /** The registered host `hostId`, or `undefined` when this Worker has none. */
41
+ export declare function onBoxVmPlacement(env: OnBoxVmEnv, hostId: string | undefined): OnBoxVmPlacement | undefined;
42
+ declare const vmStateSchema: z.ZodObject<{
43
+ running: z.ZodBoolean;
44
+ runtimeId: z.ZodNullable<z.ZodString>;
45
+ exit: z.ZodNullable<z.ZodObject<{
46
+ reason: z.ZodString;
47
+ exitedAt: z.ZodNumber;
48
+ }, z.core.$strict>>;
49
+ }, z.core.$strict>;
50
+ export type OnBoxVmState = z.infer<typeof vmStateSchema>;
51
+ declare const vmInventorySchema: z.ZodArray<z.ZodObject<{
52
+ running: z.ZodBoolean;
53
+ runtimeId: z.ZodNullable<z.ZodString>;
54
+ exit: z.ZodNullable<z.ZodObject<{
55
+ reason: z.ZodString;
56
+ exitedAt: z.ZodNumber;
57
+ }, z.core.$strict>>;
58
+ id: z.ZodString;
59
+ name: z.ZodString;
60
+ lastActive: z.ZodNumber;
61
+ }, z.core.$strict>>;
62
+ export type OnBoxVmInventory = z.infer<typeof vmInventorySchema>;
63
+ /** One Durable Object's view of its microVM through the host service. */
64
+ export declare class OnBoxVmContainerHost implements IWorkspaceContainerAPI {
65
+ #private;
66
+ constructor(placement: OnBoxVmPlacement, env: OnBoxVmEnv, vmId: string, sleepAfterMs: number);
67
+ /** Signed host-wide inventory; includes VMs outside this Hub's directory. */
68
+ inventory(): Promise<OnBoxVmInventory>;
69
+ start(env: Record<string, string>, enableInternet: boolean): Promise<{
70
+ runtimeId: string;
71
+ }>;
72
+ restart(env: Record<string, string>, enableInternet: boolean): Promise<{
73
+ runtimeId: string;
74
+ }>;
75
+ /** Stop a VM whose metered checkpoint cannot be funded or delivered. */
76
+ destroy(): Promise<{
77
+ ok: true;
78
+ }>;
79
+ /** The VM as the host service sees it now. */
80
+ state(): Promise<OnBoxVmState>;
81
+ status(): Promise<{
82
+ running: boolean;
83
+ exit: {
84
+ reason: string;
85
+ exitedAt: number;
86
+ } | null;
87
+ }>;
88
+ exitInfo(): Promise<{
89
+ reason: string;
90
+ exitedAt: number;
91
+ } | null>;
92
+ /** Whether the VM is running now; the platform container answers from `ctx.container.running`. */
93
+ running(): Promise<boolean>;
94
+ interceptOutboundHttp(host: string, workspace: WorkspaceRef): Promise<void>;
95
+ interceptAllOutboundHttp(workspace: WorkspaceRef, token: string): Promise<void>;
96
+ fetchPort(port: number, input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
97
+ /**
98
+ * A guest port, reached through the host service: the VM has no address
99
+ * this Worker can route to. The guest sees the logical authority the
100
+ * caller named, as a platform container port does.
101
+ */
102
+ port(port: number): Fetcher;
103
+ }
104
+ /** The Computer namespace the ingress door addresses by object id. */
105
+ export type ComputerObjects = Readonly<{
106
+ idFromString: (id: string) => DurableObjectId;
107
+ get: (id: DurableObjectId) => Readonly<{
108
+ fetch: (request: Request) => Promise<Response>;
109
+ }>;
110
+ }>;
111
+ /**
112
+ * The host service's door into this Worker: a VM's intercepted request
113
+ * reaches its Computer exactly as the platform container's interception
114
+ * does through `WorkspaceProxy` (the `/ws` connect upgrade, or an egress
115
+ * request carrying the workspace egress token and original URL). Returns
116
+ * `undefined` for every other path so the caller's router continues.
117
+ */
118
+ export declare function onBoxVmIngress(request: Request, env: OnBoxVmEnv, computers: ComputerObjects): Promise<Response> | undefined;
119
+ export {};
@@ -0,0 +1,294 @@
1
+ import { z } from "zod";
2
+ import { SeamReplayGuard, importSeamSigner, importSeamVerifiers, seamBodyDigest, seamHeader, signSeamRequest, unsignedPayload, verifySeamRequest, } from "@fungi.computer/seam";
3
+ function accessHeaders(env) {
4
+ const id = env.COMPUTER_VM_ACCESS_CLIENT_ID;
5
+ const secret = env.COMPUTER_VM_ACCESS_CLIENT_SECRET;
6
+ if (id === undefined && secret === undefined)
7
+ return {};
8
+ const pair = z.tuple([z.string(), z.string()]).safeParse([id, secret]);
9
+ if (!pair.success)
10
+ throw new OnBoxVmUnavailableError("access", 503, "incomplete Access token");
11
+ return {
12
+ "CF-Access-Client-Id": pair.data[0],
13
+ "CF-Access-Client-Secret": pair.data[1],
14
+ };
15
+ }
16
+ const hostIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/);
17
+ const hostRegistrySchema = z.record(hostIdSchema, z.strictObject({
18
+ origin: z
19
+ .url({ protocol: /^https?$/ })
20
+ .transform((value) => new URL(value).origin),
21
+ publicKey: z.base64().length(44),
22
+ }));
23
+ const registries = new Map();
24
+ function hostRegistry(env) {
25
+ const raw = env.COMPUTER_VM_HOSTS ?? "{}";
26
+ let registry = registries.get(raw);
27
+ if (registry === undefined) {
28
+ registry = hostRegistrySchema.parse(JSON.parse(raw));
29
+ registries.set(raw, registry);
30
+ }
31
+ return registry;
32
+ }
33
+ /** The registered host `hostId`, or `undefined` when this Worker has none. */
34
+ export function onBoxVmPlacement(env, hostId) {
35
+ if (hostId === undefined)
36
+ return undefined;
37
+ const host = hostRegistry(env)[hostIdSchema.parse(hostId)];
38
+ return host === undefined ? undefined : { hostId, origin: host.origin };
39
+ }
40
+ class OnBoxVmUnavailableError extends Error {
41
+ constructor(operation, status, detail = "") {
42
+ super(`On-box Computer VM ${operation} failed with HTTP ${String(status)}${detail ? `: ${detail}` : ""}`);
43
+ this.name = "OnBoxVmUnavailableError";
44
+ }
45
+ }
46
+ const signers = new Map();
47
+ function seamSigner(env) {
48
+ const secret = env.COMPUTER_VM_SIGNING_KEY;
49
+ if (secret === undefined)
50
+ return Promise.reject(new OnBoxVmUnavailableError("sign", 503, "no seam signing key"));
51
+ let signer = signers.get(secret);
52
+ if (signer === undefined) {
53
+ signer = importSeamSigner(secret);
54
+ signers.set(secret, signer);
55
+ }
56
+ return signer;
57
+ }
58
+ const verifierSets = new Map();
59
+ function hostVerifiers(env) {
60
+ const raw = env.COMPUTER_VM_HOSTS ?? "{}";
61
+ let verifiers = verifierSets.get(raw);
62
+ if (verifiers === undefined) {
63
+ verifiers = importSeamVerifiers(Object.fromEntries(Object.entries(hostRegistry(env)).map(([hostId, host]) => [
64
+ hostId,
65
+ host.publicKey,
66
+ ])));
67
+ verifierSets.set(raw, verifiers);
68
+ }
69
+ return verifiers;
70
+ }
71
+ /** The audience a host service verifies: requests for this host only. */
72
+ const vmHostAudience = (hostId) => `computer-vm-host:${hostId}`;
73
+ /** The audience this Worker's ingress door verifies. */
74
+ const vmIngressAudience = "computer-vm-ingress";
75
+ const exitSchema = z.strictObject({
76
+ reason: z.string().max(1_024),
77
+ exitedAt: z.number().int().nonnegative(),
78
+ });
79
+ const vmStateSchema = z.strictObject({
80
+ running: z.boolean(),
81
+ runtimeId: z.string().min(1).max(128).nullable(),
82
+ exit: exitSchema.nullable(),
83
+ });
84
+ const vmInventorySchema = z.array(vmStateSchema.extend({
85
+ id: z.string().regex(/^[0-9a-f]{64}$/),
86
+ name: z.string(),
87
+ lastActive: z.number().int().nonnegative(),
88
+ }));
89
+ const vmStartSchema = z.strictObject({
90
+ runtimeId: z.string().min(1).max(128),
91
+ });
92
+ function vmBodyBytes(body) {
93
+ return new Uint8Array(new TextEncoder().encode(body === undefined ? "" : JSON.stringify(body)));
94
+ }
95
+ async function signedVmRequest(env, placement, url, body) {
96
+ const method = body === undefined ? "GET" : "POST";
97
+ const bytes = vmBodyBytes(body);
98
+ const headers = new Headers({
99
+ ...accessHeaders(env),
100
+ [seamHeader]: await signSeamRequest(await seamSigner(env), vmHostAudience(placement.hostId), method, url, await seamBodyDigest(bytes)),
101
+ });
102
+ if (body !== undefined)
103
+ headers.set("content-type", "application/json");
104
+ return body === undefined
105
+ ? { method, headers }
106
+ : { method, headers, body: bytes };
107
+ }
108
+ /** One Durable Object's view of its microVM through the host service. */
109
+ export class OnBoxVmContainerHost {
110
+ #placement;
111
+ #env;
112
+ #vmId;
113
+ #sleepAfterMs;
114
+ constructor(placement, env, vmId, sleepAfterMs) {
115
+ this.#placement = placement;
116
+ this.#env = env;
117
+ this.#vmId = z
118
+ .string()
119
+ .regex(/^[0-9a-f]{64}$/)
120
+ .parse(vmId);
121
+ this.#sleepAfterMs = sleepAfterMs;
122
+ }
123
+ async #call(operation, schema, body) {
124
+ const path = operation === "status" ? "" : `/${operation}`;
125
+ const url = `${this.#placement.origin}/vms/${this.#vmId}${path}`;
126
+ const response = await fetch(url, await signedVmRequest(this.#env, this.#placement, url, body));
127
+ if (!response.ok) {
128
+ const detail = (await response.text()).slice(0, 200);
129
+ throw new OnBoxVmUnavailableError(operation, response.status, detail);
130
+ }
131
+ return schema.parse(await response.json());
132
+ }
133
+ async #boot(operation, env, enableInternet) {
134
+ const started = await this.#call(operation, vmStartSchema, {
135
+ env,
136
+ enableInternet,
137
+ sleepAfterMs: this.#sleepAfterMs,
138
+ });
139
+ return { runtimeId: started.runtimeId };
140
+ }
141
+ /** Signed host-wide inventory; includes VMs outside this Hub's directory. */
142
+ async inventory() {
143
+ const url = `${this.#placement.origin}/vms`;
144
+ const response = await fetch(url, await signedVmRequest(this.#env, this.#placement, url, undefined));
145
+ if (!response.ok)
146
+ throw new OnBoxVmUnavailableError("inventory", response.status);
147
+ return vmInventorySchema.parse(await response.json());
148
+ }
149
+ start(env, enableInternet) {
150
+ return this.#boot("start", env, enableInternet);
151
+ }
152
+ restart(env, enableInternet) {
153
+ return this.#boot("restart", env, enableInternet);
154
+ }
155
+ /** Stop a VM whose metered checkpoint cannot be funded or delivered. */
156
+ destroy() {
157
+ return this.#call("destroy", z.strictObject({ ok: z.literal(true) }), {});
158
+ }
159
+ /** The VM as the host service sees it now. */
160
+ state() {
161
+ return this.#call("status", vmStateSchema);
162
+ }
163
+ async status() {
164
+ const { running, exit } = await this.state();
165
+ return { running, exit };
166
+ }
167
+ async exitInfo() {
168
+ return (await this.state()).exit;
169
+ }
170
+ /** Whether the VM is running now; the platform container answers from `ctx.container.running`. */
171
+ async running() {
172
+ return (await this.state()).running;
173
+ }
174
+ async interceptOutboundHttp(host, workspace) {
175
+ await this.#call("intercept", z.strictObject({ ok: z.literal(true) }), {
176
+ host,
177
+ workspace,
178
+ });
179
+ }
180
+ async interceptAllOutboundHttp(workspace, token) {
181
+ await this.#call("intercept", z.strictObject({ ok: z.literal(true) }), {
182
+ all: true,
183
+ token,
184
+ workspace,
185
+ });
186
+ }
187
+ fetchPort(port, input, init) {
188
+ return this.port(port).fetch(input, init);
189
+ }
190
+ /**
191
+ * A guest port, reached through the host service: the VM has no address
192
+ * this Worker can route to. The guest sees the logical authority the
193
+ * caller named, as a platform container port does.
194
+ */
195
+ port(port) {
196
+ const target = z.number().int().min(1).max(65_535).parse(port);
197
+ const fetcher = {
198
+ fetch: async (input, init) => {
199
+ const request = new Request(input, init);
200
+ const logical = new URL(request.url);
201
+ const url = `${this.#placement.origin}/vms/${this.#vmId}/ports/${String(target)}${logical.pathname}${logical.search}`;
202
+ const headers = new Headers(request.headers);
203
+ for (const [key, value] of new Headers(accessHeaders(this.#env)))
204
+ headers.set(key, value);
205
+ headers.set("x-fungi-port-host", logical.host);
206
+ headers.set(seamHeader, await signSeamRequest(await seamSigner(this.#env), vmHostAudience(this.#placement.hostId), request.method, url, unsignedPayload));
207
+ return fetch(new Request(url, new Request(request, { headers })));
208
+ },
209
+ connect: () => {
210
+ throw new OnBoxVmUnavailableError("connect", 501, "raw TCP to an on-box VM port is not offered");
211
+ },
212
+ };
213
+ return fetcher;
214
+ }
215
+ }
216
+ const ingressPath = /^\/__computer-vm\/([0-9a-f]{64})(\/.*)$/;
217
+ const egressUrlSchema = z.url({ protocol: /^https?$/ });
218
+ const ingressReplay = new SeamReplayGuard();
219
+ /**
220
+ * The host service's door into this Worker: a VM's intercepted request
221
+ * reaches its Computer exactly as the platform container's interception
222
+ * does through `WorkspaceProxy` (the `/ws` connect upgrade, or an egress
223
+ * request carrying the workspace egress token and original URL). Returns
224
+ * `undefined` for every other path so the caller's router continues.
225
+ */
226
+ export function onBoxVmIngress(request, env, computers) {
227
+ const url = new URL(request.url);
228
+ const match = ingressPath.exec(url.pathname);
229
+ if (match === null)
230
+ return undefined;
231
+ const [, id = "", rest = "/"] = match;
232
+ return verifiedVmIngress(request, env, computers, id, rest, url.search);
233
+ }
234
+ /** The verified host id of a signed ingress request, or undefined. */
235
+ async function ingressHost(request, env) {
236
+ try {
237
+ return await verifySeamRequest(await hostVerifiers(env), vmIngressAudience, request.method, request.url, request.headers.get(seamHeader), null, ingressReplay);
238
+ }
239
+ catch (cause) {
240
+ console.error("VM seam verification refused", cause);
241
+ return undefined;
242
+ }
243
+ }
244
+ function vmCallbackTarget(target) {
245
+ // WorkspaceProxy's health and callback routes are the only local paths.
246
+ const callback = /^\/__workspace_connect\/([0-9a-f-]{36})\/(health|ws)$/.exec(target.pathname);
247
+ if (vmHealthRequest(target, callback))
248
+ return new Response("ok\n", {
249
+ headers: { "content-type": "text/plain; charset=utf-8" },
250
+ });
251
+ const callbackToken = vmCallbackToken(callback);
252
+ if (callbackToken !== undefined) {
253
+ target.pathname = "/ws";
254
+ target.searchParams.set("token", callbackToken);
255
+ }
256
+ if (target.pathname !== "/ws")
257
+ return new Response("not found", { status: 404 });
258
+ return null;
259
+ }
260
+ function vmHealthRequest(target, callback) {
261
+ return target.pathname === "/health" || callback?.[2] === "health";
262
+ }
263
+ function vmCallbackToken(callback) {
264
+ return callback?.[1];
265
+ }
266
+ function vmEgressTarget(target, headers, token, url) {
267
+ headers.set("x-workspace-egress-token", token);
268
+ headers.set("x-workspace-egress-url", egressUrlSchema.parse(url));
269
+ target.pathname = "/ws";
270
+ target.search = "";
271
+ }
272
+ async function verifiedVmIngress(request, env, computers, id, rest, search) {
273
+ const hostId = await ingressHost(request, env);
274
+ if (hostId === undefined)
275
+ return new Response(null, { status: 404 });
276
+ const headers = new Headers(request.headers);
277
+ headers.delete(seamHeader);
278
+ const token = headers.get("x-computer-vm-egress-token");
279
+ const egressUrl = headers.get("x-computer-vm-egress-url");
280
+ headers.delete("x-computer-vm-egress-token");
281
+ headers.delete("x-computer-vm-egress-url");
282
+ const target = new URL(`${rest}${search}`, "http://computer.internal");
283
+ if (token === null) {
284
+ const response = vmCallbackTarget(target);
285
+ if (response !== null)
286
+ return response;
287
+ }
288
+ else
289
+ vmEgressTarget(target, headers, token, egressUrl);
290
+ // The Computer hands this to the backend of the host that signed it.
291
+ target.pathname = `/__vm/${hostId}${target.pathname}`;
292
+ const computer = computers.get(computers.idFromString(id));
293
+ return computer.fetch(new Request(target, new Request(request, { headers })));
294
+ }