@nodetool-ai/compute 0.8.1 → 0.8.3
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/dist/.nodetool-build-stamp +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/manager.d.ts +12 -10
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +57 -31
- package/dist/manager.js.map +1 -1
- package/dist/providers/runpod-rest.d.ts +15 -0
- package/dist/providers/runpod-rest.d.ts.map +1 -1
- package/dist/providers/runpod-rest.js +9 -0
- package/dist/providers/runpod-rest.js.map +1 -1
- package/dist/providers/types.d.ts +22 -2
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/verda-api.d.ts +63 -0
- package/dist/providers/verda-api.d.ts.map +1 -0
- package/dist/providers/verda-api.js +205 -0
- package/dist/providers/verda-api.js.map +1 -0
- package/dist/providers/verda.d.ts +75 -0
- package/dist/providers/verda.d.ts.map +1 -0
- package/dist/providers/verda.js +560 -0
- package/dist/providers/verda.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Verda cloud API transport — authentication, retries, and response decoding.
|
|
2
|
+
//
|
|
3
|
+
// Split from `verda.ts` so the provider reads as lifecycle logic while the
|
|
4
|
+
// awkward parts of the wire protocol live here:
|
|
5
|
+
//
|
|
6
|
+
// * OAuth2 client-credentials tokens (`POST /v1/oauth2/token`) with a single
|
|
7
|
+
// in-flight refresh per client, refreshed before expiry.
|
|
8
|
+
// * The documented rate limits (500 authenticated req/min per project, 60/min
|
|
9
|
+
// per method+path) surface as 429 + `Retry-After`, honoured with bounded
|
|
10
|
+
// retries.
|
|
11
|
+
// * Verda answers instance actions asynchronously: 202 (accepted), 204 (the
|
|
12
|
+
// single action was already satisfied), 207 (a bulk action partly failed).
|
|
13
|
+
// Callers get the status code, not a decoded resource, so they can tell
|
|
14
|
+
// "accepted" from "done".
|
|
15
|
+
//
|
|
16
|
+
// Credentials are injected by the caller (the `WorkerManager` sources them from
|
|
17
|
+
// the secret store); this module never reads `process.env`.
|
|
18
|
+
/** Production base URL. The rendered API reference advertises localhost in its
|
|
19
|
+
* `servers` block, so it is pinned here rather than taken from a generator. */
|
|
20
|
+
const VERDA_API_BASE_URL = "https://api.verda.com";
|
|
21
|
+
/** Refresh a token this long before it expires, to cover clock skew + latency. */
|
|
22
|
+
const TOKEN_REFRESH_SKEW_MS = 60_000;
|
|
23
|
+
/** Cap on retries of a 429/503 before the call fails. */
|
|
24
|
+
const MAX_RETRIES = 3;
|
|
25
|
+
/** Fallback backoff when a 429 carries no usable `Retry-After`. */
|
|
26
|
+
const DEFAULT_RETRY_DELAY_MS = 2_000;
|
|
27
|
+
/**
|
|
28
|
+
* A Verda API failure carrying the HTTP status, so callers can tell apart the
|
|
29
|
+
* failures the brief requires be handled distinctly: no capacity (503),
|
|
30
|
+
* authentication (401/403), quota/funds (402/422) and malformed input (400).
|
|
31
|
+
*/
|
|
32
|
+
export class VerdaApiError extends Error {
|
|
33
|
+
status;
|
|
34
|
+
path;
|
|
35
|
+
constructor(message, status, path) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.status = status;
|
|
38
|
+
this.path = path;
|
|
39
|
+
this.name = "VerdaApiError";
|
|
40
|
+
}
|
|
41
|
+
/** The provider reported no capacity for the requested configuration. */
|
|
42
|
+
get isNoCapacity() {
|
|
43
|
+
return this.status === 503;
|
|
44
|
+
}
|
|
45
|
+
/** The credentials were rejected — retrying with the same token cannot help. */
|
|
46
|
+
get isAuth() {
|
|
47
|
+
return this.status === 401 || this.status === 403;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reject a resource identifier that would escape its intended API path.
|
|
52
|
+
*
|
|
53
|
+
* The Verda Python SDK shipped a path-traversal fix in 1.24.1 (a crafted
|
|
54
|
+
* resource name or id could redirect an authenticated request to a different
|
|
55
|
+
* endpoint). NodeTool interpolates ids into paths too — a `provider_ref` read
|
|
56
|
+
* back from the registry, a volume id from a provider response — so the same
|
|
57
|
+
* check is enforced here rather than trusted to the far side.
|
|
58
|
+
*/
|
|
59
|
+
export function assertSafeId(id, label) {
|
|
60
|
+
if (!/^[A-Za-z0-9._-]+$/.test(id)) {
|
|
61
|
+
throw new Error(`Unsafe Verda ${label} ${JSON.stringify(id)}: ids may contain only ` +
|
|
62
|
+
`letters, digits, dot, underscore and hyphen`);
|
|
63
|
+
}
|
|
64
|
+
return id;
|
|
65
|
+
}
|
|
66
|
+
/** Parse `Retry-After` (delta-seconds or HTTP-date) into milliseconds. */
|
|
67
|
+
function retryAfterMs(header) {
|
|
68
|
+
if (!header)
|
|
69
|
+
return DEFAULT_RETRY_DELAY_MS;
|
|
70
|
+
const seconds = Number(header);
|
|
71
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
72
|
+
return seconds * 1_000;
|
|
73
|
+
}
|
|
74
|
+
const date = Date.parse(header);
|
|
75
|
+
if (!Number.isNaN(date)) {
|
|
76
|
+
return Math.max(0, date - Date.now());
|
|
77
|
+
}
|
|
78
|
+
return DEFAULT_RETRY_DELAY_MS;
|
|
79
|
+
}
|
|
80
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
81
|
+
/**
|
|
82
|
+
* Authenticated Verda REST client. One instance per credential pair holds that
|
|
83
|
+
* pair's access token; refresh is serialized so a burst of concurrent calls
|
|
84
|
+
* mints one token rather than racing.
|
|
85
|
+
*/
|
|
86
|
+
export class VerdaApiClient {
|
|
87
|
+
credentials;
|
|
88
|
+
baseUrl;
|
|
89
|
+
accessToken = null;
|
|
90
|
+
expiresAt = 0;
|
|
91
|
+
/** In-flight refresh, shared by every caller that arrives during it. */
|
|
92
|
+
pending = null;
|
|
93
|
+
constructor(credentials, baseUrl = VERDA_API_BASE_URL) {
|
|
94
|
+
this.credentials = credentials;
|
|
95
|
+
this.baseUrl = baseUrl;
|
|
96
|
+
if (!credentials.clientId || !credentials.clientSecret) {
|
|
97
|
+
throw new Error("Verda requires both VERDA_CLIENT_ID and VERDA_CLIENT_SECRET");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Call an authenticated endpoint. Retries 429/503 with backoff, and retries
|
|
102
|
+
* ONCE after a 401 with a freshly minted token (a token can expire mid-flight);
|
|
103
|
+
* a second 401 is a credential problem and is surfaced.
|
|
104
|
+
*/
|
|
105
|
+
async request(method, path, body) {
|
|
106
|
+
let reauthed = false;
|
|
107
|
+
for (let attempt = 0;; attempt++) {
|
|
108
|
+
const token = await this.token();
|
|
109
|
+
const response = await this.send(method, path, token, body);
|
|
110
|
+
if (response.status === 401 && !reauthed) {
|
|
111
|
+
// The token was rejected — drop it and mint a new one exactly once.
|
|
112
|
+
reauthed = true;
|
|
113
|
+
this.invalidate();
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if ((response.status === 429 || response.status === 503) &&
|
|
117
|
+
attempt < MAX_RETRIES) {
|
|
118
|
+
await sleep(retryAfterMs(response.headers.get("retry-after")));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
return this.decode(response, method, path);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Drop the cached token, forcing the next call to mint a fresh one. */
|
|
125
|
+
invalidate() {
|
|
126
|
+
this.accessToken = null;
|
|
127
|
+
this.expiresAt = 0;
|
|
128
|
+
}
|
|
129
|
+
// --- Internals ----------------------------------------------------------
|
|
130
|
+
async send(method, path, token, body) {
|
|
131
|
+
const init = {
|
|
132
|
+
method,
|
|
133
|
+
headers: {
|
|
134
|
+
Authorization: `Bearer ${token}`,
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
Accept: "application/json",
|
|
137
|
+
},
|
|
138
|
+
signal: AbortSignal.timeout(60_000),
|
|
139
|
+
};
|
|
140
|
+
if (body && method !== "GET") {
|
|
141
|
+
init.body = JSON.stringify(body);
|
|
142
|
+
}
|
|
143
|
+
return fetch(`${this.baseUrl}${path}`, init);
|
|
144
|
+
}
|
|
145
|
+
async decode(response, method, path) {
|
|
146
|
+
const text = await response.text().catch(() => "");
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
// Response bodies can echo request fields; include only the provider's
|
|
149
|
+
// message, never the request body, which carries the worker token.
|
|
150
|
+
throw new VerdaApiError(`Verda ${method} ${path} failed (${response.status}): ${text.slice(0, 500)}`, response.status, path);
|
|
151
|
+
}
|
|
152
|
+
// 202/204 legitimately carry no body — an action was accepted, not applied.
|
|
153
|
+
let parsed = null;
|
|
154
|
+
if (text) {
|
|
155
|
+
try {
|
|
156
|
+
parsed = JSON.parse(text);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Some creates answer with a bare id rather than JSON; keep the text.
|
|
160
|
+
parsed = text;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { status: response.status, body: parsed };
|
|
164
|
+
}
|
|
165
|
+
/** Return a live access token, refreshing when absent or near expiry. */
|
|
166
|
+
token() {
|
|
167
|
+
if (this.accessToken && Date.now() < this.expiresAt - TOKEN_REFRESH_SKEW_MS) {
|
|
168
|
+
return Promise.resolve(this.accessToken);
|
|
169
|
+
}
|
|
170
|
+
// Serialize: concurrent callers share one refresh rather than each minting
|
|
171
|
+
// a token (and burning the per-path rate limit on /oauth2/token).
|
|
172
|
+
this.pending ??= this.refresh().finally(() => {
|
|
173
|
+
this.pending = null;
|
|
174
|
+
});
|
|
175
|
+
return this.pending;
|
|
176
|
+
}
|
|
177
|
+
async refresh() {
|
|
178
|
+
const response = await fetch(`${this.baseUrl}/v1/oauth2/token`, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
181
|
+
body: JSON.stringify({
|
|
182
|
+
grant_type: "client_credentials",
|
|
183
|
+
client_id: this.credentials.clientId,
|
|
184
|
+
client_secret: this.credentials.clientSecret,
|
|
185
|
+
}),
|
|
186
|
+
signal: AbortSignal.timeout(30_000),
|
|
187
|
+
});
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
// Never echo the body: the request carried the client secret and a
|
|
190
|
+
// provider error may quote it back.
|
|
191
|
+
throw new VerdaApiError(`Verda authentication failed (${response.status}). Check ` +
|
|
192
|
+
`VERDA_CLIENT_ID and VERDA_CLIENT_SECRET.`, response.status, "/v1/oauth2/token");
|
|
193
|
+
}
|
|
194
|
+
const token = (await response.json());
|
|
195
|
+
if (!token.access_token) {
|
|
196
|
+
throw new Error("Verda token response contained no access_token");
|
|
197
|
+
}
|
|
198
|
+
this.accessToken = token.access_token;
|
|
199
|
+
// Treat a missing expires_in conservatively rather than caching forever.
|
|
200
|
+
const ttl = typeof token.expires_in === "number" ? token.expires_in : 300;
|
|
201
|
+
this.expiresAt = Date.now() + ttl * 1_000;
|
|
202
|
+
return token.access_token;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=verda-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verda-api.js","sourceRoot":"","sources":["../../src/providers/verda-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type ProviderInstance, type ProvisionResult, type WorkerProvider, type WorkerSpec, type WorkerStatus } from "./types.js";
|
|
2
|
+
import { VerdaApiClient, type VerdaCredentials } from "./verda-api.js";
|
|
3
|
+
export declare class VerdaProvider implements WorkerProvider {
|
|
4
|
+
private readonly api;
|
|
5
|
+
constructor(credentials: VerdaCredentials, api?: VerdaApiClient);
|
|
6
|
+
provision(spec: WorkerSpec): Promise<ProvisionResult>;
|
|
7
|
+
status(ref: string): Promise<WorkerStatus>;
|
|
8
|
+
/**
|
|
9
|
+
* Pause: release the GPU compute, retain the OS volume and its model cache.
|
|
10
|
+
*
|
|
11
|
+
* This DELETES the Verda instance. A `shutdown` would keep billing the full
|
|
12
|
+
* instance rate (Verda removed hibernate for the same reason), so it is not
|
|
13
|
+
* the operation the cost guard needs.
|
|
14
|
+
*/
|
|
15
|
+
stop(ref: string): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Resume: create a NEW machine that boots from the retained OS volume.
|
|
18
|
+
*
|
|
19
|
+
* Returns a new provider ref (the instance id changed). Verda can refuse for
|
|
20
|
+
* lack of capacity, which surfaces as a distinct error rather than a retry.
|
|
21
|
+
*/
|
|
22
|
+
resume(ref: string): Promise<ProvisionResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Destroy the worker AND its OS volume — the real teardown that ends every
|
|
25
|
+
* charge. The volume is deleted PERMANENTLY: a trashed volume still occupies
|
|
26
|
+
* the storage quota and can be restored (at a charge for the deleted
|
|
27
|
+
* interval), which is not what "terminate" promises. The model cache is gone.
|
|
28
|
+
*/
|
|
29
|
+
terminate(ref: string): Promise<void>;
|
|
30
|
+
list(): Promise<ProviderInstance[]>;
|
|
31
|
+
private resultFor;
|
|
32
|
+
/**
|
|
33
|
+
* Pick the guest OS image. An explicit `osImage` is validated against the
|
|
34
|
+
* live catalog (a typo would otherwise fail deep inside provisioning); with
|
|
35
|
+
* none set, prefer an image that ships CUDA and Docker, which the bootstrap
|
|
36
|
+
* script needs.
|
|
37
|
+
*/
|
|
38
|
+
private resolveOsImage;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the required `location_code` against LIVE availability — a catalog
|
|
41
|
+
* entry is not a reservation, so this is re-read immediately before each create.
|
|
42
|
+
* An explicit region is verified rather than trusted.
|
|
43
|
+
*/
|
|
44
|
+
private resolveLocation;
|
|
45
|
+
/** Register the spec's public key, reusing an identical existing key. */
|
|
46
|
+
private ensureSshKey;
|
|
47
|
+
/**
|
|
48
|
+
* Create the startup script that turns a bare VM into a NodeTool worker.
|
|
49
|
+
*
|
|
50
|
+
* The script is idempotent: Verda runs it on every boot, including after a
|
|
51
|
+
* resume, and it must not fail on a machine that already has the container.
|
|
52
|
+
*/
|
|
53
|
+
private createStartupScript;
|
|
54
|
+
/** `POST /v1/instances`, returning the accepted instance id. */
|
|
55
|
+
private createInstance;
|
|
56
|
+
/**
|
|
57
|
+
* Delete an instance together with every volume attached to it, permanently.
|
|
58
|
+
*
|
|
59
|
+
* The volume ids are read back from the instance and NAMED explicitly: an
|
|
60
|
+
* empty `volume_ids` retains them all, and omitting the field deletes only
|
|
61
|
+
* the OS volume while detaching the rest — both leave billable storage
|
|
62
|
+
* behind. Returns false when the instance no longer exists.
|
|
63
|
+
*/
|
|
64
|
+
private destroyWithVolumes;
|
|
65
|
+
/**
|
|
66
|
+
* `PUT /v1/instances` delete. `volumeIds: []` retains every attached volume;
|
|
67
|
+
* a non-empty list deletes exactly those.
|
|
68
|
+
*/
|
|
69
|
+
private deleteInstance;
|
|
70
|
+
/** `GET /v1/instances/{id}`, or null when it no longer exists. */
|
|
71
|
+
private getInstance;
|
|
72
|
+
/** Poll until the instance reports `running`, or fail on a terminal state. */
|
|
73
|
+
private waitForReady;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=verda.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verda.d.ts","sourceRoot":"","sources":["../../src/providers/verda.ts"],"names":[],"mappings":"AA2BA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAEL,cAAc,EAEd,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAiJxB,qBAAa,aAAc,YAAW,cAAc;IAClD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAiB;IAErC,YAAY,WAAW,EAAE,gBAAgB,EAAE,GAAG,CAAC,EAAE,cAAc,EAE9D;IAEK,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,CA2E1D;IAEK,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAa/C;IAED;;;;;;OAMG;IACG,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAerC;IAED;;;;;OAKG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAgDlD;IAED;;;;;OAKG;IACG,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1C;IAEK,IAAI,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAUxC;IAID,OAAO,CAAC,SAAS;IASjB;;;;;OAKG;YACW,cAAc;IAwC5B;;;;OAIG;YACW,eAAe;IAkC7B,yEAAyE;YAC3D,YAAY;IAkB1B;;;;;OAKG;YACW,mBAAmB;IA0CjC,gEAAgE;YAClD,cAAc;IAmB5B;;;;;;;OAOG;YACW,kBAAkB;IAqBhC;;;OAGG;YACW,cAAc;IA6B5B,kEAAkE;YACpD,WAAW;IAoBzB,8EAA8E;YAChE,YAAY;CA2B3B"}
|