@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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** The home directory of the image's `usine` user, where every command starts. */
|
|
2
|
+
export const SANDBOX_HOME = "/home/usine";
|
|
3
|
+
/** The directory with each command's process files, one directory per process ID. */
|
|
4
|
+
export const PROCESSES_DIRECTORY = `${SANDBOX_HOME}/.usine/processes`;
|
|
5
|
+
/**
|
|
6
|
+
* The file that the image's init writes when the platform sends the container SIGTERM, before
|
|
7
|
+
* it stops the container within 15 minutes (`image/init.sh`).
|
|
8
|
+
*/
|
|
9
|
+
export const DRAIN_MARKER = `${SANDBOX_HOME}/.usine/draining`;
|
|
10
|
+
/**
|
|
11
|
+
* The certificate authority that signs the certificates of the hosts the Worker intercepts over
|
|
12
|
+
* HTTPS. The container runtime mounts it.
|
|
13
|
+
*/
|
|
14
|
+
export const CONTAINER_CA = "/etc/cloudflare/certs/cloudflare-containers-ca.crt";
|
|
15
|
+
/**
|
|
16
|
+
* The host that each provider's outbound handler serves over plain HTTP inside the sandbox, and
|
|
17
|
+
* the upstream it forwards to over HTTPS.
|
|
18
|
+
*/
|
|
19
|
+
export const PROVIDER_HOSTS = {
|
|
20
|
+
anthropic: { sandbox: "anthropic.usine.internal", upstream: "api.anthropic.com" },
|
|
21
|
+
openai: { sandbox: "openai.usine.internal", upstream: "api.openai.com" },
|
|
22
|
+
};
|
|
23
|
+
/** The GitHub hosts whose requests the GitHub outbound handler authenticates. */
|
|
24
|
+
export const GITHUB_HOSTS = ["github.com", "api.github.com"];
|
|
25
|
+
/**
|
|
26
|
+
* The value the sandbox sends where a tool expects a credential. The outbound handlers replace
|
|
27
|
+
* it, so it's not a secret.
|
|
28
|
+
*/
|
|
29
|
+
export const PLACEHOLDER_CREDENTIAL = "usine-placeholder";
|
|
30
|
+
/** The variables each provider's harness needs to call its outbound handler. */
|
|
31
|
+
const PROVIDER_ENVIRONMENT = {
|
|
32
|
+
anthropic: {
|
|
33
|
+
ANTHROPIC_BASE_URL: `http://${PROVIDER_HOSTS.anthropic.sandbox}`,
|
|
34
|
+
ANTHROPIC_API_KEY: PLACEHOLDER_CREDENTIAL,
|
|
35
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
36
|
+
},
|
|
37
|
+
openai: { OPENAI_API_KEY: PLACEHOLDER_CREDENTIAL },
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Returns the environment that points the harnesses, git, and gh at the outbound handlers, for
|
|
41
|
+
* a sandbox whose handlers hold keys for `providers`. It contains no secret.
|
|
42
|
+
*
|
|
43
|
+
* - git and gh use HTTPS to GitHub, and trust the container's certificate authority, as
|
|
44
|
+
* Cloudflare's coding-agent runner sets it up. gh sends the placeholder as its token.
|
|
45
|
+
* - Claude Code calls the Anthropic handler over plain HTTP with the placeholder as its key.
|
|
46
|
+
* - Codex reads the placeholder from `OPENAI_API_KEY`; the image's Codex configuration points
|
|
47
|
+
* its `usine` model provider at the OpenAI handler.
|
|
48
|
+
*/
|
|
49
|
+
export const harnessEnvironment = (providers) => Object.assign({
|
|
50
|
+
GIT_SSL_CAINFO: CONTAINER_CA,
|
|
51
|
+
CURL_CA_BUNDLE: CONTAINER_CA,
|
|
52
|
+
SSL_CERT_FILE: CONTAINER_CA,
|
|
53
|
+
NODE_EXTRA_CA_CERTS: CONTAINER_CA,
|
|
54
|
+
GH_TOKEN: PLACEHOLDER_CREDENTIAL,
|
|
55
|
+
GH_PROMPT_DISABLED: "1",
|
|
56
|
+
}, ...providers.map((provider) => PROVIDER_ENVIRONMENT[provider]));
|
|
57
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/api/harness.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;AAE1C,qFAAqF;AACrF,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,YAAY,mBAAmB,CAAC;AAEtE;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,YAAY,kBAAkB,CAAC;AAE9D;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,oDAAoD,CAAC;AAEjF;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,SAAS,EAAE,EAAE,OAAO,EAAE,0BAA0B,EAAE,QAAQ,EAAE,mBAAmB,EAAE;IACjF,MAAM,EAAE,EAAE,OAAO,EAAE,uBAAuB,EAAE,QAAQ,EAAE,gBAAgB,EAAE;CAIzE,CAAC;AAEF,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,YAAY,EAAE,gBAAgB,CAAU,CAAC;AAGtE;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAE1D,gFAAgF;AAChF,MAAM,oBAAoB,GAAG;IAC3B,SAAS,EAAE;QACT,kBAAkB,EAAE,UAAU,cAAc,CAAC,SAAS,CAAC,OAAO,EAAE;QAChE,iBAAiB,EAAE,sBAAsB;QACzC,wCAAwC,EAAE,GAAG;KAC9C;IACD,MAAM,EAAE,EAAE,cAAc,EAAE,sBAAsB,EAAE;CACgC,CAAC;AAErF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,SAA+C,EACvB,EAAE,CAC1B,MAAM,CAAC,MAAM,CACX;IACE,cAAc,EAAE,YAAY;IAC5B,cAAc,EAAE,YAAY;IAC5B,aAAa,EAAE,YAAY;IAC3B,mBAAmB,EAAE,YAAY;IACjC,QAAQ,EAAE,sBAAsB;IAChC,kBAAkB,EAAE,GAAG;CACxB,EACD,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAC/D,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
/**
|
|
3
|
+
* The request headers with the Cloudflare Access service token. The Worker refuses every request
|
|
4
|
+
* whose headers don't match the token it's configured with.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ACCESS_CLIENT_ID_HEADER = "cf-access-client-id";
|
|
7
|
+
export declare const ACCESS_CLIENT_SECRET_HEADER = "cf-access-client-secret";
|
|
8
|
+
/**
|
|
9
|
+
* The usine instance and Worker that a sandbox belongs to: the instance's identity, such as
|
|
10
|
+
* `github-app-123`, a dot, and the usine Worker's name. Orphan cleanup lists only its own owner's
|
|
11
|
+
* sandboxes, so instances that share a deployed Worker never remove each other's.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SandboxOwner: Schema.brand<Schema.String, "SandboxOwner">;
|
|
14
|
+
export type SandboxOwner = typeof SandboxOwner.Type;
|
|
15
|
+
/** A sandbox's Durable Object name: its owner, a dot, and the Attempt as a path segment. */
|
|
16
|
+
export declare const SandboxName: Schema.brand<Schema.String, "SandboxName">;
|
|
17
|
+
export type SandboxName = typeof SandboxName.Type;
|
|
18
|
+
/** The ID of one command a sandbox ran through its exec stream, which names its process files. */
|
|
19
|
+
export declare const ProcessId: Schema.brand<Schema.String, "ProcessId">;
|
|
20
|
+
export type ProcessId = typeof ProcessId.Type;
|
|
21
|
+
/** Returns the path of the WebSocket that runs one command in the sandbox (`stream.ts`). */
|
|
22
|
+
export declare const execPath: (name: SandboxName) => string;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the path of the WebSocket that streams a command's output from the given byte offsets
|
|
25
|
+
* of its process files, then its exit code.
|
|
26
|
+
*/
|
|
27
|
+
export declare const attachPath: (name: SandboxName, process: ProcessId) => string;
|
|
28
|
+
//# sourceMappingURL=names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../src/api/names.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC;;;GAGG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAC7D,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAErE;;;;GAIG;AACH,eAAO,MAAM,YAAY,6CAIW,CAAC;AACrC,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAC;AAEpD,4FAA4F;AAC5F,eAAO,MAAM,WAAW,4CAIW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAC;AAElD,kGAAkG;AAClG,eAAO,MAAM,SAAS,0CAEW,CAAC;AAClC,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAC;AAE9C,4FAA4F;AAC5F,eAAO,MAAM,QAAQ,SAAU,WAAW,KAAG,MAAsC,CAAC;AAEpF;;;GAGG;AACH,eAAO,MAAM,UAAU,SAAU,WAAW,WAAW,SAAS,KAAG,MACd,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Imports Schema alone, not the whole `effect` index: the executor's sandbox shell loads this
|
|
2
|
+
// module once per command, through `stream.ts`.
|
|
3
|
+
import * as Schema from "effect/Schema";
|
|
4
|
+
/**
|
|
5
|
+
* The request headers with the Cloudflare Access service token. The Worker refuses every request
|
|
6
|
+
* whose headers don't match the token it's configured with.
|
|
7
|
+
*/
|
|
8
|
+
export const ACCESS_CLIENT_ID_HEADER = "cf-access-client-id";
|
|
9
|
+
export const ACCESS_CLIENT_SECRET_HEADER = "cf-access-client-secret";
|
|
10
|
+
/**
|
|
11
|
+
* The usine instance and Worker that a sandbox belongs to: the instance's identity, such as
|
|
12
|
+
* `github-app-123`, a dot, and the usine Worker's name. Orphan cleanup lists only its own owner's
|
|
13
|
+
* sandboxes, so instances that share a deployed Worker never remove each other's.
|
|
14
|
+
*/
|
|
15
|
+
export const SandboxOwner = Schema.String.check(Schema.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*\.[a-z0-9]+(?:-[a-z0-9]+)*$/u, {
|
|
16
|
+
message: "Expected a sandbox owner: <instance>.<worker>, each in kebab case",
|
|
17
|
+
})).pipe(Schema.brand("SandboxOwner"));
|
|
18
|
+
/** A sandbox's Durable Object name: its owner, a dot, and the Attempt as a path segment. */
|
|
19
|
+
export const SandboxName = Schema.String.check(Schema.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*\.[a-z0-9]+(?:-[a-z0-9]+)*\.[A-Za-z0-9_.-]{1,160}$/u, {
|
|
20
|
+
message: "Expected a sandbox name: <instance>.<worker>.<attempt>",
|
|
21
|
+
})).pipe(Schema.brand("SandboxName"));
|
|
22
|
+
/** The ID of one command a sandbox ran through its exec stream, which names its process files. */
|
|
23
|
+
export const ProcessId = Schema.String.check(Schema.isPattern(/^[0-9a-f]{32}$/u, { message: "Expected 32 lowercase hexadecimal digits" })).pipe(Schema.brand("ProcessId"));
|
|
24
|
+
/** Returns the path of the WebSocket that runs one command in the sandbox (`stream.ts`). */
|
|
25
|
+
export const execPath = (name) => `/v1/sandboxes/${name}/exec`;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the path of the WebSocket that streams a command's output from the given byte offsets
|
|
28
|
+
* of its process files, then its exit code.
|
|
29
|
+
*/
|
|
30
|
+
export const attachPath = (name, process) => `/v1/sandboxes/${name}/processes/${process}/attach`;
|
|
31
|
+
//# sourceMappingURL=names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.js","sourceRoot":"","sources":["../../src/api/names.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,gDAAgD;AAChD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,qBAAqB,CAAC;AAC7D,MAAM,CAAC,MAAM,2BAA2B,GAAG,yBAAyB,CAAC;AAErE;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAC7C,MAAM,CAAC,SAAS,CAAC,uDAAuD,EAAE;IACxE,OAAO,EAAE,mEAAmE;CAC7E,CAAC,CACH,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;AAGrC,4FAA4F;AAC5F,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAC5C,MAAM,CAAC,SAAS,CAAC,8EAA8E,EAAE;IAC/F,OAAO,EAAE,wDAAwD;CAClE,CAAC,CACH,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;AAGpC,kGAAkG;AAClG,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAC1C,MAAM,CAAC,SAAS,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,0CAA0C,EAAE,CAAC,CAC7F,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;AAGlC,4FAA4F;AAC5F,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAiB,EAAU,EAAE,CAAC,iBAAiB,IAAI,OAAO,CAAC;AAEpF;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAiB,EAAE,OAAkB,EAAU,EAAE,CAC1E,iBAAiB,IAAI,cAAc,OAAO,SAAS,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as Option from "effect/Option";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
export { ACCESS_CLIENT_ID_HEADER, ACCESS_CLIENT_SECRET_HEADER, attachPath, execPath, ProcessId, SandboxName, } from "./names.ts";
|
|
4
|
+
declare const ExecStart_base: Schema.Class<ExecStart, Schema.TaggedStruct<"ExecStart", {
|
|
5
|
+
readonly command: Schema.NonEmptyString;
|
|
6
|
+
}>, {}>;
|
|
7
|
+
/**
|
|
8
|
+
* The first text message of an exec stream. The sandbox runs `command` with `sh -c` as the image's
|
|
9
|
+
* user in its home directory, as sshd runs a remote command line.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ExecStart extends ExecStart_base {
|
|
12
|
+
}
|
|
13
|
+
declare const StdinEnd_base: Schema.Class<StdinEnd, Schema.TaggedStruct<"StdinEnd", {}>, {}>;
|
|
14
|
+
/** Closes the command's stdin. Binary messages from the client are its stdin bytes. */
|
|
15
|
+
export declare class StdinEnd extends StdinEnd_base {
|
|
16
|
+
}
|
|
17
|
+
export declare const ClientMessage: Schema.Union<readonly [typeof ExecStart, typeof StdinEnd]>;
|
|
18
|
+
export type ClientMessage = typeof ClientMessage.Type;
|
|
19
|
+
declare const Started_base: Schema.Class<Started, Schema.TaggedStruct<"Started", {
|
|
20
|
+
readonly process: Schema.brand<Schema.String, "ProcessId">;
|
|
21
|
+
}>, {}>;
|
|
22
|
+
/** The command started. Its process files are named after `process`. */
|
|
23
|
+
export declare class Started extends Started_base {
|
|
24
|
+
}
|
|
25
|
+
declare const Exited_base: Schema.Class<Exited, Schema.TaggedStruct<"Exited", {
|
|
26
|
+
readonly exitCode: Schema.Int;
|
|
27
|
+
}>, {}>;
|
|
28
|
+
/** The command exited with `exitCode`. The stream closes after this message. */
|
|
29
|
+
export declare class Exited extends Exited_base {
|
|
30
|
+
}
|
|
31
|
+
declare const StreamFailed_base: Schema.Class<StreamFailed, Schema.TaggedStruct<"StreamFailed", {
|
|
32
|
+
readonly message: Schema.String;
|
|
33
|
+
}>, {}>;
|
|
34
|
+
/**
|
|
35
|
+
* The sandbox can't run or follow the command: no sandbox, a stopped container, or an unknown
|
|
36
|
+
* process. The stream closes after this message.
|
|
37
|
+
*/
|
|
38
|
+
export declare class StreamFailed extends StreamFailed_base {
|
|
39
|
+
}
|
|
40
|
+
export declare const ServerMessage: Schema.Union<readonly [typeof Started, typeof Exited, typeof StreamFailed]>;
|
|
41
|
+
export type ServerMessage = typeof ServerMessage.Type;
|
|
42
|
+
export declare const encodeClientMessage: (input: ExecStart | StdinEnd, options?: import("effect/SchemaAST").ParseOptions) => string;
|
|
43
|
+
export declare const decodeClientMessage: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Option.Option<ExecStart | StdinEnd>;
|
|
44
|
+
export declare const encodeServerMessage: (input: Exited | Started | StreamFailed, options?: import("effect/SchemaAST").ParseOptions) => string;
|
|
45
|
+
export declare const decodeServerMessage: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Option.Option<Exited | Started | StreamFailed>;
|
|
46
|
+
/** The command's output stream that a binary server message carries. */
|
|
47
|
+
export type OutputChannel = "stdout" | "stderr";
|
|
48
|
+
/** Returns the binary server message with `bytes` of `channel`: one channel byte, then the bytes. */
|
|
49
|
+
export declare const outputFrame: (channel: OutputChannel, bytes: Uint8Array) => Uint8Array;
|
|
50
|
+
export interface OutputChunk {
|
|
51
|
+
readonly channel: OutputChannel;
|
|
52
|
+
readonly bytes: Uint8Array;
|
|
53
|
+
}
|
|
54
|
+
/** Returns the channel and bytes of a binary server message, or `None` for an unknown channel byte. */
|
|
55
|
+
export declare const decodeOutputFrame: (frame: Uint8Array) => Option.Option<OutputChunk>;
|
|
56
|
+
/**
|
|
57
|
+
* The query of an attach stream: the bytes of each output stream the client already has, so the
|
|
58
|
+
* stream resumes after them.
|
|
59
|
+
*/
|
|
60
|
+
export declare const AttachOffsets: Schema.Struct<{
|
|
61
|
+
readonly stdout: Schema.NumberFromString;
|
|
62
|
+
readonly stderr: Schema.NumberFromString;
|
|
63
|
+
}>;
|
|
64
|
+
export type AttachOffsets = typeof AttachOffsets.Type;
|
|
65
|
+
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/api/stream.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,UAAU,EACV,QAAQ,EACR,SAAS,EACT,WAAW,GACZ,MAAM,YAAY,CAAC;;;;AAEpB;;;GAGG;AACH,qBAAa,SAAU,SAAQ,cAE7B;CAAG;;AAEL,uFAAuF;AACvF,qBAAa,QAAS,SAAQ,aAA8C;CAAG;AAE/E,eAAO,MAAM,aAAa,4DAAsC,CAAC;AACjE,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;;;;AAEtD,wEAAwE;AACxE,qBAAa,OAAQ,SAAQ,YAE3B;CAAG;;;;AAEL,gFAAgF;AAChF,qBAAa,MAAO,SAAQ,WAE1B;CAAG;;;;AAEL;;;GAGG;AACH,qBAAa,YAAa,SAAQ,iBAEhC;CAAG;AAEL,eAAO,MAAM,aAAa,6EAAgD,CAAC;AAC3E,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,eAAO,MAAM,mBAAmB,4FAA0D,CAAC;AAC3F,eAAO,MAAM,mBAAmB,4GAAmE,CAAC;AACpG,eAAO,MAAM,mBAAmB,uGAA0D,CAAC;AAC3F,eAAO,MAAM,mBAAmB,uHAAmE,CAAC;AAEpG,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAIhD,qGAAqG;AACrG,eAAO,MAAM,WAAW,YAAa,aAAa,SAAS,UAAU,KAAG,UAKvE,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,uGAAuG;AACvG,eAAO,MAAM,iBAAiB,UAAW,UAAU,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAU9E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa;;;EAGxB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Imports Option and Schema alone, not the whole `effect` index: the executor's sandbox shell
|
|
2
|
+
// loads this module once per command.
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import { ProcessId } from "./names.js";
|
|
6
|
+
export { ACCESS_CLIENT_ID_HEADER, ACCESS_CLIENT_SECRET_HEADER, attachPath, execPath, ProcessId, SandboxName, } from "./names.js";
|
|
7
|
+
/**
|
|
8
|
+
* The first text message of an exec stream. The sandbox runs `command` with `sh -c` as the image's
|
|
9
|
+
* user in its home directory, as sshd runs a remote command line.
|
|
10
|
+
*/
|
|
11
|
+
export class ExecStart extends Schema.TaggedClass()("ExecStart", {
|
|
12
|
+
command: Schema.NonEmptyString,
|
|
13
|
+
}) {
|
|
14
|
+
}
|
|
15
|
+
/** Closes the command's stdin. Binary messages from the client are its stdin bytes. */
|
|
16
|
+
export class StdinEnd extends Schema.TaggedClass()("StdinEnd", {}) {
|
|
17
|
+
}
|
|
18
|
+
export const ClientMessage = Schema.Union([ExecStart, StdinEnd]);
|
|
19
|
+
/** The command started. Its process files are named after `process`. */
|
|
20
|
+
export class Started extends Schema.TaggedClass()("Started", {
|
|
21
|
+
process: ProcessId,
|
|
22
|
+
}) {
|
|
23
|
+
}
|
|
24
|
+
/** The command exited with `exitCode`. The stream closes after this message. */
|
|
25
|
+
export class Exited extends Schema.TaggedClass()("Exited", {
|
|
26
|
+
exitCode: Schema.Int,
|
|
27
|
+
}) {
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The sandbox can't run or follow the command: no sandbox, a stopped container, or an unknown
|
|
31
|
+
* process. The stream closes after this message.
|
|
32
|
+
*/
|
|
33
|
+
export class StreamFailed extends Schema.TaggedClass()("StreamFailed", {
|
|
34
|
+
message: Schema.String,
|
|
35
|
+
}) {
|
|
36
|
+
}
|
|
37
|
+
export const ServerMessage = Schema.Union([Started, Exited, StreamFailed]);
|
|
38
|
+
export const encodeClientMessage = Schema.encodeSync(Schema.fromJsonString(ClientMessage));
|
|
39
|
+
export const decodeClientMessage = Schema.decodeUnknownOption(Schema.fromJsonString(ClientMessage));
|
|
40
|
+
export const encodeServerMessage = Schema.encodeSync(Schema.fromJsonString(ServerMessage));
|
|
41
|
+
export const decodeServerMessage = Schema.decodeUnknownOption(Schema.fromJsonString(ServerMessage));
|
|
42
|
+
const CHANNEL_BYTES = { stdout: 1, stderr: 2 };
|
|
43
|
+
/** Returns the binary server message with `bytes` of `channel`: one channel byte, then the bytes. */
|
|
44
|
+
export const outputFrame = (channel, bytes) => {
|
|
45
|
+
const frame = new Uint8Array(bytes.length + 1);
|
|
46
|
+
frame[0] = CHANNEL_BYTES[channel];
|
|
47
|
+
frame.set(bytes, 1);
|
|
48
|
+
return frame;
|
|
49
|
+
};
|
|
50
|
+
/** Returns the channel and bytes of a binary server message, or `None` for an unknown channel byte. */
|
|
51
|
+
export const decodeOutputFrame = (frame) => {
|
|
52
|
+
const bytes = frame.subarray(1);
|
|
53
|
+
switch (frame[0]) {
|
|
54
|
+
case CHANNEL_BYTES.stdout:
|
|
55
|
+
return Option.some({ channel: "stdout", bytes });
|
|
56
|
+
case CHANNEL_BYTES.stderr:
|
|
57
|
+
return Option.some({ channel: "stderr", bytes });
|
|
58
|
+
default:
|
|
59
|
+
return Option.none();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* The query of an attach stream: the bytes of each output stream the client already has, so the
|
|
64
|
+
* stream resumes after them.
|
|
65
|
+
*/
|
|
66
|
+
export const AttachOffsets = Schema.Struct({
|
|
67
|
+
stdout: Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)),
|
|
68
|
+
stderr: Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)),
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/api/stream.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,sCAAsC;AACtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,UAAU,EACV,QAAQ,EACR,SAAS,EACT,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,MAAM,OAAO,SAAU,SAAQ,MAAM,CAAC,WAAW,EAAa,CAAC,WAAW,EAAE;IAC1E,OAAO,EAAE,MAAM,CAAC,cAAc;CAC/B,CAAC;CAAG;AAEL,uFAAuF;AACvF,MAAM,OAAO,QAAS,SAAQ,MAAM,CAAC,WAAW,EAAY,CAAC,UAAU,EAAE,EAAE,CAAC;CAAG;AAE/E,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;AAGjE,wEAAwE;AACxE,MAAM,OAAO,OAAQ,SAAQ,MAAM,CAAC,WAAW,EAAW,CAAC,SAAS,EAAE;IACpE,OAAO,EAAE,SAAS;CACnB,CAAC;CAAG;AAEL,gFAAgF;AAChF,MAAM,OAAO,MAAO,SAAQ,MAAM,CAAC,WAAW,EAAU,CAAC,QAAQ,EAAE;IACjE,QAAQ,EAAE,MAAM,CAAC,GAAG;CACrB,CAAC;CAAG;AAEL;;;GAGG;AACH,MAAM,OAAO,YAAa,SAAQ,MAAM,CAAC,WAAW,EAAgB,CAAC,cAAc,EAAE;IACnF,OAAO,EAAE,MAAM,CAAC,MAAM;CACvB,CAAC;CAAG;AAEL,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;AAG3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;AAC3F,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;AACpG,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;AAC3F,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;AAKpG,MAAM,aAAa,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAmD,CAAC;AAEhG,qGAAqG;AACrG,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAsB,EAAE,KAAiB,EAAc,EAAE;IACnF,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/C,KAAK,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAClC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpB,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAOF,uGAAuG;AACvG,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAiB,EAA8B,EAAE;IACjF,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAChC,QAAQ,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACjB,KAAK,aAAa,CAAC,MAAM;YACvB,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,KAAK,aAAa,CAAC,MAAM;YACvB,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD;YACE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;AACH,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;IACvF,MAAM,EAAE,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;CACxF,CAAC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option, Schema } from "effect";
|
|
2
|
+
import { SandboxName, type SandboxSummary } from "./api/api.ts";
|
|
3
|
+
declare const RegistryStorage_base: Context.ServiceClass<RegistryStorage, "@usine/cloudflare/RegistryStorage", {
|
|
4
|
+
readonly list: Effect.Effect<ReadonlyArray<SandboxSummary>>;
|
|
5
|
+
readonly put: (sandbox: SandboxSummary) => Effect.Effect<void>;
|
|
6
|
+
readonly remove: (name: SandboxName) => Effect.Effect<void>;
|
|
7
|
+
}>;
|
|
8
|
+
/** The registry Durable Object's storage: the last summary each sandbox recorded, by name. */
|
|
9
|
+
export declare class RegistryStorage extends RegistryStorage_base {
|
|
10
|
+
}
|
|
11
|
+
declare const SandboxLookupFailed_base: Schema.Class<SandboxLookupFailed, Schema.TaggedStruct<"SandboxLookupFailed", {
|
|
12
|
+
readonly name: Schema.brand<Schema.String, "SandboxName">;
|
|
13
|
+
readonly reason: Schema.String;
|
|
14
|
+
}>, import("effect/Cause").YieldableError>;
|
|
15
|
+
/** A sandbox's Durable Object didn't answer with its summary or a 404. */
|
|
16
|
+
export declare class SandboxLookupFailed extends SandboxLookupFailed_base {
|
|
17
|
+
}
|
|
18
|
+
declare const SandboxLookup_base: Context.ServiceClass<SandboxLookup, "@usine/cloudflare/SandboxLookup", {
|
|
19
|
+
/** Returns `None` only if the sandbox's Durable Object answers that it doesn't exist. */
|
|
20
|
+
readonly get: (name: SandboxName) => Effect.Effect<Option.Option<SandboxSummary>, SandboxLookupFailed>;
|
|
21
|
+
}>;
|
|
22
|
+
/** Reads a sandbox's current summary from its own Durable Object. */
|
|
23
|
+
export declare class SandboxLookup extends SandboxLookup_base {
|
|
24
|
+
}
|
|
25
|
+
/** The registry route of `SandboxApi`. Requires `RegistryStorage` and `SandboxLookup`. */
|
|
26
|
+
export declare const registryRoutes: Layer.Layer<never, never, import("effect/http/HttpRouter").HttpRouter | (import("effect/http/HttpRouter").Request<"Requires", RegistryStorage> | import("effect/http/HttpRouter").Request<"Requires", SandboxLookup>)>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhE,OAAO,EAEL,WAAW,EAGX,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;;mBAMH,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;kBAC7C,CAAC,OAAO,EAAE,cAAc,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;qBAC7C,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;;AAN/D,8FAA8F;AAC9F,qBAAa,eAAgB,SAAQ,oBAOG;CAAG;;;;;AAE3C,0EAA0E;AAC1E,qBAAa,mBAAoB,SAAQ,wBAGxC;CAAG;;IAMA,yFAAyF;kBAC3E,CACZ,IAAI,EAAE,WAAW,KACd,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,mBAAmB,CAAC;;AAP1E,qEAAqE;AACrE,qBAAa,aAAc,SAAQ,kBAQG;CAAG;AAwDzC,0FAA0F;AAC1F,eAAO,MAAM,cAAc,wNAG1B,CAAC"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option, Schema } from "effect";
|
|
2
|
+
import { HttpServer } from "effect/http";
|
|
3
|
+
import { HttpApi, HttpApiBuilder } from "effect/http-api";
|
|
4
|
+
import { registryGroup, SandboxName, SandboxList, } from "./api/api.js";
|
|
5
|
+
/** The registry Durable Object's storage: the last summary each sandbox recorded, by name. */
|
|
6
|
+
export class RegistryStorage extends Context.Service()("@usine/cloudflare/RegistryStorage") {
|
|
7
|
+
}
|
|
8
|
+
/** A sandbox's Durable Object didn't answer with its summary or a 404. */
|
|
9
|
+
export class SandboxLookupFailed extends Schema.TaggedError()("SandboxLookupFailed", { name: SandboxName, reason: Schema.String }) {
|
|
10
|
+
}
|
|
11
|
+
/** Reads a sandbox's current summary from its own Durable Object. */
|
|
12
|
+
export class SandboxLookup extends Context.Service()("@usine/cloudflare/SandboxLookup") {
|
|
13
|
+
}
|
|
14
|
+
/** The number of sandboxes whose summary a listing reads at once. */
|
|
15
|
+
const LOOKUP_CONCURRENCY = 8;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the sandboxes of `owner`, each as its Durable Object reports it now. Forgets entries
|
|
18
|
+
* whose sandbox no longer exists, such as one whose destroy couldn't reach the registry. A sandbox
|
|
19
|
+
* whose Durable Object doesn't answer keeps its entry and is left out of this listing, so nothing
|
|
20
|
+
* destroys it until a listing can read it.
|
|
21
|
+
*/
|
|
22
|
+
const list = Effect.fn("SandboxRegistry.list")(function* (owner) {
|
|
23
|
+
yield* Effect.annotateCurrentSpan({ owner });
|
|
24
|
+
const storage = yield* RegistryStorage;
|
|
25
|
+
const lookup = yield* SandboxLookup;
|
|
26
|
+
const recorded = (yield* storage.list).filter((sandbox) => sandbox.owner === owner);
|
|
27
|
+
const current = yield* Effect.forEach(recorded, (sandbox) => lookup.get(sandbox.name).pipe(Effect.tap((found) => Option.isNone(found)
|
|
28
|
+
? storage.remove(sandbox.name).pipe(Effect.andThen(Effect.logInfo("Forgot a sandbox that no longer exists", {
|
|
29
|
+
sandbox: sandbox.name,
|
|
30
|
+
owner,
|
|
31
|
+
})))
|
|
32
|
+
: Effect.void), Effect.catchTag("SandboxLookupFailed", (failure) => Effect.logWarning("Could not read a sandbox's state; kept its registration and left it out of the listing", { sandbox: sandbox.name, owner, reason: failure.reason }).pipe(Effect.as(Option.none())))), { concurrency: LOOKUP_CONCURRENCY });
|
|
33
|
+
const sandboxes = current.flatMap(Option.toArray);
|
|
34
|
+
yield* Effect.logInfo("Listed the Worker's sandboxes", {
|
|
35
|
+
owner,
|
|
36
|
+
sandboxes: sandboxes.length,
|
|
37
|
+
running: sandboxes.filter(({ running }) => running).length,
|
|
38
|
+
});
|
|
39
|
+
return new SandboxList({ sandboxes });
|
|
40
|
+
});
|
|
41
|
+
const RegistryServedApi = HttpApi.make("SandboxApi").add(registryGroup);
|
|
42
|
+
const registryHandlers = HttpApiBuilder.group(RegistryServedApi, "registry", (routes) => routes.handle("list", ({ query }) => list(query.owner)));
|
|
43
|
+
/** The registry route of `SandboxApi`. Requires `RegistryStorage` and `SandboxLookup`. */
|
|
44
|
+
export const registryRoutes = HttpApiBuilder.layer(RegistryServedApi).pipe(Layer.provide(registryHandlers), Layer.provide(HttpServer.layerServices));
|
|
45
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CloudflareLlmProvider } from "@usine/core/worker";
|
|
2
|
+
import { Option, Redacted } from "effect";
|
|
3
|
+
import type { ProviderCredential, SandboxCredentials } from "../api/api.ts";
|
|
4
|
+
import { type GitHubHost } from "../api/harness.ts";
|
|
5
|
+
/** GitHub requests over HTTPS, with the Attempt's installation token added. */
|
|
6
|
+
interface GitHubInterception {
|
|
7
|
+
readonly _tag: "GitHub";
|
|
8
|
+
readonly host: GitHubHost;
|
|
9
|
+
readonly token: Redacted.Redacted<string>;
|
|
10
|
+
}
|
|
11
|
+
/** Plain HTTP requests to a provider's sandbox host, forwarded to the provider over HTTPS. */
|
|
12
|
+
interface ProviderInterception {
|
|
13
|
+
readonly _tag: "Provider";
|
|
14
|
+
readonly host: string;
|
|
15
|
+
readonly provider: CloudflareLlmProvider;
|
|
16
|
+
readonly credential: ProviderCredential;
|
|
17
|
+
}
|
|
18
|
+
/** HTTPS requests to an allowlisted host, such as a package registry, forwarded unchanged. */
|
|
19
|
+
interface AllowedInterception {
|
|
20
|
+
readonly _tag: "Allowed";
|
|
21
|
+
readonly host: string;
|
|
22
|
+
}
|
|
23
|
+
/** Requests to a GitHub or provider host without a credential, refused with status 403. */
|
|
24
|
+
interface DeniedInterception {
|
|
25
|
+
readonly _tag: "Denied";
|
|
26
|
+
readonly host: string;
|
|
27
|
+
readonly scheme: "http" | "https";
|
|
28
|
+
}
|
|
29
|
+
/** One host whose container traffic goes to an outbound handler instead of the internet. */
|
|
30
|
+
export type Interception = GitHubInterception | ProviderInterception | AllowedInterception | DeniedInterception;
|
|
31
|
+
/**
|
|
32
|
+
* Returns the hosts a sandbox reaches: GitHub and the providers with the Attempt's credentials,
|
|
33
|
+
* and `allowedHosts` unchanged. The container has no other way out. GitHub and provider hosts
|
|
34
|
+
* without a credential are denied rather than left out, because a running container keeps its
|
|
35
|
+
* last handler for a host; they're never treated as allowlisted.
|
|
36
|
+
*/
|
|
37
|
+
export declare const interceptionsFor: (credentials: SandboxCredentials, allowedHosts: ReadonlyArray<string>) => ReadonlyArray<Interception>;
|
|
38
|
+
/** Returns the hosts in a comma-separated `ALLOWED_HOSTS` value, lowercased, without blanks. */
|
|
39
|
+
export declare const allowedHostsOf: (value: string) => ReadonlyArray<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Returns `request` with the installation token in place of any credential the sandbox sent:
|
|
42
|
+
* HTTP basic authentication for git on `github.com`, and a token for the REST and GraphQL API
|
|
43
|
+
* on `api.github.com`.
|
|
44
|
+
*/
|
|
45
|
+
export declare const gitHubRequest: (request: Request, host: GitHubHost, token: Redacted.Redacted<string>) => Request;
|
|
46
|
+
/**
|
|
47
|
+
* Returns `request`, sent over plain HTTP to the provider's sandbox host, for the provider's
|
|
48
|
+
* upstream over HTTPS with the provider credential in place of the sandbox's placeholder.
|
|
49
|
+
* Returns `None` for a request that isn't one of the provider's `HARNESS_ROUTES`.
|
|
50
|
+
*/
|
|
51
|
+
export declare const providerRequest: (request: Request, provider: CloudflareLlmProvider, credential: ProviderCredential) => Option.Option<Request>;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=outbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/sandbox/outbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAS,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAgB,KAAK,UAAU,EAAkB,MAAM,mBAAmB,CAAC;AAElF,+EAA+E;AAC/E,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED,8FAA8F;AAC9F,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;IACzC,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;CACzC;AAED,8FAA8F;AAC9F,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,2FAA2F;AAC3F,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,4FAA4F;AAC5F,MAAM,MAAM,YAAY,GACpB,kBAAkB,GAClB,oBAAoB,GACpB,mBAAmB,GACnB,kBAAkB,CAAC;AAEvB;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,gBACd,kBAAkB,gBACjB,aAAa,CAAC,MAAM,CAAC,KAClC,aAAa,CAAC,YAAY,CAqB5B,CAAC;AAEF,gGAAgG;AAChG,eAAO,MAAM,cAAc,UAAW,MAAM,KAAG,aAAa,CAAC,MAAM,CAIjC,CAAC;AAuBnC;;;;GAIG;AACH,eAAO,MAAM,aAAa,YACf,OAAO,QACV,UAAU,SACT,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,KAC/B,OAQF,CAAC;AA4BF;;;;GAIG;AACH,eAAO,MAAM,eAAe,YACjB,OAAO,YACN,qBAAqB,cACnB,kBAAkB,KAC7B,MAAM,CAAC,MAAM,CAAC,OAAO,CAoBvB,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Match, Option, Redacted } from "effect";
|
|
2
|
+
import { GITHUB_HOSTS, PROVIDER_HOSTS } from "../api/harness.js";
|
|
3
|
+
/**
|
|
4
|
+
* Returns the hosts a sandbox reaches: GitHub and the providers with the Attempt's credentials,
|
|
5
|
+
* and `allowedHosts` unchanged. The container has no other way out. GitHub and provider hosts
|
|
6
|
+
* without a credential are denied rather than left out, because a running container keeps its
|
|
7
|
+
* last handler for a host; they're never treated as allowlisted.
|
|
8
|
+
*/
|
|
9
|
+
export const interceptionsFor = (credentials, allowedHosts) => {
|
|
10
|
+
const providerHosts = Object.values(PROVIDER_HOSTS).map(({ sandbox }) => sandbox);
|
|
11
|
+
const reserved = new Set([...GITHUB_HOSTS, ...providerHosts]);
|
|
12
|
+
const providers = Object.entries(PROVIDER_HOSTS).map(([provider, { sandbox }]) => {
|
|
13
|
+
const grant = credentials.providers.find((candidate) => candidate.provider === provider);
|
|
14
|
+
return grant === undefined
|
|
15
|
+
? { _tag: "Denied", host: sandbox, scheme: "http" }
|
|
16
|
+
: { _tag: "Provider", host: sandbox, provider: grant.provider, credential: grant.credential };
|
|
17
|
+
});
|
|
18
|
+
return [
|
|
19
|
+
...GITHUB_HOSTS.map((host) => Option.match(credentials.github, {
|
|
20
|
+
onNone: () => ({ _tag: "Denied", host, scheme: "https" }),
|
|
21
|
+
onSome: (token) => ({ _tag: "GitHub", host, token }),
|
|
22
|
+
})),
|
|
23
|
+
...providers,
|
|
24
|
+
...[...new Set(allowedHosts)]
|
|
25
|
+
.filter((host) => !reserved.has(host))
|
|
26
|
+
.map((host) => ({ _tag: "Allowed", host })),
|
|
27
|
+
];
|
|
28
|
+
};
|
|
29
|
+
/** Returns the hosts in a comma-separated `ALLOWED_HOSTS` value, lowercased, without blanks. */
|
|
30
|
+
export const allowedHostsOf = (value) => value
|
|
31
|
+
.split(",")
|
|
32
|
+
.map((host) => host.trim().toLowerCase())
|
|
33
|
+
.filter((host) => host !== "");
|
|
34
|
+
/** The headers a tool in the sandbox can use to send a credential. All are replaced. */
|
|
35
|
+
const CREDENTIAL_HEADERS = ["authorization", "x-api-key", "proxy-authorization"];
|
|
36
|
+
/**
|
|
37
|
+
* Returns the URL for `host` over HTTPS with the path and query of `request`. The host comes from
|
|
38
|
+
* the handler, never from the sandbox: a path such as `//attacker.example/x` stays a path.
|
|
39
|
+
*/
|
|
40
|
+
const upstreamUrl = (request, host) => {
|
|
41
|
+
const incoming = new URL(request.url);
|
|
42
|
+
const upstream = new URL(`https://${host}`);
|
|
43
|
+
upstream.pathname = incoming.pathname;
|
|
44
|
+
upstream.search = incoming.search;
|
|
45
|
+
return upstream;
|
|
46
|
+
};
|
|
47
|
+
const withoutCredentials = (request) => {
|
|
48
|
+
const headers = new Headers(request.headers);
|
|
49
|
+
for (const name of CREDENTIAL_HEADERS)
|
|
50
|
+
headers.delete(name);
|
|
51
|
+
return headers;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Returns `request` with the installation token in place of any credential the sandbox sent:
|
|
55
|
+
* HTTP basic authentication for git on `github.com`, and a token for the REST and GraphQL API
|
|
56
|
+
* on `api.github.com`.
|
|
57
|
+
*/
|
|
58
|
+
export const gitHubRequest = (request, host, token) => {
|
|
59
|
+
const headers = withoutCredentials(request);
|
|
60
|
+
const value = Redacted.value(token);
|
|
61
|
+
headers.set("authorization", host === "github.com" ? `Basic ${btoa(`x-access-token:${value}`)}` : `token ${value}`);
|
|
62
|
+
return new Request(upstreamUrl(request, host), new Request(request, { headers }));
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The routes each provider's handler forwards: the paths that the Gateway's `llm.<domain>` serves
|
|
66
|
+
* (ADR 0027), each with the method that the harnesses use. Each pattern matches whole plain
|
|
67
|
+
* segments, so a percent-encoded separator or a dot segment never matches, and the rest of the
|
|
68
|
+
* provider's API, such as files or organization administration, can't be reached with the key.
|
|
69
|
+
*/
|
|
70
|
+
const HARNESS_ROUTES = {
|
|
71
|
+
anthropic: [
|
|
72
|
+
{ method: "POST", path: /^\/v1\/messages$/u },
|
|
73
|
+
{ method: "POST", path: /^\/v1\/messages\/count_tokens$/u },
|
|
74
|
+
{ method: "GET", path: /^\/v1\/models$/u },
|
|
75
|
+
{ method: "GET", path: /^\/v1\/models\/[A-Za-z0-9][A-Za-z0-9._-]*$/u },
|
|
76
|
+
],
|
|
77
|
+
openai: [
|
|
78
|
+
{ method: "POST", path: /^\/v1\/responses$/u },
|
|
79
|
+
{ method: "POST", path: /^\/v1\/responses\/compact$/u },
|
|
80
|
+
{ method: "GET", path: /^\/v1\/models$/u },
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Returns `request`, sent over plain HTTP to the provider's sandbox host, for the provider's
|
|
85
|
+
* upstream over HTTPS with the provider credential in place of the sandbox's placeholder.
|
|
86
|
+
* Returns `None` for a request that isn't one of the provider's `HARNESS_ROUTES`.
|
|
87
|
+
*/
|
|
88
|
+
export const providerRequest = (request, provider, credential) => {
|
|
89
|
+
const upstream = upstreamUrl(request, PROVIDER_HOSTS[provider].upstream);
|
|
90
|
+
const routes = HARNESS_ROUTES[provider];
|
|
91
|
+
if (!routes.some(({ method, path }) => method === request.method && path.test(upstream.pathname)))
|
|
92
|
+
return Option.none();
|
|
93
|
+
const headers = withoutCredentials(request);
|
|
94
|
+
headers.delete("host");
|
|
95
|
+
Match.valueTags(credential, {
|
|
96
|
+
ApiKey: ({ key }) => {
|
|
97
|
+
switch (provider) {
|
|
98
|
+
case "anthropic":
|
|
99
|
+
headers.set("x-api-key", Redacted.value(key));
|
|
100
|
+
return;
|
|
101
|
+
case "openai":
|
|
102
|
+
headers.set("authorization", `Bearer ${Redacted.value(key)}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
return Option.some(new Request(upstream, new Request(request, { headers })));
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=outbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../src/sandbox/outbound.ts"],"names":[],"mappings":"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"}
|