@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.
- package/LICENSE +661 -0
- package/README.md +95 -2
- package/dist/access.d.ts +12 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +32 -0
- package/dist/access.js.map +1 -0
- package/dist/api/api.d.ts +215 -0
- package/dist/api/api.d.ts.map +1 -0
- package/dist/api/api.js +180 -0
- package/dist/api/api.js.map +1 -0
- package/dist/api/harness.d.ts +49 -0
- package/dist/api/harness.d.ts.map +1 -0
- package/dist/api/harness.js +57 -0
- package/dist/api/harness.js.map +1 -0
- package/dist/api/names.d.ts +28 -0
- package/dist/api/names.d.ts.map +1 -0
- package/dist/api/names.js +31 -0
- package/dist/api/names.js.map +1 -0
- package/dist/api/stream.d.ts +65 -0
- package/dist/api/stream.d.ts.map +1 -0
- package/dist/api/stream.js +70 -0
- package/dist/api/stream.js.map +1 -0
- package/dist/registry.d.ts +28 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +45 -0
- package/dist/registry.js.map +1 -0
- package/dist/sandbox/outbound.d.ts +53 -0
- package/dist/sandbox/outbound.d.ts.map +1 -0
- package/dist/sandbox/outbound.js +109 -0
- package/dist/sandbox/outbound.js.map +1 -0
- package/dist/sandbox/ports.d.ts +99 -0
- package/dist/sandbox/ports.d.ts.map +1 -0
- package/dist/sandbox/ports.js +66 -0
- package/dist/sandbox/ports.js.map +1 -0
- package/dist/sandbox/process.d.ts +55 -0
- package/dist/sandbox/process.d.ts.map +1 -0
- package/dist/sandbox/process.js +172 -0
- package/dist/sandbox/process.js.map +1 -0
- package/dist/sandbox/sandbox.d.ts +36 -0
- package/dist/sandbox/sandbox.d.ts.map +1 -0
- package/dist/sandbox/sandbox.js +387 -0
- package/dist/sandbox/sandbox.js.map +1 -0
- package/dist/sandbox/stream.d.ts +26 -0
- package/dist/sandbox/stream.d.ts.map +1 -0
- package/dist/sandbox/stream.js +167 -0
- package/dist/sandbox/stream.js.map +1 -0
- package/dist/worker.d.ts +77 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +362 -0
- package/dist/worker.js.map +1 -0
- package/image/Dockerfile +43 -0
- package/image/codex-config.toml +10 -0
- package/image/init.sh +17 -0
- package/package.json +59 -3
- package/wrangler.jsonc +32 -0
package/README.md
CHANGED
|
@@ -1,3 +1,96 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @usine/cloudflare
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Cloudflare Worker that runs each usine Attempt in its own sandbox, for `cloudflare` Workers in `usine.yaml` ([#551](https://github.com/basaltbytes/usine/issues/551)). You deploy it to your Cloudflare account; usine's executor calls it over HTTPS with a Cloudflare Access service token.
|
|
4
|
+
|
|
5
|
+
Each sandbox is a Durable Object that owns one container, started from an image in the Worker's configuration with the [Durable Object Container API](https://developers.cloudflare.com/containers/api/durable-object-container/) and the [Sandbox SDK 1.0](https://developers.cloudflare.com/sandbox/). The container has no internet access. Its requests for GitHub, the LLM providers, and the allowlisted hosts go to outbound handlers in the Worker, which hold the Attempt's credentials, so the container never receives them:
|
|
6
|
+
|
|
7
|
+
| Host in the sandbox | Handler | Upstream |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `github.com`, `api.github.com` over HTTPS | Adds the Attempt's installation token | GitHub |
|
|
10
|
+
| `anthropic.usine.internal` over HTTP | Replaces the placeholder key with the Anthropic API key | `https://api.anthropic.com` |
|
|
11
|
+
| `openai.usine.internal` over HTTP | Replaces the placeholder key with the OpenAI API key | `https://api.openai.com` |
|
|
12
|
+
| Each host in `ALLOWED_HOSTS` over HTTPS | Forwards the request unchanged | The host |
|
|
13
|
+
|
|
14
|
+
While a sandbox has no installation token or no key for a provider, such as a sandbox that the executor keeps after its Attempt, the handler for that host refuses every request with status 403. `ALLOWED_HOSTS` can't name a GitHub or provider host.
|
|
15
|
+
|
|
16
|
+
A provider handler adds the key only to the requests that Claude Code and Codex send: Anthropic's `POST /v1/messages`, `POST /v1/messages/count_tokens`, and `GET /v1/models`, and OpenAI's `POST /v1/responses`, `POST /v1/responses/compact`, and `GET /v1/models`. These are the paths that the Gateway's `llm.<domain>` serves ([ADR 0027](../../docs/adr/0027-gateway-credential-proxy.md)), each with the method that the harnesses use. The handler refuses every other method and path, such as the files or organization administration APIs, with status 404, before it adds the key.
|
|
17
|
+
|
|
18
|
+
HTTPS interception needs the container to trust Cloudflare's container certificate authority. The executor sets `GIT_SSL_CAINFO`, `CURL_CA_BUNDLE`, `SSL_CERT_FILE`, and `NODE_EXTRA_CA_CERTS` to it for every command, as Cloudflare's coding-agent runner does. Claude Code and Codex use plain HTTP to their handlers, with a placeholder key.
|
|
19
|
+
|
|
20
|
+
## Entry points
|
|
21
|
+
|
|
22
|
+
| Import | Contents |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `@usine/cloudflare` | `SandboxApi`, the JSON API contract for `HttpApiClient.make` in the executor, with its records and errors, `sandboxName`, the Access header names, and the exec and attach stream paths. |
|
|
25
|
+
| `@usine/cloudflare/stream` | The messages of the exec and attach WebSocket streams. |
|
|
26
|
+
| `@usine/cloudflare/harness` | The environment that points git, gh, Claude Code, and Codex at the outbound handlers, and the sandbox paths. |
|
|
27
|
+
| `src/worker.ts` | The Worker that `wrangler.jsonc` deploys: `AttemptSandbox`, `SandboxRegistry`, and the outbound handlers. It runs only on Cloudflare. |
|
|
28
|
+
|
|
29
|
+
## The sandbox API
|
|
30
|
+
|
|
31
|
+
Every request needs the `CF-Access-Client-Id` and `CF-Access-Client-Secret` headers of the service token that the Worker is configured with. The Worker refuses any other request with status 401.
|
|
32
|
+
|
|
33
|
+
| Route | Purpose |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `GET /v1/health` | The protocol version and the image names. |
|
|
36
|
+
| `GET /v1/sandboxes?owner=<instance>.<worker>` | The sandboxes of one usine instance's Worker, each with whether its container runs. |
|
|
37
|
+
| `PUT /v1/sandboxes/<name>` | Creates the sandbox of an Attempt, or returns it if it already runs that Attempt. |
|
|
38
|
+
| `GET /v1/sandboxes/<name>` | One sandbox. |
|
|
39
|
+
| `PUT /v1/sandboxes/<name>/credentials` | Replaces the handlers' credentials, such as a renewed installation token, or revokes them with none. |
|
|
40
|
+
| `POST /v1/sandboxes/<name>/hold` | Keeps the container running past its lease until the sandbox is destroyed. |
|
|
41
|
+
| `POST /v1/sandboxes/<name>/stop` | Stops every command that still runs in the sandbox, and every process that a command left running. |
|
|
42
|
+
| `DELETE /v1/sandboxes/<name>` | Destroys the container and forgets the sandbox. |
|
|
43
|
+
| `GET /v1/sandboxes/<name>/processes/<process>` | A command's state and output size, from its process files. |
|
|
44
|
+
| `GET /v1/sandboxes/<name>/exec` (WebSocket) | Runs one command with `sh -c`, with its stdin, stdout, stderr, and exit code on the stream. |
|
|
45
|
+
| `GET /v1/sandboxes/<name>/processes/<process>/attach?stdout=<n>&stderr=<n>` (WebSocket) | Resumes a command's output after the given byte offsets, then sends its exit code. |
|
|
46
|
+
|
|
47
|
+
Each command runs as the `usine` user in `/home/usine`, with `HOME` set, since the platform passes a command only the image's `PATH`. It runs in its own session, with its process ID, output, and exit code in files under `/home/usine/.usine/processes/<process>`. If the stream that started a command closes, the command's stdin closes, and its output keeps going to the files, so a later request can attach or read its state. The Worker closes the stream without an exit code when it can't read the command's output or exit code from the container, so the client attaches from the bytes it received.
|
|
48
|
+
|
|
49
|
+
A command, and every process that it starts, gets `USINE_PROCESS`, the command's process directory, in its environment. A stop sends TERM to every process in each command's session, and in the session of each process with `USINE_PROCESS`, so it includes what a command left running after it exited. Five seconds later, it sends KILL to the processes that still run. It fails if a process still runs five seconds after that, so usine doesn't salvage or reattach a checkout that a process can still change. A process that leaves its command's session and also drops `USINE_PROCESS` from its environment isn't stopped.
|
|
50
|
+
|
|
51
|
+
Each set of credentials carries `issuedAt`, the time usine issued it. A sandbox applies one create or credential change at a time, and refuses credentials issued before the ones it holds with status 409 and `StaleCredentials`. A renewal that's still on its way when usine revokes the credentials therefore can't restore them.
|
|
52
|
+
|
|
53
|
+
A keepalive alarm keeps the container running for two hours after the last create, credential renewal, or command, then lets the platform stop it after ten minutes of inactivity. A held sandbox keeps running until it's destroyed: usine holds the sandbox of a checkout that it keeps, such as one with unpushed work. A stopped container loses its disk.
|
|
54
|
+
|
|
55
|
+
When Cloudflare restarts a host or replaces an instance, it sends the container SIGTERM and stops it up to 15 minutes later. The image's init (`image/init.sh`) writes the time to `/home/usine/.usine/draining` and keeps the container running, and each sandbox summary reports `draining` while the file exists. The executor then drains the Attempt: it salvages the checkout and destroys the sandbox before the container stops.
|
|
56
|
+
|
|
57
|
+
## Deploy
|
|
58
|
+
|
|
59
|
+
You need a Cloudflare account with Workers Paid, which Containers require, and [Wrangler](https://developers.cloudflare.com/workers/wrangler/) logged in to it. The image is linux/amd64; Wrangler builds it with your local Docker.
|
|
60
|
+
|
|
61
|
+
1. In a usine checkout, build the Worker:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
pnpm install
|
|
65
|
+
pnpm --filter @usine/cloudflare... build
|
|
66
|
+
cd packages/cloudflare
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. In `wrangler.jsonc`, set `name` to your Worker's name, and set `ALLOWED_HOSTS` to the package registries your repositories install from.
|
|
70
|
+
|
|
71
|
+
3. Create a Cloudflare Access service token in Zero Trust under **Access** > **Service credentials** > **Service Tokens**, and store its client ID and client secret on the Worker:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
npx wrangler secret put ACCESS_CLIENT_ID
|
|
75
|
+
npx wrangler secret put ACCESS_CLIENT_SECRET
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
To also check the token at Cloudflare's edge, add an Access application for the Worker's hostname with a Service Auth policy that allows that token.
|
|
79
|
+
|
|
80
|
+
4. Deploy the Worker and its image:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
npx wrangler deploy
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The containers use the `durable_object` scheduling policy, which Wrangler can't undo for the application.
|
|
87
|
+
|
|
88
|
+
5. Add the Worker to `usine.yaml`, and set the token and the provider keys in the orchestrator's environment. See [Run attempts on Cloudflare](https://docs.usine.dev/guides/cloudflare-sandboxes/).
|
|
89
|
+
|
|
90
|
+
## EU placement
|
|
91
|
+
|
|
92
|
+
The Sandbox SDK 1.0 needs the `durable_object` scheduling policy, which rejects placement constraints, including `jurisdiction: "eu"`. Containers can't be kept in the EU until Cloudflare adds placement to that policy.
|
|
93
|
+
|
|
94
|
+
## Develop
|
|
95
|
+
|
|
96
|
+
`pnpm test` runs unit tests in Node: the routes and streams against fake ports, and the process file scripts with the local `sh`. The Durable Object's container calls need Cloudflare's container runtime, so no test runs the Worker itself.
|
package/dist/access.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Effect, Redacted } from "effect";
|
|
2
|
+
/** The Cloudflare Access service token that the Worker accepts. */
|
|
3
|
+
export interface AccessToken {
|
|
4
|
+
readonly clientId: Redacted.Redacted<string>;
|
|
5
|
+
readonly clientSecret: Redacted.Redacted<string>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Returns whether `headers` carry `token` in the Access service token headers. A request
|
|
9
|
+
* without either header is refused.
|
|
10
|
+
*/
|
|
11
|
+
export declare const accessGranted: (headers: Headers, token: AccessToken) => Effect.Effect<boolean, never, never>;
|
|
12
|
+
//# sourceMappingURL=access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAG1C,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;CAClD;AAiBD;;;GAGG;AACH,eAAO,MAAM,aAAa,gFAiBxB,CAAC"}
|
package/dist/access.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Effect, Redacted } from "effect";
|
|
2
|
+
import { ACCESS_CLIENT_ID_HEADER, ACCESS_CLIENT_SECRET_HEADER } from "./api/api.js";
|
|
3
|
+
const encoder = new TextEncoder();
|
|
4
|
+
const digest = (value) => Effect.promise(() => crypto.subtle.digest("SHA-256", encoder.encode(value))).pipe(Effect.map((bytes) => new Uint8Array(bytes)));
|
|
5
|
+
/** Returns whether two digests are equal, in constant time. */
|
|
6
|
+
const sameDigest = (left, right) => {
|
|
7
|
+
let difference = left.length ^ right.length;
|
|
8
|
+
for (let index = 0; index < Math.min(left.length, right.length); index++)
|
|
9
|
+
difference |= (left[index] ?? 0) ^ (right[index] ?? 0);
|
|
10
|
+
return difference === 0;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Returns whether `headers` carry `token` in the Access service token headers. A request
|
|
14
|
+
* without either header is refused.
|
|
15
|
+
*/
|
|
16
|
+
export const accessGranted = Effect.fn("SandboxWorker.accessGranted")(function* (headers, token) {
|
|
17
|
+
const clientId = headers.get(ACCESS_CLIENT_ID_HEADER);
|
|
18
|
+
const clientSecret = headers.get(ACCESS_CLIENT_SECRET_HEADER);
|
|
19
|
+
if (clientId === null || clientSecret === null)
|
|
20
|
+
return false;
|
|
21
|
+
const [presentedId, presentedSecret, expectedId, expectedSecret] = yield* Effect.all([
|
|
22
|
+
digest(clientId),
|
|
23
|
+
digest(clientSecret),
|
|
24
|
+
digest(Redacted.value(token.clientId)),
|
|
25
|
+
digest(Redacted.value(token.clientSecret)),
|
|
26
|
+
]);
|
|
27
|
+
// Both comparisons run, so the response time doesn't tell which header was wrong.
|
|
28
|
+
const idMatches = sameDigest(presentedId, expectedId);
|
|
29
|
+
const secretMatches = sameDigest(presentedSecret, expectedSecret);
|
|
30
|
+
return idMatches && secretMatches;
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAQpF,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAC/B,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAC/E,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAC7C,CAAC;AAEJ,+DAA+D;AAC/D,MAAM,UAAU,GAAG,CAAC,IAAgB,EAAE,KAAiB,EAAW,EAAE;IAClE,IAAI,UAAU,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE;QACtE,UAAU,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,OAAO,UAAU,KAAK,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC,6BAA6B,CAAC,CAAC,QAAQ,CAAC,EAC7E,OAAgB,EAChB,KAAkB;IAElB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IAC9D,IAAI,QAAQ,KAAK,IAAI,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC7D,MAAM,CAAC,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;QACnF,MAAM,CAAC,QAAQ,CAAC;QAChB,MAAM,CAAC,YAAY,CAAC;QACpB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;KAC3C,CAAC,CAAC;IACH,kFAAkF;IAClF,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,UAAU,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;IAClE,OAAO,SAAS,IAAI,aAAa,CAAC;AACpC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { AttemptId } from "@usine/core/attempt";
|
|
2
|
+
import { type WorkerName } from "@usine/core/worker";
|
|
3
|
+
import { Schema } from "effect";
|
|
4
|
+
import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/http-api";
|
|
5
|
+
import { SandboxName, SandboxOwner } from "./names.ts";
|
|
6
|
+
export { ACCESS_CLIENT_ID_HEADER, ACCESS_CLIENT_SECRET_HEADER, attachPath, execPath, ProcessId, SandboxName, SandboxOwner, } from "./names.ts";
|
|
7
|
+
/** The version of the sandbox API and the exec stream, reported by `/v1/health`. */
|
|
8
|
+
export declare const SANDBOX_PROTOCOL = 1;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the owner of the sandboxes that `instance` runs for `worker`. Throws if `instance` isn't
|
|
11
|
+
* in kebab case.
|
|
12
|
+
*/
|
|
13
|
+
export declare const sandboxOwner: (instance: string, worker: WorkerName) => SandboxOwner;
|
|
14
|
+
/** Returns the name of the sandbox that runs `attempt` for `owner`. */
|
|
15
|
+
export declare const sandboxName: (owner: SandboxOwner, attempt: AttemptId) => SandboxName;
|
|
16
|
+
declare const ApiKeyCredential_base: Schema.Class<ApiKeyCredential, Schema.TaggedStruct<"ApiKey", {
|
|
17
|
+
readonly key: Schema.Redacted<Schema.NonEmptyString>;
|
|
18
|
+
}>, {}>;
|
|
19
|
+
/** An API key that the provider's outbound handler sends in place of the sandbox's placeholder. */
|
|
20
|
+
export declare class ApiKeyCredential extends ApiKeyCredential_base {
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* How a provider's outbound handler authenticates the sandbox's requests. Only API keys exist;
|
|
24
|
+
* a brokered login (#549) would be another member.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ProviderCredential: Schema.Union<readonly [typeof ApiKeyCredential]>;
|
|
27
|
+
export type ProviderCredential = typeof ProviderCredential.Type;
|
|
28
|
+
declare const ProviderGrant_base: Schema.Class<ProviderGrant, Schema.Struct<{
|
|
29
|
+
readonly provider: Schema.Literals<readonly ["anthropic", "openai"]>;
|
|
30
|
+
readonly credential: Schema.Union<readonly [typeof ApiKeyCredential]>;
|
|
31
|
+
}>, {}>;
|
|
32
|
+
export declare class ProviderGrant extends ProviderGrant_base {
|
|
33
|
+
}
|
|
34
|
+
declare const SandboxCredentials_base: Schema.Class<SandboxCredentials, Schema.Struct<{
|
|
35
|
+
/**
|
|
36
|
+
* When usine issued these credentials, later than any credentials it issued before. The sandbox
|
|
37
|
+
* refuses credentials issued before the ones it holds, so a delayed request can't restore
|
|
38
|
+
* credentials that a later request replaced or revoked.
|
|
39
|
+
*/
|
|
40
|
+
readonly issuedAt: Schema.DateTimeUtc;
|
|
41
|
+
/** The Attempt's installation token, added to requests for `github.com` and `api.github.com`. */
|
|
42
|
+
readonly github: Schema.OptionFromOptionalKey<Schema.Redacted<Schema.NonEmptyString>>;
|
|
43
|
+
readonly providers: Schema.$Array<typeof ProviderGrant>;
|
|
44
|
+
}>, {}>;
|
|
45
|
+
/**
|
|
46
|
+
* The secrets that a sandbox's outbound handlers hold. The container never receives them. A host
|
|
47
|
+
* without a credential is refused, so replacing the credentials with none revokes them.
|
|
48
|
+
*/
|
|
49
|
+
export declare class SandboxCredentials extends SandboxCredentials_base {
|
|
50
|
+
}
|
|
51
|
+
declare const CreateSandbox_base: Schema.Class<CreateSandbox, Schema.Struct<{
|
|
52
|
+
readonly owner: Schema.brand<Schema.String, "SandboxOwner">;
|
|
53
|
+
readonly attempt: Schema.brand<Schema.NonEmptyString, "AttemptId">;
|
|
54
|
+
readonly image: Schema.brand<Schema.String, "CloudflareImageName">;
|
|
55
|
+
readonly instanceType: Schema.Literals<readonly ["lite", "basic", "standard-1", "standard-2", "standard-3", "standard-4"]>;
|
|
56
|
+
readonly credentials: typeof SandboxCredentials;
|
|
57
|
+
}>, {}>;
|
|
58
|
+
/** A request to create the sandbox for one Attempt, or to return it if it already runs that Attempt. */
|
|
59
|
+
export declare class CreateSandbox extends CreateSandbox_base {
|
|
60
|
+
}
|
|
61
|
+
declare const SandboxSummary_base: Schema.Class<SandboxSummary, Schema.Struct<{
|
|
62
|
+
readonly name: Schema.brand<Schema.String, "SandboxName">;
|
|
63
|
+
readonly owner: Schema.brand<Schema.String, "SandboxOwner">;
|
|
64
|
+
readonly attempt: Schema.brand<Schema.NonEmptyString, "AttemptId">;
|
|
65
|
+
readonly image: Schema.brand<Schema.String, "CloudflareImageName">;
|
|
66
|
+
readonly instanceType: Schema.Literals<readonly ["lite", "basic", "standard-1", "standard-2", "standard-3", "standard-4"]>;
|
|
67
|
+
readonly createdAt: Schema.DateTimeUtc;
|
|
68
|
+
/** Whether the container runs. A container that stopped lost its disk. */
|
|
69
|
+
readonly running: Schema.Boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Whether usine holds the sandbox: the keepalive alarm keeps its container running past the
|
|
72
|
+
* lease, until usine reconciles or destroys it, because it keeps a checkout.
|
|
73
|
+
*/
|
|
74
|
+
readonly held: Schema.Boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Whether the platform sent the container SIGTERM. It stops the container within 15 minutes, and
|
|
77
|
+
* a container that starts again has an empty disk.
|
|
78
|
+
*/
|
|
79
|
+
readonly draining: Schema.Boolean;
|
|
80
|
+
}>, {}>;
|
|
81
|
+
export declare class SandboxSummary extends SandboxSummary_base {
|
|
82
|
+
}
|
|
83
|
+
declare const SandboxList_base: Schema.Class<SandboxList, Schema.Struct<{
|
|
84
|
+
readonly sandboxes: Schema.$Array<typeof SandboxSummary>;
|
|
85
|
+
}>, {}>;
|
|
86
|
+
export declare class SandboxList extends SandboxList_base {
|
|
87
|
+
}
|
|
88
|
+
declare const HealthReport_base: Schema.Class<HealthReport, Schema.Struct<{
|
|
89
|
+
readonly protocol: Schema.Literal<1>;
|
|
90
|
+
/** The names in the Worker's `containers[].images`. */
|
|
91
|
+
readonly images: Schema.$Array<Schema.brand<Schema.String, "CloudflareImageName">>;
|
|
92
|
+
}>, {}>;
|
|
93
|
+
export declare class HealthReport extends HealthReport_base {
|
|
94
|
+
}
|
|
95
|
+
declare const Stopped_base: Schema.Class<Stopped, Schema.Struct<{
|
|
96
|
+
readonly stopped: Schema.Int;
|
|
97
|
+
}>, {}>;
|
|
98
|
+
/** The response to a stop: the number of commands that were still running. */
|
|
99
|
+
export declare class Stopped extends Stopped_base {
|
|
100
|
+
}
|
|
101
|
+
declare const Destroyed_base: Schema.Class<Destroyed, Schema.Struct<{
|
|
102
|
+
readonly destroyed: Schema.Boolean;
|
|
103
|
+
}>, {}>;
|
|
104
|
+
/** The response to a destroy. `destroyed` is whether the sandbox existed. */
|
|
105
|
+
export declare class Destroyed extends Destroyed_base {
|
|
106
|
+
}
|
|
107
|
+
declare const ProcessRunning_base: Schema.Class<ProcessRunning, Schema.TaggedStruct<"Running", {}>, {}>;
|
|
108
|
+
export declare class ProcessRunning extends ProcessRunning_base {
|
|
109
|
+
}
|
|
110
|
+
declare const ProcessExited_base: Schema.Class<ProcessExited, Schema.TaggedStruct<"Exited", {
|
|
111
|
+
readonly exitCode: Schema.Int;
|
|
112
|
+
}>, {}>;
|
|
113
|
+
export declare class ProcessExited extends ProcessExited_base {
|
|
114
|
+
}
|
|
115
|
+
export declare const ProcessState: Schema.Union<readonly [typeof ProcessRunning, typeof ProcessExited]>;
|
|
116
|
+
export type ProcessState = typeof ProcessState.Type;
|
|
117
|
+
declare const ProcessStatus_base: Schema.Class<ProcessStatus, Schema.Struct<{
|
|
118
|
+
readonly process: Schema.brand<Schema.String, "ProcessId">;
|
|
119
|
+
/** The command's process ID in the container. */
|
|
120
|
+
readonly pid: Schema.Int;
|
|
121
|
+
readonly state: Schema.Union<readonly [typeof ProcessRunning, typeof ProcessExited]>;
|
|
122
|
+
readonly stdoutBytes: Schema.Int;
|
|
123
|
+
readonly stderrBytes: Schema.Int;
|
|
124
|
+
}>, {}>;
|
|
125
|
+
/** A command's state and output size, read from its process files. */
|
|
126
|
+
export declare class ProcessStatus extends ProcessStatus_base {
|
|
127
|
+
}
|
|
128
|
+
declare const SandboxNotFound_base: Schema.Class<SandboxNotFound, Schema.TaggedStruct<"SandboxNotFound", {
|
|
129
|
+
readonly name: Schema.brand<Schema.String, "SandboxName">;
|
|
130
|
+
}>, import("effect/Cause").YieldableError>;
|
|
131
|
+
export declare class SandboxNotFound extends SandboxNotFound_base {
|
|
132
|
+
}
|
|
133
|
+
declare const SandboxConflict_base: Schema.Class<SandboxConflict, Schema.TaggedStruct<"SandboxConflict", {
|
|
134
|
+
readonly name: Schema.brand<Schema.String, "SandboxName">;
|
|
135
|
+
readonly owner: Schema.brand<Schema.String, "SandboxOwner">;
|
|
136
|
+
readonly attempt: Schema.brand<Schema.NonEmptyString, "AttemptId">;
|
|
137
|
+
}>, import("effect/Cause").YieldableError>;
|
|
138
|
+
/** The sandbox exists for another owner or Attempt. */
|
|
139
|
+
export declare class SandboxConflict extends SandboxConflict_base {
|
|
140
|
+
}
|
|
141
|
+
declare const StaleCredentials_base: Schema.Class<StaleCredentials, Schema.TaggedStruct<"StaleCredentials", {
|
|
142
|
+
readonly name: Schema.brand<Schema.String, "SandboxName">;
|
|
143
|
+
readonly issuedAt: Schema.DateTimeUtc;
|
|
144
|
+
readonly current: Schema.DateTimeUtc;
|
|
145
|
+
}>, import("effect/Cause").YieldableError>;
|
|
146
|
+
/** The sandbox holds credentials issued at `current`, after the request's, which it refused. */
|
|
147
|
+
export declare class StaleCredentials extends StaleCredentials_base {
|
|
148
|
+
}
|
|
149
|
+
declare const ImageNotFound_base: Schema.Class<ImageNotFound, Schema.TaggedStruct<"ImageNotFound", {
|
|
150
|
+
readonly image: Schema.brand<Schema.String, "CloudflareImageName">;
|
|
151
|
+
readonly images: Schema.$Array<Schema.String>;
|
|
152
|
+
}>, import("effect/Cause").YieldableError>;
|
|
153
|
+
/** The Worker's `containers[].images` has no image with this name. */
|
|
154
|
+
export declare class ImageNotFound extends ImageNotFound_base {
|
|
155
|
+
}
|
|
156
|
+
declare const ProcessNotFound_base: Schema.Class<ProcessNotFound, Schema.TaggedStruct<"ProcessNotFound", {
|
|
157
|
+
readonly name: Schema.brand<Schema.String, "SandboxName">;
|
|
158
|
+
readonly process: Schema.brand<Schema.String, "ProcessId">;
|
|
159
|
+
}>, import("effect/Cause").YieldableError>;
|
|
160
|
+
export declare class ProcessNotFound extends ProcessNotFound_base {
|
|
161
|
+
}
|
|
162
|
+
declare const ContainerUnavailable_base: Schema.Class<ContainerUnavailable, Schema.TaggedStruct<"ContainerUnavailable", {
|
|
163
|
+
readonly name: Schema.brand<Schema.String, "SandboxName">;
|
|
164
|
+
readonly message: Schema.String;
|
|
165
|
+
}>, import("effect/Cause").YieldableError>;
|
|
166
|
+
/** The container didn't start, stopped, or failed a command. `message` comes from the runtime. */
|
|
167
|
+
export declare class ContainerUnavailable extends ContainerUnavailable_base {
|
|
168
|
+
}
|
|
169
|
+
/** `GET /v1/health`, served by any sandbox Durable Object. */
|
|
170
|
+
export declare const healthGroup: HttpApiGroup.HttpApiGroup<"health", HttpApiEndpoint.HttpApiEndpoint<"health", "GET", "/v1/health", never, never, never, never, Schema.toCodecJson<typeof HealthReport>, never, never, never>, false>;
|
|
171
|
+
/** `GET /v1/sandboxes`, served by the registry Durable Object. */
|
|
172
|
+
export declare const registryGroup: HttpApiGroup.HttpApiGroup<"registry", HttpApiEndpoint.HttpApiEndpoint<"list", "GET", "/v1/sandboxes", never, Schema.toCodecStringTree<Schema.Struct<{
|
|
173
|
+
owner: Schema.brand<Schema.String, "SandboxOwner">;
|
|
174
|
+
}>>, never, never, Schema.toCodecJson<typeof SandboxList>, never, never, never>, false>;
|
|
175
|
+
/** The routes of one sandbox, served by its Durable Object. */
|
|
176
|
+
export declare const sandboxGroup: HttpApiGroup.HttpApiGroup<"sandbox", HttpApiEndpoint.HttpApiEndpoint<"create", "PUT", "/v1/sandboxes/:name", Schema.toCodecStringTree<Schema.Struct<{
|
|
177
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
178
|
+
}>>, never, Schema.toCodecJson<typeof CreateSandbox>, never, Schema.toCodecJson<typeof SandboxSummary>, Schema.toCodecJson<typeof SandboxConflict | typeof StaleCredentials | typeof ImageNotFound | typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"credentials", "PUT", "/v1/sandboxes/:name/credentials", Schema.toCodecStringTree<Schema.Struct<{
|
|
179
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
180
|
+
}>>, never, Schema.toCodecJson<typeof SandboxCredentials>, never, Schema.toCodecJson<typeof SandboxSummary>, Schema.toCodecJson<typeof SandboxNotFound | typeof StaleCredentials | typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"destroy", "DELETE", "/v1/sandboxes/:name", Schema.toCodecStringTree<Schema.Struct<{
|
|
181
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
182
|
+
}>>, never, never, never, Schema.toCodecJson<typeof Destroyed>, Schema.toCodecJson<typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"get", "GET", "/v1/sandboxes/:name", Schema.toCodecStringTree<Schema.Struct<{
|
|
183
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
184
|
+
}>>, never, never, never, Schema.toCodecJson<typeof SandboxSummary>, Schema.toCodecJson<typeof SandboxNotFound>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"hold", "POST", "/v1/sandboxes/:name/hold", Schema.toCodecStringTree<Schema.Struct<{
|
|
185
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
186
|
+
}>>, never, never, never, Schema.toCodecJson<typeof SandboxSummary>, Schema.toCodecJson<typeof SandboxNotFound | typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"process", "GET", "/v1/sandboxes/:name/processes/:process", Schema.toCodecStringTree<Schema.Struct<{
|
|
187
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
188
|
+
process: Schema.brand<Schema.String, "ProcessId">;
|
|
189
|
+
}>>, never, never, never, Schema.toCodecJson<typeof ProcessStatus>, Schema.toCodecJson<typeof SandboxNotFound | typeof ProcessNotFound | typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"stop", "POST", "/v1/sandboxes/:name/stop", Schema.toCodecStringTree<Schema.Struct<{
|
|
190
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
191
|
+
}>>, never, never, never, Schema.toCodecJson<typeof Stopped>, Schema.toCodecJson<typeof SandboxNotFound | typeof ContainerUnavailable>, never, never>, false>;
|
|
192
|
+
/**
|
|
193
|
+
* The JSON API of the Worker that usine ships (`@usine/cloudflare/worker`). Every request carries
|
|
194
|
+
* the Access service token headers. Commands run through the exec stream at `execPath`, not
|
|
195
|
+
* through this API.
|
|
196
|
+
*/
|
|
197
|
+
export declare const SandboxApi: HttpApi.HttpApi<"SandboxApi", HttpApiGroup.HttpApiGroup<"health", HttpApiEndpoint.HttpApiEndpoint<"health", "GET", "/v1/health", never, never, never, never, Schema.toCodecJson<typeof HealthReport>, never, never, never>, false> | HttpApiGroup.HttpApiGroup<"registry", HttpApiEndpoint.HttpApiEndpoint<"list", "GET", "/v1/sandboxes", never, Schema.toCodecStringTree<Schema.Struct<{
|
|
198
|
+
owner: Schema.brand<Schema.String, "SandboxOwner">;
|
|
199
|
+
}>>, never, never, Schema.toCodecJson<typeof SandboxList>, never, never, never>, false> | HttpApiGroup.HttpApiGroup<"sandbox", HttpApiEndpoint.HttpApiEndpoint<"create", "PUT", "/v1/sandboxes/:name", Schema.toCodecStringTree<Schema.Struct<{
|
|
200
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
201
|
+
}>>, never, Schema.toCodecJson<typeof CreateSandbox>, never, Schema.toCodecJson<typeof SandboxSummary>, Schema.toCodecJson<typeof SandboxConflict | typeof StaleCredentials | typeof ImageNotFound | typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"credentials", "PUT", "/v1/sandboxes/:name/credentials", Schema.toCodecStringTree<Schema.Struct<{
|
|
202
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
203
|
+
}>>, never, Schema.toCodecJson<typeof SandboxCredentials>, never, Schema.toCodecJson<typeof SandboxSummary>, Schema.toCodecJson<typeof SandboxNotFound | typeof StaleCredentials | typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"destroy", "DELETE", "/v1/sandboxes/:name", Schema.toCodecStringTree<Schema.Struct<{
|
|
204
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
205
|
+
}>>, never, never, never, Schema.toCodecJson<typeof Destroyed>, Schema.toCodecJson<typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"get", "GET", "/v1/sandboxes/:name", Schema.toCodecStringTree<Schema.Struct<{
|
|
206
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
207
|
+
}>>, never, never, never, Schema.toCodecJson<typeof SandboxSummary>, Schema.toCodecJson<typeof SandboxNotFound>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"hold", "POST", "/v1/sandboxes/:name/hold", Schema.toCodecStringTree<Schema.Struct<{
|
|
208
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
209
|
+
}>>, never, never, never, Schema.toCodecJson<typeof SandboxSummary>, Schema.toCodecJson<typeof SandboxNotFound | typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"process", "GET", "/v1/sandboxes/:name/processes/:process", Schema.toCodecStringTree<Schema.Struct<{
|
|
210
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
211
|
+
process: Schema.brand<Schema.String, "ProcessId">;
|
|
212
|
+
}>>, never, never, never, Schema.toCodecJson<typeof ProcessStatus>, Schema.toCodecJson<typeof SandboxNotFound | typeof ProcessNotFound | typeof ContainerUnavailable>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"stop", "POST", "/v1/sandboxes/:name/stop", Schema.toCodecStringTree<Schema.Struct<{
|
|
213
|
+
name: Schema.brand<Schema.String, "SandboxName">;
|
|
214
|
+
}>>, never, never, never, Schema.toCodecJson<typeof Stopped>, Schema.toCodecJson<typeof SandboxNotFound | typeof ContainerUnavailable>, never, never>, false>>;
|
|
215
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAa,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAElE,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,UAAU,EACV,QAAQ,EACR,SAAS,EACT,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,YAAY,aAAc,MAAM,UAAU,UAAU,KAAG,YACxB,CAAC;AAE7C,uEAAuE;AACvE,eAAO,MAAM,WAAW,UAAW,YAAY,WAAW,SAAS,KAAG,WAChB,CAAC;;;;AAEvD,mGAAmG;AACnG,qBAAa,gBAAiB,SAAQ,qBAEpC;CAAG;AAEL;;;GAGG;AACH,eAAO,MAAM,kBAAkB,kDAAmC,CAAC;AACnE,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC,IAAI,CAAC;;;;;AAEhE,qBAAa,aAAc,SAAQ,kBAGjC;CAAG;;IAcH;;;;OAIG;;IAEH,iGAAiG;;;;AAbnG;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,uBAYtC;CAAG;;;;;;;;AAEL,wGAAwG;AACxG,qBAAa,aAAc,SAAQ,kBAMjC;CAAG;;;;;;;;IAWH,0EAA0E;;IAE1E;;;OAGG;;IAEH;;;OAGG;;;AAnBL,qBAAa,cAAe,SAAQ,mBAqBlC;CAAG;;;;AAEL,qBAAa,WAAY,SAAQ,gBAE/B;CAAG;;;IAIH,uDAAuD;;;AAFzD,qBAAa,YAAa,SAAQ,iBAIhC;CAAG;;;;AAEL,8EAA8E;AAC9E,qBAAa,OAAQ,SAAQ,YAE3B;CAAG;;;;AAEL,6EAA6E;AAC7E,qBAAa,SAAU,SAAQ,cAE7B;CAAG;;AAEL,qBAAa,cAAe,SAAQ,mBAAmD;CAAG;;;;AAE1F,qBAAa,aAAc,SAAQ,kBAEjC;CAAG;AAEL,eAAO,MAAM,YAAY,sEAAgD,CAAC;AAC1E,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAC;;;IAKlD,iDAAiD;;;;;;AAHnD,sEAAsE;AACtE,qBAAa,aAAc,SAAQ,kBAOjC;CAAG;;;;AAEL,qBAAa,eAAgB,SAAQ,oBAIpC;CAAG;;;;;;AAEJ,uDAAuD;AACvD,qBAAa,eAAgB,SAAQ,oBAIpC;CAAG;;;;;;AAEJ,gGAAgG;AAChG,qBAAa,gBAAiB,SAAQ,qBAIrC;CAAG;;;;;AAEJ,sEAAsE;AACtE,qBAAa,aAAc,SAAQ,kBAIlC;CAAG;;;;;AAEJ,qBAAa,eAAgB,SAAQ,oBAIpC;CAAG;;;;;AAEJ,kGAAkG;AAClG,qBAAa,oBAAqB,SAAQ,yBAIzC;CAAG;AAIJ,8DAA8D;AAC9D,eAAO,MAAM,WAAW,sMAEvB,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,aAAa;;uFAKzB,CAAC;AAEF,+DAA+D;AAC/D,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;6JAwC8C,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;8JAA2E,CAAC"}
|
package/dist/api/api.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { AttemptId } from "@usine/core/attempt";
|
|
2
|
+
import { safeSegment } from "@usine/core/repository";
|
|
3
|
+
import { CloudflareImageName, CloudflareInstanceType, CloudflareLlmProvider, } from "@usine/core/worker";
|
|
4
|
+
import { Schema } from "effect";
|
|
5
|
+
import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/http-api";
|
|
6
|
+
import { ProcessId, SandboxName, SandboxOwner } from "./names.js";
|
|
7
|
+
export { ACCESS_CLIENT_ID_HEADER, ACCESS_CLIENT_SECRET_HEADER, attachPath, execPath, ProcessId, SandboxName, SandboxOwner, } from "./names.js";
|
|
8
|
+
/** The version of the sandbox API and the exec stream, reported by `/v1/health`. */
|
|
9
|
+
export const SANDBOX_PROTOCOL = 1;
|
|
10
|
+
/**
|
|
11
|
+
* Returns the owner of the sandboxes that `instance` runs for `worker`. Throws if `instance` isn't
|
|
12
|
+
* in kebab case.
|
|
13
|
+
*/
|
|
14
|
+
export const sandboxOwner = (instance, worker) => SandboxOwner.make(`${instance}.${worker}`);
|
|
15
|
+
/** Returns the name of the sandbox that runs `attempt` for `owner`. */
|
|
16
|
+
export const sandboxName = (owner, attempt) => SandboxName.make(`${owner}.${safeSegment(attempt)}`);
|
|
17
|
+
/** An API key that the provider's outbound handler sends in place of the sandbox's placeholder. */
|
|
18
|
+
export class ApiKeyCredential extends Schema.TaggedClass()("ApiKey", {
|
|
19
|
+
key: Schema.Redacted(Schema.NonEmptyString),
|
|
20
|
+
}) {
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* How a provider's outbound handler authenticates the sandbox's requests. Only API keys exist;
|
|
24
|
+
* a brokered login (#549) would be another member.
|
|
25
|
+
*/
|
|
26
|
+
export const ProviderCredential = Schema.Union([ApiKeyCredential]);
|
|
27
|
+
export class ProviderGrant extends Schema.Class("@usine/cloudflare/ProviderGrant")({
|
|
28
|
+
provider: CloudflareLlmProvider,
|
|
29
|
+
credential: ProviderCredential,
|
|
30
|
+
}) {
|
|
31
|
+
}
|
|
32
|
+
const uniqueProviders = Schema.makeFilter((grants) => new Set(grants.map(({ provider }) => provider)).size === grants.length, { message: "Expected each provider once" });
|
|
33
|
+
/**
|
|
34
|
+
* The secrets that a sandbox's outbound handlers hold. The container never receives them. A host
|
|
35
|
+
* without a credential is refused, so replacing the credentials with none revokes them.
|
|
36
|
+
*/
|
|
37
|
+
export class SandboxCredentials extends Schema.Class("@usine/cloudflare/SandboxCredentials")({
|
|
38
|
+
/**
|
|
39
|
+
* When usine issued these credentials, later than any credentials it issued before. The sandbox
|
|
40
|
+
* refuses credentials issued before the ones it holds, so a delayed request can't restore
|
|
41
|
+
* credentials that a later request replaced or revoked.
|
|
42
|
+
*/
|
|
43
|
+
issuedAt: Schema.DateTimeUtc,
|
|
44
|
+
/** The Attempt's installation token, added to requests for `github.com` and `api.github.com`. */
|
|
45
|
+
github: Schema.OptionFromOptionalKey(Schema.Redacted(Schema.NonEmptyString)),
|
|
46
|
+
providers: Schema.Array(ProviderGrant).check(uniqueProviders),
|
|
47
|
+
}) {
|
|
48
|
+
}
|
|
49
|
+
/** A request to create the sandbox for one Attempt, or to return it if it already runs that Attempt. */
|
|
50
|
+
export class CreateSandbox extends Schema.Class("@usine/cloudflare/CreateSandbox")({
|
|
51
|
+
owner: SandboxOwner,
|
|
52
|
+
attempt: AttemptId,
|
|
53
|
+
image: CloudflareImageName,
|
|
54
|
+
instanceType: CloudflareInstanceType,
|
|
55
|
+
credentials: SandboxCredentials,
|
|
56
|
+
}) {
|
|
57
|
+
}
|
|
58
|
+
export class SandboxSummary extends Schema.Class("@usine/cloudflare/SandboxSummary")({
|
|
59
|
+
name: SandboxName,
|
|
60
|
+
owner: SandboxOwner,
|
|
61
|
+
attempt: AttemptId,
|
|
62
|
+
image: CloudflareImageName,
|
|
63
|
+
instanceType: CloudflareInstanceType,
|
|
64
|
+
createdAt: Schema.DateTimeUtc,
|
|
65
|
+
/** Whether the container runs. A container that stopped lost its disk. */
|
|
66
|
+
running: Schema.Boolean,
|
|
67
|
+
/**
|
|
68
|
+
* Whether usine holds the sandbox: the keepalive alarm keeps its container running past the
|
|
69
|
+
* lease, until usine reconciles or destroys it, because it keeps a checkout.
|
|
70
|
+
*/
|
|
71
|
+
held: Schema.Boolean,
|
|
72
|
+
/**
|
|
73
|
+
* Whether the platform sent the container SIGTERM. It stops the container within 15 minutes, and
|
|
74
|
+
* a container that starts again has an empty disk.
|
|
75
|
+
*/
|
|
76
|
+
draining: Schema.Boolean,
|
|
77
|
+
}) {
|
|
78
|
+
}
|
|
79
|
+
export class SandboxList extends Schema.Class("@usine/cloudflare/SandboxList")({
|
|
80
|
+
sandboxes: Schema.Array(SandboxSummary),
|
|
81
|
+
}) {
|
|
82
|
+
}
|
|
83
|
+
export class HealthReport extends Schema.Class("@usine/cloudflare/HealthReport")({
|
|
84
|
+
protocol: Schema.Literal(SANDBOX_PROTOCOL),
|
|
85
|
+
/** The names in the Worker's `containers[].images`. */
|
|
86
|
+
images: Schema.Array(CloudflareImageName),
|
|
87
|
+
}) {
|
|
88
|
+
}
|
|
89
|
+
/** The response to a stop: the number of commands that were still running. */
|
|
90
|
+
export class Stopped extends Schema.Class("@usine/cloudflare/Stopped")({
|
|
91
|
+
stopped: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
92
|
+
}) {
|
|
93
|
+
}
|
|
94
|
+
/** The response to a destroy. `destroyed` is whether the sandbox existed. */
|
|
95
|
+
export class Destroyed extends Schema.Class("@usine/cloudflare/Destroyed")({
|
|
96
|
+
destroyed: Schema.Boolean,
|
|
97
|
+
}) {
|
|
98
|
+
}
|
|
99
|
+
export class ProcessRunning extends Schema.TaggedClass()("Running", {}) {
|
|
100
|
+
}
|
|
101
|
+
export class ProcessExited extends Schema.TaggedClass()("Exited", {
|
|
102
|
+
exitCode: Schema.Int,
|
|
103
|
+
}) {
|
|
104
|
+
}
|
|
105
|
+
export const ProcessState = Schema.Union([ProcessRunning, ProcessExited]);
|
|
106
|
+
/** A command's state and output size, read from its process files. */
|
|
107
|
+
export class ProcessStatus extends Schema.Class("@usine/cloudflare/ProcessStatus")({
|
|
108
|
+
process: ProcessId,
|
|
109
|
+
/** The command's process ID in the container. */
|
|
110
|
+
pid: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
|
111
|
+
state: ProcessState,
|
|
112
|
+
stdoutBytes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
113
|
+
stderrBytes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
114
|
+
}) {
|
|
115
|
+
}
|
|
116
|
+
export class SandboxNotFound extends Schema.TaggedError()("SandboxNotFound", { name: SandboxName }, { httpApiStatus: 404 }) {
|
|
117
|
+
}
|
|
118
|
+
/** The sandbox exists for another owner or Attempt. */
|
|
119
|
+
export class SandboxConflict extends Schema.TaggedError()("SandboxConflict", { name: SandboxName, owner: SandboxOwner, attempt: AttemptId }, { httpApiStatus: 409 }) {
|
|
120
|
+
}
|
|
121
|
+
/** The sandbox holds credentials issued at `current`, after the request's, which it refused. */
|
|
122
|
+
export class StaleCredentials extends Schema.TaggedError()("StaleCredentials", { name: SandboxName, issuedAt: Schema.DateTimeUtc, current: Schema.DateTimeUtc }, { httpApiStatus: 409 }) {
|
|
123
|
+
}
|
|
124
|
+
/** The Worker's `containers[].images` has no image with this name. */
|
|
125
|
+
export class ImageNotFound extends Schema.TaggedError()("ImageNotFound", { image: CloudflareImageName, images: Schema.Array(Schema.String) }, { httpApiStatus: 422 }) {
|
|
126
|
+
}
|
|
127
|
+
export class ProcessNotFound extends Schema.TaggedError()("ProcessNotFound", { name: SandboxName, process: ProcessId }, { httpApiStatus: 404 }) {
|
|
128
|
+
}
|
|
129
|
+
/** The container didn't start, stopped, or failed a command. `message` comes from the runtime. */
|
|
130
|
+
export class ContainerUnavailable extends Schema.TaggedError()("ContainerUnavailable", { name: SandboxName, message: Schema.String }, { httpApiStatus: 503 }) {
|
|
131
|
+
}
|
|
132
|
+
const sandbox = { name: SandboxName };
|
|
133
|
+
/** `GET /v1/health`, served by any sandbox Durable Object. */
|
|
134
|
+
export const healthGroup = HttpApiGroup.make("health").add(HttpApiEndpoint.get("health", "/v1/health", { success: HealthReport }));
|
|
135
|
+
/** `GET /v1/sandboxes`, served by the registry Durable Object. */
|
|
136
|
+
export const registryGroup = HttpApiGroup.make("registry").add(HttpApiEndpoint.get("list", "/v1/sandboxes", {
|
|
137
|
+
query: { owner: SandboxOwner },
|
|
138
|
+
success: SandboxList,
|
|
139
|
+
}));
|
|
140
|
+
/** The routes of one sandbox, served by its Durable Object. */
|
|
141
|
+
export const sandboxGroup = HttpApiGroup.make("sandbox")
|
|
142
|
+
.add(HttpApiEndpoint.put("create", "/v1/sandboxes/:name", {
|
|
143
|
+
params: sandbox,
|
|
144
|
+
payload: CreateSandbox,
|
|
145
|
+
success: SandboxSummary,
|
|
146
|
+
error: [SandboxConflict, StaleCredentials, ImageNotFound, ContainerUnavailable],
|
|
147
|
+
}), HttpApiEndpoint.get("get", "/v1/sandboxes/:name", {
|
|
148
|
+
params: sandbox,
|
|
149
|
+
success: SandboxSummary,
|
|
150
|
+
error: SandboxNotFound,
|
|
151
|
+
}), HttpApiEndpoint.put("credentials", "/v1/sandboxes/:name/credentials", {
|
|
152
|
+
params: sandbox,
|
|
153
|
+
payload: SandboxCredentials,
|
|
154
|
+
success: SandboxSummary,
|
|
155
|
+
error: [SandboxNotFound, StaleCredentials, ContainerUnavailable],
|
|
156
|
+
}), HttpApiEndpoint.post("hold", "/v1/sandboxes/:name/hold", {
|
|
157
|
+
params: sandbox,
|
|
158
|
+
success: SandboxSummary,
|
|
159
|
+
error: [SandboxNotFound, ContainerUnavailable],
|
|
160
|
+
}), HttpApiEndpoint.post("stop", "/v1/sandboxes/:name/stop", {
|
|
161
|
+
params: sandbox,
|
|
162
|
+
success: Stopped,
|
|
163
|
+
error: [SandboxNotFound, ContainerUnavailable],
|
|
164
|
+
}), HttpApiEndpoint.delete("destroy", "/v1/sandboxes/:name", {
|
|
165
|
+
params: sandbox,
|
|
166
|
+
success: Destroyed,
|
|
167
|
+
error: ContainerUnavailable,
|
|
168
|
+
}), HttpApiEndpoint.get("process", "/v1/sandboxes/:name/processes/:process", {
|
|
169
|
+
params: { ...sandbox, process: ProcessId },
|
|
170
|
+
success: ProcessStatus,
|
|
171
|
+
error: [SandboxNotFound, ProcessNotFound, ContainerUnavailable],
|
|
172
|
+
}))
|
|
173
|
+
.annotate(HttpApi.PayloadParseOptions, { onExcessProperty: "error" });
|
|
174
|
+
/**
|
|
175
|
+
* The JSON API of the Worker that usine ships (`@usine/cloudflare/worker`). Every request carries
|
|
176
|
+
* the Access service token headers. Commands run through the exec stream at `execPath`, not
|
|
177
|
+
* through this API.
|
|
178
|
+
*/
|
|
179
|
+
export const SandboxApi = HttpApi.make("SandboxApi").add(healthGroup, registryGroup, sandboxGroup);
|
|
180
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { CloudflareLlmProvider } from "@usine/core/worker";
|
|
2
|
+
/** The home directory of the image's `usine` user, where every command starts. */
|
|
3
|
+
export declare const SANDBOX_HOME = "/home/usine";
|
|
4
|
+
/** The directory with each command's process files, one directory per process ID. */
|
|
5
|
+
export declare const PROCESSES_DIRECTORY = "/home/usine/.usine/processes";
|
|
6
|
+
/**
|
|
7
|
+
* The file that the image's init writes when the platform sends the container SIGTERM, before
|
|
8
|
+
* it stops the container within 15 minutes (`image/init.sh`).
|
|
9
|
+
*/
|
|
10
|
+
export declare const DRAIN_MARKER = "/home/usine/.usine/draining";
|
|
11
|
+
/**
|
|
12
|
+
* The certificate authority that signs the certificates of the hosts the Worker intercepts over
|
|
13
|
+
* HTTPS. The container runtime mounts it.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CONTAINER_CA = "/etc/cloudflare/certs/cloudflare-containers-ca.crt";
|
|
16
|
+
/**
|
|
17
|
+
* The host that each provider's outbound handler serves over plain HTTP inside the sandbox, and
|
|
18
|
+
* the upstream it forwards to over HTTPS.
|
|
19
|
+
*/
|
|
20
|
+
export declare const PROVIDER_HOSTS: {
|
|
21
|
+
readonly anthropic: {
|
|
22
|
+
readonly sandbox: "anthropic.usine.internal";
|
|
23
|
+
readonly upstream: "api.anthropic.com";
|
|
24
|
+
};
|
|
25
|
+
readonly openai: {
|
|
26
|
+
readonly sandbox: "openai.usine.internal";
|
|
27
|
+
readonly upstream: "api.openai.com";
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/** The GitHub hosts whose requests the GitHub outbound handler authenticates. */
|
|
31
|
+
export declare const GITHUB_HOSTS: readonly ["github.com", "api.github.com"];
|
|
32
|
+
export type GitHubHost = (typeof GITHUB_HOSTS)[number];
|
|
33
|
+
/**
|
|
34
|
+
* The value the sandbox sends where a tool expects a credential. The outbound handlers replace
|
|
35
|
+
* it, so it's not a secret.
|
|
36
|
+
*/
|
|
37
|
+
export declare const PLACEHOLDER_CREDENTIAL = "usine-placeholder";
|
|
38
|
+
/**
|
|
39
|
+
* Returns the environment that points the harnesses, git, and gh at the outbound handlers, for
|
|
40
|
+
* a sandbox whose handlers hold keys for `providers`. It contains no secret.
|
|
41
|
+
*
|
|
42
|
+
* - git and gh use HTTPS to GitHub, and trust the container's certificate authority, as
|
|
43
|
+
* Cloudflare's coding-agent runner sets it up. gh sends the placeholder as its token.
|
|
44
|
+
* - Claude Code calls the Anthropic handler over plain HTTP with the placeholder as its key.
|
|
45
|
+
* - Codex reads the placeholder from `OPENAI_API_KEY`; the image's Codex configuration points
|
|
46
|
+
* its `usine` model provider at the OpenAI handler.
|
|
47
|
+
*/
|
|
48
|
+
export declare const harnessEnvironment: (providers: ReadonlyArray<CloudflareLlmProvider>) => Record<string, string>;
|
|
49
|
+
//# sourceMappingURL=harness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/api/harness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhE,kFAAkF;AAClF,eAAO,MAAM,YAAY,gBAAgB,CAAC;AAE1C,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,iCAAqC,CAAC;AAEtE;;;GAGG;AACH,eAAO,MAAM,YAAY,gCAAoC,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,YAAY,uDAAuD,CAAC;AAEjF;;;GAGG;AACH,eAAO,MAAM,cAAc;;iBACZ,OAAO,EAAE,0BAA0B;iBAAE,QAAQ,EAAE,mBAAmB;;;iBACrE,OAAO,EAAE,uBAAuB;iBAAE,QAAQ,EAAE,gBAAgB;;CAIvE,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,YAAY,YAAI,YAAY,EAAE,gBAAgB,CAAU,CAAC;AACtE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAY1D;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,cAClB,aAAa,CAAC,qBAAqB,CAAC,KAC9C,MAAM,CAAC,MAAM,EAAE,MAAM,CAWrB,CAAC"}
|