@usine/cloudflare 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +95 -2
  3. package/dist/access.d.ts +12 -0
  4. package/dist/access.d.ts.map +1 -0
  5. package/dist/access.js +32 -0
  6. package/dist/access.js.map +1 -0
  7. package/dist/api/api.d.ts +215 -0
  8. package/dist/api/api.d.ts.map +1 -0
  9. package/dist/api/api.js +180 -0
  10. package/dist/api/api.js.map +1 -0
  11. package/dist/api/harness.d.ts +49 -0
  12. package/dist/api/harness.d.ts.map +1 -0
  13. package/dist/api/harness.js +57 -0
  14. package/dist/api/harness.js.map +1 -0
  15. package/dist/api/names.d.ts +28 -0
  16. package/dist/api/names.d.ts.map +1 -0
  17. package/dist/api/names.js +31 -0
  18. package/dist/api/names.js.map +1 -0
  19. package/dist/api/stream.d.ts +65 -0
  20. package/dist/api/stream.d.ts.map +1 -0
  21. package/dist/api/stream.js +70 -0
  22. package/dist/api/stream.js.map +1 -0
  23. package/dist/registry.d.ts +28 -0
  24. package/dist/registry.d.ts.map +1 -0
  25. package/dist/registry.js +45 -0
  26. package/dist/registry.js.map +1 -0
  27. package/dist/sandbox/outbound.d.ts +53 -0
  28. package/dist/sandbox/outbound.d.ts.map +1 -0
  29. package/dist/sandbox/outbound.js +109 -0
  30. package/dist/sandbox/outbound.js.map +1 -0
  31. package/dist/sandbox/ports.d.ts +99 -0
  32. package/dist/sandbox/ports.d.ts.map +1 -0
  33. package/dist/sandbox/ports.js +66 -0
  34. package/dist/sandbox/ports.js.map +1 -0
  35. package/dist/sandbox/process.d.ts +55 -0
  36. package/dist/sandbox/process.d.ts.map +1 -0
  37. package/dist/sandbox/process.js +172 -0
  38. package/dist/sandbox/process.js.map +1 -0
  39. package/dist/sandbox/sandbox.d.ts +36 -0
  40. package/dist/sandbox/sandbox.d.ts.map +1 -0
  41. package/dist/sandbox/sandbox.js +387 -0
  42. package/dist/sandbox/sandbox.js.map +1 -0
  43. package/dist/sandbox/stream.d.ts +26 -0
  44. package/dist/sandbox/stream.d.ts.map +1 -0
  45. package/dist/sandbox/stream.js +167 -0
  46. package/dist/sandbox/stream.js.map +1 -0
  47. package/dist/worker.d.ts +77 -0
  48. package/dist/worker.d.ts.map +1 -0
  49. package/dist/worker.js +362 -0
  50. package/dist/worker.js.map +1 -0
  51. package/image/Dockerfile +43 -0
  52. package/image/codex-config.toml +10 -0
  53. package/image/init.sh +17 -0
  54. package/package.json +59 -3
  55. package/wrangler.jsonc +32 -0
