withruntime 0.8.2 → 0.8.4
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/BLAXEL.md +44 -42
- package/DAYTONA.md +16 -16
- package/E2B.md +1 -1
- package/README.md +26 -5
- package/README.src.md +26 -5
- package/VERCEL.md +1 -1
- package/dist/blaxel/index.d.ts +1 -1
- package/dist/blaxel/index.js +2 -2
- package/dist/blaxel/sandbox.d.ts +8 -2
- package/dist/blaxel/sandbox.js +16 -4
- package/dist/blaxel/session.d.ts +31 -0
- package/dist/blaxel/session.js +134 -0
- package/dist/blaxel/unsupported.d.ts +2 -4
- package/dist/blaxel/unsupported.js +2 -4
- package/dist/browser-stub.d.ts +1 -0
- package/dist/browser-stub.js +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +403 -21
- package/dist/daytona/daytona.js +7 -3
- package/dist/daytona/sandbox.js +3 -3
- package/dist/e2b/unsupported.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/products/index.d.ts +2 -0
- package/dist/products/index.js +2 -0
- package/dist/products/jobs.d.ts +213 -0
- package/dist/products/jobs.js +186 -0
- package/dist/products/secrets.d.ts +81 -9
- package/dist/products/secrets.js +214 -19
- package/dist/sandbox.d.ts +63 -1
- package/dist/sandbox.js +114 -9
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/dist/vercel/sandbox.js +1 -1
- package/package.json +7 -1
package/dist/products/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { sso } from "./sso.js";
|
|
|
13
13
|
import { switching } from "./switching.js";
|
|
14
14
|
import { limits } from "./limits.js";
|
|
15
15
|
import { volumes } from "./volumes.js";
|
|
16
|
+
import { jobs } from "./jobs.js";
|
|
16
17
|
import { sandboxMounts } from "./mounts.js";
|
|
17
18
|
import { secrets } from "./secrets.js";
|
|
18
19
|
import { mcp, sandboxMcp } from "./mcp.js";
|
|
@@ -20,6 +21,7 @@ import { events, otel, sandboxMetrics, webhooks } from "./observability.js";
|
|
|
20
21
|
export declare const clientExtensions: {
|
|
21
22
|
images: typeof images;
|
|
22
23
|
volumes: typeof volumes;
|
|
24
|
+
jobs: typeof jobs;
|
|
23
25
|
referrals: typeof referrals;
|
|
24
26
|
billing: typeof billing;
|
|
25
27
|
limits: typeof limits;
|
package/dist/products/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { sso } from "./sso.js";
|
|
|
11
11
|
import { switching } from "./switching.js";
|
|
12
12
|
import { limits } from "./limits.js";
|
|
13
13
|
import { volumes } from "./volumes.js";
|
|
14
|
+
import { jobs } from "./jobs.js";
|
|
14
15
|
import { sandboxMounts } from "./mounts.js";
|
|
15
16
|
import { secrets } from "./secrets.js";
|
|
16
17
|
import { mcp, sandboxMcp } from "./mcp.js";
|
|
@@ -33,6 +34,7 @@ import { events, otel, sandboxMetrics, webhooks } from "./observability.js";
|
|
|
33
34
|
export const clientExtensions = {
|
|
34
35
|
images,
|
|
35
36
|
volumes,
|
|
37
|
+
jobs,
|
|
36
38
|
referrals,
|
|
37
39
|
billing,
|
|
38
40
|
limits,
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Page } from "../page.js";
|
|
2
|
+
import type { RequestOptions, Transport } from "../transport.js";
|
|
3
|
+
/** A run's sandbox. Every field has a default; a run is a paid sandbox of this
|
|
4
|
+
* size, billed at the sandbox rates. */
|
|
5
|
+
export type JobCompute = {
|
|
6
|
+
region: string;
|
|
7
|
+
vcpu: number;
|
|
8
|
+
cpuMode: "shared" | "reserved";
|
|
9
|
+
/** Guaranteed CPU in thousandths of a vCPU while shared. */
|
|
10
|
+
cpuFloorMillis: number;
|
|
11
|
+
memoryMiB: number;
|
|
12
|
+
diskMiB: number;
|
|
13
|
+
/** How long each run's sandbox is paid for, at most an hour. Defaults to
|
|
14
|
+
* the timeout plus a minute to start. */
|
|
15
|
+
durationSeconds: number;
|
|
16
|
+
/** The most one run may hold, in microdollars. */
|
|
17
|
+
maxCostMicros?: number;
|
|
18
|
+
};
|
|
19
|
+
export type JobSchedule = {
|
|
20
|
+
kind: "once"; /** Unix milliseconds. */
|
|
21
|
+
at: number;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "cron";
|
|
24
|
+
expression: string;
|
|
25
|
+
timezone?: string;
|
|
26
|
+
};
|
|
27
|
+
export type JobDefinition = {
|
|
28
|
+
name: string;
|
|
29
|
+
schedule: JobSchedule;
|
|
30
|
+
compute: JobCompute & {
|
|
31
|
+
memoryGuarantee?: "reclaimable";
|
|
32
|
+
};
|
|
33
|
+
command: {
|
|
34
|
+
argv: string[];
|
|
35
|
+
cwd?: string;
|
|
36
|
+
};
|
|
37
|
+
timeoutSeconds: number;
|
|
38
|
+
maxTotalCostMicros?: number;
|
|
39
|
+
retry: {
|
|
40
|
+
maxAttempts: number;
|
|
41
|
+
backoffSeconds: number;
|
|
42
|
+
};
|
|
43
|
+
secrets: Array<{
|
|
44
|
+
name: string;
|
|
45
|
+
secretId: string;
|
|
46
|
+
}>;
|
|
47
|
+
};
|
|
48
|
+
export type Job = {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
definition: JobDefinition;
|
|
52
|
+
state: "active" | "paused" | "canceled" | "finished";
|
|
53
|
+
version: number;
|
|
54
|
+
/** Unix milliseconds, or null when nothing more is scheduled. */
|
|
55
|
+
nextRunAt: number | null;
|
|
56
|
+
/** Why the last due run could not start, retried every 30 seconds:
|
|
57
|
+
* `credits` (add credit), `capacity`, `cost_cap` (maxTotalCostMicros or a
|
|
58
|
+
* key's daily limit) or `permission` (the key that made the job lost it). */
|
|
59
|
+
blockedReason: "permission" | "capacity" | "credits" | "cost_cap" | null;
|
|
60
|
+
createdAt: number;
|
|
61
|
+
updatedAt: number;
|
|
62
|
+
};
|
|
63
|
+
export type JobRunState = "provisioning" | "starting" | "running" | "succeeded" | "failed" | "canceling" | "canceled" | "unknown";
|
|
64
|
+
/** One attempt of one occurrence, in a fresh sandbox (`resourceId`). */
|
|
65
|
+
export type JobRun = {
|
|
66
|
+
id: string;
|
|
67
|
+
jobId: string;
|
|
68
|
+
occurrenceId: string;
|
|
69
|
+
attempt: number;
|
|
70
|
+
scheduledFor: number;
|
|
71
|
+
resourceId: string;
|
|
72
|
+
/** `unknown`: the outcome could not be observed (the sandbox stopped under
|
|
73
|
+
* it). It is never retried by itself. */
|
|
74
|
+
state: JobRunState;
|
|
75
|
+
runGeneration: number;
|
|
76
|
+
executionEpoch: number | null;
|
|
77
|
+
deadlineAt: number | null;
|
|
78
|
+
startedAt: number | null;
|
|
79
|
+
endedAt: number | null;
|
|
80
|
+
exitCode: number | null;
|
|
81
|
+
reason: string | null;
|
|
82
|
+
logBytes: number;
|
|
83
|
+
logsTruncated: boolean;
|
|
84
|
+
createdAt: number;
|
|
85
|
+
};
|
|
86
|
+
export type JobLogs = {
|
|
87
|
+
chunks: Array<{
|
|
88
|
+
offset: number;
|
|
89
|
+
stream: "stdout" | "stderr";
|
|
90
|
+
text: string;
|
|
91
|
+
}>;
|
|
92
|
+
/** Pass back as `cursor` for what follows. */
|
|
93
|
+
nextCursor: number;
|
|
94
|
+
truncated: boolean;
|
|
95
|
+
/** The run has ended and every byte has been read. */
|
|
96
|
+
complete: boolean;
|
|
97
|
+
};
|
|
98
|
+
/** What `jobs.create` takes. Everything but `name`, `schedule` and `command`
|
|
99
|
+
* has a default. */
|
|
100
|
+
export type CreateJob = {
|
|
101
|
+
/** Letters, digits, `.`, `_` and `-`, starting with a letter or digit; at most 80. */
|
|
102
|
+
name: string;
|
|
103
|
+
/** `{ at }` once (a Date, an ISO time or Unix milliseconds; a time already
|
|
104
|
+
* past runs at once), or `{ cron: "0 3 * * *", timezone?: "Europe/Berlin" }`,
|
|
105
|
+
* UTC by default. The raw API shape (`{ kind: ... }`) is accepted too. */
|
|
106
|
+
schedule: {
|
|
107
|
+
at: Date | string | number;
|
|
108
|
+
} | {
|
|
109
|
+
cron: string;
|
|
110
|
+
timezone?: string;
|
|
111
|
+
} | JobSchedule;
|
|
112
|
+
/** An argv array, run as given (no shell); wrap a script in
|
|
113
|
+
* `["bash", "-lc", "..."]`. Or `{ argv, cwd }`, cwd under /workspace. */
|
|
114
|
+
command: string[] | {
|
|
115
|
+
argv: string[];
|
|
116
|
+
cwd?: string;
|
|
117
|
+
};
|
|
118
|
+
/** Seconds a run may take before it is stopped. Default 1800. */
|
|
119
|
+
timeoutSeconds?: number;
|
|
120
|
+
compute?: Partial<JobCompute>;
|
|
121
|
+
/** Attempts per occurrence after a failure (exit code not 0), 1 to 5.
|
|
122
|
+
* Default one attempt, no retry. */
|
|
123
|
+
retry?: {
|
|
124
|
+
maxAttempts: number;
|
|
125
|
+
backoffSeconds: number;
|
|
126
|
+
};
|
|
127
|
+
/** Job secrets (`runtime.secrets.set(name, { value, jobs: true })`) put
|
|
128
|
+
* into the run's environment: `{ name: "ENV_NAME", secretId }`. */
|
|
129
|
+
secrets?: Array<{
|
|
130
|
+
name: string;
|
|
131
|
+
secretId: string;
|
|
132
|
+
}>;
|
|
133
|
+
/** The most every run of this job together may hold, in microdollars. */
|
|
134
|
+
maxTotalCostMicros?: number;
|
|
135
|
+
};
|
|
136
|
+
/** The size a run gets when `compute` leaves a field out: the sandbox
|
|
137
|
+
* defaults, in the region `us-east`. */
|
|
138
|
+
export declare const JOB_DEFAULTS: {
|
|
139
|
+
region: string;
|
|
140
|
+
vcpu: number;
|
|
141
|
+
cpuMode: "shared";
|
|
142
|
+
cpuFloorMillis: number;
|
|
143
|
+
memoryMiB: number;
|
|
144
|
+
diskMiB: number;
|
|
145
|
+
timeoutSeconds: number;
|
|
146
|
+
/** Added to the timeout for the sandbox to start, within the hour. */
|
|
147
|
+
startSeconds: number;
|
|
148
|
+
};
|
|
149
|
+
/** A run is one sandbox: at most 16 vCPU and 64 GiB, a disk no smaller than
|
|
150
|
+
* the system image, and paid for at most an hour. */
|
|
151
|
+
export declare const JOB_LIMITS: {
|
|
152
|
+
maxVcpu: number;
|
|
153
|
+
maxMemoryMiB: number;
|
|
154
|
+
minDiskMiB: number;
|
|
155
|
+
maxDurationSeconds: number;
|
|
156
|
+
maxAttempts: number;
|
|
157
|
+
maxSecrets: number;
|
|
158
|
+
};
|
|
159
|
+
/** Unix milliseconds from a Date, an ISO time or a number. */
|
|
160
|
+
export declare function jobTime(at: Date | string | number): number;
|
|
161
|
+
/** The API's job body from `CreateJob`, with every default filled in and the
|
|
162
|
+
* sandbox's size checked before anything is sent. */
|
|
163
|
+
export declare function jobBody(input: CreateJob): Record<string, unknown>;
|
|
164
|
+
/** The API answers a product switched off where it runs with 503
|
|
165
|
+
* `unavailable` and words that do not name it ("This product is not enabled
|
|
166
|
+
* on this deployment."). This names the product, keeps the code, status and
|
|
167
|
+
* request id, and says no retry will change it. */
|
|
168
|
+
export declare function switchedOff(product: string): (error: unknown) => never;
|
|
169
|
+
/** Scheduled jobs: a command run in a fresh sandbox, once or on a cron
|
|
170
|
+
* schedule. Each run is a paid sandbox, charged at the sandbox rates through
|
|
171
|
+
* the same holds and spending limits; the trial's hours do not fund jobs.
|
|
172
|
+
*
|
|
173
|
+
* const job = await runtime.jobs.create({
|
|
174
|
+
* name: "nightly-report",
|
|
175
|
+
* schedule: { cron: "0 3 * * *", timezone: "Europe/Berlin" },
|
|
176
|
+
* command: ["python3", "/workspace/report.py"],
|
|
177
|
+
* });
|
|
178
|
+
*
|
|
179
|
+
* When jobs are switched off where you call, every method fails at once with
|
|
180
|
+
* `ServiceUnavailableError` (code `unavailable`, "Jobs are unavailable."),
|
|
181
|
+
* which retrying does not change. */
|
|
182
|
+
export declare function jobs(transport: Transport): {
|
|
183
|
+
/** Schedule a job. Nothing runs until its time comes; a time already past
|
|
184
|
+
* runs at once. Sizes, timeout and region default to a 2 vCPU, 4 GiB
|
|
185
|
+
* sandbox for up to 30 minutes. */
|
|
186
|
+
create: (input: CreateJob, options?: RequestOptions) => Promise<Job>;
|
|
187
|
+
/** Your jobs, oldest first. Await for a page, or `for await` for all. */
|
|
188
|
+
list: (query?: {
|
|
189
|
+
limit?: number;
|
|
190
|
+
cursor?: string;
|
|
191
|
+
}, options?: RequestOptions) => Promise<Page<Job>>;
|
|
192
|
+
get: (id: string, options?: RequestOptions) => Promise<Job>;
|
|
193
|
+
/** A job's runs, oldest first: each attempt of each occurrence. */
|
|
194
|
+
runs: (jobId: string, query?: {
|
|
195
|
+
limit?: number;
|
|
196
|
+
cursor?: string;
|
|
197
|
+
}, options?: RequestOptions) => Promise<Page<JobRun>>;
|
|
198
|
+
/** One run by its id. */
|
|
199
|
+
run: (runId: string, options?: RequestOptions) => Promise<JobRun>;
|
|
200
|
+
/** A run's output from `cursor` (a byte offset, 0 by default), at most
|
|
201
|
+
* `limitBytes` (4 to 65536, default 16384). Keep calling with `nextCursor`
|
|
202
|
+
* until `complete`. A run keeps its last 256 KiB. */
|
|
203
|
+
logs: (runId: string, query?: {
|
|
204
|
+
cursor?: number;
|
|
205
|
+
limitBytes?: number;
|
|
206
|
+
}, options?: RequestOptions) => Promise<JobLogs>;
|
|
207
|
+
/** Stop scheduling new runs; a run already going finishes. */
|
|
208
|
+
pause: (id: string, options?: RequestOptions) => Promise<Job>;
|
|
209
|
+
resume: (id: string, options?: RequestOptions) => Promise<Job>;
|
|
210
|
+
/** End the job for good and stop any run in progress. A run that already
|
|
211
|
+
* finished keeps its result. Cancelling again answers the same job. */
|
|
212
|
+
cancel: (id: string, options?: RequestOptions) => Promise<Job>;
|
|
213
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Page } from "../page.js";
|
|
2
|
+
import { RuntimeError, ServiceUnavailableError } from "../errors.js";
|
|
3
|
+
/** The size a run gets when `compute` leaves a field out: the sandbox
|
|
4
|
+
* defaults, in the region `us-east`. */
|
|
5
|
+
export const JOB_DEFAULTS = {
|
|
6
|
+
region: "us-east",
|
|
7
|
+
vcpu: 2,
|
|
8
|
+
cpuMode: "shared",
|
|
9
|
+
cpuFloorMillis: 50,
|
|
10
|
+
memoryMiB: 4096,
|
|
11
|
+
diskMiB: 4096,
|
|
12
|
+
timeoutSeconds: 1800,
|
|
13
|
+
/** Added to the timeout for the sandbox to start, within the hour. */
|
|
14
|
+
startSeconds: 60,
|
|
15
|
+
};
|
|
16
|
+
/** A run is one sandbox: at most 16 vCPU and 64 GiB, a disk no smaller than
|
|
17
|
+
* the system image, and paid for at most an hour. */
|
|
18
|
+
export const JOB_LIMITS = {
|
|
19
|
+
maxVcpu: 16,
|
|
20
|
+
maxMemoryMiB: 65_536,
|
|
21
|
+
minDiskMiB: 3072,
|
|
22
|
+
maxDurationSeconds: 3600,
|
|
23
|
+
maxAttempts: 5,
|
|
24
|
+
maxSecrets: 16,
|
|
25
|
+
};
|
|
26
|
+
function invalid(message, field) {
|
|
27
|
+
return new RuntimeError({
|
|
28
|
+
message,
|
|
29
|
+
code: "invalid_request",
|
|
30
|
+
status: 0,
|
|
31
|
+
details: { field },
|
|
32
|
+
hint: "Nothing was sent. Fix the field and call again.",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Unix milliseconds from a Date, an ISO time or a number. */
|
|
36
|
+
export function jobTime(at) {
|
|
37
|
+
const ms = at instanceof Date
|
|
38
|
+
? at.getTime()
|
|
39
|
+
: typeof at === "number"
|
|
40
|
+
? at
|
|
41
|
+
: /^\d+$/.test(at)
|
|
42
|
+
? Number(at)
|
|
43
|
+
: Date.parse(at);
|
|
44
|
+
if (!Number.isSafeInteger(ms) || ms < 0)
|
|
45
|
+
throw invalid(`schedule.at must be a time: a Date, an ISO time such as 2026-10-01T03:00:00Z, or Unix milliseconds; got ${String(at)}.`, "schedule.at");
|
|
46
|
+
return ms;
|
|
47
|
+
}
|
|
48
|
+
/** The API's job body from `CreateJob`, with every default filled in and the
|
|
49
|
+
* sandbox's size checked before anything is sent. */
|
|
50
|
+
export function jobBody(input) {
|
|
51
|
+
const s = input.schedule;
|
|
52
|
+
let schedule;
|
|
53
|
+
if (s.kind === "once" || (s.kind === undefined && s.at !== undefined))
|
|
54
|
+
schedule = { kind: "once", at: jobTime(s.at) };
|
|
55
|
+
else if (s.kind === "cron" || (s.kind === undefined && s.cron !== undefined)) {
|
|
56
|
+
const expression = String(s.expression ?? s.cron).trim();
|
|
57
|
+
if (expression.split(/\s+/).length !== 5)
|
|
58
|
+
throw invalid(`A cron schedule has five fields, minute hour day-of-month month day-of-week, such as "0 3 * * *"; got "${expression}".`, "schedule.cron");
|
|
59
|
+
schedule = {
|
|
60
|
+
kind: "cron",
|
|
61
|
+
expression,
|
|
62
|
+
...(s.timezone === undefined ? {} : { timezone: s.timezone }),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
else
|
|
66
|
+
throw invalid("schedule takes { at } or { cron }.", "schedule");
|
|
67
|
+
const command = Array.isArray(input.command) ? { argv: input.command } : input.command;
|
|
68
|
+
if (!command?.argv?.length)
|
|
69
|
+
throw invalid('command is an argv array such as ["python3", "job.py"].', "command");
|
|
70
|
+
const timeoutSeconds = input.timeoutSeconds ?? JOB_DEFAULTS.timeoutSeconds;
|
|
71
|
+
const given = input.compute ?? {};
|
|
72
|
+
const vcpu = given.vcpu ?? JOB_DEFAULTS.vcpu;
|
|
73
|
+
const cpuMode = given.cpuMode ?? JOB_DEFAULTS.cpuMode;
|
|
74
|
+
const compute = {
|
|
75
|
+
region: given.region ?? JOB_DEFAULTS.region,
|
|
76
|
+
vcpu,
|
|
77
|
+
cpuMode,
|
|
78
|
+
cpuFloorMillis: given.cpuFloorMillis ??
|
|
79
|
+
(cpuMode === "reserved" ? vcpu * 1000 : Math.min(JOB_DEFAULTS.cpuFloorMillis, vcpu * 1000)),
|
|
80
|
+
memoryMiB: given.memoryMiB ?? JOB_DEFAULTS.memoryMiB,
|
|
81
|
+
diskMiB: given.diskMiB ?? JOB_DEFAULTS.diskMiB,
|
|
82
|
+
durationSeconds: given.durationSeconds ??
|
|
83
|
+
Math.min(JOB_LIMITS.maxDurationSeconds, timeoutSeconds + JOB_DEFAULTS.startSeconds),
|
|
84
|
+
...(given.maxCostMicros === undefined ? {} : { maxCostMicros: given.maxCostMicros }),
|
|
85
|
+
};
|
|
86
|
+
if (compute.vcpu > JOB_LIMITS.maxVcpu || compute.memoryMiB > JOB_LIMITS.maxMemoryMiB)
|
|
87
|
+
throw invalid(`A run's sandbox is at most ${JOB_LIMITS.maxVcpu} vCPU and ${JOB_LIMITS.maxMemoryMiB / 1024} GiB of memory; asked for ${compute.vcpu} vCPU and ${compute.memoryMiB} MiB.`, compute.vcpu > JOB_LIMITS.maxVcpu ? "compute.vcpu" : "compute.memoryMiB");
|
|
88
|
+
if (compute.diskMiB < JOB_LIMITS.minDiskMiB)
|
|
89
|
+
throw invalid(`A run's disk is at least ${JOB_LIMITS.minDiskMiB} MiB, the size of the system image; asked for ${compute.diskMiB}.`, "compute.diskMiB");
|
|
90
|
+
if (timeoutSeconds > compute.durationSeconds ||
|
|
91
|
+
compute.durationSeconds > JOB_LIMITS.maxDurationSeconds)
|
|
92
|
+
throw invalid(`timeoutSeconds (${timeoutSeconds}) must fit in the time a run is paid for (compute.durationSeconds ${compute.durationSeconds}), which is at most ${JOB_LIMITS.maxDurationSeconds} seconds.`, "timeoutSeconds");
|
|
93
|
+
return {
|
|
94
|
+
name: input.name,
|
|
95
|
+
schedule,
|
|
96
|
+
compute,
|
|
97
|
+
command,
|
|
98
|
+
timeoutSeconds,
|
|
99
|
+
...(input.retry === undefined ? {} : { retry: input.retry }),
|
|
100
|
+
...(input.secrets === undefined ? {} : { secrets: input.secrets }),
|
|
101
|
+
...(input.maxTotalCostMicros === undefined
|
|
102
|
+
? {}
|
|
103
|
+
: { maxTotalCostMicros: input.maxTotalCostMicros }),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const enc = encodeURIComponent;
|
|
107
|
+
/** The API answers a product switched off where it runs with 503
|
|
108
|
+
* `unavailable` and words that do not name it ("This product is not enabled
|
|
109
|
+
* on this deployment."). This names the product, keeps the code, status and
|
|
110
|
+
* request id, and says no retry will change it. */
|
|
111
|
+
export function switchedOff(product) {
|
|
112
|
+
return (error) => {
|
|
113
|
+
if (error instanceof RuntimeError && error.code === "unavailable")
|
|
114
|
+
throw new ServiceUnavailableError({
|
|
115
|
+
message: `${product} are not enabled on this Runtime API yet.`,
|
|
116
|
+
code: error.code,
|
|
117
|
+
status: error.status,
|
|
118
|
+
hint: `${product} are switched off here for now, so retrying will not help. Tell us you need them: runtime feedback "need ${product.toLowerCase()}".`,
|
|
119
|
+
...(error.requestId ? { requestId: error.requestId } : {}),
|
|
120
|
+
...(error.details ? { details: error.details } : {}),
|
|
121
|
+
cause: error,
|
|
122
|
+
});
|
|
123
|
+
throw error;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** Scheduled jobs: a command run in a fresh sandbox, once or on a cron
|
|
127
|
+
* schedule. Each run is a paid sandbox, charged at the sandbox rates through
|
|
128
|
+
* the same holds and spending limits; the trial's hours do not fund jobs.
|
|
129
|
+
*
|
|
130
|
+
* const job = await runtime.jobs.create({
|
|
131
|
+
* name: "nightly-report",
|
|
132
|
+
* schedule: { cron: "0 3 * * *", timezone: "Europe/Berlin" },
|
|
133
|
+
* command: ["python3", "/workspace/report.py"],
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* When jobs are switched off where you call, every method fails at once with
|
|
137
|
+
* `ServiceUnavailableError` (code `unavailable`, "Jobs are unavailable."),
|
|
138
|
+
* which retrying does not change. */
|
|
139
|
+
export function jobs(transport) {
|
|
140
|
+
const off = switchedOff("Jobs");
|
|
141
|
+
const t = {
|
|
142
|
+
json: (call) => transport.json(call).catch(off),
|
|
143
|
+
};
|
|
144
|
+
const list = async (query = {}, options) => {
|
|
145
|
+
const page = await t.json({ method: "GET", path: "/v1/jobs", query, ...options });
|
|
146
|
+
return new Page(page.items, page.nextCursor, (cursor) => list({ ...query, cursor }, options));
|
|
147
|
+
};
|
|
148
|
+
const runs = async (jobId, query = {}, options) => {
|
|
149
|
+
const page = await t.json({
|
|
150
|
+
method: "GET",
|
|
151
|
+
path: `/v1/jobs/${enc(jobId)}/runs`,
|
|
152
|
+
query,
|
|
153
|
+
...options,
|
|
154
|
+
});
|
|
155
|
+
return new Page(page.items, page.nextCursor, (cursor) => runs(jobId, { ...query, cursor }, options));
|
|
156
|
+
};
|
|
157
|
+
const verb = (id, action, options) => t.json({ method: "POST", path: `/v1/jobs/${enc(id)}:${action}`, body: {}, ...options });
|
|
158
|
+
return {
|
|
159
|
+
/** Schedule a job. Nothing runs until its time comes; a time already past
|
|
160
|
+
* runs at once. Sizes, timeout and region default to a 2 vCPU, 4 GiB
|
|
161
|
+
* sandbox for up to 30 minutes. */
|
|
162
|
+
create: (input, options) => t.json({ method: "POST", path: "/v1/jobs", body: jobBody(input), ...options }),
|
|
163
|
+
/** Your jobs, oldest first. Await for a page, or `for await` for all. */
|
|
164
|
+
list,
|
|
165
|
+
get: (id, options) => t.json({ method: "GET", path: `/v1/jobs/${enc(id)}`, ...options }),
|
|
166
|
+
/** A job's runs, oldest first: each attempt of each occurrence. */
|
|
167
|
+
runs,
|
|
168
|
+
/** One run by its id. */
|
|
169
|
+
run: (runId, options) => t.json({ method: "GET", path: `/v1/job-runs/${enc(runId)}`, ...options }),
|
|
170
|
+
/** A run's output from `cursor` (a byte offset, 0 by default), at most
|
|
171
|
+
* `limitBytes` (4 to 65536, default 16384). Keep calling with `nextCursor`
|
|
172
|
+
* until `complete`. A run keeps its last 256 KiB. */
|
|
173
|
+
logs: (runId, query = {}, options) => t.json({
|
|
174
|
+
method: "GET",
|
|
175
|
+
path: `/v1/job-runs/${enc(runId)}/logs`,
|
|
176
|
+
query,
|
|
177
|
+
...options,
|
|
178
|
+
}),
|
|
179
|
+
/** Stop scheduling new runs; a run already going finishes. */
|
|
180
|
+
pause: (id, options) => verb(id, "pause", options),
|
|
181
|
+
resume: (id, options) => verb(id, "resume", options),
|
|
182
|
+
/** End the job for good and stop any run in progress. A run that already
|
|
183
|
+
* finished keeps its result. Cancelling again answers the same job. */
|
|
184
|
+
cancel: (id, options) => verb(id, "cancel", options),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RuntimeError } from "../errors.js";
|
|
1
2
|
import type { RequestOptions, Transport } from "../transport.js";
|
|
2
3
|
/** Which requests to a secret's hosts carry it: `methods` (every method when
|
|
3
4
|
* absent) and `paths`, each exact (`/v1/chat/completions`) or a prefix ending
|
|
@@ -25,10 +26,13 @@ export type Secret = {
|
|
|
25
26
|
updatedAt: string;
|
|
26
27
|
};
|
|
27
28
|
export type SetSecret = {
|
|
28
|
-
/** Visible ASCII and spaces, at most 8 KiB
|
|
29
|
+
/** Visible ASCII and spaces, at most 8 KiB for sandboxes (64 KiB of UTF-8
|
|
30
|
+
* for a jobs-only secret). Stored sealed; the sandboxes' copy is never
|
|
31
|
+
* returned. */
|
|
29
32
|
value: string;
|
|
30
|
-
/** Where the value may go: `api.openai.com`, `*.github.com`. 1 to 16.
|
|
31
|
-
|
|
33
|
+
/** Where the value may go: `api.openai.com`, `*.github.com`. 1 to 16.
|
|
34
|
+
* Required unless `jobs` is true. */
|
|
35
|
+
hosts?: string[];
|
|
32
36
|
/** Set this header on every request to the hosts, replacing the sandbox's own. */
|
|
33
37
|
header?: string;
|
|
34
38
|
/** With `header`: its value, `{value}` where the secret goes. Default `{value}`. */
|
|
@@ -37,18 +41,86 @@ export type SetSecret = {
|
|
|
37
41
|
* Absent: every request to the hosts; replacing a secret without rules
|
|
38
42
|
* clears them. */
|
|
39
43
|
rules?: SecretRule[];
|
|
44
|
+
/** Also keep a copy for scheduled jobs, which a job puts into its run's
|
|
45
|
+
* environment (`jobs.create({ secrets: [...] })`). Unlike the sandboxes'
|
|
46
|
+
* copy, the jobs copy is yours rather than the account's, versioned, and can
|
|
47
|
+
* be read back with `reveal`. Setting it again rotates it. */
|
|
48
|
+
jobs?: boolean;
|
|
49
|
+
};
|
|
50
|
+
/** The jobs copy of a secret: metadata only, never the value. Times are Unix
|
|
51
|
+
* milliseconds. Owned by the person whose key stored it. */
|
|
52
|
+
export type JobSecret = {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
ownerId: string;
|
|
56
|
+
createdBy: string;
|
|
57
|
+
version: number;
|
|
58
|
+
createdAt: number;
|
|
59
|
+
updatedAt: number;
|
|
60
|
+
deletedAt: number | null;
|
|
61
|
+
};
|
|
62
|
+
/** One secret by name, and where its value may go: to sandboxes through the
|
|
63
|
+
* egress proxy (`sandboxes`), into scheduled jobs' environments (`jobs`), or
|
|
64
|
+
* both. */
|
|
65
|
+
export type SecretUse = {
|
|
66
|
+
name: string;
|
|
67
|
+
sandboxes: Secret | null;
|
|
68
|
+
jobs: JobSecret | null;
|
|
40
69
|
};
|
|
70
|
+
/** A write that reached one copy of a secret and not the other: `stored`
|
|
71
|
+
* names what took effect. Calling again with the same arguments is safe. */
|
|
72
|
+
export declare class SecretPartlyStoredError extends RuntimeError {
|
|
73
|
+
}
|
|
41
74
|
export declare function secrets(t: Transport): {
|
|
42
|
-
/** Store or replace a secret.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
75
|
+
/** Store or replace a secret. With `hosts`, sandboxes see a placeholder
|
|
76
|
+
* and the egress proxy puts the value into HTTPS requests to them
|
|
77
|
+
* (replacing keeps the placeholder). With `jobs: true`, a copy scheduled
|
|
78
|
+
* jobs can put into their environment; setting it again rotates it. Both
|
|
79
|
+
* at once store both; if only the first lands, `SecretPartlyStoredError`
|
|
80
|
+
* says so and the same call again finishes it. */
|
|
81
|
+
set: {
|
|
82
|
+
(name: string, input: SetSecret & {
|
|
83
|
+
hosts: string[];
|
|
84
|
+
}, options?: RequestOptions): Promise<Secret & {
|
|
85
|
+
enforced: boolean;
|
|
86
|
+
jobs?: JobSecret;
|
|
87
|
+
}>;
|
|
88
|
+
(name: string, input: SetSecret & {
|
|
89
|
+
jobs: true;
|
|
90
|
+
}, options?: RequestOptions): Promise<{
|
|
91
|
+
name: string;
|
|
92
|
+
jobs: JobSecret;
|
|
93
|
+
} & Partial<Secret & {
|
|
94
|
+
enforced: boolean;
|
|
95
|
+
}>>;
|
|
96
|
+
};
|
|
97
|
+
/** The sandboxes' secrets: names, hosts and placeholders. Never values.
|
|
98
|
+
* `all()` includes the jobs copies. */
|
|
47
99
|
list: (options?: RequestOptions) => Promise<Secret[]>;
|
|
48
|
-
/**
|
|
100
|
+
/** Every secret by name, with where its value may go. */
|
|
101
|
+
all: (options?: RequestOptions) => Promise<SecretUse[]>;
|
|
102
|
+
/** One secret by name, with where its value may go. */
|
|
103
|
+
get: (name: string, options?: RequestOptions) => Promise<SecretUse>;
|
|
104
|
+
/** Replace the jobs copy's value with a new version. Runs already bound to
|
|
105
|
+
* the old version are refused it. Only the jobs copy has versions; to
|
|
106
|
+
* change the sandboxes' copy, `set` it again. */
|
|
107
|
+
rotate: (name: string, value: string, options?: RequestOptions) => Promise<JobSecret>;
|
|
108
|
+
/** Read the jobs copy's value back (needs the key's secrets_reveal
|
|
109
|
+
* permission). The sandboxes' copy can never be read back. */
|
|
110
|
+
reveal: (name: string, query?: {
|
|
111
|
+
version?: number;
|
|
112
|
+
}, options?: RequestOptions) => Promise<{
|
|
113
|
+
id: string;
|
|
114
|
+
version: number;
|
|
115
|
+
value: string;
|
|
116
|
+
}>;
|
|
117
|
+
/** Delete a secret everywhere it is kept: the sandboxes' value is erased
|
|
118
|
+
* and its placeholder stops working, and the jobs copy stops being given
|
|
119
|
+
* to runs (its name stays taken). */
|
|
49
120
|
delete: (name: string, options?: RequestOptions) => Promise<{
|
|
50
121
|
name: string;
|
|
51
122
|
deleted: true;
|
|
52
123
|
enforced: boolean;
|
|
124
|
+
from: ("jobs" | "sandboxes")[];
|
|
53
125
|
}>;
|
|
54
126
|
};
|