hyperbrane 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/README.md +41 -0
- package/dist/agent/agentService.d.ts +31 -0
- package/dist/agent/agentService.js +388 -0
- package/dist/agent/agentStorage.d.ts +27 -0
- package/dist/agent/agentStorage.js +52 -0
- package/dist/agent/api.d.ts +264 -0
- package/dist/agent/api.js +14 -0
- package/dist/agent/gatewaySignIns.d.ts +5 -0
- package/dist/agent/gatewaySignIns.js +11 -0
- package/dist/agent/session/client.d.ts +14 -0
- package/dist/agent/session/client.js +47 -0
- package/dist/agent/session/sessionStorage.d.ts +66 -0
- package/dist/agent/session/sessionStorage.js +89 -0
- package/dist/agent/session/sharedConnection.d.ts +13 -0
- package/dist/agent/session/sharedConnection.js +55 -0
- package/dist/agent/session/worker/jsonRpc.d.ts +39 -0
- package/dist/agent/session/worker/jsonRpc.js +102 -0
- package/dist/agent/session/worker/protocol.d.ts +120 -0
- package/dist/agent/session/worker/protocol.js +32 -0
- package/dist/agent/session/workerConnection.d.ts +29 -0
- package/dist/agent/session/workerConnection.js +44 -0
- package/dist/agent/session/workerSseConnection.d.ts +15 -0
- package/dist/agent/session/workerSseConnection.js +26 -0
- package/dist/agent/workerBundle.d.ts +19 -0
- package/dist/agent/workerBundle.generated.d.ts +1 -0
- package/dist/agent/workerBundle.generated.js +2 -0
- package/dist/agent/workerBundle.js +31 -0
- package/dist/cli/agent.d.ts +3 -0
- package/dist/cli/agent.js +423 -0
- package/dist/cli/command.d.ts +46 -0
- package/dist/cli/command.js +98 -0
- package/dist/cli/dimension.d.ts +2 -0
- package/dist/cli/dimension.js +85 -0
- package/dist/cli/gateway.d.ts +6 -0
- package/dist/cli/gateway.js +245 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +125 -0
- package/dist/cli/portal.d.ts +2 -0
- package/dist/cli/portal.js +39 -0
- package/dist/cli/services.d.ts +18 -0
- package/dist/cli/services.js +33 -0
- package/dist/cli/streamPrinter.d.ts +23 -0
- package/dist/cli/streamPrinter.js +251 -0
- package/dist/cli/table.d.ts +7 -0
- package/dist/cli/table.js +54 -0
- package/dist/dimension/api.d.ts +67 -0
- package/dist/dimension/api.js +81 -0
- package/dist/dimension/http.d.ts +7 -0
- package/dist/dimension/http.js +77 -0
- package/dist/dimension/image.d.ts +13 -0
- package/dist/dimension/image.js +43 -0
- package/dist/dimension/imageFiles.generated.d.ts +1 -0
- package/dist/dimension/imageFiles.generated.js +5 -0
- package/dist/dimension/paths.d.ts +5 -0
- package/dist/dimension/paths.js +7 -0
- package/dist/dimension/service.d.ts +22 -0
- package/dist/dimension/service.js +242 -0
- package/dist/dimension/storage.d.ts +39 -0
- package/dist/dimension/storage.js +78 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +25 -0
- package/dist/lib/docker-api.d.ts +13787 -0
- package/dist/lib/docker-api.js +5 -0
- package/dist/lib/docker.d.ts +77 -0
- package/dist/lib/docker.js +214 -0
- package/dist/lib/image.d.ts +14 -0
- package/dist/lib/image.js +29 -0
- package/dist/lib/jsonDirectory.d.ts +28 -0
- package/dist/lib/jsonDirectory.js +102 -0
- package/dist/lib/sleep.d.ts +2 -0
- package/dist/lib/sleep.js +4 -0
- package/dist/lib/tar.d.ts +2 -0
- package/dist/lib/tar.js +65 -0
- package/dist/lib/version.d.ts +1 -0
- package/dist/lib/version.js +1 -0
- package/dist/portal-gateway/api.d.ts +98 -0
- package/dist/portal-gateway/api.js +12 -0
- package/dist/portal-gateway/fakes.d.ts +3 -0
- package/dist/portal-gateway/fakes.js +11 -0
- package/dist/portal-gateway/gatewayService.d.ts +37 -0
- package/dist/portal-gateway/gatewayService.js +380 -0
- package/dist/portal-gateway/gatewayStorage.d.ts +66 -0
- package/dist/portal-gateway/gatewayStorage.js +125 -0
- package/dist/portal-gateway/image.d.ts +6 -0
- package/dist/portal-gateway/image.js +13 -0
- package/dist/portal-gateway/imageFiles.generated.d.ts +1 -0
- package/dist/portal-gateway/imageFiles.generated.js +8 -0
- package/dist/portal-gateway/portalService.d.ts +48 -0
- package/dist/portal-gateway/portalService.js +327 -0
- package/dist/portal-gateway/portalStorage.d.ts +34 -0
- package/dist/portal-gateway/portalStorage.js +44 -0
- package/dist/portal-gateway/upstream.d.ts +157 -0
- package/dist/portal-gateway/upstream.js +4 -0
- package/dist/sandbox/api.d.ts +184 -0
- package/dist/sandbox/api.js +14 -0
- package/dist/sandbox/boatService.d.ts +52 -0
- package/dist/sandbox/boatService.js +335 -0
- package/dist/sandbox/containerService.d.ts +15 -0
- package/dist/sandbox/containerService.js +177 -0
- package/dist/sandbox/endpointProxy.d.ts +90 -0
- package/dist/sandbox/endpointProxy.js +701 -0
- package/dist/sandbox/exeDevAuth.d.ts +16 -0
- package/dist/sandbox/exeDevAuth.js +49 -0
- package/dist/sandbox/localService.d.ts +19 -0
- package/dist/sandbox/localService.js +182 -0
- package/dist/sandbox/sseConnection.d.ts +41 -0
- package/dist/sandbox/sseConnection.js +166 -0
- package/dist/sandbox/sshService.d.ts +30 -0
- package/dist/sandbox/sshService.js +647 -0
- package/dist/upstreams/anthropic.d.ts +7 -0
- package/dist/upstreams/anthropic.js +27 -0
- package/dist/upstreams/index.d.ts +6 -0
- package/dist/upstreams/index.js +7 -0
- package/dist/upstreams/openai.d.ts +7 -0
- package/dist/upstreams/openai.js +40 -0
- package/dist/vendor-types/claude/agent-sdk/agentSdkTypes.d.ts +1 -0
- package/dist/vendor-types/claude/agent-sdk/bridge.d.ts +388 -0
- package/dist/vendor-types/claude/agent-sdk/browser-sdk.d.ts +184 -0
- package/dist/vendor-types/claude/agent-sdk/core.d.ts +20 -0
- package/dist/vendor-types/claude/agent-sdk/extractFromBunfs.d.ts +1 -0
- package/dist/vendor-types/claude/agent-sdk/sdk-tools.d.ts +4153 -0
- package/dist/vendor-types/claude/agent-sdk/sdk.d.ts +9833 -0
- package/package.json +55 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An upstream: a service a gateway holds a credential for, described as data (adapted from v2's
|
|
3
|
+
* UpstreamDef). SDK clients define upstreams in TypeScript with `defineUpstream`; the definition
|
|
4
|
+
* is plain JSON, sent to the gateway and applied there by generic code that knows nothing about
|
|
5
|
+
* any particular upstream.
|
|
6
|
+
*
|
|
7
|
+
* The secret never reaches a dimension. Dimensions hold fakes, shaped like the real tokens so their
|
|
8
|
+
* clients accept them, and the gateway replaces them in transit. Clients sign in from dimensions as
|
|
9
|
+
* they normally would: the first sign-in goes to the upstream and the gateway keeps the tokens; once
|
|
10
|
+
* it holds them, it completes every later sign-in itself (see OAuthMode.tokenUrl and `answers`).
|
|
11
|
+
*
|
|
12
|
+
* Credentials are scoped to the gateway: one per upstream, used for every dimension with a portal
|
|
13
|
+
* to it. Switching accounts means deleting the gateway's credential, so the next sign-in goes to
|
|
14
|
+
* the upstream again.
|
|
15
|
+
*/
|
|
16
|
+
export type UpstreamDefinition = {
|
|
17
|
+
name: string;
|
|
18
|
+
/** At least one. The gateway swaps the fakes of every mode that has a credential. */
|
|
19
|
+
modes: {
|
|
20
|
+
oauth?: OAuthMode;
|
|
21
|
+
apiKey?: ApiKeyMode;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
type ModeBase = {
|
|
25
|
+
/**
|
|
26
|
+
* Requests to these hosts get the credential. Matched on host alone: v1 also matched the
|
|
27
|
+
* user-agent, and a client changing its user-agent silently stopped getting its token.
|
|
28
|
+
*/
|
|
29
|
+
hosts: string[];
|
|
30
|
+
/** Where the credential goes on a matching request, replacing any fake the client sent. */
|
|
31
|
+
present: {
|
|
32
|
+
header: string;
|
|
33
|
+
scheme?: "Bearer";
|
|
34
|
+
} | {
|
|
35
|
+
query: string;
|
|
36
|
+
};
|
|
37
|
+
/** What to force onto every matching request, whatever the client sent. */
|
|
38
|
+
force?: {
|
|
39
|
+
/** Retarget the request, e.g. "https://api.internal:8443". */
|
|
40
|
+
target?: string;
|
|
41
|
+
/** Prefix prepended to the request path, so dimensions needn't know where the API is mounted. */
|
|
42
|
+
path?: string;
|
|
43
|
+
/** Query params the client doesn't get to choose. */
|
|
44
|
+
query?: Record<string, string>;
|
|
45
|
+
/** Headers the client never sees the values of. */
|
|
46
|
+
headers?: Record<string, string>;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export type OAuthMode = ModeBase & {
|
|
50
|
+
/**
|
|
51
|
+
* The token endpoint. Until the gateway holds a credential, requests go through and the response
|
|
52
|
+
* is kept as the credential, reaching the dimension with fakes in place of the tokens. From then
|
|
53
|
+
* on the gateway answers them itself from that credential: a sign-in (whatever code it carries)
|
|
54
|
+
* and a refresh made with a fake refresh token. A client's refresh goes upstream, replayed with the
|
|
55
|
+
* real refresh token (its encoding, client id, scope, User-Agent: nothing about refreshing needs
|
|
56
|
+
* declaring); refresh tokens rotate and every dimension shares the credential, so refreshes are
|
|
57
|
+
* made one at a time and those arriving together share the result. Every client holds the same
|
|
58
|
+
* fakes, so all of them get the new tokens. A sign-in to an expired credential refreshes it first;
|
|
59
|
+
* one the gateway can't refresh (no client has refreshed yet, or upstream refuses) counts as none,
|
|
60
|
+
* and the sign-in goes to the upstream again.
|
|
61
|
+
*/
|
|
62
|
+
tokenUrl: string;
|
|
63
|
+
/** What the fakes look like, per token in the token response. */
|
|
64
|
+
fakes: {
|
|
65
|
+
access: FakeShape;
|
|
66
|
+
refresh: FakeShape;
|
|
67
|
+
id?: FakeShape;
|
|
68
|
+
};
|
|
69
|
+
/** Other secrets the token endpoint returns, dropped before the response reaches the dimension. */
|
|
70
|
+
strip?: {
|
|
71
|
+
fields?: string[];
|
|
72
|
+
headers?: string[];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Other sign-in steps the gateway answers itself once it holds a credential, so the client's
|
|
76
|
+
* sign-in completes without a browser: e.g. the endpoint a device flow polls until approved.
|
|
77
|
+
*/
|
|
78
|
+
answers?: {
|
|
79
|
+
url: string;
|
|
80
|
+
status?: number;
|
|
81
|
+
json: unknown;
|
|
82
|
+
}[];
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* The gateway holds any number of named keys for the upstream and gives each its own fake. A request
|
|
86
|
+
* goes out with the real key its fake maps to, so a dimension can use different keys side by side
|
|
87
|
+
* (one per process, per config entry, ...). A value that isn't one of the fakes is left as it is.
|
|
88
|
+
*/
|
|
89
|
+
export type ApiKeyMode = ModeBase & {
|
|
90
|
+
fake: FakeShape;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* `prefix`: the fake is the prefix plus random characters, as long as the real value (clients check
|
|
94
|
+
* both; claude blanks a credentials file whose token it doesn't recognise).
|
|
95
|
+
* `jwt`: the fake carries the real token's header and claims with a signature that can't verify,
|
|
96
|
+
* for clients that read claims (codex decides when to refresh from `exp`). It's re-issued whenever
|
|
97
|
+
* the real token changes, so its claims keep tracking the real ones.
|
|
98
|
+
*/
|
|
99
|
+
export type FakeShape = {
|
|
100
|
+
prefix: string;
|
|
101
|
+
} | {
|
|
102
|
+
jwt: true;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* What the gateway keeps for an upstream, in its state directory, one file per writer:
|
|
106
|
+
* credentials/<upstream>.oauth.json (the gateway, as it captures and refreshes tokens) and
|
|
107
|
+
* credentials/<upstream>.api-keys.json (the service).
|
|
108
|
+
*/
|
|
109
|
+
export type OAuthCredential = {
|
|
110
|
+
access: string;
|
|
111
|
+
refresh?: string;
|
|
112
|
+
id?: string;
|
|
113
|
+
/** Epoch ms. */
|
|
114
|
+
expiresAt?: number;
|
|
115
|
+
refreshExpiresAt?: number;
|
|
116
|
+
scopes?: string[];
|
|
117
|
+
/** Epoch ms of the last sign-in or refresh. */
|
|
118
|
+
lastRefresh: number;
|
|
119
|
+
/**
|
|
120
|
+
* Issued once and kept across refreshes and sign-ins, so every dimension's copy stays valid. A
|
|
121
|
+
* prefix fake is recognised by its value; a JWT fake by its signature, since its header and
|
|
122
|
+
* claims follow the real token's, so a stale copy still maps to the current token.
|
|
123
|
+
*/
|
|
124
|
+
fakes: {
|
|
125
|
+
access: StoredFake;
|
|
126
|
+
refresh?: StoredFake;
|
|
127
|
+
id?: StoredFake;
|
|
128
|
+
};
|
|
129
|
+
/** The last token response minus stripped fields; the gateway answers sign-ins and refreshes from it. */
|
|
130
|
+
response: Record<string, unknown>;
|
|
131
|
+
/**
|
|
132
|
+
* The last refresh request a client made, minus its refresh token: the gateway replays it with the
|
|
133
|
+
* real one, so upstream sees the CLI's own request (User-Agent null when the client sent none).
|
|
134
|
+
*/
|
|
135
|
+
refreshRequest?: {
|
|
136
|
+
contentType: string;
|
|
137
|
+
userAgent: string | null;
|
|
138
|
+
fields: Record<string, unknown>;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
export type StoredFake = {
|
|
142
|
+
value: string;
|
|
143
|
+
} | {
|
|
144
|
+
jwtSignature: string;
|
|
145
|
+
};
|
|
146
|
+
export type ApiKeyCredential = {
|
|
147
|
+
/** Key name -> key. */
|
|
148
|
+
keys: Record<string, ApiKey>;
|
|
149
|
+
};
|
|
150
|
+
/** `fake` is issued once and kept, so a dimension's configuration stays valid when the key changes. */
|
|
151
|
+
export type ApiKey = {
|
|
152
|
+
key: string;
|
|
153
|
+
fake: string;
|
|
154
|
+
};
|
|
155
|
+
/** Declares an upstream; checks the shape at compile time and returns it unchanged. */
|
|
156
|
+
export declare function defineUpstream(definition: UpstreamDefinition): UpstreamDefinition;
|
|
157
|
+
export {};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
export type RunCommandOptions = {
|
|
2
|
+
/** Sandbox path; defaults to the sandbox root. */
|
|
3
|
+
cwd?: string;
|
|
4
|
+
env?: Record<string, string>;
|
|
5
|
+
stdin?: string | Uint8Array;
|
|
6
|
+
};
|
|
7
|
+
export type RunCommandResult = {
|
|
8
|
+
exitCode: number;
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
};
|
|
12
|
+
export type SandboxFileInfo = {
|
|
13
|
+
type: "file" | "directory" | "other";
|
|
14
|
+
uid: number;
|
|
15
|
+
gid: number;
|
|
16
|
+
};
|
|
17
|
+
export type SandboxFetchInit = {
|
|
18
|
+
method?: string;
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
body?: string | Uint8Array<ArrayBuffer>;
|
|
21
|
+
/** Sandbox path of a unix socket to send the request over (e.g. the docker API socket). */
|
|
22
|
+
unix?: string;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
/**
|
|
25
|
+
* Idle timeout in milliseconds: the request fails once nothing has been sent or received for this
|
|
26
|
+
* long. `false` disables it, for streams that can stay quiet indefinitely. Defaults to the backend's.
|
|
27
|
+
*/
|
|
28
|
+
timeout?: number | false;
|
|
29
|
+
};
|
|
30
|
+
/** A stream connection to a unix socket inside a sandbox (see Sandbox.connect). */
|
|
31
|
+
export type SandboxConnection = {
|
|
32
|
+
write(data: Uint8Array): void;
|
|
33
|
+
/** Receives every chunk read from the socket, in order. */
|
|
34
|
+
onData(listener: (data: Uint8Array) => void): void;
|
|
35
|
+
/** Resolves once the connection is closed, by either side or by an error. */
|
|
36
|
+
closed: Promise<void>;
|
|
37
|
+
close(): void;
|
|
38
|
+
};
|
|
39
|
+
/** What an accessible endpoint forwards to, inside the sandbox. */
|
|
40
|
+
export type SandboxEndpointTarget = {
|
|
41
|
+
port: number;
|
|
42
|
+
} | {
|
|
43
|
+
unix: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* What a client must present to an accessible endpoint. There is deliberately no unauthenticated
|
|
47
|
+
* variant: every endpoint requires one of these.
|
|
48
|
+
*/
|
|
49
|
+
export type SandboxEndpointCredentials =
|
|
50
|
+
/** `Authorization: Bearer <token>`. */
|
|
51
|
+
{
|
|
52
|
+
scheme: "bearer";
|
|
53
|
+
token: string;
|
|
54
|
+
}
|
|
55
|
+
/** `Authorization: Basic ...`; browsers prompt for it on their own. */
|
|
56
|
+
| {
|
|
57
|
+
scheme: "basic";
|
|
58
|
+
username: string;
|
|
59
|
+
password: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Logged in with exe.dev, as a user the VM is shared with; exe.dev's proxy checks it (exeDevAuth.ts).
|
|
63
|
+
* The login is per port: a browser that isn't logged in on the endpoint's goes through `loginUrl`
|
|
64
|
+
* first (exeDevLoginUrl in agent/session/workerSseConnection.ts).
|
|
65
|
+
*/
|
|
66
|
+
| {
|
|
67
|
+
scheme: "exe.dev";
|
|
68
|
+
loginUrl: string;
|
|
69
|
+
};
|
|
70
|
+
export type SandboxEndpointScheme = SandboxEndpointCredentials["scheme"];
|
|
71
|
+
export type SandboxEndpointRequest = {
|
|
72
|
+
target: SandboxEndpointTarget;
|
|
73
|
+
/** Required: the endpoint is never open to unauthenticated clients. */
|
|
74
|
+
scheme: SandboxEndpointScheme;
|
|
75
|
+
/** Port the endpoint listens on; a free one (that its auth allows) if omitted. */
|
|
76
|
+
port?: number;
|
|
77
|
+
/**
|
|
78
|
+
* "http" (the default): the target speaks HTTP, and requests go through to it. "sse": the target
|
|
79
|
+
* is a stream socket speaking lines of text (an agent worker's JSON-RPC); each `GET /` opens a
|
|
80
|
+
* connection to it and streams its lines back as server-sent events, and `POST /<connection>`
|
|
81
|
+
* writes the request body to it (see endpointProxy.ts).
|
|
82
|
+
*/
|
|
83
|
+
transport?: "http" | "sse";
|
|
84
|
+
/**
|
|
85
|
+
* Web origins (e.g. "https://app.example.com") whose pages may call the endpoint from a browser:
|
|
86
|
+
* the proxy answers their CORS preflights and lets them read "sse" responses. None by default.
|
|
87
|
+
*/
|
|
88
|
+
allowedOrigins?: string[];
|
|
89
|
+
};
|
|
90
|
+
export type SandboxEndpoint = {
|
|
91
|
+
id: string;
|
|
92
|
+
/**
|
|
93
|
+
* Where the client reaches the endpoint, directly over the network (not through the backend's own
|
|
94
|
+
* connection), at the host the sandbox was created with.
|
|
95
|
+
*/
|
|
96
|
+
url: string;
|
|
97
|
+
/** What the client presents; requests without it are refused before reaching the target. */
|
|
98
|
+
credentials: SandboxEndpointCredentials;
|
|
99
|
+
/** As requested: fixed for the endpoint's life. */
|
|
100
|
+
allowedOrigins: string[];
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Paths are sandbox paths: relative or absolute, both resolved against the sandbox root.
|
|
104
|
+
* Operations that report a missing path throw SandboxNotFoundError, whatever the backend.
|
|
105
|
+
*/
|
|
106
|
+
export interface SandboxFileOperations {
|
|
107
|
+
/** Throws SandboxNotFoundError if missing. */
|
|
108
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
109
|
+
/** Atomically replaces the file (readers never see partial content). Creates missing parent directories. */
|
|
110
|
+
writeFile(path: string, data: string | Uint8Array): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Writes the file only if it doesn't exist yet; returns false if it does. Atomic across concurrent
|
|
113
|
+
* callers and never exposes partial content. Creates missing parent directories.
|
|
114
|
+
*/
|
|
115
|
+
writeFileExclusive(path: string, data: string | Uint8Array): Promise<boolean>;
|
|
116
|
+
/** No-op if missing. */
|
|
117
|
+
removeFile(path: string): Promise<void>;
|
|
118
|
+
/** Creates missing parents; no-op if it exists. */
|
|
119
|
+
createDirectory(path: string): Promise<void>;
|
|
120
|
+
/** Removes the directory and everything in it. No-op if missing. */
|
|
121
|
+
removeDirectory(path: string): Promise<void>;
|
|
122
|
+
/** Entry names, unsorted. Throws SandboxNotFoundError if missing. */
|
|
123
|
+
listDirectory(path: string): Promise<string[]>;
|
|
124
|
+
/** Throws SandboxNotFoundError if missing. */
|
|
125
|
+
stat(path: string): Promise<SandboxFileInfo>;
|
|
126
|
+
/** Sandbox path of the home directory of the user operations run as. */
|
|
127
|
+
homeDirectory(): Promise<string>;
|
|
128
|
+
}
|
|
129
|
+
export interface SandboxCommands {
|
|
130
|
+
runCommand(cmd: string[], options?: RunCommandOptions): Promise<RunCommandResult>;
|
|
131
|
+
}
|
|
132
|
+
/** Reaching into the sandbox from the caller's side. */
|
|
133
|
+
export interface SandboxNetwork {
|
|
134
|
+
/** HTTP request made from inside the sandbox. */
|
|
135
|
+
fetch(url: string, init?: SandboxFetchInit): Promise<Response>;
|
|
136
|
+
/** Opens a stream connection to the unix socket at `path` (e.g. an agent worker's). */
|
|
137
|
+
connect(path: string): Promise<SandboxConnection>;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Makes something listening in the sandbox reachable from the SDK's client over the network, without
|
|
141
|
+
* going through the backend's own connection. Declared when the sandbox is created (with the host
|
|
142
|
+
* clients reach it at), by the backends that can. Always authenticated: a scheme the sandbox's auth
|
|
143
|
+
* can't enforce is refused with SandboxEndpointError. An endpoint lasts until it is closed, past the
|
|
144
|
+
* sandbox object that exposed it.
|
|
145
|
+
*/
|
|
146
|
+
export interface SandboxAccessibleEndpoint {
|
|
147
|
+
/**
|
|
148
|
+
* How clients reach the machine, as the sandbox was created with ("host" or "host:port"). Several
|
|
149
|
+
* sandboxes may drive the same machine, each reached its own way: their endpoints are their own.
|
|
150
|
+
*/
|
|
151
|
+
readonly accessibleHost: string;
|
|
152
|
+
exposeEndpoint(request: SandboxEndpointRequest): Promise<SandboxEndpoint>;
|
|
153
|
+
/** Stops accepting connections and revokes its credentials. No-op if already closed. */
|
|
154
|
+
closeEndpoint(endpointId: string): Promise<void>;
|
|
155
|
+
/**
|
|
156
|
+
* Makes sure it is served, starting its proxy again if that stopped (a reboot), with the same URL
|
|
157
|
+
* and credentials; false if it was closed, or its port has since been taken (expose another).
|
|
158
|
+
*/
|
|
159
|
+
ensureEndpoint(endpointId: string): Promise<boolean>;
|
|
160
|
+
}
|
|
161
|
+
/** A sandbox that stops itself on a timer (Boat). */
|
|
162
|
+
export interface SandboxExpiration {
|
|
163
|
+
/** When it stops itself; null if it never does. */
|
|
164
|
+
getExpiration(): Promise<Date | null>;
|
|
165
|
+
/** Makes it stop itself `seconds` from now; null: never. Returns when it now will. */
|
|
166
|
+
setExpiration(seconds: number | null): Promise<Date | null>;
|
|
167
|
+
}
|
|
168
|
+
/** Whether the sandbox has the SandboxExpiration capability. */
|
|
169
|
+
export declare function hasExpiration<T extends object>(sandbox: T): sandbox is T & SandboxExpiration;
|
|
170
|
+
/** What every backend provides. */
|
|
171
|
+
export type Sandbox = SandboxFileOperations & SandboxCommands & SandboxNetwork & {
|
|
172
|
+
/** Releases what the sandbox holds open (a remote connection). Optional: most hold nothing. */
|
|
173
|
+
close?(): void;
|
|
174
|
+
};
|
|
175
|
+
export declare class SandboxPathError extends Error {
|
|
176
|
+
name: string;
|
|
177
|
+
}
|
|
178
|
+
export declare class SandboxNotFoundError extends Error {
|
|
179
|
+
name: string;
|
|
180
|
+
}
|
|
181
|
+
/** The endpoint can't be exposed as asked (a scheme or port its auth doesn't allow, ...). */
|
|
182
|
+
export declare class SandboxEndpointError extends Error {
|
|
183
|
+
name: string;
|
|
184
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Whether the sandbox has the SandboxExpiration capability. */
|
|
2
|
+
export function hasExpiration(sandbox) {
|
|
3
|
+
return "getExpiration" in sandbox && "setExpiration" in sandbox;
|
|
4
|
+
}
|
|
5
|
+
export class SandboxPathError extends Error {
|
|
6
|
+
name = "SandboxPathError";
|
|
7
|
+
}
|
|
8
|
+
export class SandboxNotFoundError extends Error {
|
|
9
|
+
name = "SandboxNotFoundError";
|
|
10
|
+
}
|
|
11
|
+
/** The endpoint can't be exposed as asked (a scheme or port its auth doesn't allow, ...). */
|
|
12
|
+
export class SandboxEndpointError extends Error {
|
|
13
|
+
name = "SandboxEndpointError";
|
|
14
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BoatApi, type Sandbox as BoatMachine } from "@boatdev/sdk";
|
|
2
|
+
import { type Sandbox, type SandboxAccessibleEndpoint, type SandboxExpiration } from "./api.ts";
|
|
3
|
+
import { type SandboxEndpointAuth } from "./endpointProxy.ts";
|
|
4
|
+
export declare const BOAT_API_URL = "https://boat.dev/api/v1";
|
|
5
|
+
export declare function createBoatApi(apiKey: string, basePath?: string): BoatApi;
|
|
6
|
+
/** A call to Boat that failed: its HTTP status and Boat's error code. */
|
|
7
|
+
export declare class BoatError extends Error {
|
|
8
|
+
readonly status: number;
|
|
9
|
+
readonly code: string;
|
|
10
|
+
name: string;
|
|
11
|
+
constructor(status: number, code: string, message: string);
|
|
12
|
+
}
|
|
13
|
+
export type BoatSandboxOptions = {
|
|
14
|
+
/** Where the sandbox's endpoint proxy listens; Boat publishes it. Defaults to 8900. */
|
|
15
|
+
proxyPort?: number;
|
|
16
|
+
/** Sandbox path the endpoint proxy keeps its records in; defaults to ~/.local/state/hyperbrane/endpoints. */
|
|
17
|
+
stateDirectory?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A Boat sandbox (https://boat.dev), driven through Boat's API alone (its SDK): files through its
|
|
21
|
+
* file calls, commands through its command call (one shell line, up to 600s), and what needs a
|
|
22
|
+
* stream into the sandbox through the sandbox's own endpoints: `connect` is an "sse" endpoint to
|
|
23
|
+
* the socket, `fetch` an "http" one (so docker's responses stream). Those endpoints, like any the
|
|
24
|
+
* sandbox exposes, sit behind one proxy that Boat publishes on a URL of its own (boatHostPortAuth).
|
|
25
|
+
* Sandbox paths are paths in the sandbox; the sandbox must be running (ensureBoatRunning).
|
|
26
|
+
*/
|
|
27
|
+
export declare function createBoatSandbox(api: BoatApi, sandboxId: string, { proxyPort, stateDirectory }?: BoatSandboxOptions): Sandbox & SandboxAccessibleEndpoint & SandboxExpiration;
|
|
28
|
+
/**
|
|
29
|
+
* Endpoints published by Boat: the proxy listens on the accessible host's port inside the sandbox
|
|
30
|
+
* and checks the bearer or basic credentials it mints per endpoint (proxyCredentialsAuth), and
|
|
31
|
+
* Boat's hostPort makes that port public, with Boat's TLS, on a URL of its own:
|
|
32
|
+
* https://<sandbox>-<port>.on.boat.dev. Boat's own guard for a port (a token it trades for a
|
|
33
|
+
* cookie) isn't used: a client can't carry it through a POST, nor a browser keep it across sites.
|
|
34
|
+
*/
|
|
35
|
+
export declare function boatHostPortAuth(api: BoatApi, sandboxId: string): SandboxEndpointAuth;
|
|
36
|
+
/** Creates a sandbox, names it, and waits until it runs commands. */
|
|
37
|
+
export declare function createBoatMachine(api: BoatApi, { name }: {
|
|
38
|
+
name: string;
|
|
39
|
+
}): Promise<BoatMachine>;
|
|
40
|
+
/** The sandbox, running: one that was stopped (archived) is resumed, its disk as it was, first. */
|
|
41
|
+
export declare function ensureBoatRunning(api: BoatApi, sandboxId: string): Promise<BoatMachine>;
|
|
42
|
+
/** Stops the sandbox, saving its disk first (Boat archives it), for a later ensureBoatRunning. */
|
|
43
|
+
export declare function archiveBoatMachine(api: BoatApi, sandboxId: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Lets `publicKey` (OpenSSH) log in to the running sandbox, and says where: Boat's SSH endpoint for
|
|
46
|
+
* it and its user. The endpoint may change when the sandbox is resumed on another machine.
|
|
47
|
+
*/
|
|
48
|
+
export declare function authorizeBoatSshKey(api: BoatApi, sandboxId: string, publicKey: string): Promise<{
|
|
49
|
+
host: string;
|
|
50
|
+
port: number;
|
|
51
|
+
user: string;
|
|
52
|
+
}>;
|