@nodetool-ai/deploy 0.7.0-rc.23 → 0.8.2
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/.nodetool-build-stamp +1 -0
- package/dist/admin-client.d.ts +5 -39
- package/dist/admin-client.d.ts.map +1 -1
- package/dist/admin-client.js +9 -230
- package/dist/admin-client.js.map +1 -1
- package/dist/api-user-manager.d.ts.map +1 -1
- package/dist/api-user-manager.js +5 -2
- package/dist/api-user-manager.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +0 -3
- package/dist/auth.js.map +1 -1
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +5 -6
- package/dist/compose.js.map +1 -1
- package/dist/configure.d.ts +1 -43
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +2 -43
- package/dist/configure.js.map +1 -1
- package/dist/deployment-config.d.ts +28 -786
- package/dist/deployment-config.d.ts.map +1 -1
- package/dist/deployment-config.js +34 -218
- package/dist/deployment-config.js.map +1 -1
- package/dist/docker-run.d.ts +2 -1
- package/dist/docker-run.d.ts.map +1 -1
- package/dist/docker-run.js +6 -22
- package/dist/docker-run.js.map +1 -1
- package/dist/docker.d.ts +6 -21
- package/dist/docker.d.ts.map +1 -1
- package/dist/docker.js +6 -53
- package/dist/docker.js.map +1 -1
- package/dist/image-spec.d.ts +1 -1
- package/dist/index.d.ts +0 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -17
- package/dist/index.js.map +1 -1
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +0 -44
- package/dist/manager.js.map +1 -1
- package/dist/progress.d.ts.map +1 -1
- package/dist/progress.js +0 -1
- package/dist/progress.js.map +1 -1
- package/dist/remote-users.d.ts.map +1 -1
- package/dist/remote-users.js +1 -4
- package/dist/remote-users.js.map +1 -1
- package/dist/runpod-rest.d.ts +114 -0
- package/dist/runpod-rest.d.ts.map +1 -0
- package/dist/runpod-rest.js +150 -0
- package/dist/runpod-rest.js.map +1 -0
- package/dist/self-hosted.d.ts +17 -43
- package/dist/self-hosted.d.ts.map +1 -1
- package/dist/self-hosted.js +50 -124
- package/dist/self-hosted.js.map +1 -1
- package/dist/ssh.d.ts +9 -0
- package/dist/ssh.d.ts.map +1 -1
- package/dist/ssh.js +1 -5
- package/dist/ssh.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +1 -11
- package/dist/state.js.map +1 -1
- package/dist/workflow-syncer.d.ts +3 -32
- package/dist/workflow-syncer.d.ts.map +1 -1
- package/dist/workflow-syncer.js +3 -162
- package/dist/workflow-syncer.js.map +1 -1
- package/package.json +29 -14
- package/dist/admin-operations.d.ts +0 -197
- package/dist/admin-operations.d.ts.map +0 -1
- package/dist/admin-operations.js +0 -433
- package/dist/admin-operations.js.map +0 -1
- package/dist/admin-routes.d.ts +0 -306
- package/dist/admin-routes.d.ts.map +0 -1
- package/dist/admin-routes.js +0 -383
- package/dist/admin-routes.js.map +0 -1
- package/dist/collection-routes.d.ts +0 -37
- package/dist/collection-routes.d.ts.map +0 -1
- package/dist/collection-routes.js +0 -43
- package/dist/collection-routes.js.map +0 -1
- package/dist/deploy-to-gcp.d.ts +0 -67
- package/dist/deploy-to-gcp.d.ts.map +0 -1
- package/dist/deploy-to-gcp.js +0 -268
- package/dist/deploy-to-gcp.js.map +0 -1
- package/dist/deploy-to-runpod.d.ts +0 -53
- package/dist/deploy-to-runpod.d.ts.map +0 -1
- package/dist/deploy-to-runpod.js +0 -207
- package/dist/deploy-to-runpod.js.map +0 -1
- package/dist/fly.d.ts +0 -40
- package/dist/fly.d.ts.map +0 -1
- package/dist/fly.js +0 -291
- package/dist/fly.js.map +0 -1
- package/dist/gcp.d.ts +0 -59
- package/dist/gcp.d.ts.map +0 -1
- package/dist/gcp.js +0 -339
- package/dist/gcp.js.map +0 -1
- package/dist/google-cloud-run-api.d.ts +0 -134
- package/dist/google-cloud-run-api.d.ts.map +0 -1
- package/dist/google-cloud-run-api.js +0 -532
- package/dist/google-cloud-run-api.js.map +0 -1
- package/dist/huggingface.d.ts +0 -69
- package/dist/huggingface.d.ts.map +0 -1
- package/dist/huggingface.js +0 -346
- package/dist/huggingface.js.map +0 -1
- package/dist/railway.d.ts +0 -65
- package/dist/railway.d.ts.map +0 -1
- package/dist/railway.js +0 -284
- package/dist/railway.js.map +0 -1
- package/dist/runpod-api.d.ts +0 -206
- package/dist/runpod-api.d.ts.map +0 -1
- package/dist/runpod-api.js +0 -703
- package/dist/runpod-api.js.map +0 -1
- package/dist/runpod.d.ts +0 -82
- package/dist/runpod.d.ts.map +0 -1
- package/dist/runpod.js +0 -188
- package/dist/runpod.js.map +0 -1
- package/dist/storage-routes.d.ts +0 -55
- package/dist/storage-routes.d.ts.map +0 -1
- package/dist/storage-routes.js +0 -160
- package/dist/storage-routes.js.map +0 -1
- package/dist/sync.d.ts +0 -38
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -130
- package/dist/sync.js.map +0 -1
package/dist/runpod-api.js
DELETED
|
@@ -1,703 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RunPod API Module
|
|
3
|
-
*
|
|
4
|
-
* Provides a clean interface to RunPod's REST and GraphQL APIs for managing
|
|
5
|
-
* templates and endpoints. Handles authentication, request/response processing,
|
|
6
|
-
* and error handling for all RunPod operations.
|
|
7
|
-
*
|
|
8
|
-
* Key Features:
|
|
9
|
-
* - Template management (create, update, delete, get)
|
|
10
|
-
* - Endpoint management (create, update, delete)
|
|
11
|
-
* - Network volume management
|
|
12
|
-
* - GraphQL query support
|
|
13
|
-
* - Proper error handling and logging
|
|
14
|
-
*/
|
|
15
|
-
const RUNPOD_REST_BASE_URL = "https://rest.runpod.io/v1";
|
|
16
|
-
/** Escape a string for safe interpolation inside a GraphQL quoted string. */
|
|
17
|
-
function gqlEscape(s) {
|
|
18
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
19
|
-
}
|
|
20
|
-
const HTTP_STATUS_UNAUTHORIZED = 401;
|
|
21
|
-
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Enums
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
/** GPU types available in RunPod using their official GPU ID codes. */
|
|
26
|
-
export var GPUType;
|
|
27
|
-
(function (GPUType) {
|
|
28
|
-
// Ada Lovelace Architecture
|
|
29
|
-
GPUType["ADA_24"] = "ADA_24";
|
|
30
|
-
GPUType["ADA_32_PRO"] = "ADA_32_PRO";
|
|
31
|
-
GPUType["ADA_48_PRO"] = "ADA_48_PRO";
|
|
32
|
-
GPUType["ADA_80_PRO"] = "ADA_80_PRO";
|
|
33
|
-
// Ampere Architecture
|
|
34
|
-
GPUType["AMPERE_16"] = "AMPERE_16";
|
|
35
|
-
GPUType["AMPERE_24"] = "AMPERE_24";
|
|
36
|
-
GPUType["AMPERE_48"] = "AMPERE_48";
|
|
37
|
-
GPUType["AMPERE_80"] = "AMPERE_80";
|
|
38
|
-
// Hopper Architecture
|
|
39
|
-
GPUType["HOPPER_141"] = "HOPPER_141";
|
|
40
|
-
})(GPUType || (GPUType = {}));
|
|
41
|
-
/** Compute types for RunPod endpoints. */
|
|
42
|
-
export var ComputeType;
|
|
43
|
-
(function (ComputeType) {
|
|
44
|
-
ComputeType["CPU"] = "CPU";
|
|
45
|
-
ComputeType["GPU"] = "GPU";
|
|
46
|
-
})(ComputeType || (ComputeType = {}));
|
|
47
|
-
/** CPU flavor IDs for RunPod endpoints. */
|
|
48
|
-
export var CPUFlavor;
|
|
49
|
-
(function (CPUFlavor) {
|
|
50
|
-
CPUFlavor["CPU_3C"] = "cpu3c";
|
|
51
|
-
CPUFlavor["CPU_3G"] = "cpu3g";
|
|
52
|
-
CPUFlavor["CPU_5C"] = "cpu5c";
|
|
53
|
-
CPUFlavor["CPU_5G"] = "cpu5g";
|
|
54
|
-
})(CPUFlavor || (CPUFlavor = {}));
|
|
55
|
-
/** Data center locations for RunPod endpoints. */
|
|
56
|
-
export var DataCenter;
|
|
57
|
-
(function (DataCenter) {
|
|
58
|
-
// United States
|
|
59
|
-
DataCenter["US_CALIFORNIA_2"] = "US-CA-2";
|
|
60
|
-
DataCenter["US_DELAWARE_1"] = "US-DE-1";
|
|
61
|
-
DataCenter["US_GEORGIA_1"] = "US-GA-1";
|
|
62
|
-
DataCenter["US_GEORGIA_2"] = "US-GA-2";
|
|
63
|
-
DataCenter["US_ILLINOIS_1"] = "US-IL-1";
|
|
64
|
-
DataCenter["US_KANSAS_2"] = "US-KS-2";
|
|
65
|
-
DataCenter["US_KANSAS_3"] = "US-KS-3";
|
|
66
|
-
DataCenter["US_NORTH_CAROLINA_1"] = "US-NC-1";
|
|
67
|
-
DataCenter["US_TEXAS_1"] = "US-TX-1";
|
|
68
|
-
DataCenter["US_TEXAS_3"] = "US-TX-3";
|
|
69
|
-
DataCenter["US_TEXAS_4"] = "US-TX-4";
|
|
70
|
-
DataCenter["US_WASHINGTON_1"] = "US-WA-1";
|
|
71
|
-
// Canada
|
|
72
|
-
DataCenter["CA_MONTREAL_1"] = "CA-MTL-1";
|
|
73
|
-
DataCenter["CA_MONTREAL_2"] = "CA-MTL-2";
|
|
74
|
-
DataCenter["CA_MONTREAL_3"] = "CA-MTL-3";
|
|
75
|
-
// Europe
|
|
76
|
-
DataCenter["EU_CZECH_REPUBLIC_1"] = "EU-CZ-1";
|
|
77
|
-
DataCenter["EU_FRANCE_1"] = "EU-FR-1";
|
|
78
|
-
DataCenter["EU_NETHERLANDS_1"] = "EU-NL-1";
|
|
79
|
-
DataCenter["EU_ROMANIA_1"] = "EU-RO-1";
|
|
80
|
-
DataCenter["EU_SWEDEN_1"] = "EU-SE-1";
|
|
81
|
-
// Europe Extended
|
|
82
|
-
DataCenter["EUR_ICELAND_1"] = "EUR-IS-1";
|
|
83
|
-
DataCenter["EUR_ICELAND_2"] = "EUR-IS-2";
|
|
84
|
-
DataCenter["EUR_ICELAND_3"] = "EUR-IS-3";
|
|
85
|
-
DataCenter["EUR_NORWAY_1"] = "EUR-NO-1";
|
|
86
|
-
// Asia-Pacific
|
|
87
|
-
DataCenter["AP_JAPAN_1"] = "AP-JP-1";
|
|
88
|
-
// Oceania
|
|
89
|
-
DataCenter["OC_AUSTRALIA_1"] = "OC-AU-1";
|
|
90
|
-
})(DataCenter || (DataCenter = {}));
|
|
91
|
-
/** CUDA versions available in RunPod. */
|
|
92
|
-
export var CUDAVersion;
|
|
93
|
-
(function (CUDAVersion) {
|
|
94
|
-
CUDAVersion["CUDA_11_8"] = "11.8";
|
|
95
|
-
CUDAVersion["CUDA_12_0"] = "12.0";
|
|
96
|
-
CUDAVersion["CUDA_12_1"] = "12.1";
|
|
97
|
-
CUDAVersion["CUDA_12_2"] = "12.2";
|
|
98
|
-
CUDAVersion["CUDA_12_3"] = "12.3";
|
|
99
|
-
CUDAVersion["CUDA_12_4"] = "12.4";
|
|
100
|
-
CUDAVersion["CUDA_12_5"] = "12.5";
|
|
101
|
-
CUDAVersion["CUDA_12_6"] = "12.6";
|
|
102
|
-
CUDAVersion["CUDA_12_7"] = "12.7";
|
|
103
|
-
CUDAVersion["CUDA_12_8"] = "12.8";
|
|
104
|
-
})(CUDAVersion || (CUDAVersion = {}));
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
|
-
// Helpers
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
/** Return all values of a string enum. */
|
|
109
|
-
export function enumValues(e) {
|
|
110
|
-
return Object.values(e);
|
|
111
|
-
}
|
|
112
|
-
/** Print available options for an enum. */
|
|
113
|
-
export function printEnumOptions(enumObj, title) {
|
|
114
|
-
console.log(`\n${title}:`);
|
|
115
|
-
for (const value of Object.values(enumObj)) {
|
|
116
|
-
console.log(` ${value}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
// Shared helpers
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
function getApiKey() {
|
|
123
|
-
const key = process.env.RUNPOD_API_KEY;
|
|
124
|
-
if (!key) {
|
|
125
|
-
throw new Error("RUNPOD_API_KEY environment variable is not set");
|
|
126
|
-
}
|
|
127
|
-
return key;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Run a GraphQL query against the RunPod API.
|
|
131
|
-
*/
|
|
132
|
-
export async function runGraphqlQuery(query) {
|
|
133
|
-
const apiUrlBase = process.env.RUNPOD_API_BASE_URL ?? "https://api.runpod.io";
|
|
134
|
-
const url = `${apiUrlBase}/graphql`;
|
|
135
|
-
const apiKey = getApiKey();
|
|
136
|
-
const response = await fetch(url, {
|
|
137
|
-
method: "POST",
|
|
138
|
-
headers: {
|
|
139
|
-
"Content-Type": "application/json",
|
|
140
|
-
"User-Agent": USER_AGENT,
|
|
141
|
-
Authorization: `Bearer ${apiKey}`
|
|
142
|
-
},
|
|
143
|
-
body: JSON.stringify({ query }),
|
|
144
|
-
signal: AbortSignal.timeout(30_000)
|
|
145
|
-
});
|
|
146
|
-
if (response.status === HTTP_STATUS_UNAUTHORIZED) {
|
|
147
|
-
throw new Error("Unauthorized request, please check your API key.");
|
|
148
|
-
}
|
|
149
|
-
const json = (await response.json());
|
|
150
|
-
if (json.errors && json.errors.length > 0) {
|
|
151
|
-
throw new Error(json.errors[0].message);
|
|
152
|
-
}
|
|
153
|
-
return json;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Make a REST API call to RunPod.
|
|
157
|
-
*
|
|
158
|
-
* @param endpoint - The API endpoint (e.g., "endpoints", "templates")
|
|
159
|
-
* @param method - HTTP method
|
|
160
|
-
* @param data - Request body for POST/PUT/PATCH
|
|
161
|
-
* @returns API response data, or empty object for DELETE 204 responses
|
|
162
|
-
*/
|
|
163
|
-
export async function makeRunpodApiCall(endpoint, method = "GET", data) {
|
|
164
|
-
const url = `${RUNPOD_REST_BASE_URL}/${endpoint}`;
|
|
165
|
-
const apiKey = getApiKey();
|
|
166
|
-
const headers = {
|
|
167
|
-
Authorization: `Bearer ${apiKey}`,
|
|
168
|
-
"Content-Type": "application/json",
|
|
169
|
-
"User-Agent": USER_AGENT,
|
|
170
|
-
Accept: "*/*",
|
|
171
|
-
"Accept-Language": "en-US,en;q=0.9"
|
|
172
|
-
};
|
|
173
|
-
const init = {
|
|
174
|
-
method,
|
|
175
|
-
headers,
|
|
176
|
-
signal: AbortSignal.timeout(60_000)
|
|
177
|
-
};
|
|
178
|
-
if (data && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
179
|
-
init.body = JSON.stringify(data);
|
|
180
|
-
}
|
|
181
|
-
let response;
|
|
182
|
-
try {
|
|
183
|
-
response = await fetch(url, init);
|
|
184
|
-
}
|
|
185
|
-
catch (err) {
|
|
186
|
-
console.error("RunPod API call failed:", err);
|
|
187
|
-
throw err;
|
|
188
|
-
}
|
|
189
|
-
if (!response.ok) {
|
|
190
|
-
const body = await response.text().catch(() => "");
|
|
191
|
-
console.error(`RunPod API call failed: ${response.status}`);
|
|
192
|
-
if (body) {
|
|
193
|
-
console.error("Response body:", body);
|
|
194
|
-
}
|
|
195
|
-
throw new Error(`RunPod API ${method} ${endpoint} failed with status ${response.status}: ${body}`);
|
|
196
|
-
}
|
|
197
|
-
// DELETE 204 may have no body
|
|
198
|
-
if (method === "DELETE" && response.status === 204) {
|
|
199
|
-
return {};
|
|
200
|
-
}
|
|
201
|
-
const text = await response.text();
|
|
202
|
-
if (!text) {
|
|
203
|
-
return {};
|
|
204
|
-
}
|
|
205
|
-
return JSON.parse(text);
|
|
206
|
-
}
|
|
207
|
-
// ---------------------------------------------------------------------------
|
|
208
|
-
// Network Volume Management
|
|
209
|
-
// ---------------------------------------------------------------------------
|
|
210
|
-
/**
|
|
211
|
-
* Create a new network volume.
|
|
212
|
-
*/
|
|
213
|
-
export async function createNetworkVolume(name, size, dataCenterId) {
|
|
214
|
-
return makeRunpodApiCall("networkvolumes", "POST", {
|
|
215
|
-
dataCenterId,
|
|
216
|
-
name,
|
|
217
|
-
size
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* List all network volumes.
|
|
222
|
-
*/
|
|
223
|
-
export async function listNetworkVolumes() {
|
|
224
|
-
return makeRunpodApiCall("networkvolumes", "GET");
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Get details of a specific network volume.
|
|
228
|
-
*/
|
|
229
|
-
export async function getNetworkVolume(volumeId) {
|
|
230
|
-
return makeRunpodApiCall(`networkvolumes/${volumeId}`, "GET");
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Update a network volume.
|
|
234
|
-
*/
|
|
235
|
-
export async function updateNetworkVolume(volumeId, name, size) {
|
|
236
|
-
const data = {};
|
|
237
|
-
if (name != null)
|
|
238
|
-
data.name = name;
|
|
239
|
-
if (size != null)
|
|
240
|
-
data.size = size;
|
|
241
|
-
return makeRunpodApiCall(`networkvolumes/${volumeId}`, "PATCH", data);
|
|
242
|
-
}
|
|
243
|
-
// ---------------------------------------------------------------------------
|
|
244
|
-
// Template Management
|
|
245
|
-
// ---------------------------------------------------------------------------
|
|
246
|
-
/**
|
|
247
|
-
* Get a RunPod template by name.
|
|
248
|
-
*/
|
|
249
|
-
export async function getRunpodTemplateByName(templateName) {
|
|
250
|
-
try {
|
|
251
|
-
const result = await makeRunpodApiCall("templates", "GET");
|
|
252
|
-
const templates = Array.isArray(result)
|
|
253
|
-
? result
|
|
254
|
-
: (result.templates ?? []);
|
|
255
|
-
for (const tpl of templates) {
|
|
256
|
-
if (tpl.name === templateName) {
|
|
257
|
-
return tpl;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
catch (err) {
|
|
263
|
-
console.error(`Error fetching template '${templateName}':`, err);
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Delete a RunPod template by name.
|
|
269
|
-
*/
|
|
270
|
-
export async function deleteRunpodTemplateByName(templateName) {
|
|
271
|
-
try {
|
|
272
|
-
const template = await getRunpodTemplateByName(templateName);
|
|
273
|
-
if (!template) {
|
|
274
|
-
console.log(`Template '${templateName}' not found (may not exist)`);
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
const templateId = template.id;
|
|
278
|
-
await makeRunpodApiCall(`templates/${templateId}`, "DELETE");
|
|
279
|
-
console.log(`Template '${templateName}' (ID: ${templateId}) deleted successfully`);
|
|
280
|
-
return true;
|
|
281
|
-
}
|
|
282
|
-
catch (err) {
|
|
283
|
-
console.error(`Error deleting template '${templateName}':`, err);
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Update an existing RunPod template with a new Docker image.
|
|
289
|
-
*/
|
|
290
|
-
export async function updateRunpodTemplate(templateData, imageName, tag) {
|
|
291
|
-
try {
|
|
292
|
-
const templateId = templateData.id;
|
|
293
|
-
const env = templateData.env ?? {};
|
|
294
|
-
env.PORT = "8000";
|
|
295
|
-
env.PORT_HEALTH = "8000";
|
|
296
|
-
const updateData = {
|
|
297
|
-
containerDiskInGb: templateData.containerDiskInGb ?? 20,
|
|
298
|
-
imageName: `${imageName}:${tag}`,
|
|
299
|
-
name: templateData.name,
|
|
300
|
-
ports: templateData.ports ?? ["8000/http"],
|
|
301
|
-
volumeInGb: templateData.volumeInGb ?? 0,
|
|
302
|
-
volumeMountPath: templateData.volumeMountPath ?? "/workspace",
|
|
303
|
-
isPublic: templateData.isPublic ?? false,
|
|
304
|
-
env
|
|
305
|
-
};
|
|
306
|
-
if (templateData.dockerEntrypoint) {
|
|
307
|
-
updateData.dockerEntrypoint = templateData.dockerEntrypoint;
|
|
308
|
-
}
|
|
309
|
-
if (templateData.dockerStartCmd) {
|
|
310
|
-
updateData.dockerStartCmd = templateData.dockerStartCmd;
|
|
311
|
-
}
|
|
312
|
-
if (templateData.readme) {
|
|
313
|
-
updateData.readme = templateData.readme;
|
|
314
|
-
}
|
|
315
|
-
console.log("Updating template with data:");
|
|
316
|
-
console.log(JSON.stringify(updateData, null, 2));
|
|
317
|
-
await makeRunpodApiCall(`templates/${templateId}`, "PATCH", updateData);
|
|
318
|
-
console.log(`Template '${templateData.name}' updated with image: ${imageName}:${tag}`);
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
|
-
catch (err) {
|
|
322
|
-
console.error("Error updating template:", err);
|
|
323
|
-
return false;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Create or update a RunPod template with the latest Docker image.
|
|
328
|
-
*
|
|
329
|
-
* Checks if a template with the given name exists:
|
|
330
|
-
* - If it exists, updates it with the new Docker image
|
|
331
|
-
* - If it doesn't exist, creates a new template
|
|
332
|
-
*
|
|
333
|
-
* @returns The template ID
|
|
334
|
-
*/
|
|
335
|
-
export async function createOrUpdateRunpodTemplate(templateName, imageName, tag, env) {
|
|
336
|
-
const envVars = env ?? {};
|
|
337
|
-
console.log(`Checking for existing template: ${templateName}`);
|
|
338
|
-
const existing = await getRunpodTemplateByName(templateName);
|
|
339
|
-
if (existing) {
|
|
340
|
-
const templateId = existing.id;
|
|
341
|
-
console.log(`Found existing template (ID: ${templateId})`);
|
|
342
|
-
console.log(`Current image: ${existing.imageName ?? "unknown"}`);
|
|
343
|
-
console.log(`Updating with new image: ${imageName}:${tag}`);
|
|
344
|
-
if (await updateRunpodTemplate(existing, imageName, tag)) {
|
|
345
|
-
console.log(`Template '${templateName}' updated successfully`);
|
|
346
|
-
return templateId;
|
|
347
|
-
}
|
|
348
|
-
throw new Error(`Failed to update template '${templateName}'`);
|
|
349
|
-
}
|
|
350
|
-
// Create new template
|
|
351
|
-
console.log(`Template not found, creating new template: ${templateName}`);
|
|
352
|
-
const templateData = {
|
|
353
|
-
containerDiskInGb: 20,
|
|
354
|
-
imageName: `${imageName}:${tag}`,
|
|
355
|
-
name: templateName,
|
|
356
|
-
ports: ["8000/http", "22/ssh"],
|
|
357
|
-
volumeInGb: 0,
|
|
358
|
-
volumeMountPath: "/workspace",
|
|
359
|
-
isPublic: false,
|
|
360
|
-
env: envVars
|
|
361
|
-
};
|
|
362
|
-
console.log("Creating template with data:");
|
|
363
|
-
console.log(JSON.stringify(templateData, null, 2));
|
|
364
|
-
const result = await makeRunpodApiCall("templates", "POST", templateData);
|
|
365
|
-
const templateId = result.id;
|
|
366
|
-
if (!templateId) {
|
|
367
|
-
console.error("Error: No template ID returned");
|
|
368
|
-
console.error(`Response data: ${JSON.stringify(result, null, 2)}`);
|
|
369
|
-
throw new Error("No template ID returned from RunPod API");
|
|
370
|
-
}
|
|
371
|
-
console.log(`Template created successfully: ${templateId}`);
|
|
372
|
-
return templateId;
|
|
373
|
-
}
|
|
374
|
-
// ---------------------------------------------------------------------------
|
|
375
|
-
// Endpoint Management
|
|
376
|
-
// ---------------------------------------------------------------------------
|
|
377
|
-
/**
|
|
378
|
-
* Get a RunPod endpoint by name.
|
|
379
|
-
*/
|
|
380
|
-
export async function getRunpodEndpointByName(endpointName, quiet = false) {
|
|
381
|
-
const endpointsResponse = await makeRunpodApiCall("endpoints", "GET");
|
|
382
|
-
const endpoints = Array.isArray(endpointsResponse)
|
|
383
|
-
? endpointsResponse
|
|
384
|
-
: (endpointsResponse.endpoints ?? []);
|
|
385
|
-
if (!quiet) {
|
|
386
|
-
console.log(`Looking for endpoint: '${endpointName}'`);
|
|
387
|
-
console.log(`Found ${endpoints.length} total endpoints`);
|
|
388
|
-
}
|
|
389
|
-
if (endpoints.length > 0 && !quiet) {
|
|
390
|
-
console.log("Available endpoints:");
|
|
391
|
-
endpoints.forEach((ep, i) => {
|
|
392
|
-
const name = ep.name ?? "<no name>";
|
|
393
|
-
const id = ep.id ?? "<no id>";
|
|
394
|
-
console.log(` [${i + 1}] Name: '${name}' (ID: ${id})`);
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
// Exact match first
|
|
398
|
-
const exact = endpoints.find((ep) => ep.name === endpointName);
|
|
399
|
-
if (exact) {
|
|
400
|
-
if (!quiet) {
|
|
401
|
-
console.log(`Found exact match for endpoint: '${endpointName}'`);
|
|
402
|
-
}
|
|
403
|
-
return exact;
|
|
404
|
-
}
|
|
405
|
-
// Prefix match fallback
|
|
406
|
-
for (const ep of endpoints) {
|
|
407
|
-
const name = ep.name ?? "";
|
|
408
|
-
if (name.startsWith(endpointName)) {
|
|
409
|
-
if (!quiet) {
|
|
410
|
-
console.log(`Found prefix match for endpoint: '${endpointName}' -> '${name}'`);
|
|
411
|
-
}
|
|
412
|
-
return ep;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (!quiet) {
|
|
416
|
-
console.log(`No endpoint found with name: '${endpointName}'`);
|
|
417
|
-
}
|
|
418
|
-
return null;
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Update an existing RunPod endpoint with a new template.
|
|
422
|
-
*/
|
|
423
|
-
export async function updateRunpodEndpoint(endpointId, templateId, extra) {
|
|
424
|
-
try {
|
|
425
|
-
const updateData = {
|
|
426
|
-
templateId,
|
|
427
|
-
...extra
|
|
428
|
-
};
|
|
429
|
-
// Remove undefined values
|
|
430
|
-
for (const key of Object.keys(updateData)) {
|
|
431
|
-
if (updateData[key] === undefined) {
|
|
432
|
-
delete updateData[key];
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
console.log(`Updating endpoint ${endpointId} with template ${templateId}`);
|
|
436
|
-
await makeRunpodApiCall(`endpoints/${endpointId}`, "PATCH", updateData);
|
|
437
|
-
console.log(`Endpoint '${endpointId}' updated successfully with template: ${templateId}`);
|
|
438
|
-
return true;
|
|
439
|
-
}
|
|
440
|
-
catch (err) {
|
|
441
|
-
console.error(`Error updating endpoint '${endpointId}':`, err);
|
|
442
|
-
return false;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Delete a RunPod endpoint by name.
|
|
447
|
-
*/
|
|
448
|
-
export async function deleteRunpodEndpointByName(endpointName) {
|
|
449
|
-
try {
|
|
450
|
-
const result = await makeRunpodApiCall("endpoints", "GET");
|
|
451
|
-
const endpoints = result.endpoints ?? [];
|
|
452
|
-
let endpointId;
|
|
453
|
-
console.log(`Looking for endpoint to delete: '${endpointName}'`);
|
|
454
|
-
console.log(`Found ${endpoints.length} total endpoints`);
|
|
455
|
-
// Exact match
|
|
456
|
-
for (const ep of endpoints) {
|
|
457
|
-
if (ep.name === endpointName) {
|
|
458
|
-
endpointId = ep.id;
|
|
459
|
-
console.log(`Found exact match for deletion: '${endpointName}' (ID: ${endpointId})`);
|
|
460
|
-
break;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
// Case-insensitive fallback
|
|
464
|
-
if (!endpointId) {
|
|
465
|
-
for (const ep of endpoints) {
|
|
466
|
-
const name = (ep.name ?? "").toLowerCase();
|
|
467
|
-
if (name === endpointName.toLowerCase()) {
|
|
468
|
-
endpointId = ep.id;
|
|
469
|
-
console.log(`Found case-insensitive match for deletion: '${endpointName}' -> '${ep.name}' (ID: ${endpointId})`);
|
|
470
|
-
break;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
if (!endpointId) {
|
|
475
|
-
console.log(`Endpoint '${endpointName}' not found (may not exist)`);
|
|
476
|
-
return true;
|
|
477
|
-
}
|
|
478
|
-
await makeRunpodApiCall(`endpoints/${endpointId}`, "DELETE");
|
|
479
|
-
console.log(`Endpoint '${endpointName}' (ID: ${endpointId}) deleted successfully`);
|
|
480
|
-
return true;
|
|
481
|
-
}
|
|
482
|
-
catch (err) {
|
|
483
|
-
console.error(`Error deleting endpoint '${endpointName}':`, err);
|
|
484
|
-
return false;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* Create or update a RunPod serverless endpoint using the REST API.
|
|
489
|
-
*
|
|
490
|
-
* If an endpoint with the same name already exists, it will be updated
|
|
491
|
-
* with the new template. Otherwise a new endpoint is created.
|
|
492
|
-
*
|
|
493
|
-
* @returns The endpoint ID
|
|
494
|
-
*/
|
|
495
|
-
export async function createOrUpdateRunpodEndpoint(opts) {
|
|
496
|
-
const { templateId, name, computeType = ComputeType.GPU, gpuCount, cpuFlavorIds, vcpuCount, workersMin = 0, workersMax = 3, idleTimeout = 5, executionTimeoutMs, flashboot = false, networkVolumeId, allowedCudaVersions } = opts;
|
|
497
|
-
let gpuTypeIds = opts.gpuTypeIds;
|
|
498
|
-
let dataCenterIds = opts.dataCenterIds ?? undefined;
|
|
499
|
-
// Handle network volume and data center coordination
|
|
500
|
-
if (networkVolumeId) {
|
|
501
|
-
console.log(`Network volume specified: ${networkVolumeId}`);
|
|
502
|
-
const volumeInfo = await getNetworkVolume(networkVolumeId);
|
|
503
|
-
const volumeDataCenter = volumeInfo.dataCenterId;
|
|
504
|
-
if (volumeDataCenter) {
|
|
505
|
-
console.log(`Network volume is in data center: ${volumeDataCenter}`);
|
|
506
|
-
dataCenterIds = [volumeDataCenter];
|
|
507
|
-
console.log(`Endpoint will be deployed to same region: ${volumeDataCenter}`);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
if (gpuTypeIds == null && computeType === ComputeType.GPU) {
|
|
511
|
-
gpuTypeIds = ["NVIDIA GeForce RTX 4090"];
|
|
512
|
-
}
|
|
513
|
-
if (dataCenterIds == null) {
|
|
514
|
-
dataCenterIds = [];
|
|
515
|
-
}
|
|
516
|
-
// Check if endpoint already exists
|
|
517
|
-
console.log(`Checking for existing endpoint: ${name}`);
|
|
518
|
-
const existingEndpoint = await getRunpodEndpointByName(name);
|
|
519
|
-
if (existingEndpoint) {
|
|
520
|
-
const endpointId = existingEndpoint.id;
|
|
521
|
-
console.log(`Found existing endpoint (ID: ${endpointId})`);
|
|
522
|
-
console.log(`Current template: ${existingEndpoint.templateId ?? "unknown"}`);
|
|
523
|
-
console.log(`Updating with new template: ${templateId}`);
|
|
524
|
-
if (await updateRunpodEndpoint(endpointId, templateId)) {
|
|
525
|
-
console.log(`Endpoint '${name}' updated successfully`);
|
|
526
|
-
return endpointId;
|
|
527
|
-
}
|
|
528
|
-
console.log(`Failed to update endpoint '${name}', creating new one...`);
|
|
529
|
-
await deleteRunpodEndpointByName(name);
|
|
530
|
-
}
|
|
531
|
-
console.log(`Creating new endpoint: ${name}`);
|
|
532
|
-
console.log(` Compute type: ${computeType}`);
|
|
533
|
-
if (computeType === ComputeType.GPU) {
|
|
534
|
-
console.log(` GPU types: ${JSON.stringify(gpuTypeIds)}`);
|
|
535
|
-
if (gpuCount)
|
|
536
|
-
console.log(` GPU count: ${gpuCount}`);
|
|
537
|
-
}
|
|
538
|
-
else {
|
|
539
|
-
console.log(` CPU flavors: ${JSON.stringify(cpuFlavorIds)}`);
|
|
540
|
-
if (vcpuCount)
|
|
541
|
-
console.log(` vCPU count: ${vcpuCount}`);
|
|
542
|
-
}
|
|
543
|
-
console.log(` Data centers: ${dataCenterIds.length > 0 ? JSON.stringify(dataCenterIds) : "Auto-selected"}`);
|
|
544
|
-
console.log(` Workers: ${workersMin}-${workersMax}`);
|
|
545
|
-
const endpointData = {
|
|
546
|
-
computeType,
|
|
547
|
-
templateId,
|
|
548
|
-
name,
|
|
549
|
-
workersMin,
|
|
550
|
-
workersMax,
|
|
551
|
-
scalerType: "REQUEST_COUNT",
|
|
552
|
-
scalerValue: 4,
|
|
553
|
-
idleTimeout,
|
|
554
|
-
flashboot
|
|
555
|
-
};
|
|
556
|
-
if (computeType === ComputeType.GPU) {
|
|
557
|
-
endpointData.gpuTypeIds = gpuTypeIds ?? ["NVIDIA GeForce RTX 4090"];
|
|
558
|
-
endpointData.gpuCount = gpuCount ?? 1;
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
endpointData.cpuFlavorIds = cpuFlavorIds ?? ["cpu3c"];
|
|
562
|
-
if (vcpuCount)
|
|
563
|
-
endpointData.vcpuCount = vcpuCount;
|
|
564
|
-
}
|
|
565
|
-
if (allowedCudaVersions && allowedCudaVersions.length > 0) {
|
|
566
|
-
endpointData.allowedCudaVersions = allowedCudaVersions;
|
|
567
|
-
}
|
|
568
|
-
if (dataCenterIds.length > 0) {
|
|
569
|
-
endpointData.dataCenterIds = dataCenterIds;
|
|
570
|
-
}
|
|
571
|
-
if (executionTimeoutMs) {
|
|
572
|
-
endpointData.executionTimeoutMs = executionTimeoutMs;
|
|
573
|
-
}
|
|
574
|
-
if (networkVolumeId) {
|
|
575
|
-
endpointData.networkVolumeId = networkVolumeId;
|
|
576
|
-
}
|
|
577
|
-
console.log("\nCreating endpoint with data:");
|
|
578
|
-
console.log(JSON.stringify(endpointData, null, 2));
|
|
579
|
-
const result = await makeRunpodApiCall("endpoints", "POST", endpointData);
|
|
580
|
-
const endpointId = result.id;
|
|
581
|
-
if (!endpointId) {
|
|
582
|
-
console.error("Error: No endpoint ID returned");
|
|
583
|
-
console.error(`Response data: ${JSON.stringify(result, null, 2)}`);
|
|
584
|
-
throw new Error("No endpoint ID returned from RunPod API");
|
|
585
|
-
}
|
|
586
|
-
console.log("\nEndpoint created successfully!");
|
|
587
|
-
console.log(` ID: ${endpointId}`);
|
|
588
|
-
console.log(` Name: ${result.name ?? name}`);
|
|
589
|
-
console.log(` GPU Configuration: ${JSON.stringify(result.gpuTypeIds)}`);
|
|
590
|
-
console.log(` Workers: ${result.workersMin}-${result.workersMax}`);
|
|
591
|
-
return endpointId;
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* Create a RunPod serverless endpoint using raw GraphQL queries.
|
|
595
|
-
*
|
|
596
|
-
* If an endpoint with the same name already exists, it will be deleted first.
|
|
597
|
-
*
|
|
598
|
-
* @returns The endpoint ID
|
|
599
|
-
*/
|
|
600
|
-
export async function createRunpodEndpointGraphql(opts) {
|
|
601
|
-
const { templateId, name, computeType = ComputeType.GPU, gpuTypeIds, gpuCount, vcpuCount, workersMin = 0, workersMax = 3, idleTimeout = 5, executionTimeoutMs, flashboot = false, networkVolumeId } = opts;
|
|
602
|
-
const dataCenterIds = opts.dataCenterIds ?? [];
|
|
603
|
-
// Ensure API key
|
|
604
|
-
getApiKey();
|
|
605
|
-
// Delete existing endpoint if it exists
|
|
606
|
-
console.log(`Checking for existing endpoint: ${name}`);
|
|
607
|
-
if (await deleteRunpodEndpointByName(name)) {
|
|
608
|
-
console.log(`Existing endpoint '${name}' deleted successfully`);
|
|
609
|
-
}
|
|
610
|
-
else {
|
|
611
|
-
console.log(`Note: Endpoint '${name}' may not have existed`);
|
|
612
|
-
}
|
|
613
|
-
console.log(`Creating new endpoint: ${name}`);
|
|
614
|
-
console.log(` Compute type: ${computeType}`);
|
|
615
|
-
console.log(` GPU types: ${JSON.stringify(gpuTypeIds)}`);
|
|
616
|
-
console.log(` GPU count: ${gpuCount}`);
|
|
617
|
-
console.log(` Data centers: ${dataCenterIds.length > 0 ? JSON.stringify(dataCenterIds) : "Auto-selected"}`);
|
|
618
|
-
console.log(` Workers: ${workersMin}-${workersMax}`);
|
|
619
|
-
console.log(` Idle timeout: ${idleTimeout}s`);
|
|
620
|
-
console.log(` Execution timeout: ${executionTimeoutMs}ms`);
|
|
621
|
-
console.log(` Flashboot: ${flashboot}`);
|
|
622
|
-
console.log(` Network volume: ${networkVolumeId}`);
|
|
623
|
-
const gpuIdsStr = gpuTypeIds ? gpuTypeIds.join(",") : "AMPERE_24";
|
|
624
|
-
// Build mutation parts
|
|
625
|
-
const endpointName = flashboot ? `${name}-fb` : name;
|
|
626
|
-
const mutationParts = [];
|
|
627
|
-
mutationParts.push(`name: "${gqlEscape(endpointName)}"`);
|
|
628
|
-
mutationParts.push(`templateId: "${gqlEscape(templateId)}"`);
|
|
629
|
-
mutationParts.push('type: "LB"');
|
|
630
|
-
mutationParts.push(`workersMin: ${workersMin}`);
|
|
631
|
-
mutationParts.push(`workersMax: ${workersMax}`);
|
|
632
|
-
mutationParts.push(`idleTimeout: ${idleTimeout}`);
|
|
633
|
-
mutationParts.push('scalerType: "REQUEST_COUNT"');
|
|
634
|
-
mutationParts.push("scalerValue: 4");
|
|
635
|
-
mutationParts.push(`executionTimeoutMs: ${executionTimeoutMs ?? 300000}`);
|
|
636
|
-
mutationParts.push(`gpuIds: "${gqlEscape(gpuIdsStr)}"`);
|
|
637
|
-
mutationParts.push(`gpuCount: ${gpuCount ?? 1}`);
|
|
638
|
-
if (vcpuCount) {
|
|
639
|
-
mutationParts.push(`vcpuCount: ${vcpuCount}`);
|
|
640
|
-
}
|
|
641
|
-
if (dataCenterIds.length > 0) {
|
|
642
|
-
mutationParts.push(`locations: ${JSON.stringify(dataCenterIds.map(gqlEscape))}`);
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
mutationParts.push("locations: null");
|
|
646
|
-
}
|
|
647
|
-
if (networkVolumeId) {
|
|
648
|
-
mutationParts.push(`networkVolumeId: "${gqlEscape(networkVolumeId)}"`);
|
|
649
|
-
}
|
|
650
|
-
else {
|
|
651
|
-
mutationParts.push("networkVolumeId: null");
|
|
652
|
-
}
|
|
653
|
-
const mutation = `
|
|
654
|
-
mutation {
|
|
655
|
-
saveEndpoint(input: {
|
|
656
|
-
${mutationParts.join(", ")}
|
|
657
|
-
}) {
|
|
658
|
-
id
|
|
659
|
-
name
|
|
660
|
-
gpuIds
|
|
661
|
-
idleTimeout
|
|
662
|
-
locations
|
|
663
|
-
type
|
|
664
|
-
networkVolumeId
|
|
665
|
-
executionTimeoutMs
|
|
666
|
-
scalerType
|
|
667
|
-
scalerValue
|
|
668
|
-
templateId
|
|
669
|
-
userId
|
|
670
|
-
workersMax
|
|
671
|
-
workersMin
|
|
672
|
-
gpuCount
|
|
673
|
-
__typename
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
`;
|
|
677
|
-
console.log("\nExecuting GraphQL mutation:");
|
|
678
|
-
console.log(mutation);
|
|
679
|
-
const result = await runGraphqlQuery(mutation);
|
|
680
|
-
if (result.errors) {
|
|
681
|
-
const errors = result.errors;
|
|
682
|
-
const msg = errors[0]?.message ?? "Unknown error";
|
|
683
|
-
console.error(`Error creating endpoint: ${msg}`);
|
|
684
|
-
console.error(`Full error response: ${JSON.stringify(result, null, 2)}`);
|
|
685
|
-
throw new Error(`Failed to create RunPod endpoint: ${msg}`);
|
|
686
|
-
}
|
|
687
|
-
const data = result.data ?? {};
|
|
688
|
-
const endpointData = data.saveEndpoint ?? {};
|
|
689
|
-
const endpointId = endpointData.id;
|
|
690
|
-
if (!endpointId) {
|
|
691
|
-
console.error("Error: No endpoint ID returned");
|
|
692
|
-
console.error(`Response data: ${JSON.stringify(result, null, 2)}`);
|
|
693
|
-
throw new Error("No endpoint ID returned from RunPod GraphQL API");
|
|
694
|
-
}
|
|
695
|
-
console.log("\nEndpoint created successfully!");
|
|
696
|
-
console.log(` ID: ${endpointId}`);
|
|
697
|
-
console.log(` Name: ${endpointData.name}`);
|
|
698
|
-
console.log(` GPU Configuration: ${endpointData.gpuIds}`);
|
|
699
|
-
console.log(` Workers: ${endpointData.workersMin}-${endpointData.workersMax}`);
|
|
700
|
-
console.log(` Scaler: ${endpointData.scalerType}`);
|
|
701
|
-
return endpointId;
|
|
702
|
-
}
|
|
703
|
-
//# sourceMappingURL=runpod-api.js.map
|