@@ -0,0 +1,77 @@
1
+ import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
2
+ import { Schema } from "effect";
3
+ /** The bindings and variables that `wrangler.jsonc` and `wrangler secret put` give the Worker. */
4
+ interface Env {
5
+ readonly SANDBOX: DurableObjectNamespace<AttemptSandbox>;
6
+ readonly REGISTRY: DurableObjectNamespace<SandboxRegistry>;
7
+ readonly ACCESS_CLIENT_ID?: string;
8
+ readonly ACCESS_CLIENT_SECRET?: string;
9
+ readonly ALLOWED_HOSTS?: string;
10
+ }
11
+ /**
12
+ * The Worker's HTTP surface. Every request must carry the Access service token; the routes go to
13
+ * the sandbox's Durable Object, the registry, or any sandbox Durable Object for the health check.
14
+ */
15
+ declare const _default: {
16
+ fetch(request: Request<unknown, IncomingRequestCfProperties<unknown>>, env: Env): Promise<Response>;
17
+ };
18
+ export default _default;
19
+ /**
20
+ * The Durable Object and container of one Attempt's sandbox. It serves the sandbox routes of
21
+ * `SandboxApi` and the exec and attach streams, and keeps the container running with an alarm
22
+ * while the Attempt holds its lease.
23
+ */
24
+ export declare class AttemptSandbox extends DurableObject<Env> {
25
+ #private;
26
+ constructor(ctx: DurableObjectState, env: Env);
27
+ fetch(request: Request): Promise<Response>;
28
+ alarm(): Promise<void>;
29
+ }
30
+ /**
31
+ * The registry of every sandbox, by owner. Sandboxes record themselves when they're created and
32
+ * leave when they're destroyed; `/v1/sandboxes` reads each one's current state.
33
+ */
34
+ export declare class SandboxRegistry extends DurableObject<Env> {
35
+ #private;
36
+ constructor(ctx: DurableObjectState, env: Env);
37
+ fetch(request: Request): Promise<Response>;
38
+ /** Records `sandbox`, the JSON encoding of a `SandboxSummary`. */
39
+ record(sandbox: Schema.Json): Promise<void>;
40
+ forget(name: string): Promise<void>;
41
+ }
42
+ declare const GitHubProps: Schema.Struct<{
43
+ readonly host: Schema.Literals<readonly ["github.com", "api.github.com"]>;
44
+ readonly token: Schema.NonEmptyString;
45
+ }>;
46
+ declare const ProviderProps: Schema.Struct<{
47
+ readonly provider: Schema.Literals<readonly ["anthropic", "openai"]>;
48
+ readonly credential: Schema.Union<readonly [typeof import("./api/api.ts").ApiKeyCredential]>;
49
+ }>;
50
+ /** Adds the Attempt's installation token to the sandbox's requests for GitHub. */
51
+ export declare class GitHubOutbound extends WorkerEntrypoint<Env, typeof GitHubProps.Encoded> {
52
+ fetch(request: Request): Promise<Response>;
53
+ }
54
+ /**
55
+ * Sends the sandbox's plain HTTP requests for a provider to the provider, with its API key.
56
+ * Refuses a request for a route that the harnesses don't call with status 404.
57
+ */
58
+ export declare class ProviderOutbound extends WorkerEntrypoint<Env, typeof ProviderProps.Encoded> {
59
+ fetch(request: Request): Promise<Response>;
60
+ }
61
+ /** Forwards the sandbox's requests for an allowlisted host unchanged. */
62
+ export declare class AllowedOutbound extends WorkerEntrypoint<Env, Record<string, never>> {
63
+ fetch(request: Request): Promise<Response>;
64
+ }
65
+ /** Refuses the sandbox's requests for a GitHub or provider host that has no credential. */
66
+ export declare class DeniedOutbound extends WorkerEntrypoint<Env, Record<string, never>> {
67
+ fetch(request: Request): Promise<Response>;
68
+ }
69
+ declare global {
70
+ namespace Cloudflare {
71
+ interface GlobalProps {
72
+ mainModule: typeof import("./worker.ts");
73
+ durableNamespaces: "AttemptSandbox" | "SandboxRegistry";
74
+ }
75
+ }
76
+ }
77
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAWL,MAAM,EACP,MAAM,QAAQ,CAAC;AAqBhB,kGAAkG;AAClG,UAAU,GAAG;IACX,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAC,cAAc,CAAC,CAAC;IACzD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC,eAAe,CAAC,CAAC;IAC3D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAkDD;;;GAGG;;;;;AAuLH;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,aAAa,CAAC,GAAG,CAAC;;IAMpD,YAAY,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,GAAG,EA2C5C;IAYc,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgCxD;IAEc,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAEpC;CACF;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,aAAa,CAAC,GAAG,CAAC;;IAGrD,YAAY,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,GAAG,EAqD5C;IAEc,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAExD;IAED,kEAAkE;IAC5D,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhD;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAExC;CACF;AAED,QAAA,MAAM,WAAW;;;EAGf,CAAC;AACH,QAAA,MAAM,aAAa;;;EAGjB,CAAC;AAcH,kFAAkF;AAClF,qBAAa,cAAe,SAAQ,gBAAgB,CAAC,GAAG,EAAE,OAAO,WAAW,CAAC,OAAO,CAAC;IACpE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAKxD;CACF;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,gBAAgB,CAAC,GAAG,EAAE,OAAO,aAAa,CAAC,OAAO,CAAC;IACxE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoBxD;CACF;AAED,yEAAyE;AACzE,qBAAa,eAAgB,SAAQ,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAChE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAIxD;CACF;AAED,2FAA2F;AAC3F,qBAAa,cAAe,SAAQ,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC/D,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAQxD;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,UAAU,CAAC;QACnB,UAAU,WAAW;YACnB,UAAU,EAAE,cAAc,aAAa,CAAC,CAAC;YACzC,iBAAiB,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;SACzD;KACF;CACF"}
package/dist/worker.js ADDED
@@ -0,0 +1,362 @@
1
+ import { Files, SandboxFileError } from "@cloudflare/sandbox";
2
+ import { CloudflareLlmProvider } from "@usine/core/worker";
3
+ import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
4
+ import { DateTime, Duration, Effect, Layer, Logger, Match, Option, Queue, Redacted, Schema, } from "effect";
5
+ import { HttpRouter } from "effect/http";
6
+ import { accessGranted } from "./access.js";
7
+ import { ProcessId, ProviderCredential, SandboxName, SandboxSummary } from "./api/api.js";
8
+ import { GITHUB_HOSTS } from "./api/harness.js";
9
+ import { AttachOffsets } from "./api/stream.js";
10
+ import { registryRoutes, RegistryStorage, SandboxLookup, SandboxLookupFailed } from "./registry.js";
11
+ import { allowedHostsOf, gitHubRequest, providerRequest } from "./sandbox/outbound.js";
12
+ import { ContainerFailure, SandboxContainer, SandboxDirectory, SandboxSettings, SandboxStorage, StoredSandbox, } from "./sandbox/ports.js";
13
+ import { commandOptions, readFromCommand } from "./sandbox/process.js";
14
+ import { collectBytes, keepAlive, restore, sandboxRoutes } from "./sandbox/sandbox.js";
15
+ import { attachStream, execStream } from "./sandbox/stream.js";
16
+ const WorkerVariables = Schema.Struct({
17
+ ACCESS_CLIENT_ID: Schema.NonEmptyString,
18
+ ACCESS_CLIENT_SECRET: Schema.NonEmptyString,
19
+ ALLOWED_HOSTS: Schema.optionalKey(Schema.String),
20
+ });
21
+ const decodeVariables = Schema.decodeUnknownOption(WorkerVariables);
22
+ /** Returns the Worker's settings, or `None` if the Access service token secrets are unset. */
23
+ const settingsOf = (env) => Option.map(decodeVariables(env), (variables) => ({
24
+ access: {
25
+ clientId: Redacted.make(variables.ACCESS_CLIENT_ID),
26
+ clientSecret: Redacted.make(variables.ACCESS_CLIENT_SECRET),
27
+ },
28
+ allowedHosts: allowedHostsOf(variables.ALLOWED_HOSTS ?? ""),
29
+ }));
30
+ const logger = Logger.layer([Logger.consoleJson]);
31
+ /** Runs `effect` with the Worker's JSON logger. */
32
+ const run = (effect) => Effect.runPromise(effect.pipe(Effect.provide(logger)));
33
+ /** The name of the Durable Object that answers `/v1/health`. It never starts a container. */
34
+ const HEALTH_SANDBOX = "usine-health";
35
+ /** The name of the registry Durable Object. */
36
+ const REGISTRY = "usine-registry";
37
+ const ROUTES = {
38
+ health: /^\/v1\/health$/u,
39
+ registry: /^\/v1\/sandboxes$/u,
40
+ sandbox: /^\/v1\/sandboxes\/([^/]+)(?:\/.*)?$/u,
41
+ exec: /^\/v1\/sandboxes\/([^/]+)\/exec$/u,
42
+ attach: /^\/v1\/sandboxes\/([^/]+)\/processes\/([^/]+)\/attach$/u,
43
+ };
44
+ const decodeSandboxName = Schema.decodeUnknownOption(SandboxName);
45
+ const decodeProcessId = Schema.decodeUnknownOption(ProcessId);
46
+ const decodeOffsets = Schema.decodeUnknownOption(AttachOffsets);
47
+ const plain = (status, message) => new Response(message, { status, headers: { "content-type": "text/plain; charset=utf-8" } });
48
+ /**
49
+ * The Worker's HTTP surface. Every request must carry the Access service token; the routes go to
50
+ * the sandbox's Durable Object, the registry, or any sandbox Durable Object for the health check.
51
+ */
52
+ export default {
53
+ async fetch(request, env) {
54
+ const settings = settingsOf(env);
55
+ if (Option.isNone(settings)) {
56
+ await run(Effect.logError("The Worker has no Access service token; set ACCESS_CLIENT_ID and ACCESS_CLIENT_SECRET with wrangler secret put"));
57
+ return plain(500, "The Worker has no Access service token configured.\n");
58
+ }
59
+ const url = new URL(request.url);
60
+ if (!(await run(accessGranted(request.headers, settings.value.access)))) {
61
+ await run(Effect.logWarning("Refused a request without the Access service token", {
62
+ path: url.pathname,
63
+ }));
64
+ return plain(401, "The request lacks the Access service token.\n");
65
+ }
66
+ if (ROUTES.health.test(url.pathname))
67
+ return env.SANDBOX.getByName(HEALTH_SANDBOX).fetch(request);
68
+ if (ROUTES.registry.test(url.pathname))
69
+ return env.REGISTRY.getByName(REGISTRY).fetch(request);
70
+ const name = decodeSandboxName(ROUTES.sandbox.exec(url.pathname)?.[1]);
71
+ if (Option.isNone(name))
72
+ return plain(404, "Not found.\n");
73
+ return env.SANDBOX.getByName(name.value).fetch(request);
74
+ },
75
+ };
76
+ const containerFailure = (operation) => (cause) => new ContainerFailure({
77
+ operation,
78
+ message: cause instanceof Error ? cause.message : String(cause),
79
+ });
80
+ const isMissingFile = (cause) => SandboxFileError.is(cause) && cause.code === "ENOENT";
81
+ /** Returns the `SandboxContainer` over the Durable Object Container API and the Sandbox SDK. */
82
+ const containerOf = (container, exports) => {
83
+ const files = new Files(container);
84
+ const tryContainer = (operation, evaluate) => Effect.tryPromise({ try: evaluate, catch: containerFailure(operation) });
85
+ return {
86
+ running: Effect.sync(() => container.running),
87
+ images: Effect.sync(() => container.images),
88
+ start: (image, instanceType) => Effect.try({
89
+ try: () => container.start({ image, instance: instanceType, enableInternet: false }),
90
+ catch: containerFailure("start"),
91
+ }),
92
+ exec: (command, stdin) => tryContainer("exec", () => container.exec([...command], commandOptions(stdin))),
93
+ read: (path) => tryContainer("read", async () => {
94
+ try {
95
+ const response = await files.readFile(path);
96
+ return Option.some(new Uint8Array(await response.arrayBuffer()));
97
+ }
98
+ catch (cause) {
99
+ if (isMissingFile(cause))
100
+ return Option.none();
101
+ throw cause;
102
+ }
103
+ }),
104
+ readFrom: (path, offset) => tryContainer("read", () => container.exec([...readFromCommand(path, offset)], commandOptions("closed"))).pipe(
105
+ // Keeps the bytes as they are: the attach stream counts them to resume at an offset.
106
+ Effect.flatMap((process) => collectBytes(process)), Effect.flatMap(({ stdout, exitCode, stderr }) => {
107
+ if (exitCode === 0)
108
+ return Effect.succeed(Option.some(stdout));
109
+ const message = new TextDecoder().decode(stderr).trim();
110
+ return message.includes("No such file")
111
+ ? Effect.succeedNone
112
+ : Effect.fail(new ContainerFailure({ operation: "read", message }));
113
+ })),
114
+ size: (path) => tryContainer("read", async () => {
115
+ try {
116
+ return Option.some(Number((await files.stat(path)).size));
117
+ }
118
+ catch (cause) {
119
+ if (isMissingFile(cause))
120
+ return Option.none();
121
+ throw cause;
122
+ }
123
+ }),
124
+ destroy: tryContainer("destroy", () => container.destroy()),
125
+ keepAlive: (inactivity) => tryContainer("keep-alive", () => container.setInactivityTimeout(Duration.toMillis(inactivity))),
126
+ intercept: (interception) => Match.valueTags(interception, {
127
+ GitHub: ({ host, token }) => tryContainer("intercept", () => container.interceptOutboundHttps(host, exports.GitHubOutbound({ props: { host, token: Redacted.value(token) } }))),
128
+ Provider: ({ host, provider, credential }) => tryContainer("intercept", () => container.interceptOutboundHttp(host, exports.ProviderOutbound({
129
+ props: { provider, credential: Schema.encodeSync(ProviderCredential)(credential) },
130
+ }))),
131
+ Allowed: ({ host }) => tryContainer("intercept", () => container.interceptOutboundHttps(host, exports.AllowedOutbound({ props: {} }))),
132
+ Denied: ({ host, scheme }) => tryContainer("intercept", () => scheme === "https"
133
+ ? container.interceptOutboundHttps(host, exports.DeniedOutbound({ props: {} }))
134
+ : container.interceptOutboundHttp(host, exports.DeniedOutbound({ props: {} }))),
135
+ }),
136
+ };
137
+ };
138
+ const STORED_KEY = "sandbox";
139
+ const encodeStored = Schema.encodeSync(Schema.toCodecJson(StoredSandbox));
140
+ const decodeStored = Schema.decodeUnknownOption(Schema.toCodecJson(StoredSandbox));
141
+ /** Returns `SandboxStorage` over the Durable Object's storage, with the sandbox as JSON. */
142
+ const storageOf = (storage) => ({
143
+ load: Effect.promise(() => storage.get(STORED_KEY)).pipe(Effect.map((value) => (value === undefined ? Option.none() : decodeStored(value)))),
144
+ // No other request runs between the read and the write: the input gate holds events while a
145
+ // storage read runs, and nothing but storage is awaited until the write.
146
+ modify: (change) => Effect.promise(async () => {
147
+ const value = await storage.get(STORED_KEY);
148
+ const [result, changed] = change(value === undefined ? Option.none() : decodeStored(value));
149
+ if (Option.isSome(changed))
150
+ await storage.put(STORED_KEY, encodeStored(changed.value));
151
+ return result;
152
+ }),
153
+ clear: Effect.promise(async () => {
154
+ await storage.deleteAlarm();
155
+ await storage.deleteAll();
156
+ }),
157
+ alarmAt: (at) => Effect.promise(() => storage.setAlarm(DateTime.toEpochMillis(at))),
158
+ });
159
+ const encodeSummary = Schema.encodeSync(Schema.toCodecJson(SandboxSummary));
160
+ const decodeSummary = Schema.decodeUnknownOption(Schema.toCodecJson(SandboxSummary));
161
+ /** Returns a `StreamSocket` over the server end of a `WebSocketPair`, which it accepts. */
162
+ const socketOf = (socket) => Effect.gen(function* () {
163
+ const messages = yield* Queue.unbounded();
164
+ socket.accept();
165
+ socket.addEventListener("message", (event) => {
166
+ Queue.offerUnsafe(messages, event.data instanceof ArrayBuffer ? new Uint8Array(event.data) : String(event.data));
167
+ });
168
+ socket.addEventListener("close", () => Queue.endUnsafe(messages));
169
+ socket.addEventListener("error", () => Queue.endUnsafe(messages));
170
+ const attempt = (action) => Effect.sync(() => {
171
+ try {
172
+ action();
173
+ }
174
+ catch {
175
+ // The client already closed the socket.
176
+ }
177
+ });
178
+ return {
179
+ messages,
180
+ send: (message) => attempt(() => socket.send(message)),
181
+ close: (code, reason) => attempt(() => socket.close(code, reason)),
182
+ };
183
+ });
184
+ /**
185
+ * The Durable Object and container of one Attempt's sandbox. It serves the sandbox routes of
186
+ * `SandboxApi` and the exec and attach streams, and keeps the container running with an alarm
187
+ * while the Attempt holds its lease.
188
+ */
189
+ export class AttemptSandbox extends DurableObject {
190
+ #services;
191
+ #web;
192
+ constructor(ctx, env) {
193
+ super(ctx, env);
194
+ const container = ctx.container;
195
+ if (container === undefined)
196
+ throw new Error("AttemptSandbox needs a container: add it to the containers of wrangler.jsonc");
197
+ const registry = env.REGISTRY.getByName(REGISTRY);
198
+ const directory = {
199
+ record: (sandbox) => Effect.tryPromise(() => registry.record(encodeSummary(sandbox))).pipe(Effect.tapError(() => Effect.logError("Could not record the sandbox in the registry", {
200
+ sandbox: sandbox.name,
201
+ })), Effect.ignore),
202
+ forget: (name) => Effect.tryPromise(() => registry.forget(name)).pipe(Effect.tapError(() => Effect.logError("Could not remove the sandbox from the registry", { sandbox: name })), Effect.ignore),
203
+ };
204
+ this.#services = Layer.mergeAll(Layer.succeed(SandboxContainer)(containerOf(container, ctx.exports)), Layer.succeed(SandboxStorage)(storageOf(ctx.storage)), Layer.succeed(SandboxDirectory)(directory), Layer.succeed(SandboxSettings)({
205
+ allowedHosts: Option.match(settingsOf(env), {
206
+ onNone: () => [],
207
+ onSome: ({ allowedHosts }) => allowedHosts,
208
+ }),
209
+ }), logger);
210
+ const web = HttpRouter.toWebHandler(sandboxRoutes.pipe(HttpRouter.provideRequest(this.#services)));
211
+ this.#web = (request) => web.handler(request);
212
+ void ctx.blockConcurrencyWhile(() => this.#run(restore()));
213
+ }
214
+ #run(effect) {
215
+ return Effect.runPromise(effect.pipe(Effect.provide(this.#services)));
216
+ }
217
+ async fetch(request) {
218
+ const url = new URL(request.url);
219
+ const exec = ROUTES.exec.exec(url.pathname);
220
+ const attach = ROUTES.attach.exec(url.pathname);
221
+ if (exec === null && attach === null)
222
+ return this.#web(request);
223
+ if (request.headers.get("upgrade")?.toLowerCase() !== "websocket")
224
+ return plain(426, "Open this path as a WebSocket.\n");
225
+ const name = decodeSandboxName((exec ?? attach)?.[1]);
226
+ if (Option.isNone(name))
227
+ return plain(404, "Not found.\n");
228
+ const pair = new WebSocketPair();
229
+ const [client, server] = [pair[0], pair[1]];
230
+ const stream = attach === null
231
+ ? Effect.flatMap(socketOf(server), (socket) => execStream(name.value, socket))
232
+ : Option.match(Option.all({
233
+ process: decodeProcessId(attach[2]),
234
+ offsets: decodeOffsets(Object.fromEntries(url.searchParams)),
235
+ }), {
236
+ onNone: () => Effect.flatMap(socketOf(server), (socket) => socket.close(1008, "Expected a process ID and stdout and stderr offsets")),
237
+ onSome: ({ process, offsets }) => Effect.flatMap(socketOf(server), (socket) => attachStream(name.value, process, offsets, socket)),
238
+ });
239
+ this.ctx.waitUntil(this.#run(stream.pipe(Effect.scoped)));
240
+ return new Response(null, { status: 101, webSocket: client });
241
+ }
242
+ async alarm() {
243
+ await this.#run(keepAlive());
244
+ }
245
+ }
246
+ /**
247
+ * The registry of every sandbox, by owner. Sandboxes record themselves when they're created and
248
+ * leave when they're destroyed; `/v1/sandboxes` reads each one's current state.
249
+ */
250
+ export class SandboxRegistry extends DurableObject {
251
+ #web;
252
+ constructor(ctx, env) {
253
+ super(ctx, env);
254
+ const storage = {
255
+ list: Effect.promise(() => ctx.storage.list({ prefix: "sandbox:" })).pipe(Effect.map((entries) => [...entries.values()].flatMap((value) => Option.toArray(decodeSummary(value))))),
256
+ put: (sandbox) => Effect.promise(() => ctx.storage.put(`sandbox:${sandbox.name}`, encodeSummary(sandbox))),
257
+ remove: (name) => Effect.promise(() => ctx.storage.delete(`sandbox:${name}`)).pipe(Effect.asVoid),
258
+ };
259
+ const lookup = {
260
+ get: (name) => {
261
+ const failed = (reason) => new SandboxLookupFailed({ name, reason });
262
+ return Effect.tryPromise({
263
+ try: () => env.SANDBOX.getByName(name).fetch(`https://sandbox.internal/v1/sandboxes/${name}`),
264
+ catch: (cause) => failed(`no response: ${cause instanceof Error ? cause.message : String(cause)}`),
265
+ }).pipe(Effect.flatMap((response) => {
266
+ // Only a 404 from the sandbox's own Durable Object says that the sandbox is gone.
267
+ if (response.status === 404)
268
+ return Effect.succeedNone;
269
+ if (!response.ok)
270
+ return Effect.fail(failed(`status ${String(response.status)}`));
271
+ return Effect.tryPromise({
272
+ try: () => response.json(),
273
+ catch: () => failed("the response isn't JSON"),
274
+ }).pipe(Effect.flatMap((body) => Option.match(decodeSummary(body), {
275
+ onNone: () => Effect.fail(failed("the response isn't a sandbox summary")),
276
+ onSome: (summary) => Effect.succeed(Option.some(summary)),
277
+ })));
278
+ }));
279
+ },
280
+ };
281
+ const web = HttpRouter.toWebHandler(registryRoutes.pipe(HttpRouter.provideRequest(Layer.mergeAll(Layer.succeed(RegistryStorage)(storage), Layer.succeed(SandboxLookup)(lookup), logger))));
282
+ this.#web = (request) => web.handler(request);
283
+ }
284
+ async fetch(request) {
285
+ return this.#web(request);
286
+ }
287
+ /** Records `sandbox`, the JSON encoding of a `SandboxSummary`. */
288
+ async record(sandbox) {
289
+ const decoded = decodeSummary(sandbox);
290
+ if (Option.isSome(decoded))
291
+ await this.ctx.storage.put(`sandbox:${decoded.value.name}`, encodeSummary(decoded.value));
292
+ }
293
+ async forget(name) {
294
+ await this.ctx.storage.delete(`sandbox:${name}`);
295
+ }
296
+ }
297
+ const GitHubProps = Schema.Struct({
298
+ host: Schema.Literals(GITHUB_HOSTS),
299
+ token: Schema.NonEmptyString,
300
+ });
301
+ const ProviderProps = Schema.Struct({
302
+ provider: CloudflareLlmProvider,
303
+ credential: ProviderCredential,
304
+ });
305
+ /** Logs one forwarded request without its headers, body, or query string. */
306
+ const logForwarded = (handler, request, response) => run(Effect.logInfo("Forwarded a sandbox request", {
307
+ handler,
308
+ method: request.method,
309
+ host: new URL(request.url).host,
310
+ path: new URL(request.url).pathname,
311
+ status: response.status,
312
+ }));
313
+ /** Adds the Attempt's installation token to the sandbox's requests for GitHub. */
314
+ export class GitHubOutbound extends WorkerEntrypoint {
315
+ async fetch(request) {
316
+ const props = Schema.decodeUnknownSync(GitHubProps)(this.ctx.props);
317
+ const response = await fetch(gitHubRequest(request, props.host, Redacted.make(props.token)));
318
+ await logForwarded("github", request, response);
319
+ return response;
320
+ }
321
+ }
322
+ /**
323
+ * Sends the sandbox's plain HTTP requests for a provider to the provider, with its API key.
324
+ * Refuses a request for a route that the harnesses don't call with status 404.
325
+ */
326
+ export class ProviderOutbound extends WorkerEntrypoint {
327
+ async fetch(request) {
328
+ const props = Schema.decodeUnknownSync(ProviderProps)(this.ctx.props);
329
+ const forwarded = providerRequest(request, props.provider, props.credential);
330
+ if (Option.isNone(forwarded)) {
331
+ await run(Effect.logWarning("Refused a sandbox request for a route the harnesses don't call", {
332
+ handler: props.provider,
333
+ method: request.method,
334
+ host: new URL(request.url).host,
335
+ path: new URL(request.url).pathname,
336
+ }));
337
+ return plain(404, `usine forwards only the routes Claude Code and Codex call to ${props.provider}.\n`);
338
+ }
339
+ const response = await fetch(forwarded.value);
340
+ await logForwarded(props.provider, request, response);
341
+ return response;
342
+ }
343
+ }
344
+ /** Forwards the sandbox's requests for an allowlisted host unchanged. */
345
+ export class AllowedOutbound extends WorkerEntrypoint {
346
+ async fetch(request) {
347
+ const response = await fetch(request);
348
+ await logForwarded("allowed", request, response);
349
+ return response;
350
+ }
351
+ }
352
+ /** Refuses the sandbox's requests for a GitHub or provider host that has no credential. */
353
+ export class DeniedOutbound extends WorkerEntrypoint {
354
+ async fetch(request) {
355
+ await run(Effect.logWarning("Refused a sandbox request for a host without a credential", {
356
+ method: request.method,
357
+ host: new URL(request.url).host,
358
+ }));
359
+ return plain(403, "usine attached no credential for this host to the sandbox.\n");
360
+ }
361
+ }
362
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAEL,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,KAAK,EACL,MAAM,EACN,KAAK,EACL,MAAM,EACN,KAAK,EACL,QAAQ,EACR,MAAM,GACP,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAoB,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EACL,gBAAgB,EAEhB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAqB,MAAM,qBAAqB,CAAC;AAWlF,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC;IACpC,gBAAgB,EAAE,MAAM,CAAC,cAAc;IACvC,oBAAoB,EAAE,MAAM,CAAC,cAAc;IAC3C,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC;CACjD,CAAC,CAAC;AACH,MAAM,eAAe,GAAG,MAAM,CAAC,mBAAmB,CAAC,eAAe,CAAC,CAAC;AAOpE,8FAA8F;AAC9F,MAAM,UAAU,GAAG,CAAC,GAAQ,EAAiC,EAAE,CAC7D,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC/C,MAAM,EAAE;QACN,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC;QACnD,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC;KAC5D;IACD,YAAY,EAAE,cAAc,CAAC,SAAS,CAAC,aAAa,IAAI,EAAE,CAAC;CAC5D,CAAC,CAAC,CAAC;AAEN,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAElD,mDAAmD;AACnD,MAAM,GAAG,GAAG,CAAI,MAAwB,EAAc,EAAE,CACtD,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAEzD,6FAA6F;AAC7F,MAAM,cAAc,GAAG,cAAc,CAAC;AACtC,+CAA+C;AAC/C,MAAM,QAAQ,GAAG,gBAAgB,CAAC;AAElC,MAAM,MAAM,GAAG;IACb,MAAM,EAAE,iBAAiB;IACzB,QAAQ,EAAE,oBAAoB;IAC9B,OAAO,EAAE,sCAAsC;IAC/C,IAAI,EAAE,mCAAmC;IACzC,MAAM,EAAE,yDAAyD;CACzD,CAAC;AAEX,MAAM,iBAAiB,GAAG,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;AAClE,MAAM,eAAe,GAAG,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;AAC9D,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC;AAEhE,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,OAAe,EAAE,EAAE,CAChD,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,2BAA2B,EAAE,EAAE,CAAC,CAAC;AAE9F;;;GAGG;AACH,eAAe;IACb,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG;QACtB,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,MAAM,GAAG,CACP,MAAM,CAAC,QAAQ,CACb,gHAAgH,CACjH,CACF,CAAC;YACF,OAAO,KAAK,CAAC,GAAG,EAAE,sDAAsD,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACxE,MAAM,GAAG,CACP,MAAM,CAAC,UAAU,CAAC,oDAAoD,EAAE;gBACtE,IAAI,EAAE,GAAG,CAAC,QAAQ;aACnB,CAAC,CACH,CAAC;YACF,OAAO,KAAK,CAAC,GAAG,EAAE,+CAA+C,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClC,OAAO,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9D,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/F,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,OAAO,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1D,CAAC;CAC6B,CAAC;AAEjC,MAAM,gBAAgB,GAAG,CAAC,SAA6B,EAAE,EAAE,CAAC,CAAC,KAAc,EAAE,EAAE,CAC7E,IAAI,gBAAgB,CAAC;IACnB,SAAS;IACT,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;CAChE,CAAC,CAAC;AAEL,MAAM,aAAa,GAAG,CAAC,KAAc,EAAW,EAAE,CAChD,gBAAgB,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;AAExD,gGAAgG;AAChG,MAAM,WAAW,GAAG,CAClB,SAAoB,EACpB,OAA2B,EACE,EAAE;IAC/B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;IACnC,MAAM,YAAY,GAAG,CAAI,SAA6B,EAAE,QAA0B,EAAE,EAAE,CACpF,MAAM,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC;QAC7C,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC;QAC3C,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,CAC7B,MAAM,CAAC,GAAG,CAAC;YACT,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;YACpF,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC;SACjC,CAAC;QACJ,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CACvB,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QACjF,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CACb,YAAY,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;YAC9B,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBAC5C,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;YACnE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,aAAa,CAAC,KAAK,CAAC;oBAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC/C,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC,CAAC;QACJ,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CACzB,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,CACxB,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,CAC7E,CAAC,IAAI;QACJ,qFAAqF;QACrF,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EAClD,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE;YAC9C,IAAI,QAAQ,KAAK,CAAC;gBAAE,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/D,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YACxD,OAAO,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;gBACrC,CAAC,CAAC,MAAM,CAAC,WAAW;gBACpB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QACxE,CAAC,CAAC,CACH;QACH,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CACb,YAAY,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;YAC9B,IAAI,CAAC;gBACH,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,aAAa,CAAC,KAAK,CAAC;oBAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC/C,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC,CAAC;QACJ,OAAO,EAAE,YAAY,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QAC3D,SAAS,EAAE,CAAC,UAAU,EAAE,EAAE,CACxB,YAAY,CAAC,YAAY,EAAE,GAAG,EAAE,CAC9B,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAC9D;QACH,SAAS,EAAE,CAAC,YAAY,EAAE,EAAE,CAC1B,KAAK,CAAC,SAAS,CAAC,YAAY,EAAE;YAC5B,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAC1B,YAAY,CAAC,WAAW,EAAE,GAAG,EAAE,CAC7B,SAAS,CAAC,sBAAsB,CAC9B,IAAI,EACJ,OAAO,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAC1E,CACF;YACH,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,CAC3C,YAAY,CAAC,WAAW,EAAE,GAAG,EAAE,CAC7B,SAAS,CAAC,qBAAqB,CAC7B,IAAI,EACJ,OAAO,CAAC,gBAAgB,CAAC;gBACvB,KAAK,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,UAAU,CAAC,EAAE;aACnF,CAAC,CACH,CACF;YACH,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACpB,YAAY,CAAC,WAAW,EAAE,GAAG,EAAE,CAC7B,SAAS,CAAC,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAC/E;YACH,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAC3B,YAAY,CAAC,WAAW,EAAE,GAAG,EAAE,CAC7B,MAAM,KAAK,OAAO;gBAChB,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC/E,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CACjF;SACJ,CAAC;KACL,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,SAAS,CAAC;AAC7B,MAAM,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;AAC1E,MAAM,YAAY,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;AAEnF,4FAA4F;AAC5F,MAAM,SAAS,GAAG,CAAC,OAA6B,EAA6B,EAAE,CAAC,CAAC;IAC/E,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CACtD,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CACnF;IACD,4FAA4F;IAC5F,yEAAyE;IACzE,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CACjB,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;QACxB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5F,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;YAAE,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACvF,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IACJ,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;QAC/B,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;QAC5B,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC;IAC5B,CAAC,CAAC;IACF,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC;CACpF,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC;AAC5E,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC;AAErF,2FAA2F;AAC3F,MAAM,QAAQ,GAAG,CAAC,MAAiB,EAA+B,EAAE,CAClE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,EAAmC,CAAC;IAC3E,MAAM,CAAC,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAC3C,KAAK,CAAC,WAAW,CACf,QAAQ,EACR,KAAK,CAAC,IAAI,YAAY,WAAW,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CACpF,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClE,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,CAAC,MAAkB,EAAE,EAAE,CACrC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;QACf,IAAI,CAAC;YACH,MAAM,EAAE,CAAC;QACX,CAAC;QAAC,MAAM,CAAC;YACP,wCAAwC;QAC1C,CAAC;IACH,CAAC,CAAC,CAAC;IACL,OAAO;QACL,QAAQ;QACR,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;KAC5C,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEL;;;;GAIG;AACH,MAAM,OAAO,cAAe,SAAQ,aAAkB;IAC3C,SAAS,CAEhB;IACO,IAAI,CAA0C;IAEvD,YAAY,GAAuB,EAAE,GAAQ;QAC3C,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAChB,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;QAChC,IAAI,SAAS,KAAK,SAAS;YACzB,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;QACJ,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClD,MAAM,SAAS,GAAgC;YAC7C,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAClB,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CACnE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CACnB,MAAM,CAAC,QAAQ,CAAC,8CAA8C,EAAE;gBAC9D,OAAO,EAAE,OAAO,CAAC,IAAI;aACtB,CAAC,CACH,EACD,MAAM,CAAC,MAAM,CACd;YACH,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CACjD,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CACnB,MAAM,CAAC,QAAQ,CAAC,gDAAgD,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CACrF,EACD,MAAM,CAAC,MAAM,CACd;SACJ,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,QAAQ,CAC7B,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,WAAW,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,EACpE,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EACrD,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,SAAS,CAAC,EAC1C,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;YAC7B,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;gBAC1C,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;gBAChB,MAAM,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,YAAY;aAC3C,CAAC;SACH,CAAC,EACF,MAAM,CACP,CAAC;QACF,MAAM,GAAG,GAAG,UAAU,CAAC,YAAY,CACjC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAC9D,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,GAAG,CAAC,qBAAqB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,IAAI,CACF,MAIC;QAED,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC;IAEQ,KAAK,CAAC,KAAK,CAAC,OAAgB;QACnC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChE,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK,WAAW;YAC/D,OAAO,KAAK,CAAC,GAAG,EAAE,kCAAkC,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,iBAAiB,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,MAAM,GACV,MAAM,KAAK,IAAI;YACb,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC9E,CAAC,CAAC,MAAM,CAAC,KAAK,CACV,MAAM,CAAC,GAAG,CAAC;gBACT,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACnC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;aAC7D,CAAC,EACF;gBACE,MAAM,EAAE,GAAG,EAAE,CACX,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,qDAAqD,CAAC,CAC1E;gBACH,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAC/B,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAC1C,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CACnD;aACJ,CACF,CAAC;QACR,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC1D,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;IAChE,CAAC;IAEQ,KAAK,CAAC,KAAK;QAClB,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAC/B,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,eAAgB,SAAQ,aAAkB;IAC5C,IAAI,CAA0C;IAEvD,YAAY,GAAuB,EAAE,GAAQ;QAC3C,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAChB,MAAM,OAAO,GAA+B;YAC1C,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CACvE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACrB,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAC/E,CACF;YACD,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CACf,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,CAAC,IAAI,EAAE,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;YAC1F,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CACf,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;SAClF,CAAC;QACF,MAAM,MAAM,GAA6B;YACvC,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE;gBACZ,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,IAAI,mBAAmB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC7E,OAAO,MAAM,CAAC,UAAU,CAAC;oBACvB,GAAG,EAAE,GAAG,EAAE,CACR,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,yCAAyC,IAAI,EAAE,CAAC;oBACpF,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,MAAM,CAAC,gBAAgB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;iBACnF,CAAC,CAAC,IAAI,CACL,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;oBAC1B,kFAAkF;oBAClF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;wBAAE,OAAO,MAAM,CAAC,WAAW,CAAC;oBACvD,IAAI,CAAC,QAAQ,CAAC,EAAE;wBAAE,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;oBAClF,OAAO,MAAM,CAAC,UAAU,CAAC;wBACvB,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE;wBAC1B,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,yBAAyB,CAAC;qBAC/C,CAAC,CAAC,IAAI,CACL,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CACtB,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE;wBAChC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,sCAAsC,CAAC,CAAC;wBACzE,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;qBAC1D,CAAC,CACH,CACF,CAAC;gBACJ,CAAC,CAAC,CACH,CAAC;YACJ,CAAC;SACF,CAAC;QACF,MAAM,GAAG,GAAG,UAAU,CAAC,YAAY,CACjC,cAAc,CAAC,IAAI,CACjB,UAAU,CAAC,cAAc,CACvB,KAAK,CAAC,QAAQ,CACZ,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,EACvC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,EACpC,MAAM,CACP,CACF,CACF,CACF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IAEQ,KAAK,CAAC,KAAK,CAAC,OAAgB;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,MAAM,CAAC,OAAoB;QAC/B,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;YACxB,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9F,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;CACF;AAED,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC,cAAc;CAC7B,CAAC,CAAC;AACH,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE,kBAAkB;CAC/B,CAAC,CAAC;AAEH,6EAA6E;AAC7E,MAAM,YAAY,GAAG,CAAC,OAAe,EAAE,OAAgB,EAAE,QAAkB,EAAE,EAAE,CAC7E,GAAG,CACD,MAAM,CAAC,OAAO,CAAC,6BAA6B,EAAE;IAC5C,OAAO;IACP,MAAM,EAAE,OAAO,CAAC,MAAM;IACtB,IAAI,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI;IAC/B,IAAI,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ;IACnC,MAAM,EAAE,QAAQ,CAAC,MAAM;CACxB,CAAC,CACH,CAAC;AAEJ,kFAAkF;AAClF,MAAM,OAAO,cAAe,SAAQ,gBAAiD;IAC1E,KAAK,CAAC,KAAK,CAAC,OAAgB;QACnC,MAAM,KAAK,GAAG,MAAM,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACpE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7F,MAAM,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAChD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,gBAAiB,SAAQ,gBAAmD;IAC9E,KAAK,CAAC,KAAK,CAAC,OAAgB;QACnC,MAAM,KAAK,GAAG,MAAM,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtE,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAC7E,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,MAAM,GAAG,CACP,MAAM,CAAC,UAAU,CAAC,gEAAgE,EAAE;gBAClF,OAAO,EAAE,KAAK,CAAC,QAAQ;gBACvB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,IAAI,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI;gBAC/B,IAAI,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ;aACpC,CAAC,CACH,CAAC;YACF,OAAO,KAAK,CACV,GAAG,EACH,gEAAgE,KAAK,CAAC,QAAQ,KAAK,CACpF,CAAC;QACJ,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QACtD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAED,yEAAyE;AACzE,MAAM,OAAO,eAAgB,SAAQ,gBAA4C;IACtE,KAAK,CAAC,KAAK,CAAC,OAAgB;QACnC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QACjD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAED,2FAA2F;AAC3F,MAAM,OAAO,cAAe,SAAQ,gBAA4C;IACrE,KAAK,CAAC,KAAK,CAAC,OAAgB;QACnC,MAAM,GAAG,CACP,MAAM,CAAC,UAAU,CAAC,2DAA2D,EAAE;YAC7E,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,IAAI,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI;SAChC,CAAC,CACH,CAAC;QACF,OAAO,KAAK,CAAC,GAAG,EAAE,8DAA8D,CAAC,CAAC;IACpF,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ # The image of a usine sandbox: Node.js 22, pnpm, Git, gh, Claude Code, and Codex, with the
2
+ # Sandbox SDK's file helper. The npm versions match infra/hetzner/cloud-init.yaml;
3
+ # test/image.test.ts checks them.
4
+ FROM docker.io/cloudflare/sandbox:1.0.3 AS sandbox-tools
5
+
6
+ FROM --platform=linux/amd64 docker.io/library/node:22-bookworm-slim
7
+
8
+ RUN apt-get update \
9
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
10
+ ca-certificates curl git gpg procps tini \
11
+ && install -d -m 0755 /etc/apt/keyrings \
12
+ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
13
+ -o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
14
+ && chmod 0644 /etc/apt/keyrings/githubcli-archive-keyring.gpg \
15
+ && echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
16
+ > /etc/apt/sources.list.d/github-cli.list \
17
+ && apt-get update \
18
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends gh \
19
+ && rm -rf /var/lib/apt/lists/*
20
+
21
+ RUN npm install -g pnpm@11.20.0 @anthropic-ai/claude-code@2.1.308 @openai/codex@0.159.2 \
22
+ && npm cache clean --force
23
+
24
+ # Files in @cloudflare/sandbox runs this helper to read the process files.
25
+ COPY --from=sandbox-tools /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim
26
+
27
+ # Records the platform's SIGTERM for the Worker; see init.sh.
28
+ COPY init.sh /usr/local/bin/usine-sandbox-init
29
+ RUN chmod 0755 /usr/local/bin/usine-sandbox-init
30
+
31
+ RUN useradd --create-home --shell /bin/bash usine \
32
+ && install -d -o usine -g usine /home/usine/.codex /home/usine/.usine/processes /home/usine/workspace
33
+ COPY --chown=usine:usine codex-config.toml /home/usine/.codex/config.toml
34
+
35
+ USER usine
36
+ WORKDIR /home/usine
37
+ ENV HOME=/home/usine
38
+
39
+ # tini reaps the commands that the Worker starts and passes the platform's SIGTERM to the init
40
+ # script. The container runs until the Worker destroys it, its inactivity timeout passes, or the
41
+ # platform stops it.
42
+ ENTRYPOINT ["/usr/bin/tini", "--"]
43
+ CMD ["/usr/local/bin/usine-sandbox-init"]
@@ -0,0 +1,10 @@
1
+ # Codex's model provider in a usine sandbox. The Worker's OpenAI outbound handler serves
2
+ # openai.usine.internal over plain HTTP and replaces the placeholder in OPENAI_API_KEY with the
3
+ # Attempt's API key.
4
+ model_provider = "usine"
5
+
6
+ [model_providers.usine]
7
+ name = "OpenAI through the usine sandbox Worker"
8
+ base_url = "http://openai.usine.internal/v1"
9
+ env_key = "OPENAI_API_KEY"
10
+ wire_api = "responses"
package/image/init.sh ADDED
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ # The init process of a usine sandbox, under tini. When the platform restarts the host or replaces
3
+ # the instance, it sends SIGTERM and stops the container up to 15 minutes later, and a new
4
+ # container starts with an empty disk. This script records that time in the drain marker, which
5
+ # the Worker reports to usine, and keeps the container running until the platform stops it or
6
+ # usine destroys it, so that the Attempt can salvage its checkout first.
7
+ set -u
8
+ marker="$HOME/.usine/draining"
9
+ drain() {
10
+ mkdir -p "$(dirname "$marker")"
11
+ date -u +%Y-%m-%dT%H:%M:%SZ >"$marker"
12
+ }
13
+ trap drain TERM
14
+ while :; do
15
+ sleep 3600 &
16
+ wait "$!" || true
17
+ done
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
2
  "name": "@usine/cloudflare",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "The Cloudflare Worker that runs each usine Attempt in its own sandbox, and the sandbox API the executor calls.",
5
+ "license": "AGPL-3.0-only",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/basaltbytes/usine.git",
9
+ "directory": "packages/cloudflare"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "!dist/tsconfig.tsbuildinfo",
14
+ "image",
15
+ "wrangler.jsonc"
16
+ ],
17
+ "type": "module",
18
+ "sideEffects": false,
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/api/api.d.ts",
22
+ "default": "./dist/api/api.js"
23
+ },
24
+ "./harness": {
25
+ "types": "./dist/api/harness.d.ts",
26
+ "default": "./dist/api/harness.js"
27
+ },
28
+ "./stream": {
29
+ "types": "./dist/api/stream.d.ts",
30
+ "default": "./dist/api/stream.js"
31
+ }
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@cloudflare/sandbox": "1.0.3",
38
+ "@usine/core": "0.5.0"
39
+ },
40
+ "devDependencies": {
41
+ "@cloudflare/workers-types": "5.20261019.1",
42
+ "@effect/vitest": "4.0.6",
43
+ "@types/node": "^26.6.5",
44
+ "effect": "4.0.6",
45
+ "typescript": "7.0.2",
46
+ "vitest": "5.0.5",
47
+ "wrangler": "4.151.2",
48
+ "@usine/tsconfig": "0.0.0"
49
+ },
50
+ "peerDependencies": {
51
+ "effect": "4.0.6"
52
+ },
53
+ "engines": {
54
+ "node": ">=22"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc -b",
58
+ "check": "tsc -p tsconfig.check.json",
59
+ "test": "vitest run",
60
+ "deploy": "wrangler deploy"
61
+ }
6
62
  }