withruntime 0.12.0 → 0.14.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 +3 -3
- package/README.src.md +2 -2
- package/dist/billing-cli.js +8 -4
- package/dist/blaxel/sandbox.d.ts +1 -1
- package/dist/blaxel/sandbox.js +1 -1
- package/dist/cli.d.ts +17 -2
- package/dist/cli.js +242 -69
- package/dist/credentials.d.ts +3 -0
- package/dist/credentials.js +15 -0
- package/dist/daytona/daytona.d.ts +1 -1
- package/dist/daytona/daytona.js +1 -1
- package/dist/e2b/sandbox.d.ts +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/machine.d.ts +15 -1
- package/dist/machine.js +26 -11
- package/dist/mcp.js +144 -20
- package/dist/products/api-keys.d.ts +73 -0
- package/dist/products/api-keys.js +23 -0
- package/dist/products/apps.d.ts +12 -31
- package/dist/products/apps.js +8 -113
- package/dist/products/balancers.d.ts +87 -0
- package/dist/products/balancers.js +8 -0
- package/dist/products/builds.d.ts +30 -0
- package/dist/products/builds.js +6 -0
- package/dist/products/index.d.ts +2 -0
- package/dist/products/index.js +2 -0
- package/dist/products/jobs.d.ts +2 -2
- package/dist/products/jobs.js +1 -1
- package/dist/products/observability.d.ts +7 -2
- package/dist/products/resources.d.ts +54 -0
- package/dist/products/resources.js +122 -0
- package/dist/products/secrets.d.ts +22 -0
- package/dist/products/secrets.js +8 -0
- package/dist/products/volumes.d.ts +3 -0
- package/dist/sandbox.d.ts +4 -0
- package/dist/sandbox.js +27 -2
- package/dist/transport.d.ts +4 -1
- package/dist/transport.js +25 -4
- package/dist/types.d.ts +9 -5
- package/dist/vercel/sandbox.d.ts +1 -1
- package/dist/vercel/sandbox.js +1 -1
- package/dist/wallet.d.ts +4 -1
- package/dist/wallet.js +2 -1
- package/package.json +2 -14
- package/dist/cli/logs.test.d.ts +0 -1
- package/dist/cli/logs.test.js +0 -272
- package/dist/products/logs.test.d.ts +0 -1
- package/dist/products/logs.test.js +0 -343
package/dist/machine.d.ts
CHANGED
|
@@ -146,7 +146,7 @@ export declare abstract class Machines<M extends Machine<Info, object, Deleted>,
|
|
|
146
146
|
#private;
|
|
147
147
|
constructor(product: MachineProduct, t: Transport, wrap: (t: Transport, info: Info) => M);
|
|
148
148
|
/** Creates a sandbox and waits until it is running. Every field is optional:
|
|
149
|
-
* with none you get the
|
|
149
|
+
* with none you get the free usage while it lasts, the default region and a
|
|
150
150
|
* 2 vCPU / 4 GiB machine from a warm template. `wait: false` returns at once.
|
|
151
151
|
* When every slot without credit or the account's quota is taken, it waits for one to
|
|
152
152
|
* free, up to `waitForCapacityMs` (the client's, two minutes by default). */
|
|
@@ -186,4 +186,18 @@ export declare abstract class Machines<M extends Machine<Info, object, Deleted>,
|
|
|
186
186
|
error: unknown;
|
|
187
187
|
}[];
|
|
188
188
|
}>;
|
|
189
|
+
/** Deletes for good every sandbox whose labels all match, stopped ones
|
|
190
|
+
* included, eight at a time, as `delete()` does one: its disk and paused
|
|
191
|
+
* memory go, its snapshots stay. Says which were deleted and which failed;
|
|
192
|
+
* one failure does not stop the rest. Needs at least one label, so deleting
|
|
193
|
+
* everything is never one call. */
|
|
194
|
+
deleteAll(filter: {
|
|
195
|
+
labels: Record<string, string>;
|
|
196
|
+
}, options?: RequestOptions): Promise<{
|
|
197
|
+
deleted: string[];
|
|
198
|
+
failed: {
|
|
199
|
+
id: string;
|
|
200
|
+
error: unknown;
|
|
201
|
+
}[];
|
|
202
|
+
}>;
|
|
189
203
|
}
|
package/dist/machine.js
CHANGED
|
@@ -408,7 +408,7 @@ export class Machines {
|
|
|
408
408
|
this.#wrap = wrap;
|
|
409
409
|
}
|
|
410
410
|
/** Creates a sandbox and waits until it is running. Every field is optional:
|
|
411
|
-
* with none you get the
|
|
411
|
+
* with none you get the free usage while it lasts, the default region and a
|
|
412
412
|
* 2 vCPU / 4 GiB machine from a warm template. `wait: false` returns at once.
|
|
413
413
|
* When every slot without credit or the account's quota is taken, it waits for one to
|
|
414
414
|
* free, up to `waitForCapacityMs` (the client's, two minutes by default). */
|
|
@@ -493,29 +493,44 @@ export class Machines {
|
|
|
493
493
|
* says which stopped and which failed; one failure does not stop the rest.
|
|
494
494
|
* Needs at least one label, so stopping everything is never one call. */
|
|
495
495
|
async stopAll(filter, options = {}) {
|
|
496
|
+
const { done, failed } = await this.#eachLabelled("stop", filter, false, options, (m, each) => m.stop(each));
|
|
497
|
+
return { stopped: done, failed };
|
|
498
|
+
}
|
|
499
|
+
/** Deletes for good every sandbox whose labels all match, stopped ones
|
|
500
|
+
* included, eight at a time, as `delete()` does one: its disk and paused
|
|
501
|
+
* memory go, its snapshots stay. Says which were deleted and which failed;
|
|
502
|
+
* one failure does not stop the rest. Needs at least one label, so deleting
|
|
503
|
+
* everything is never one call. */
|
|
504
|
+
async deleteAll(filter, options = {}) {
|
|
505
|
+
const { done, failed } = await this.#eachLabelled("delete", filter, true, options, (m, each) => m.delete(each));
|
|
506
|
+
return { deleted: done, failed };
|
|
507
|
+
}
|
|
508
|
+
/** Lists every match first, then acts on each, eight at a time: acting
|
|
509
|
+
* while paging would move the pages under the cursor. */
|
|
510
|
+
async #eachLabelled(verb, filter, includeStopped, options, act) {
|
|
496
511
|
if (!filter?.labels || Object.keys(filter.labels).length === 0) {
|
|
497
512
|
const one = this.#product.noun.toLowerCase();
|
|
498
513
|
throw new RuntimeError({
|
|
499
|
-
message:
|
|
514
|
+
message: `${verb}All needs at least one label to match.`,
|
|
500
515
|
code: "invalid_request",
|
|
501
516
|
status: 0,
|
|
502
|
-
hint:
|
|
517
|
+
hint: `${verb === "stop" ? "Stop" : "Delete"} one ${one} with ${one}.${verb}(), or label the ones to ${verb} together.`,
|
|
503
518
|
});
|
|
504
519
|
}
|
|
505
|
-
const
|
|
506
|
-
// One key cannot name several
|
|
520
|
+
const matches = await (await this.list({ labels: filter.labels, limit: 100, ...(includeStopped ? { includeStopped } : {}) }, options)).toArray(Infinity);
|
|
521
|
+
// One key cannot name several acts: each makes its own.
|
|
507
522
|
const { idempotencyKey: _one, ...each } = options;
|
|
508
523
|
// In list order, oldest first, whichever finishes first.
|
|
509
|
-
const errors = new Array(
|
|
524
|
+
const errors = new Array(matches.length);
|
|
510
525
|
let next = 0;
|
|
511
526
|
const worker = async () => {
|
|
512
|
-
for (let at = next++; at <
|
|
513
|
-
await
|
|
527
|
+
for (let at = next++; at < matches.length; at = next++)
|
|
528
|
+
await act(matches[at], each).catch((error) => (errors[at] = error ?? "failed"));
|
|
514
529
|
};
|
|
515
|
-
await Promise.all(Array.from({ length: Math.min(8,
|
|
530
|
+
await Promise.all(Array.from({ length: Math.min(8, matches.length) }, worker));
|
|
516
531
|
return {
|
|
517
|
-
|
|
518
|
-
failed:
|
|
532
|
+
done: matches.filter((_, at) => errors[at] === undefined).map((m) => m.id),
|
|
533
|
+
failed: matches.flatMap((m, at) => errors[at] === undefined ? [] : [{ id: m.id, error: errors[at] }]),
|
|
519
534
|
};
|
|
520
535
|
}
|
|
521
536
|
}
|
package/dist/mcp.js
CHANGED
|
@@ -1,16 +1,61 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
-
import { resolveCredential } from "./credentials.js";
|
|
2
|
+
import { checkBrowserOrigins, connectionOrigins, resolveCredential, saveWalletConnection, } from "./credentials.js";
|
|
3
3
|
import { RuntimeError } from "./errors.js";
|
|
4
4
|
import { beginDeviceLogin } from "./login.js";
|
|
5
5
|
import { envFetch } from "./proxy.js";
|
|
6
6
|
import { apiOrigin, defaultBaseUrl, VERSION } from "./transport.js";
|
|
7
|
-
const CONNECT_INSTRUCTIONS = "Runtime is not connected on this machine yet. Call runtime_connect: it gives you a link and a code for the person you work for to approve in their browser (no key to copy). Once they have, call runtime_connect again; Runtime's tools then appear (sandboxes, commands, files and the rest).";
|
|
7
|
+
const CONNECT_INSTRUCTIONS = "Runtime is not connected on this machine yet. Call runtime_connect: it gives you a link and a code for the person you work for to approve in their browser (no key to copy). Once they have, call runtime_connect again; Runtime's tools then appear (sandboxes, commands, files and the rest). No person to approve? Open an account paid in USDC: runtime_wallet_open gives an address and the exact amount to send, and runtime_wallet_claim connects once the payment lands (in a shell, `npx withruntime billing topup 20 --new-account --accept-terms --wait` does the same; then call runtime_connect). There is no free trial on that path.";
|
|
8
8
|
const connectTool = {
|
|
9
9
|
name: "runtime_connect",
|
|
10
10
|
title: "Connect this machine to Runtime",
|
|
11
11
|
description: "Connect Runtime on this machine with a browser approval. The first call returns a link and a code: show them to the person you work for and ask them to approve. Call again after they have; it waits up to 45 seconds for the approval, then Runtime's tools appear.",
|
|
12
12
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
13
13
|
};
|
|
14
|
+
const walletOpenTool = {
|
|
15
|
+
name: "runtime_wallet_open",
|
|
16
|
+
title: "Open a Runtime account paid with a wallet",
|
|
17
|
+
description: "For an agent with no person to approve a browser login: opens a Runtime account and its first top-up, paid in USDC on Base, Solana or Tempo. No free trial: the account gets its key only once the payment lands. Answers with an address per network, the exact amount to send and a claim code, shown once: keep it secret, it is the only way to the account. Send exactly that amount from any wallet, then call runtime_wallet_claim with the claim code. Opening an account means agreeing to https://withruntime.com/legal/terms for whoever you act for.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
usd: {
|
|
22
|
+
type: "number",
|
|
23
|
+
description: "Dollars of credit to buy: $10 to $500 in an account's first week.",
|
|
24
|
+
},
|
|
25
|
+
acceptTerms: {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
description: "true agrees to https://withruntime.com/legal/terms. Required.",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ["usd", "acceptTerms"],
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const walletClaimTool = {
|
|
35
|
+
name: "runtime_wallet_claim",
|
|
36
|
+
title: "Connect to a Runtime account opened with a wallet",
|
|
37
|
+
description: "Once a runtime_wallet_open top-up is paid, saves the account's key on this machine (not shown) and connects; Runtime's tools then appear. Until the payment lands it waits up to 45 seconds and says the top-up's status: call it again.",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
claimCode: { type: "string", description: "The claim code runtime_wallet_open gave." },
|
|
42
|
+
},
|
|
43
|
+
required: ["claimCode"],
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
/** Where to send a wallet account's first top-up, for an agent to act on. */
|
|
48
|
+
function describeOpened(opened) {
|
|
49
|
+
const lines = [
|
|
50
|
+
`Opened a Runtime account. Send exactly ${opened.amount} USDC (for $${opened.amountUsd} of credit), on one of these networks:`,
|
|
51
|
+
"",
|
|
52
|
+
];
|
|
53
|
+
for (const network of opened.networks)
|
|
54
|
+
for (const token of network.tokens)
|
|
55
|
+
lines.push(`- ${network.network}: ${token.currency.toUpperCase()} to ${network.address} (token contract ${token.contract})`);
|
|
56
|
+
lines.push("", ...(opened.payBy ? [`Send before ${opened.payBy}.`] : []), "A different amount, token or network cannot be matched or returned automatically.", "", `Claim code (shown once, keep it secret): ${opened.claimCode}`, "Once sent, call runtime_wallet_claim with this claim code: it connects when the payment lands, a few minutes after the transfer.", "A person can take the account over at https://withruntime.com/account/claim with the same code.");
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
}
|
|
14
59
|
/** Bridges an MCP client that speaks stdio to Runtime's hosted MCP endpoint.
|
|
15
60
|
* Newline-delimited JSON-RPC in, the same out; every message is forwarded as
|
|
16
61
|
* it is, so the server's tools, schemas and `instructions` reach the client
|
|
@@ -115,7 +160,73 @@ export async function serveMcp(env = process.env, options = {}) {
|
|
|
115
160
|
for (const reply of answer.messages)
|
|
116
161
|
write(reply);
|
|
117
162
|
}
|
|
118
|
-
/**
|
|
163
|
+
/** The website's wallet sign-up, at the same origins a login would use. */
|
|
164
|
+
function wallet() {
|
|
165
|
+
const origins = connectionOrigins(env);
|
|
166
|
+
checkBrowserOrigins(origins);
|
|
167
|
+
const walletOptions = {
|
|
168
|
+
authUrl: origins.auth,
|
|
169
|
+
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
open: async (input) => (await import("./wallet.js")).openWalletAccount(input, walletOptions),
|
|
173
|
+
claim: async (code) => (await import("./wallet.js")).claimWalletAccount(code, walletOptions),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** A wallet account's claim: once paid, saves its key and connects. Waits up
|
|
177
|
+
* to 45 seconds for a payment on its way, as runtime_connect does. */
|
|
178
|
+
async function claimWallet(code) {
|
|
179
|
+
const until = Date.now() + 45_000;
|
|
180
|
+
for (;;) {
|
|
181
|
+
const claim = await wallet().claim(code);
|
|
182
|
+
if (claim.status === "paid") {
|
|
183
|
+
// The website issues the key once: a save that fails must not lose it.
|
|
184
|
+
let unsaved;
|
|
185
|
+
try {
|
|
186
|
+
await saveWalletConnection(env, claim.apiKey, claim.orgId);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
unsaved = error.message;
|
|
190
|
+
}
|
|
191
|
+
const said = await connected(claim.apiKey, "a wallet account");
|
|
192
|
+
return [
|
|
193
|
+
unsaved === undefined
|
|
194
|
+
? said
|
|
195
|
+
: `${said}\nThe key could not be saved (${unsaved}), and it is issued only once: keep it secret and safe now, as RUNTIME_API_KEY: ${claim.apiKey}`,
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
if (claim.status === "expired" || claim.status === "failed")
|
|
199
|
+
return [
|
|
200
|
+
`The top-up ${claim.status}: nothing was credited. Open a new one with runtime_wallet_open.`,
|
|
201
|
+
true,
|
|
202
|
+
];
|
|
203
|
+
// A top-up that never reached Stripe has no address: waiting cannot help.
|
|
204
|
+
if (claim.topup?.status === "open" && !claim.topup.networks.length)
|
|
205
|
+
return [claim.next ?? "This top-up has no address to pay to yet.", true];
|
|
206
|
+
if (Date.now() + 10_000 > until)
|
|
207
|
+
return [
|
|
208
|
+
claim.status === "pending"
|
|
209
|
+
? "The payment has arrived and is being confirmed. Call runtime_wallet_claim again in a minute."
|
|
210
|
+
: "Not paid yet. Once the transfer has gone through, call runtime_wallet_claim again with the same claim code.",
|
|
211
|
+
];
|
|
212
|
+
await new Promise((resolve) => setTimeout(resolve, 10_000));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Starts forwarding with this key and tells the client its tools changed. */
|
|
216
|
+
async function connected(found, agentName) {
|
|
217
|
+
key = found;
|
|
218
|
+
pending = undefined;
|
|
219
|
+
await post(JSON.stringify({
|
|
220
|
+
jsonrpc: "2.0",
|
|
221
|
+
id: "runtime-connect",
|
|
222
|
+
method: "initialize",
|
|
223
|
+
params: initialize ?? {},
|
|
224
|
+
}));
|
|
225
|
+
await post(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }));
|
|
226
|
+
write({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
|
|
227
|
+
return `Connected${agentName ? ` as ${agentName}` : ""}. Runtime's tools are available now: list the tools again, then start with runtime_sandboxes_create.`;
|
|
228
|
+
}
|
|
229
|
+
/** Not connected yet: answer the client here, with the tools that connect. */
|
|
119
230
|
async function connectMode(message, id) {
|
|
120
231
|
const method = message.method;
|
|
121
232
|
const reply = (result) => write({ jsonrpc: "2.0", id, result });
|
|
@@ -133,38 +244,51 @@ export async function serveMcp(env = process.env, options = {}) {
|
|
|
133
244
|
if (method === "ping")
|
|
134
245
|
return reply({});
|
|
135
246
|
if (method === "tools/list")
|
|
136
|
-
return reply({ tools: [connectTool] });
|
|
247
|
+
return reply({ tools: [connectTool, walletOpenTool, walletClaimTool] });
|
|
248
|
+
const params = (message.params ?? {});
|
|
137
249
|
if (method !== "tools/call" ||
|
|
138
|
-
|
|
250
|
+
!["runtime_connect", "runtime_wallet_open", "runtime_wallet_claim"].includes(params.name))
|
|
139
251
|
return write(failure(id, "Runtime is not connected yet: call runtime_connect first."));
|
|
140
|
-
const text = (words) => reply({ content: [{ type: "text", text: words }] });
|
|
252
|
+
const text = (words, isError = false) => reply({ content: [{ type: "text", text: words }], ...(isError ? { isError } : {}) });
|
|
253
|
+
if (params.name !== "runtime_connect") {
|
|
254
|
+
const input = (params.arguments ?? {});
|
|
255
|
+
try {
|
|
256
|
+
if (params.name === "runtime_wallet_open") {
|
|
257
|
+
if (input.acceptTerms !== true)
|
|
258
|
+
return text("Opening an account means agreeing to https://withruntime.com/legal/terms. Call again with acceptTerms: true to agree to them for whoever you act for.", true);
|
|
259
|
+
if (typeof input.usd !== "number" || !(input.usd > 0))
|
|
260
|
+
return text("Say how many dollars of credit to buy in usd, such as 20.", true);
|
|
261
|
+
return text(describeOpened(await wallet().open({ usd: input.usd, acceptTerms: true })));
|
|
262
|
+
}
|
|
263
|
+
if (typeof input.claimCode !== "string" || !input.claimCode.trim())
|
|
264
|
+
return text("Give the claimCode runtime_wallet_open answered.", true);
|
|
265
|
+
return text(...(await claimWallet(input.claimCode.trim())));
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
return text(error instanceof Error ? error.message : String(error), true);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
141
271
|
try {
|
|
272
|
+
// A key saved since this server started (a wallet account's claim, a
|
|
273
|
+
// login in a shell) connects without a browser.
|
|
274
|
+
const saved = await resolveCredential(env).catch(() => undefined);
|
|
275
|
+
if (saved)
|
|
276
|
+
return text(await connected(saved));
|
|
142
277
|
if (!pending) {
|
|
143
278
|
pending = await beginDeviceLogin(env, env.RUNTIME_AGENT_NAME ?? "MCP agent", {
|
|
144
279
|
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
145
280
|
});
|
|
146
281
|
if (!env.RUNTIME_NO_BROWSER && !pending.resumed)
|
|
147
282
|
await (options.openBrowser ?? openBrowser)(pending.url).catch(() => undefined);
|
|
148
|
-
return text(`Ask the person you work for to open ${pending.url} (it may already be open in their browser), sign in to Runtime, check that it shows the code ${pending.userCode}, and choose Connect agent. Then call runtime_connect again.`);
|
|
283
|
+
return text(`Ask the person you work for to open ${pending.url} (it may already be open in their browser), sign in to Runtime, check that it shows the code ${pending.userCode}, and choose Connect agent. Then call runtime_connect again. No person to approve? See this server's instructions to open an account paid in USDC.`);
|
|
149
284
|
}
|
|
150
285
|
const until = Math.min(pending.expiresAt, Date.now() + 45_000);
|
|
151
286
|
while (Date.now() < until) {
|
|
152
287
|
const outcome = await pending.check();
|
|
153
288
|
if (outcome === "expired")
|
|
154
289
|
break;
|
|
155
|
-
if (outcome !== "pending")
|
|
156
|
-
|
|
157
|
-
pending = undefined;
|
|
158
|
-
await post(JSON.stringify({
|
|
159
|
-
jsonrpc: "2.0",
|
|
160
|
-
id: "runtime-connect",
|
|
161
|
-
method: "initialize",
|
|
162
|
-
params: initialize ?? {},
|
|
163
|
-
}));
|
|
164
|
-
await post(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }));
|
|
165
|
-
write({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
|
|
166
|
-
return text(`Connected as ${outcome.agentName}. Runtime's tools are available now: list the tools again, then start with runtime_sandboxes_create.`);
|
|
167
|
-
}
|
|
290
|
+
if (outcome !== "pending")
|
|
291
|
+
return text(await connected(await resolveCredential(env), outcome.agentName));
|
|
168
292
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
169
293
|
}
|
|
170
294
|
if (Date.now() >= pending.expiresAt) {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { RequestOptions, Transport } from "../transport.js";
|
|
2
|
+
export type NewKey = {
|
|
3
|
+
/** The key's name, and its agent's: up to 80 characters. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** full (the default): every action. read: every read, nothing else. */
|
|
6
|
+
access?: "full" | "read";
|
|
7
|
+
/** agent (the default): the key sees and acts on only what it makes.
|
|
8
|
+
* account: everything in the account; only an account-wide key makes one. */
|
|
9
|
+
reach?: "agent" | "account";
|
|
10
|
+
/** The most its agent may commit in any 24 hours, or null for none. Needed
|
|
11
|
+
* when the making key has a limit: the limits of the keys a key made add
|
|
12
|
+
* up to at most its own. */
|
|
13
|
+
dailyLimitMicros?: number | null;
|
|
14
|
+
};
|
|
15
|
+
export type MadeKey = {
|
|
16
|
+
/** The key itself. Shown once: store it now. */
|
|
17
|
+
key: string;
|
|
18
|
+
keyId: string;
|
|
19
|
+
agentId: string;
|
|
20
|
+
orgId: string;
|
|
21
|
+
name: string;
|
|
22
|
+
access: "full" | "read";
|
|
23
|
+
reach: "agent" | "account";
|
|
24
|
+
dailyLimitMicros: number | null;
|
|
25
|
+
/** The making key's expiry, or null for until revoked. */
|
|
26
|
+
expires: string | null;
|
|
27
|
+
/** The id of the key that made it. */
|
|
28
|
+
madeBy: string;
|
|
29
|
+
};
|
|
30
|
+
export type ListedKey = {
|
|
31
|
+
keyId: string;
|
|
32
|
+
agentId: string;
|
|
33
|
+
name: string;
|
|
34
|
+
access: "full" | "read" | "selected";
|
|
35
|
+
reach: "agent" | "account";
|
|
36
|
+
dailyLimitMicros: number | null;
|
|
37
|
+
/** Committed in the last 24 hours: settled plus held, as the limit counts. */
|
|
38
|
+
dailyUsedMicros: number;
|
|
39
|
+
/** The key that made it, or null for one made on the website. */
|
|
40
|
+
madeBy: string | null;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
expiresAt: string | null;
|
|
43
|
+
revokedAt: string | null;
|
|
44
|
+
};
|
|
45
|
+
/** One key's spend, from `GET /v1/usage?key=`. Microdollars as strings. */
|
|
46
|
+
export type KeyUsage = {
|
|
47
|
+
keyId: string;
|
|
48
|
+
agentId: string;
|
|
49
|
+
name: string;
|
|
50
|
+
madeBy: string | null;
|
|
51
|
+
revokedAt: string | null;
|
|
52
|
+
dailyLimitMicros: string | null;
|
|
53
|
+
dailyUsedMicros: string;
|
|
54
|
+
spentMicros: string;
|
|
55
|
+
heldMicros: string;
|
|
56
|
+
runningSandboxes: number;
|
|
57
|
+
resources: number;
|
|
58
|
+
};
|
|
59
|
+
export declare function keys(t: Transport): {
|
|
60
|
+
/** Make a key with this one. It never has more than this key, and is in
|
|
61
|
+
* the audit log as made by it. At most 100 live in an account. */
|
|
62
|
+
create: (input: NewKey, options?: RequestOptions) => Promise<MadeKey>;
|
|
63
|
+
/** This key and the keys it made (every key, for an account-wide key),
|
|
64
|
+
* live first, with what each spent in the last 24 hours. */
|
|
65
|
+
list: (options?: RequestOptions) => Promise<ListedKey[]>;
|
|
66
|
+
/** Revoke this key or one it made: a worker's, when the worker is done. */
|
|
67
|
+
revoke: (keyId: string, options?: RequestOptions) => Promise<{
|
|
68
|
+
keyId: string;
|
|
69
|
+
revokedAt: string;
|
|
70
|
+
}>;
|
|
71
|
+
/** What one key spent: itself, or a key it made. */
|
|
72
|
+
usage: (keyId: string, options?: RequestOptions) => Promise<KeyUsage>;
|
|
73
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function keys(t) {
|
|
2
|
+
return {
|
|
3
|
+
/** Make a key with this one. It never has more than this key, and is in
|
|
4
|
+
* the audit log as made by it. At most 100 live in an account. */
|
|
5
|
+
create: (input, options) => t.json({ method: "POST", path: "/v1/keys", body: input, ...options }),
|
|
6
|
+
/** This key and the keys it made (every key, for an account-wide key),
|
|
7
|
+
* live first, with what each spent in the last 24 hours. */
|
|
8
|
+
list: async (options) => (await t.json({ method: "GET", path: "/v1/keys", ...options })).keys,
|
|
9
|
+
/** Revoke this key or one it made: a worker's, when the worker is done. */
|
|
10
|
+
revoke: (keyId, options) => t.json({
|
|
11
|
+
method: "DELETE",
|
|
12
|
+
path: `/v1/keys/${encodeURIComponent(keyId)}`,
|
|
13
|
+
...options,
|
|
14
|
+
}),
|
|
15
|
+
/** What one key spent: itself, or a key it made. */
|
|
16
|
+
usage: async (keyId, options) => (await t.json({
|
|
17
|
+
method: "GET",
|
|
18
|
+
path: "/v1/usage",
|
|
19
|
+
query: { key: keyId },
|
|
20
|
+
...options,
|
|
21
|
+
})).key,
|
|
22
|
+
};
|
|
23
|
+
}
|
package/dist/products/apps.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Page } from "../page.js";
|
|
2
1
|
import type { RequestOptions, Transport } from "../transport.js";
|
|
2
|
+
import { type ResourceDeleteOptions, type ResourceGetOptions, type ResourceListQuery, type ResourceWaitOptions } from "./resources.js";
|
|
3
3
|
/** Private until launch. A deployment's completion follows its own record,
|
|
4
4
|
* not the app's state. */
|
|
5
5
|
export type AppState = "waiting" | "live" | "failed" | "deleting" | "deleted";
|
|
@@ -112,36 +112,17 @@ export type DeployGetOptions = RequestOptions & {
|
|
|
112
112
|
waitFor?: DeployState;
|
|
113
113
|
timeoutSeconds?: number;
|
|
114
114
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
state?: readonly AppState[];
|
|
121
|
-
/** 1 to 100; defaults to 50. */
|
|
122
|
-
limit?: number;
|
|
123
|
-
cursor?: string;
|
|
124
|
-
};
|
|
125
|
-
export type AppGetOptions = RequestOptions & {
|
|
126
|
-
waitFor?: AppState;
|
|
127
|
-
/** Server wait, 1 to 60 seconds; defaults to 30. Check the returned state. */
|
|
128
|
-
timeoutSeconds?: number;
|
|
129
|
-
};
|
|
130
|
-
export type AppWaitOptions = RequestOptions & {
|
|
131
|
-
/** Whole wait, including retries; defaults to 300000 ms. 0 disables it. */
|
|
132
|
-
timeoutMs?: number;
|
|
133
|
-
/** Delay between reads; defaults to 250 ms. */
|
|
134
|
-
pollMs?: number;
|
|
135
|
-
};
|
|
136
|
-
export type AppDeleteOptions = AppWaitOptions & {
|
|
137
|
-
wait?: boolean;
|
|
138
|
-
};
|
|
115
|
+
/** Defaults to waiting, live and failed. */
|
|
116
|
+
export type AppListQuery = ResourceListQuery<AppState>;
|
|
117
|
+
export type AppGetOptions = ResourceGetOptions<AppState>;
|
|
118
|
+
export type AppWaitOptions = ResourceWaitOptions;
|
|
119
|
+
export type AppDeleteOptions = ResourceDeleteOptions;
|
|
139
120
|
export declare function apps(t: Transport): {
|
|
140
121
|
/** Create a resource waiting for its first version. Defaults belong to
|
|
141
122
|
* the API, so the SDK does not override them. */
|
|
142
123
|
create: (input: CreateApp, options?: RequestOptions) => Promise<App>;
|
|
143
|
-
get: (id: string, options?:
|
|
144
|
-
list: (query?:
|
|
124
|
+
get: (id: string, options?: ResourceGetOptions<AppState>) => Promise<App>;
|
|
125
|
+
list: (query?: ResourceListQuery<AppState>, options?: RequestOptions) => Promise<import("../page.js").Page<App>>;
|
|
145
126
|
deploy: (id: string, input: DeployApp, options?: AppWaitOptions) => Promise<AppDeploy>;
|
|
146
127
|
getDeploy: (id: string, deployId: string, options?: DeployGetOptions) => Promise<AppDeploy>;
|
|
147
128
|
/** Resume by deploy id after a process restart, without creating another version. */
|
|
@@ -153,14 +134,14 @@ export declare function apps(t: Transport): {
|
|
|
153
134
|
/** Follow resource state, not a deployment: an
|
|
154
135
|
* older live version can survive a failed rollout of a newer version. */
|
|
155
136
|
wait: (id: string, target: "live" | "deleted", options?: AppWaitOptions) => Promise<App>;
|
|
156
|
-
delete: (id: string, options?:
|
|
137
|
+
delete: (id: string, options?: ResourceDeleteOptions) => Promise<App>;
|
|
157
138
|
};
|
|
158
139
|
export declare function functions(t: Transport): {
|
|
159
140
|
/** Create a resource waiting for its first version. Defaults belong to
|
|
160
141
|
* the API, so the SDK does not override them. */
|
|
161
142
|
create: (input: CreateFunction, options?: RequestOptions) => Promise<RuntimeFunction>;
|
|
162
|
-
get: (id: string, options?:
|
|
163
|
-
list: (query?:
|
|
143
|
+
get: (id: string, options?: ResourceGetOptions<AppState>) => Promise<RuntimeFunction>;
|
|
144
|
+
list: (query?: ResourceListQuery<AppState>, options?: RequestOptions) => Promise<import("../page.js").Page<RuntimeFunction>>;
|
|
164
145
|
deploy: (id: string, input: DeployApp, options?: AppWaitOptions) => Promise<AppDeploy>;
|
|
165
146
|
getDeploy: (id: string, deployId: string, options?: DeployGetOptions) => Promise<AppDeploy>;
|
|
166
147
|
/** Resume by deploy id after a process restart, without creating another version. */
|
|
@@ -172,5 +153,5 @@ export declare function functions(t: Transport): {
|
|
|
172
153
|
/** Follow resource state, not a deployment: an
|
|
173
154
|
* older live version can survive a failed rollout of a newer version. */
|
|
174
155
|
wait: (id: string, target: "live" | "deleted", options?: AppWaitOptions) => Promise<RuntimeFunction>;
|
|
175
|
-
delete: (id: string, options?:
|
|
156
|
+
delete: (id: string, options?: ResourceDeleteOptions) => Promise<RuntimeFunction>;
|
|
176
157
|
};
|
package/dist/products/apps.js
CHANGED
|
@@ -1,106 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Page } from "../page.js";
|
|
3
|
-
import { pause } from "../wait.js";
|
|
4
|
-
const enc = encodeURIComponent;
|
|
5
|
-
function invalid(field) {
|
|
6
|
-
return new RuntimeError({
|
|
7
|
-
code: "invalid_request",
|
|
8
|
-
status: 0,
|
|
9
|
-
message: `${field} must be a finite, nonnegative number.`,
|
|
10
|
-
hint: "Fix the option and call again; nothing was sent.",
|
|
11
|
-
details: { field },
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
/** One deadline covers the write, retries and every read. A timed-out delete
|
|
15
|
-
* can be retried after a process restart with this same key. */
|
|
16
|
-
async function following(options, work) {
|
|
17
|
-
const { pollMs = 250, timeoutMs = 300_000, signal, ...rest } = options;
|
|
18
|
-
if (!Number.isFinite(pollMs) || pollMs < 0)
|
|
19
|
-
throw invalid("pollMs");
|
|
20
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs < 0)
|
|
21
|
-
throw invalid("timeoutMs");
|
|
22
|
-
const deadline = new AbortController();
|
|
23
|
-
const combined = signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal;
|
|
24
|
-
const timer = timeoutMs > 0 ? setTimeout(() => deadline.abort(), timeoutMs) : undefined;
|
|
25
|
-
const request = { ...rest, timeoutMs: 0, signal: combined };
|
|
26
|
-
const poll = () => pause(pollMs, combined);
|
|
27
|
-
try {
|
|
28
|
-
combined.throwIfAborted();
|
|
29
|
-
const result = await work(request, poll);
|
|
30
|
-
combined.throwIfAborted();
|
|
31
|
-
return result;
|
|
32
|
-
}
|
|
33
|
-
catch (cause) {
|
|
34
|
-
if (!combined.aborted) {
|
|
35
|
-
// A read failure still belongs to the write that started this wait.
|
|
36
|
-
// Keep its key so a caller can recover after losing the reply.
|
|
37
|
-
if (cause instanceof RuntimeError && rest.idempotencyKey && !cause.idempotencyKey)
|
|
38
|
-
throw new RuntimeError({
|
|
39
|
-
message: cause.message,
|
|
40
|
-
code: cause.code,
|
|
41
|
-
status: cause.status,
|
|
42
|
-
requestId: cause.requestId,
|
|
43
|
-
hint: cause.hint,
|
|
44
|
-
details: cause.details,
|
|
45
|
-
retryAfterMs: cause.retryAfterMs,
|
|
46
|
-
idempotencyKey: rest.idempotencyKey,
|
|
47
|
-
cause,
|
|
48
|
-
});
|
|
49
|
-
throw cause;
|
|
50
|
-
}
|
|
51
|
-
throw new RuntimeError({
|
|
52
|
-
code: "timeout",
|
|
53
|
-
status: 0,
|
|
54
|
-
message: "Waiting was cancelled or reached its deadline; the resource may still change.",
|
|
55
|
-
hint: "Read the resource to resume waiting. Retry a write with the same idempotencyKey.",
|
|
56
|
-
...(rest.idempotencyKey === undefined ? {} : { idempotencyKey: rest.idempotencyKey }),
|
|
57
|
-
cause,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
clearTimeout(timer);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
1
|
+
import { enc, following, resource, } from "./resources.js";
|
|
64
2
|
function copies(t, plural) {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const { waitFor, timeoutSeconds, ...request } = options;
|
|
68
|
-
return t.json({
|
|
69
|
-
method: "GET",
|
|
70
|
-
path: `${base}/${enc(id)}`,
|
|
71
|
-
query: { waitFor, timeoutSeconds },
|
|
72
|
-
...request,
|
|
73
|
-
});
|
|
74
|
-
};
|
|
75
|
-
const list = async (query = {}, options) => {
|
|
76
|
-
const page = await t.json({
|
|
77
|
-
method: "GET",
|
|
78
|
-
path: base,
|
|
79
|
-
query,
|
|
80
|
-
...options,
|
|
81
|
-
});
|
|
82
|
-
return new Page(page.data, page.nextCursor, (cursor) => list({ ...query, cursor }, options));
|
|
83
|
-
};
|
|
84
|
-
const follow = async (id, target, request, pause, initial) => {
|
|
85
|
-
let record = initial ?? (await get(id, request));
|
|
86
|
-
for (;;) {
|
|
87
|
-
if (record.state === target)
|
|
88
|
-
return record;
|
|
89
|
-
if (["failed", "deleted", "live"].includes(record.state))
|
|
90
|
-
throw new RuntimeError({
|
|
91
|
-
code: record.error?.code ?? "unexpected_state",
|
|
92
|
-
status: 0,
|
|
93
|
-
message: record.error?.message ?? `Expected ${target}, but ${id} reached ${record.state}.`,
|
|
94
|
-
hint: "Read the resource and inspect its error before starting another change.",
|
|
95
|
-
details: { id, state: record.state, target, resource: record },
|
|
96
|
-
...(request.idempotencyKey === undefined
|
|
97
|
-
? {}
|
|
98
|
-
: { idempotencyKey: request.idempotencyKey }),
|
|
99
|
-
});
|
|
100
|
-
await pause();
|
|
101
|
-
record = await get(id, request);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
3
|
+
const shape = resource(t, plural, ["failed", "deleted", "live"]);
|
|
4
|
+
const { base, get } = shape;
|
|
104
5
|
const getDeploy = (id, deployId, options = {}) => {
|
|
105
6
|
const { waitFor, timeoutSeconds, ...request } = options;
|
|
106
7
|
return t.json({
|
|
@@ -128,9 +29,9 @@ function copies(t, plural) {
|
|
|
128
29
|
return {
|
|
129
30
|
/** Create a resource waiting for its first version. Defaults belong to
|
|
130
31
|
* the API, so the SDK does not override them. */
|
|
131
|
-
create:
|
|
32
|
+
create: shape.create,
|
|
132
33
|
get,
|
|
133
|
-
list,
|
|
34
|
+
list: shape.list,
|
|
134
35
|
deploy: (id, input, options = {}) => {
|
|
135
36
|
const { wait = false, ...body } = input;
|
|
136
37
|
return writeDeploy(id, "deploys", body, wait, options);
|
|
@@ -144,17 +45,11 @@ function copies(t, plural) {
|
|
|
144
45
|
},
|
|
145
46
|
versions: (id, options) => t.json({ method: "GET", path: `${base}/${enc(id)}/versions`, ...options }),
|
|
146
47
|
copies: (id, options) => t.json({ method: "GET", path: `${base}/${enc(id)}/copies`, ...options }),
|
|
147
|
-
update:
|
|
48
|
+
update: shape.update,
|
|
148
49
|
/** Follow resource state, not a deployment: an
|
|
149
50
|
* older live version can survive a failed rollout of a newer version. */
|
|
150
|
-
wait: (id, target, options = {}) =>
|
|
151
|
-
delete:
|
|
152
|
-
const { wait = false, pollMs, ...request } = options;
|
|
153
|
-
const remove = (rest) => t.json({ method: "DELETE", path: `${base}/${enc(id)}`, ...rest });
|
|
154
|
-
if (!wait)
|
|
155
|
-
return remove(request);
|
|
156
|
-
return following({ ...request, pollMs, idempotencyKey: request.idempotencyKey ?? crypto.randomUUID() }, async (rest, pause) => follow(id, "deleted", rest, pause, await remove(rest)));
|
|
157
|
-
},
|
|
51
|
+
wait: (id, target, options = {}) => shape.wait(id, target, options),
|
|
52
|
+
delete: shape.delete,
|
|
158
53
|
};
|
|
159
54
|
}
|
|
160
55
|
export function apps(t) {
|