@runpod/mcp-server 2.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -2
- package/README.md +59 -309
- package/dist/chunk-OM6RHPQC.mjs +3023 -0
- package/dist/chunk-OM6RHPQC.mjs.map +1 -0
- package/dist/http.cjs +1621 -489
- package/dist/http.cjs.map +1 -1
- package/dist/http.d.cts +35 -2
- package/dist/http.d.ts +35 -2
- package/dist/http.mjs +247 -17
- package/dist/http.mjs.map +1 -1
- package/dist/stdio.cjs +1420 -516
- package/dist/stdio.cjs.map +1 -1
- package/dist/stdio.mjs +1420 -516
- package/dist/stdio.mjs.map +1 -1
- package/dist/tools.cjs +1293 -399
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +14 -0
- package/dist/tools.d.ts +14 -0
- package/dist/tools.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZP3TCVXG.mjs +0 -2116
- package/dist/chunk-ZP3TCVXG.mjs.map +0 -1
package/dist/tools.cjs
CHANGED
|
@@ -41,7 +41,8 @@ var import_node_crypto = require("crypto");
|
|
|
41
41
|
// src/_shared/http.ts
|
|
42
42
|
var HttpError = class extends Error {
|
|
43
43
|
constructor(prefix, status, body) {
|
|
44
|
-
|
|
44
|
+
const hint = status === 401 ? " (the Runpod API rejected the credential \u2014 the API key may be expired or revoked; re-authenticate or update RUNPOD_API_KEY)" : "";
|
|
45
|
+
super(`${prefix}: ${status} - ${body}${hint}`);
|
|
45
46
|
this.name = "HttpError";
|
|
46
47
|
this.status = status;
|
|
47
48
|
this.body = body;
|
|
@@ -97,6 +98,106 @@ function buildTrackingHeaders(input) {
|
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
// src/tools/logs.ts
|
|
102
|
+
var import_zod = require("zod");
|
|
103
|
+
function parseLogSse(raw) {
|
|
104
|
+
const items = [];
|
|
105
|
+
for (const block of raw.split(/\r?\n\r?\n/)) {
|
|
106
|
+
const dataLines = block.split(/\r?\n/).filter((l) => l.startsWith("data:"));
|
|
107
|
+
if (!dataLines.length) continue;
|
|
108
|
+
const payload = dataLines.map((l) => l.slice(5).replace(/^ /, "")).join("\n");
|
|
109
|
+
if (!payload.trim()) continue;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(payload);
|
|
112
|
+
items.push(
|
|
113
|
+
parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : { raw: payload }
|
|
114
|
+
);
|
|
115
|
+
} catch {
|
|
116
|
+
items.push({ raw: payload });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return items;
|
|
120
|
+
}
|
|
121
|
+
var LOG_STREAM_DEFAULT_WAIT_MS = 5e3;
|
|
122
|
+
var LOG_STREAM_MAX_BYTES = 256 * 1024;
|
|
123
|
+
async function readSseSnapshot(fetchImpl, url, headers, opts) {
|
|
124
|
+
const controller = new AbortController();
|
|
125
|
+
const timer = setTimeout(() => controller.abort(), opts.maxWaitMs);
|
|
126
|
+
const chunks = [];
|
|
127
|
+
let bytes = 0;
|
|
128
|
+
let truncated = false;
|
|
129
|
+
try {
|
|
130
|
+
const res = await fetchImpl(url, {
|
|
131
|
+
method: "GET",
|
|
132
|
+
headers,
|
|
133
|
+
signal: controller.signal
|
|
134
|
+
});
|
|
135
|
+
if (!res.ok) {
|
|
136
|
+
throw new HttpError(
|
|
137
|
+
"Runpod API Error",
|
|
138
|
+
res.status,
|
|
139
|
+
await res.text().catch(() => "")
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
for await (const chunk of res.body ?? []) {
|
|
143
|
+
chunks.push(chunk);
|
|
144
|
+
bytes += chunk.length;
|
|
145
|
+
if (bytes >= opts.maxBytes) {
|
|
146
|
+
truncated = true;
|
|
147
|
+
controller.abort();
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
if (!(err instanceof Error && err.name === "AbortError")) throw err;
|
|
153
|
+
} finally {
|
|
154
|
+
clearTimeout(timer);
|
|
155
|
+
}
|
|
156
|
+
return { raw: Buffer.concat(chunks).toString("utf8"), truncated };
|
|
157
|
+
}
|
|
158
|
+
var logStreamParams = {
|
|
159
|
+
source: import_zod.z.enum(["container", "system", "both"]).optional().describe("Which log source to read (default: both)"),
|
|
160
|
+
tail: import_zod.z.number().int().min(0).max(5e3).optional().describe(
|
|
161
|
+
"Historical lines to backfill before live output (API default 100, max 5000; 0 = live only). Ignored when `since` is set."
|
|
162
|
+
),
|
|
163
|
+
since: import_zod.z.string().optional().describe("RFC3339 timestamp to resume from; when set, `tail` is ignored."),
|
|
164
|
+
maxWaitMs: import_zod.z.number().int().min(500).max(3e4).optional().describe("How long to read the stream, in ms (default 5000, max 30000)")
|
|
165
|
+
};
|
|
166
|
+
async function collectLogSnapshot(streamSse, logsUrl, params) {
|
|
167
|
+
const qs = new URLSearchParams();
|
|
168
|
+
if (params.source && params.source !== "both")
|
|
169
|
+
qs.append("source", params.source);
|
|
170
|
+
if (params.tail !== void 0) qs.append("tail", String(params.tail));
|
|
171
|
+
if (params.since) qs.append("since", params.since);
|
|
172
|
+
const query = qs.toString() ? `?${qs}` : "";
|
|
173
|
+
const { raw, truncated } = await streamSse(`${logsUrl}${query}`, {
|
|
174
|
+
maxWaitMs: params.maxWaitMs ?? LOG_STREAM_DEFAULT_WAIT_MS,
|
|
175
|
+
maxBytes: LOG_STREAM_MAX_BYTES
|
|
176
|
+
});
|
|
177
|
+
const items = parseLogSse(raw);
|
|
178
|
+
if (truncated) items.pop();
|
|
179
|
+
return { items, count: items.length, truncated };
|
|
180
|
+
}
|
|
181
|
+
async function streamLogsReply(rt, tool, params) {
|
|
182
|
+
const backend = rt.backendFor(tool.resource);
|
|
183
|
+
if (backend.version === "v1") {
|
|
184
|
+
return rt.jsonReply({
|
|
185
|
+
error: `${tool.name} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
|
|
186
|
+
status: 501
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
return rt.jsonReply(
|
|
191
|
+
await collectLogSnapshot(rt.streamSse, tool.logsUrl(backend), params)
|
|
192
|
+
);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (error instanceof HttpError) {
|
|
195
|
+
return rt.jsonReply({ error: error.message, status: error.status });
|
|
196
|
+
}
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
100
201
|
// src/_shared/mappers.ts
|
|
101
202
|
function compact(obj) {
|
|
102
203
|
const out = {};
|
|
@@ -109,6 +210,9 @@ function persistentMount(size, path) {
|
|
|
109
210
|
if (size === void 0 || path === void 0) return void 0;
|
|
110
211
|
return { persistent: { size, path } };
|
|
111
212
|
}
|
|
213
|
+
function joinStartCmd(cmd) {
|
|
214
|
+
return cmd && cmd.length ? cmd.join(" ") : void 0;
|
|
215
|
+
}
|
|
112
216
|
function containerConfigToV2(p) {
|
|
113
217
|
return compact({
|
|
114
218
|
image: p.imageName,
|
|
@@ -127,6 +231,13 @@ function mapPodCreateToV2(params) {
|
|
|
127
231
|
return compact({
|
|
128
232
|
...containerConfigToV2(params),
|
|
129
233
|
name: params.name,
|
|
234
|
+
// Private-image registry credential (v2 ContainerConfig `registry`). When
|
|
235
|
+
// deploying from a template, this spreads over the template's registry, so
|
|
236
|
+
// passing it overrides the inherited credential. An empty string is an
|
|
237
|
+
// explicit opt-out: emit `registry: null` (v2 accepts null) to CLEAR a
|
|
238
|
+
// template's inherited credential. Undefined is dropped by compact, so an
|
|
239
|
+
// omitted param leaves the template's registry in place.
|
|
240
|
+
registry: params.containerRegistryAuthId === "" ? null : params.containerRegistryAuthId,
|
|
130
241
|
cloud: params.cloudType,
|
|
131
242
|
// v2 CreatePodRequest takes `dataCenterIds` as an ARRAY (preferred data
|
|
132
243
|
// centers; omit/empty = scheduler chooses). Pass the v1 list straight
|
|
@@ -144,15 +255,27 @@ function mapPodUpdateToV2(params) {
|
|
|
144
255
|
...compact({ name: params.name })
|
|
145
256
|
};
|
|
146
257
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
258
|
+
var DEFAULT_ENDPOINT_TYPE = "QUEUE";
|
|
259
|
+
var DEFAULT_SCALER_VALUE = 4;
|
|
260
|
+
function defaultScalerType(endpointType) {
|
|
261
|
+
return endpointType === "LOAD_BALANCER" ? "REQUEST_COUNT" : "QUEUE_DELAY";
|
|
262
|
+
}
|
|
263
|
+
function endpointScaling(scalerType, value) {
|
|
264
|
+
return scalerType === "REQUEST_COUNT" ? { type: "REQUEST_COUNT", requestCount: value } : { type: "QUEUE_DELAY", queueDelay: value };
|
|
150
265
|
}
|
|
151
266
|
function endpointGpuConfig(pools, count) {
|
|
152
267
|
if (!pools?.length) return void 0;
|
|
153
268
|
return compact({ pools, count });
|
|
154
269
|
}
|
|
155
|
-
function
|
|
270
|
+
function endpointWorkers(params) {
|
|
271
|
+
const workers = compact({
|
|
272
|
+
min: params.workersMin,
|
|
273
|
+
max: params.workersMax,
|
|
274
|
+
idleTimeout: params.idleTimeout
|
|
275
|
+
});
|
|
276
|
+
return Object.keys(workers).length ? workers : void 0;
|
|
277
|
+
}
|
|
278
|
+
function endpointCommonToV2(params) {
|
|
156
279
|
return compact({
|
|
157
280
|
name: params.name,
|
|
158
281
|
image: params.imageName,
|
|
@@ -162,28 +285,39 @@ function mapEndpointToV2(params) {
|
|
|
162
285
|
env: params.env,
|
|
163
286
|
registry: params.containerRegistryAuthId,
|
|
164
287
|
gpu: endpointGpuConfig(params.gpuPoolIds, params.gpuCount),
|
|
165
|
-
workers:
|
|
166
|
-
min: params.workersMin,
|
|
167
|
-
max: params.workersMax
|
|
168
|
-
}),
|
|
169
|
-
scaling: nestedOrUndefined({
|
|
170
|
-
type: params.scalerType,
|
|
171
|
-
value: params.scalerValue,
|
|
172
|
-
idleTimeout: params.idleTimeout
|
|
173
|
-
}),
|
|
288
|
+
workers: endpointWorkers(params),
|
|
174
289
|
dataCenterIds: params.dataCenterIds?.length ? params.dataCenterIds : void 0,
|
|
175
290
|
networkVolumes: params.networkVolumeIds?.length ? params.networkVolumeIds : void 0,
|
|
176
291
|
timeout: params.executionTimeoutMs,
|
|
177
292
|
flashboot: params.flashboot
|
|
178
293
|
});
|
|
179
294
|
}
|
|
180
|
-
|
|
181
|
-
|
|
295
|
+
function mapEndpointCreateToV2(params) {
|
|
296
|
+
const endpointType = params.endpointType ?? DEFAULT_ENDPOINT_TYPE;
|
|
297
|
+
return compact({
|
|
298
|
+
...endpointCommonToV2(params),
|
|
299
|
+
type: endpointType,
|
|
300
|
+
scaling: endpointScaling(
|
|
301
|
+
params.scalerType ?? defaultScalerType(endpointType),
|
|
302
|
+
params.scalerValue ?? DEFAULT_SCALER_VALUE
|
|
303
|
+
)
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function mapEndpointUpdateToV2(params) {
|
|
307
|
+
return compact({
|
|
308
|
+
...endpointCommonToV2(params),
|
|
309
|
+
scaling: params.scalerType ? endpointScaling(
|
|
310
|
+
params.scalerType,
|
|
311
|
+
params.scalerValue ?? DEFAULT_SCALER_VALUE
|
|
312
|
+
) : void 0
|
|
313
|
+
});
|
|
314
|
+
}
|
|
182
315
|
function mapNetworkVolumeCreateToV2(params) {
|
|
183
316
|
return compact({
|
|
184
317
|
name: params.name,
|
|
185
318
|
size: params.size,
|
|
186
|
-
dataCenter: params.dataCenterId
|
|
319
|
+
dataCenter: params.dataCenterId,
|
|
320
|
+
type: params.volumeType
|
|
187
321
|
});
|
|
188
322
|
}
|
|
189
323
|
function mapTemplateCreateToV2(params) {
|
|
@@ -192,18 +326,39 @@ function mapTemplateCreateToV2(params) {
|
|
|
192
326
|
...compact({
|
|
193
327
|
name: params.name,
|
|
194
328
|
serverless: params.isServerless,
|
|
195
|
-
registry: params.containerRegistryAuthId
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
329
|
+
registry: params.containerRegistryAuthId,
|
|
330
|
+
args: joinStartCmd(params.dockerStartCmd),
|
|
331
|
+
// No longer required by v2 (it defaults to NVIDIA server-side), so an
|
|
332
|
+
// unset category is dropped by compact instead of being forced here.
|
|
333
|
+
category: params.category
|
|
334
|
+
})
|
|
199
335
|
};
|
|
200
336
|
}
|
|
337
|
+
function podBodyFromTemplate(template) {
|
|
338
|
+
const out = {};
|
|
339
|
+
const keys = [
|
|
340
|
+
"name",
|
|
341
|
+
"image",
|
|
342
|
+
"args",
|
|
343
|
+
"disk",
|
|
344
|
+
"ports",
|
|
345
|
+
"env",
|
|
346
|
+
"mounts",
|
|
347
|
+
"registry"
|
|
348
|
+
];
|
|
349
|
+
for (const key of keys) {
|
|
350
|
+
const value = template[key];
|
|
351
|
+
if (value !== void 0 && value !== null) out[key] = value;
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
201
355
|
function mapTemplateUpdateToV2(params) {
|
|
202
356
|
return {
|
|
203
357
|
...containerConfigToV2(params),
|
|
204
358
|
...compact({
|
|
205
359
|
name: params.name,
|
|
206
|
-
registry: params.containerRegistryAuthId
|
|
360
|
+
registry: params.containerRegistryAuthId,
|
|
361
|
+
args: joinStartCmd(params.dockerStartCmd)
|
|
207
362
|
})
|
|
208
363
|
};
|
|
209
364
|
}
|
|
@@ -220,7 +375,6 @@ var V2_UNWRAP_KEY = {
|
|
|
220
375
|
dataCenters: "dataCenters",
|
|
221
376
|
endpoints: "endpoints",
|
|
222
377
|
jobs: "jobs",
|
|
223
|
-
tags: "tags",
|
|
224
378
|
workers: "workers",
|
|
225
379
|
billing: "records"
|
|
226
380
|
};
|
|
@@ -234,7 +388,6 @@ var RESOURCE_ENV_KEY = {
|
|
|
234
388
|
dataCenters: "DATA_CENTERS",
|
|
235
389
|
endpoints: "ENDPOINTS",
|
|
236
390
|
jobs: "JOBS",
|
|
237
|
-
tags: "TAGS",
|
|
238
391
|
workers: "WORKERS",
|
|
239
392
|
billing: "BILLING"
|
|
240
393
|
};
|
|
@@ -250,6 +403,9 @@ function serverlessBase(env) {
|
|
|
250
403
|
function publicGraphqlBase(env) {
|
|
251
404
|
return env.RUNPOD_PUBLIC_GRAPHQL_URL ?? "https://api.runpod.io/graphql";
|
|
252
405
|
}
|
|
406
|
+
function authedGraphqlBase(env) {
|
|
407
|
+
return env.RUNPOD_AUTHED_GRAPHQL_URL ?? "https://api.runpod.io/graphql";
|
|
408
|
+
}
|
|
253
409
|
function unwrapList(resource, version, raw) {
|
|
254
410
|
if (version === "v1") return Array.isArray(raw) ? raw : [];
|
|
255
411
|
if (raw === null || typeof raw !== "object") return [];
|
|
@@ -288,7 +444,6 @@ var CATALOG = /* @__PURE__ */ new Set([
|
|
|
288
444
|
"dataCenters"
|
|
289
445
|
]);
|
|
290
446
|
var V2_ONLY = /* @__PURE__ */ new Set([
|
|
291
|
-
"tags",
|
|
292
447
|
"workers",
|
|
293
448
|
"billing"
|
|
294
449
|
]);
|
|
@@ -306,7 +461,6 @@ var V2_REST_PATHS = {
|
|
|
306
461
|
list: "/catalog/datacenters",
|
|
307
462
|
get: (id) => `/catalog/datacenters/${id}`
|
|
308
463
|
},
|
|
309
|
-
tags: { list: "/tags", get: (id) => `/tags/${id}` },
|
|
310
464
|
// Endpoints live under /v2/serverless on v2 (NOT /v2/endpoints). The v2 list
|
|
311
465
|
// response wraps rows under `endpoints` (see V2_UNWRAP_KEY), so the existing
|
|
312
466
|
// unwrap key stays correct.
|
|
@@ -404,7 +558,7 @@ function resolveBackend(opts) {
|
|
|
404
558
|
}
|
|
405
559
|
|
|
406
560
|
// src/tools/runtime.ts
|
|
407
|
-
var MCP_SERVER_VERSION = true ? "
|
|
561
|
+
var MCP_SERVER_VERSION = true ? "3.1.0" : "dev";
|
|
408
562
|
var SESSION_ID = (0, import_node_crypto.randomUUID)();
|
|
409
563
|
function trackingHeaders(server, ctx) {
|
|
410
564
|
const info = server.server.getClientVersion();
|
|
@@ -441,14 +595,18 @@ var DESTRUCTIVE = {
|
|
|
441
595
|
idempotentHint: true,
|
|
442
596
|
openWorldHint: true
|
|
443
597
|
};
|
|
444
|
-
async function graphqlRequest(query, tracking, fetchImpl, url) {
|
|
598
|
+
async function graphqlRequest(query, tracking, fetchImpl, url, options) {
|
|
445
599
|
const response = await fetchImpl(url, {
|
|
446
600
|
method: "POST",
|
|
447
601
|
headers: {
|
|
448
602
|
"Content-Type": "application/json",
|
|
603
|
+
...options?.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : {},
|
|
449
604
|
...tracking()
|
|
450
605
|
},
|
|
451
|
-
body: JSON.stringify({
|
|
606
|
+
body: JSON.stringify({
|
|
607
|
+
query,
|
|
608
|
+
...options?.variables ? { variables: options.variables } : {}
|
|
609
|
+
})
|
|
452
610
|
});
|
|
453
611
|
const result = await response.json();
|
|
454
612
|
if (result.errors && result.errors.length > 0) {
|
|
@@ -463,7 +621,17 @@ async function graphqlRequest(query, tracking, fetchImpl, url) {
|
|
|
463
621
|
}
|
|
464
622
|
function createToolRuntime(server, ctx, deps = {}) {
|
|
465
623
|
const tracking = () => trackingHeaders(server, ctx);
|
|
466
|
-
|
|
624
|
+
function observeUnauthorized(fn) {
|
|
625
|
+
const notify = ctx.onUnauthorized;
|
|
626
|
+
if (!notify) return fn;
|
|
627
|
+
return async (...args) => {
|
|
628
|
+
const response = await fn(...args);
|
|
629
|
+
if (response.status === 401) notify();
|
|
630
|
+
return response;
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
const rawFetch = deps.fetch ?? defaultFetch;
|
|
634
|
+
const httpFetch = observeUnauthorized(rawFetch);
|
|
467
635
|
const v1Client = createHttpClient({
|
|
468
636
|
apiKey: ctx.apiKey,
|
|
469
637
|
fetch: httpFetch,
|
|
@@ -498,47 +666,19 @@ function createToolRuntime(server, ctx, deps = {}) {
|
|
|
498
666
|
"Error calling Runpod API:",
|
|
499
667
|
() => restClient(url, method, body)
|
|
500
668
|
);
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (!res.ok) {
|
|
515
|
-
throw new HttpError(
|
|
516
|
-
"Runpod API Error",
|
|
517
|
-
res.status,
|
|
518
|
-
await res.text().catch(() => "")
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
const chunks = [];
|
|
522
|
-
let bytes = 0;
|
|
523
|
-
let truncated = false;
|
|
524
|
-
try {
|
|
525
|
-
for await (const chunk of res.body) {
|
|
526
|
-
chunks.push(chunk);
|
|
527
|
-
bytes += chunk.length;
|
|
528
|
-
if (bytes >= opts.maxBytes) {
|
|
529
|
-
truncated = true;
|
|
530
|
-
controller.abort();
|
|
531
|
-
break;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
} catch (err) {
|
|
535
|
-
if (!(err instanceof Error && err.name === "AbortError")) throw err;
|
|
536
|
-
}
|
|
537
|
-
return { raw: Buffer.concat(chunks).toString("utf8"), truncated };
|
|
538
|
-
} finally {
|
|
539
|
-
clearTimeout(timer);
|
|
540
|
-
}
|
|
541
|
-
});
|
|
669
|
+
const sseFetch = observeUnauthorized(
|
|
670
|
+
deps.sseFetch ?? import_node_fetch.default
|
|
671
|
+
);
|
|
672
|
+
const streamSse = deps.streamSse ?? ((url, opts) => readSseSnapshot(
|
|
673
|
+
sseFetch,
|
|
674
|
+
url,
|
|
675
|
+
{
|
|
676
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
677
|
+
Accept: "text/event-stream",
|
|
678
|
+
...tracking()
|
|
679
|
+
},
|
|
680
|
+
opts
|
|
681
|
+
));
|
|
542
682
|
const backendFor = (resource) => resolveBackend({
|
|
543
683
|
resource,
|
|
544
684
|
env: process.env,
|
|
@@ -560,9 +700,18 @@ function createToolRuntime(server, ctx, deps = {}) {
|
|
|
560
700
|
graphql: (query) => graphqlRequest(
|
|
561
701
|
query,
|
|
562
702
|
tracking,
|
|
563
|
-
httpFetch
|
|
703
|
+
// rawFetch, not httpFetch: this call carries no credential (see above).
|
|
704
|
+
rawFetch,
|
|
564
705
|
publicGraphqlBase(process.env)
|
|
565
706
|
),
|
|
707
|
+
graphqlAuthed: (query, variables) => graphqlRequest(
|
|
708
|
+
query,
|
|
709
|
+
tracking,
|
|
710
|
+
httpFetch,
|
|
711
|
+
// NOT publicGraphqlBase: this call carries the caller's API key.
|
|
712
|
+
authedGraphqlBase(process.env),
|
|
713
|
+
{ variables, apiKey: ctx.apiKey }
|
|
714
|
+
),
|
|
566
715
|
runpodRequest,
|
|
567
716
|
serverlessRequest,
|
|
568
717
|
callRestUrl,
|
|
@@ -574,17 +723,17 @@ function createToolRuntime(server, ctx, deps = {}) {
|
|
|
574
723
|
}
|
|
575
724
|
|
|
576
725
|
// src/tools/catalog.ts
|
|
577
|
-
var
|
|
726
|
+
var import_zod3 = require("zod");
|
|
578
727
|
|
|
579
728
|
// src/pagination.ts
|
|
580
|
-
var
|
|
729
|
+
var import_zod2 = require("zod");
|
|
581
730
|
var DEFAULT_LIST_LIMIT = 20;
|
|
582
731
|
var MAX_LIST_LIMIT = 100;
|
|
583
732
|
var listPaginationParams = {
|
|
584
|
-
limit:
|
|
733
|
+
limit: import_zod2.z.number().int().positive().max(MAX_LIST_LIMIT).optional().describe(
|
|
585
734
|
`Maximum number of items to return (default ${DEFAULT_LIST_LIMIT}, max ${MAX_LIST_LIMIT}). Use the returned nextCursor to fetch the next page.`
|
|
586
735
|
),
|
|
587
|
-
cursor:
|
|
736
|
+
cursor: import_zod2.z.string().optional().describe(
|
|
588
737
|
"Opaque pagination cursor from a previous response (nextCursor). Omit to start from the beginning."
|
|
589
738
|
)
|
|
590
739
|
};
|
|
@@ -642,25 +791,32 @@ function registerCatalogTools(server, rt) {
|
|
|
642
791
|
const { jsonReply: jsonReply2, graphql, callRestUrl, backendFor } = rt;
|
|
643
792
|
server.tool(
|
|
644
793
|
"list-gpu-types",
|
|
645
|
-
"List available GPU types with stock/pricing and capability filters (minimum VRAM, secure/community cloud, name search). Use this to discover valid gpuTypeIds before creating a pod or endpoint.
|
|
794
|
+
"List available GPU types with stock/pricing and capability filters (minimum VRAM, secure/community cloud, name search). Use this to discover valid gpuTypeIds before creating a pod or endpoint. By default the full catalog is returned: each result includes an `availability` summary (HIGH/MEDIUM/LOW/NONE) and results are sorted with the most-available GPUs first, but nothing is hidden. Set includeUnavailable:false to drop out-of-stock GPUs and list only deployable ones (set includeAvailability:false to skip the stock lookup entirely). For per-datacenter availability (to pick a dataCenterIds), use get-gpu-type.",
|
|
646
795
|
{
|
|
647
796
|
...listPaginationParams,
|
|
648
|
-
minMemoryGb:
|
|
649
|
-
secureCloudOnly:
|
|
650
|
-
communityCloudOnly:
|
|
651
|
-
searchTerm:
|
|
797
|
+
minMemoryGb: import_zod3.z.number().optional().describe("Filter to GPUs with at least this much VRAM in GB"),
|
|
798
|
+
secureCloudOnly: import_zod3.z.boolean().optional().describe("Filter to only GPUs available in secure cloud"),
|
|
799
|
+
communityCloudOnly: import_zod3.z.boolean().optional().describe("Filter to only GPUs available in community cloud"),
|
|
800
|
+
searchTerm: import_zod3.z.string().optional().describe(
|
|
652
801
|
"Search term to filter GPUs by name (e.g., 'A100', 'RTX 4090')"
|
|
653
802
|
),
|
|
654
|
-
includeUnavailable:
|
|
655
|
-
"
|
|
803
|
+
includeUnavailable: import_zod3.z.boolean().optional().describe(
|
|
804
|
+
"Out-of-stock GPUs are included by default (annotated availability:NONE and sorted last). Set false to hide them and list only currently-deployable GPUs."
|
|
805
|
+
),
|
|
806
|
+
includeAvailability: import_zod3.z.boolean().optional().describe(
|
|
807
|
+
"Request realtime stock and annotate each GPU with an availability summary (HIGH/MEDIUM/LOW/NONE). Default true. Set false to skip the availability lookup \u2014 then out-of-stock GPUs cannot be filtered."
|
|
656
808
|
)
|
|
657
809
|
},
|
|
658
810
|
{ title: "List GPU types", ...READ_ONLY },
|
|
659
811
|
async (params) => {
|
|
660
812
|
const backend = backendFor("gpus");
|
|
661
813
|
if (backend.version === "v2") {
|
|
662
|
-
const
|
|
814
|
+
const wantAvailability = params.includeAvailability !== false;
|
|
815
|
+
const raw = await callRestUrl(
|
|
816
|
+
`${backend.base}${backend.list}${wantAvailability ? "?include=AVAILABILITY" : ""}`
|
|
817
|
+
);
|
|
663
818
|
let gpus = backend.unwrap(raw);
|
|
819
|
+
gpus = gpus.filter((g) => g.id !== "unknown");
|
|
664
820
|
if (params.minMemoryGb !== void 0)
|
|
665
821
|
gpus = gpus.filter(
|
|
666
822
|
(g) => Number(g.memory ?? 0) >= params.minMemoryGb
|
|
@@ -673,6 +829,18 @@ function registerCatalogTools(server, rt) {
|
|
|
673
829
|
(g) => String(g.id ?? "").toLowerCase().includes(t) || String(g.name ?? "").toLowerCase().includes(t)
|
|
674
830
|
);
|
|
675
831
|
}
|
|
832
|
+
if (params.includeUnavailable === false)
|
|
833
|
+
gpus = gpus.filter((g) => g.availability !== "NONE");
|
|
834
|
+
const rank = {
|
|
835
|
+
HIGH: 3,
|
|
836
|
+
MEDIUM: 2,
|
|
837
|
+
LOW: 1,
|
|
838
|
+
NONE: 0
|
|
839
|
+
};
|
|
840
|
+
gpus.sort(
|
|
841
|
+
(a, b) => (rank[String(b.availability)] ?? 0) - (rank[String(a.availability)] ?? 0)
|
|
842
|
+
);
|
|
843
|
+
gpus = gpus.map(({ dataCenters: _dataCenters, ...rest }) => rest);
|
|
676
844
|
return capListResult(gpus, {
|
|
677
845
|
limit: params.limit,
|
|
678
846
|
cursor: params.cursor
|
|
@@ -702,7 +870,7 @@ function registerCatalogTools(server, rt) {
|
|
|
702
870
|
return !!status && status !== "Out";
|
|
703
871
|
};
|
|
704
872
|
let gpuTypes = data.gpuTypes.filter((gpu) => gpu.id !== "unknown");
|
|
705
|
-
if (
|
|
873
|
+
if (params.includeUnavailable === false) {
|
|
706
874
|
gpuTypes = gpuTypes.filter(isAvailable);
|
|
707
875
|
}
|
|
708
876
|
if (params.minMemoryGb) {
|
|
@@ -748,7 +916,7 @@ function registerCatalogTools(server, rt) {
|
|
|
748
916
|
"List Runpod data centers (id, name, region/location). Use this to discover valid dataCenterIds for placing pods, endpoints, or network volumes.",
|
|
749
917
|
{
|
|
750
918
|
...listPaginationParams,
|
|
751
|
-
region:
|
|
919
|
+
region: import_zod3.z.string().optional().describe(
|
|
752
920
|
"Filter by region/location (e.g., 'United States', 'Europe', 'Canada')"
|
|
753
921
|
)
|
|
754
922
|
},
|
|
@@ -813,9 +981,12 @@ function registerCatalogTools(server, rt) {
|
|
|
813
981
|
);
|
|
814
982
|
server.tool(
|
|
815
983
|
"get-gpu-type",
|
|
816
|
-
"Get details for a single GPU type by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-gpu-types there).",
|
|
984
|
+
"Get details for a single GPU type by id, including per-datacenter availability. v2-only \u2014 returns a 501 notice on the v1 API (use list-gpu-types there). Use the returned dataCenters[].availability to pick a dataCenterIds with stock before creating a pod.",
|
|
817
985
|
{
|
|
818
|
-
gpuTypeId:
|
|
986
|
+
gpuTypeId: import_zod3.z.string().describe("ID of the GPU type to retrieve"),
|
|
987
|
+
includeAvailability: import_zod3.z.boolean().optional().describe(
|
|
988
|
+
"Include realtime per-datacenter availability (HIGH/MEDIUM/LOW/NONE). Default true."
|
|
989
|
+
)
|
|
819
990
|
},
|
|
820
991
|
{ title: "Get GPU type", ...READ_ONLY },
|
|
821
992
|
async (params) => {
|
|
@@ -826,9 +997,9 @@ function registerCatalogTools(server, rt) {
|
|
|
826
997
|
status: 501
|
|
827
998
|
});
|
|
828
999
|
}
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
);
|
|
1000
|
+
const path = backend.get(encodeURIComponent(params.gpuTypeId));
|
|
1001
|
+
const query = params.includeAvailability === false ? "" : "?include=AVAILABILITY";
|
|
1002
|
+
const result = await callRestUrl(`${backend.base}${path}${query}`);
|
|
832
1003
|
return jsonReply2(result);
|
|
833
1004
|
}
|
|
834
1005
|
);
|
|
@@ -836,7 +1007,7 @@ function registerCatalogTools(server, rt) {
|
|
|
836
1007
|
"get-cpu-type",
|
|
837
1008
|
"Get details for a single CPU flavor type by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-cpu-types there).",
|
|
838
1009
|
{
|
|
839
|
-
cpuTypeId:
|
|
1010
|
+
cpuTypeId: import_zod3.z.string().describe("ID of the CPU type to retrieve")
|
|
840
1011
|
},
|
|
841
1012
|
{ title: "Get CPU type", ...READ_ONLY },
|
|
842
1013
|
async (params) => {
|
|
@@ -857,7 +1028,7 @@ function registerCatalogTools(server, rt) {
|
|
|
857
1028
|
"get-data-center",
|
|
858
1029
|
"Get details for a single data center by id. v2-only \u2014 returns a 501 notice on the v1 API (use list-data-centers there).",
|
|
859
1030
|
{
|
|
860
|
-
dataCenterId:
|
|
1031
|
+
dataCenterId: import_zod3.z.string().describe("ID of the data center to retrieve")
|
|
861
1032
|
},
|
|
862
1033
|
{ title: "Get data center", ...READ_ONLY },
|
|
863
1034
|
async (params) => {
|
|
@@ -876,8 +1047,579 @@ function registerCatalogTools(server, rt) {
|
|
|
876
1047
|
);
|
|
877
1048
|
}
|
|
878
1049
|
|
|
1050
|
+
// src/tools/hub.ts
|
|
1051
|
+
var import_zod4 = require("zod");
|
|
1052
|
+
function parseReleaseConfig(config) {
|
|
1053
|
+
if (!config) return void 0;
|
|
1054
|
+
try {
|
|
1055
|
+
return JSON.parse(config);
|
|
1056
|
+
} catch {
|
|
1057
|
+
return config;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function listingsQuery(withConfig) {
|
|
1061
|
+
return `
|
|
1062
|
+
query {
|
|
1063
|
+
listings(input: {}) {
|
|
1064
|
+
id
|
|
1065
|
+
repoId
|
|
1066
|
+
title
|
|
1067
|
+
description
|
|
1068
|
+
repoName
|
|
1069
|
+
repoOwner
|
|
1070
|
+
createdAt
|
|
1071
|
+
updatedAt
|
|
1072
|
+
views
|
|
1073
|
+
stars
|
|
1074
|
+
deploys
|
|
1075
|
+
language
|
|
1076
|
+
category
|
|
1077
|
+
tags
|
|
1078
|
+
type
|
|
1079
|
+
listedRelease {
|
|
1080
|
+
id
|
|
1081
|
+
name
|
|
1082
|
+
tagName
|
|
1083
|
+
createdAt${withConfig ? "\n config" : ""}
|
|
1084
|
+
build {
|
|
1085
|
+
id
|
|
1086
|
+
imageName
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
`;
|
|
1092
|
+
}
|
|
1093
|
+
function serializeBoolean(value, input) {
|
|
1094
|
+
if (value === "true") return input.trueValue ?? "true";
|
|
1095
|
+
if (value === "false") return input.falseValue ?? "false";
|
|
1096
|
+
return value;
|
|
1097
|
+
}
|
|
1098
|
+
function buildHubEnv(config, overrides) {
|
|
1099
|
+
const env = [];
|
|
1100
|
+
const missingRequired = [];
|
|
1101
|
+
const remaining = { ...overrides };
|
|
1102
|
+
for (const entry of config.env ?? []) {
|
|
1103
|
+
const input = entry.input ?? {};
|
|
1104
|
+
const override = entry.key in remaining ? remaining[entry.key] : void 0;
|
|
1105
|
+
delete remaining[entry.key];
|
|
1106
|
+
if (override !== void 0 && override !== "") {
|
|
1107
|
+
env.push({
|
|
1108
|
+
key: entry.key,
|
|
1109
|
+
value: input.type === "boolean" ? serializeBoolean(override, input) : override
|
|
1110
|
+
});
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (input.default !== void 0 && input.default !== null) {
|
|
1114
|
+
env.push({
|
|
1115
|
+
key: entry.key,
|
|
1116
|
+
value: typeof input.default === "boolean" ? input.default ? input.trueValue ?? "true" : input.falseValue ?? "false" : String(input.default)
|
|
1117
|
+
});
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (input.required) {
|
|
1121
|
+
missingRequired.push(entry.key);
|
|
1122
|
+
env.push({ key: entry.key, value: "" });
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
for (const [key, value] of Object.entries(remaining)) {
|
|
1127
|
+
env.push({ key, value });
|
|
1128
|
+
}
|
|
1129
|
+
return { env, missingRequired };
|
|
1130
|
+
}
|
|
1131
|
+
function minCudaVersion(versions) {
|
|
1132
|
+
if (!versions || versions.length === 0) return null;
|
|
1133
|
+
const compare = (a, b) => {
|
|
1134
|
+
const pa = a.split(".").map(Number);
|
|
1135
|
+
const pb = b.split(".").map(Number);
|
|
1136
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1137
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1138
|
+
if (d !== 0) return d;
|
|
1139
|
+
}
|
|
1140
|
+
return 0;
|
|
1141
|
+
};
|
|
1142
|
+
return [...versions].sort(compare)[0];
|
|
1143
|
+
}
|
|
1144
|
+
function randomSuffix() {
|
|
1145
|
+
return Math.random().toString(36).slice(2, 8);
|
|
1146
|
+
}
|
|
1147
|
+
function registerHubTools(server, rt) {
|
|
1148
|
+
const { graphql, graphqlAuthed, jsonReply: jsonReply2 } = rt;
|
|
1149
|
+
server.tool(
|
|
1150
|
+
"list-hub-repos",
|
|
1151
|
+
"List repos published to the Runpod Hub (prebuilt Serverless workers and Pod templates, e.g. vLLM, ComfyUI). Public catalog \u2014 no auth required. Each result includes the currently listed release with its hubReleaseId and prebuilt image name. Results are sorted by deploy count (most popular first). Set includeConfig:true to also return the release config (hardware requirements and environment-variable schema) \u2014 it is large, so prefer requesting it for a single repo via the repoOwner/searchTerm filters.",
|
|
1152
|
+
{
|
|
1153
|
+
...listPaginationParams,
|
|
1154
|
+
searchTerm: import_zod4.z.string().optional().describe(
|
|
1155
|
+
"Case-insensitive search across title, description, repo name, owner, and tags (e.g. 'vllm', 'comfyui', 'fine-tuning')"
|
|
1156
|
+
),
|
|
1157
|
+
category: import_zod4.z.string().optional().describe(
|
|
1158
|
+
"Filter by category (e.g. 'language', 'image', 'audio', 'video', 'embedding')"
|
|
1159
|
+
),
|
|
1160
|
+
type: import_zod4.z.enum(["SERVERLESS", "POD"]).optional().describe(
|
|
1161
|
+
"Filter by listing type: SERVERLESS (endpoint workers) or POD (pod templates)"
|
|
1162
|
+
),
|
|
1163
|
+
repoOwner: import_zod4.z.string().optional().describe(
|
|
1164
|
+
"Filter to listings from this GitHub owner/org (e.g. 'runpod-workers', 'axolotl-ai-cloud')"
|
|
1165
|
+
),
|
|
1166
|
+
includeConfig: import_zod4.z.boolean().optional().describe(
|
|
1167
|
+
"Include the listed release config (hardware requirements + env-var input schema, parsed from .runpod/hub.json). Large \u2014 off by default."
|
|
1168
|
+
)
|
|
1169
|
+
},
|
|
1170
|
+
{ title: "List Hub repos", ...READ_ONLY },
|
|
1171
|
+
async (params) => {
|
|
1172
|
+
const data = await graphql(
|
|
1173
|
+
listingsQuery(params.includeConfig === true)
|
|
1174
|
+
);
|
|
1175
|
+
let listings = data.listings;
|
|
1176
|
+
if (params.type) {
|
|
1177
|
+
listings = listings.filter(
|
|
1178
|
+
(l) => (l.type ?? "").toUpperCase() === params.type
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
if (params.category) {
|
|
1182
|
+
const term = params.category.toLowerCase();
|
|
1183
|
+
listings = listings.filter(
|
|
1184
|
+
(l) => (l.category ?? "").toLowerCase() === term
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
if (params.repoOwner) {
|
|
1188
|
+
const term = params.repoOwner.toLowerCase();
|
|
1189
|
+
listings = listings.filter(
|
|
1190
|
+
(l) => (l.repoOwner ?? "").toLowerCase() === term
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
if (params.searchTerm) {
|
|
1194
|
+
const term = params.searchTerm.toLowerCase();
|
|
1195
|
+
listings = listings.filter(
|
|
1196
|
+
(l) => [
|
|
1197
|
+
l.title,
|
|
1198
|
+
l.description,
|
|
1199
|
+
l.repoName,
|
|
1200
|
+
l.repoOwner,
|
|
1201
|
+
...l.tags ?? []
|
|
1202
|
+
].some((field) => (field ?? "").toLowerCase().includes(term))
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
listings = [...listings].sort(
|
|
1206
|
+
(a, b) => (b.deploys ?? 0) - (a.deploys ?? 0) || a.id.localeCompare(b.id)
|
|
1207
|
+
);
|
|
1208
|
+
const result = listings.map((l) => ({
|
|
1209
|
+
id: l.id,
|
|
1210
|
+
repo: `${l.repoOwner}/${l.repoName}`,
|
|
1211
|
+
title: l.title,
|
|
1212
|
+
description: l.description,
|
|
1213
|
+
type: l.type,
|
|
1214
|
+
category: l.category,
|
|
1215
|
+
tags: l.tags,
|
|
1216
|
+
language: l.language,
|
|
1217
|
+
stars: l.stars,
|
|
1218
|
+
deploys: l.deploys,
|
|
1219
|
+
views: l.views,
|
|
1220
|
+
githubUrl: `https://github.com/${l.repoOwner}/${l.repoName}`,
|
|
1221
|
+
hubUrl: `https://console.runpod.io/hub/${l.repoOwner}/${l.repoName}`,
|
|
1222
|
+
createdAt: l.createdAt,
|
|
1223
|
+
updatedAt: l.updatedAt,
|
|
1224
|
+
listedRelease: l.listedRelease ? {
|
|
1225
|
+
hubReleaseId: l.listedRelease.id,
|
|
1226
|
+
name: l.listedRelease.name,
|
|
1227
|
+
tagName: l.listedRelease.tagName,
|
|
1228
|
+
createdAt: l.listedRelease.createdAt,
|
|
1229
|
+
imageName: l.listedRelease.build?.imageName,
|
|
1230
|
+
...params.includeConfig ? { config: parseReleaseConfig(l.listedRelease.config) } : {}
|
|
1231
|
+
} : null
|
|
1232
|
+
}));
|
|
1233
|
+
return capListResult(result, {
|
|
1234
|
+
limit: params.limit,
|
|
1235
|
+
cursor: params.cursor
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
);
|
|
1239
|
+
server.tool(
|
|
1240
|
+
"deploy-hub-repo",
|
|
1241
|
+
"Deploy a Runpod Hub repo's listed release as a new Serverless endpoint (the same as clicking Deploy on the Hub). Identify the repo by `repo` (\"owner/name\" from list-hub-repos) or by `hubReleaseId`. The release supplies the prebuilt image, container disk, CUDA constraints, and env-var defaults; pass `env` to override or fill in values (required keys without a default must be provided \u2014 check list-hub-repos with includeConfig:true for the schema). GPU selection comes from the release config when it specifies one; otherwise pass gpuIds (GPU pool names, e.g. 'ADA_24' or 'ADA_80_PRO,AMPERE_80'). Uses the authenticated GraphQL API (no REST home for Hub deploys yet), so it works the same on v1 and v2.",
|
|
1242
|
+
{
|
|
1243
|
+
repo: import_zod4.z.string().optional().describe(
|
|
1244
|
+
`The Hub repo to deploy as "owner/name" (e.g. 'runpod-workers/worker-comfyui'). Deploys its currently listed release. Provide this or hubReleaseId.`
|
|
1245
|
+
),
|
|
1246
|
+
hubReleaseId: import_zod4.z.string().optional().describe(
|
|
1247
|
+
"The Hub release ID to deploy (from list-hub-repos listedRelease.hubReleaseId). Provide this or repo."
|
|
1248
|
+
),
|
|
1249
|
+
name: import_zod4.z.string().optional().describe(
|
|
1250
|
+
"Name for the new endpoint. Defaults to '<listing title> <release tag>'."
|
|
1251
|
+
),
|
|
1252
|
+
env: import_zod4.z.record(import_zod4.z.string()).optional().describe(
|
|
1253
|
+
"Environment variable overrides, merged over the release config defaults. Keys not in the release schema are passed through as-is."
|
|
1254
|
+
),
|
|
1255
|
+
gpuIds: import_zod4.z.string().optional().describe(
|
|
1256
|
+
"Comma-separated GPU pool names for workers (e.g. 'ADA_24' or 'ADA_80_PRO,AMPERE_80'). To pin specific GPU SKUs within a pool, append exclusions prefixed with '-' using GPU type ids from list-gpu-types (e.g. 'AMPERE_16,-NVIDIA RTX 2000 Ada Generation,-NVIDIA RTX A4500' allows only the pool's remaining SKUs). Defaults to the release config's gpuIds; required when the config does not specify one."
|
|
1257
|
+
),
|
|
1258
|
+
gpuCount: import_zod4.z.number().int().positive().optional().describe("GPUs per worker. Defaults to the release config (or 1)."),
|
|
1259
|
+
containerDiskInGb: import_zod4.z.number().int().positive().optional().describe("Container disk size in GB. Defaults to the release config."),
|
|
1260
|
+
workersMin: import_zod4.z.number().int().min(0).optional().describe("Minimum workers (default 0 \u2014 scale to zero)."),
|
|
1261
|
+
workersMax: import_zod4.z.number().int().positive().optional().describe("Maximum workers (default: account default)."),
|
|
1262
|
+
idleTimeout: import_zod4.z.number().int().positive().optional().describe("Seconds a worker idles before scaling down (default 5)."),
|
|
1263
|
+
scalerType: import_zod4.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe("Autoscaler type (default QUEUE_DELAY)."),
|
|
1264
|
+
scalerValue: import_zod4.z.number().optional().describe("Autoscaler target value (default 4)."),
|
|
1265
|
+
executionTimeoutMs: import_zod4.z.number().int().positive().optional().describe("Per-job execution timeout in ms (default 600000)."),
|
|
1266
|
+
flashboot: import_zod4.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (default FLASHBOOT).")
|
|
1267
|
+
},
|
|
1268
|
+
{ title: "Deploy Hub repo", ...WRITE },
|
|
1269
|
+
async (params) => {
|
|
1270
|
+
if (!params.repo && !params.hubReleaseId) {
|
|
1271
|
+
return jsonReply2({
|
|
1272
|
+
error: 'Provide either repo ("owner/name") or hubReleaseId. Use list-hub-repos to discover both.'
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
const catalog = await graphql(listingsQuery(true));
|
|
1276
|
+
let listing;
|
|
1277
|
+
if (params.hubReleaseId) {
|
|
1278
|
+
listing = catalog.listings.find(
|
|
1279
|
+
(l) => l.listedRelease?.id === params.hubReleaseId
|
|
1280
|
+
);
|
|
1281
|
+
} else {
|
|
1282
|
+
const repoKey = params.repo.toLowerCase();
|
|
1283
|
+
listing = catalog.listings.find(
|
|
1284
|
+
(l) => `${l.repoOwner}/${l.repoName}`.toLowerCase() === repoKey
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
if (!listing) {
|
|
1288
|
+
return jsonReply2({
|
|
1289
|
+
error: `No Hub listing found for ${params.hubReleaseId ? `hubReleaseId "${params.hubReleaseId}" (only each repo's currently listed release is deployable)` : `repo "${params.repo}"`}. Use list-hub-repos to see the catalog.`
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
const release = listing.listedRelease;
|
|
1293
|
+
if (!release?.build?.imageName) {
|
|
1294
|
+
return jsonReply2({
|
|
1295
|
+
error: `Hub repo ${listing.repoOwner}/${listing.repoName} has no listed release with a built image, so it cannot be deployed.`
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
if ((listing.type ?? "").toUpperCase() !== "SERVERLESS") {
|
|
1299
|
+
return jsonReply2({
|
|
1300
|
+
error: `Hub repo ${listing.repoOwner}/${listing.repoName} is a ${listing.type} listing \u2014 only SERVERLESS listings deploy as endpoints.`
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
const config = parseReleaseConfig(release.config) ?? {};
|
|
1304
|
+
const gpuIds = params.gpuIds ?? config.gpuIds;
|
|
1305
|
+
if (!gpuIds) {
|
|
1306
|
+
return jsonReply2({
|
|
1307
|
+
error: 'This release does not specify a GPU pool \u2014 pass gpuIds (comma-separated pool names, e.g. "ADA_80_PRO,AMPERE_80"; see the pool field on list-gpu-types).'
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
const { env, missingRequired } = buildHubEnv(config, params.env ?? {});
|
|
1311
|
+
if (missingRequired.length > 0) {
|
|
1312
|
+
return jsonReply2({
|
|
1313
|
+
error: `Missing required environment variables for this release: ${missingRequired.join(
|
|
1314
|
+
", "
|
|
1315
|
+
)}. Pass them via the env parameter.`
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
const endpointName = params.name ?? `${listing.title} ${release.tagName}`;
|
|
1319
|
+
const minCuda = minCudaVersion(config.allowedCudaVersions);
|
|
1320
|
+
const input = {
|
|
1321
|
+
name: endpointName,
|
|
1322
|
+
hubReleaseId: release.id,
|
|
1323
|
+
type: "QB",
|
|
1324
|
+
gpuIds,
|
|
1325
|
+
gpuCount: params.gpuCount ?? config.gpuCount ?? 1,
|
|
1326
|
+
workersMin: params.workersMin ?? 0,
|
|
1327
|
+
workersMax: params.workersMax ?? null,
|
|
1328
|
+
idleTimeout: params.idleTimeout ?? 5,
|
|
1329
|
+
scalerType: params.scalerType ?? "QUEUE_DELAY",
|
|
1330
|
+
scalerValue: params.scalerValue ?? 4,
|
|
1331
|
+
executionTimeoutMs: params.executionTimeoutMs ?? 6e5,
|
|
1332
|
+
flashBootType: params.flashboot ?? "FLASHBOOT",
|
|
1333
|
+
locations: null,
|
|
1334
|
+
networkVolumeIds: null,
|
|
1335
|
+
compliance: [],
|
|
1336
|
+
modelReferences: [],
|
|
1337
|
+
...minCuda ? {
|
|
1338
|
+
minCudaVersion: minCuda,
|
|
1339
|
+
allowedCudaVersions: config.allowedCudaVersions.join(",")
|
|
1340
|
+
} : {},
|
|
1341
|
+
template: {
|
|
1342
|
+
name: `${endpointName}__template__${randomSuffix()}`,
|
|
1343
|
+
imageName: release.build.imageName,
|
|
1344
|
+
containerDiskInGb: params.containerDiskInGb ?? config.containerDiskInGb ?? 20,
|
|
1345
|
+
containerRegistryAuthId: "",
|
|
1346
|
+
dockerArgs: "",
|
|
1347
|
+
startScript: "",
|
|
1348
|
+
ports: "",
|
|
1349
|
+
env
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
const data = await graphqlAuthed(
|
|
1353
|
+
`
|
|
1354
|
+
mutation saveEndpoint($input: EndpointInput!) {
|
|
1355
|
+
saveEndpoint(input: $input) {
|
|
1356
|
+
id
|
|
1357
|
+
name
|
|
1358
|
+
gpuIds
|
|
1359
|
+
gpuCount
|
|
1360
|
+
workersMin
|
|
1361
|
+
workersMax
|
|
1362
|
+
idleTimeout
|
|
1363
|
+
scalerType
|
|
1364
|
+
scalerValue
|
|
1365
|
+
flashBootType
|
|
1366
|
+
templateId
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
`,
|
|
1370
|
+
{ input }
|
|
1371
|
+
);
|
|
1372
|
+
return jsonReply2({
|
|
1373
|
+
endpoint: data.saveEndpoint,
|
|
1374
|
+
deployed: {
|
|
1375
|
+
repo: `${listing.repoOwner}/${listing.repoName}`,
|
|
1376
|
+
release: release.tagName,
|
|
1377
|
+
hubReleaseId: release.id,
|
|
1378
|
+
imageName: release.build.imageName
|
|
1379
|
+
},
|
|
1380
|
+
note: `Endpoint created. Submit jobs with run-endpoint/runsync-endpoint using endpointId "${data.saveEndpoint.id}".`
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// src/tools/public-endpoints.ts
|
|
1387
|
+
var import_zod5 = require("zod");
|
|
1388
|
+
function parseMetadata(metadata) {
|
|
1389
|
+
if (!metadata) return {};
|
|
1390
|
+
try {
|
|
1391
|
+
const parsed = JSON.parse(metadata);
|
|
1392
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1393
|
+
} catch {
|
|
1394
|
+
return {};
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
function registerPublicEndpointTools(server, rt) {
|
|
1398
|
+
const { graphql } = rt;
|
|
1399
|
+
server.tool(
|
|
1400
|
+
"list-public-endpoints",
|
|
1401
|
+
"List Runpod Public Endpoints \u2014 managed, pay-per-use model APIs (text, image, video, audio) that require no deployment. Public catalog, no auth required. Each result includes the endpointId to call with run-endpoint/runsync-endpoint (or via https://api.runpod.ai/v2/{endpointId}), the model name, modality, owner, and pricing. Only live endpoints are returned by default; set includeOffline:true to also list ones that are not currently live.",
|
|
1402
|
+
{
|
|
1403
|
+
...listPaginationParams,
|
|
1404
|
+
searchTerm: import_zod5.z.string().optional().describe(
|
|
1405
|
+
"Case-insensitive search across display name, model name, endpoint id, description, owner, and tag (e.g. 'kimi', 'video', 'flux')"
|
|
1406
|
+
),
|
|
1407
|
+
modality: import_zod5.z.string().optional().describe(
|
|
1408
|
+
"Filter by modality/category (the metadata source field, e.g. 'language', 'image', 'video', 'audio')"
|
|
1409
|
+
),
|
|
1410
|
+
owner: import_zod5.z.string().optional().describe(
|
|
1411
|
+
"Filter by model owner (e.g. 'moonshot', 'minimax', 'google')"
|
|
1412
|
+
),
|
|
1413
|
+
includeOffline: import_zod5.z.boolean().optional().describe(
|
|
1414
|
+
"Include endpoints that are not currently live (isLive:false). Default false \u2014 only live endpoints are listed."
|
|
1415
|
+
)
|
|
1416
|
+
},
|
|
1417
|
+
{ title: "List public endpoints", ...READ_ONLY },
|
|
1418
|
+
async (params) => {
|
|
1419
|
+
const data = await graphql(`
|
|
1420
|
+
query {
|
|
1421
|
+
allAiApiPublicConfigs {
|
|
1422
|
+
id
|
|
1423
|
+
aiApiId
|
|
1424
|
+
modelName
|
|
1425
|
+
displayName
|
|
1426
|
+
description
|
|
1427
|
+
metadata
|
|
1428
|
+
isLive
|
|
1429
|
+
createdAt
|
|
1430
|
+
updatedAt
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
`);
|
|
1434
|
+
let configs = data.allAiApiPublicConfigs.map((c) => ({
|
|
1435
|
+
config: c,
|
|
1436
|
+
meta: parseMetadata(c.metadata)
|
|
1437
|
+
}));
|
|
1438
|
+
if (!params.includeOffline) {
|
|
1439
|
+
configs = configs.filter(({ config }) => config.isLive);
|
|
1440
|
+
}
|
|
1441
|
+
if (params.modality) {
|
|
1442
|
+
const term = params.modality.toLowerCase();
|
|
1443
|
+
configs = configs.filter(
|
|
1444
|
+
({ meta }) => (meta.source ?? "").toLowerCase() === term
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
if (params.owner) {
|
|
1448
|
+
const term = params.owner.toLowerCase();
|
|
1449
|
+
configs = configs.filter(
|
|
1450
|
+
({ meta }) => (meta.owner ?? "").toLowerCase() === term
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
if (params.searchTerm) {
|
|
1454
|
+
const term = params.searchTerm.toLowerCase();
|
|
1455
|
+
configs = configs.filter(
|
|
1456
|
+
({ config, meta }) => [
|
|
1457
|
+
config.displayName,
|
|
1458
|
+
config.modelName,
|
|
1459
|
+
config.aiApiId,
|
|
1460
|
+
config.description,
|
|
1461
|
+
meta.owner,
|
|
1462
|
+
meta.tag,
|
|
1463
|
+
meta.source
|
|
1464
|
+
].some((field) => (field ?? "").toLowerCase().includes(term))
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
configs = [...configs].sort(
|
|
1468
|
+
(a, b) => a.config.displayName.localeCompare(b.config.displayName)
|
|
1469
|
+
);
|
|
1470
|
+
const result = configs.map(({ config, meta }) => ({
|
|
1471
|
+
endpointId: config.aiApiId,
|
|
1472
|
+
displayName: config.displayName,
|
|
1473
|
+
modelName: config.modelName,
|
|
1474
|
+
description: config.description,
|
|
1475
|
+
modality: meta.source,
|
|
1476
|
+
tag: meta.tag,
|
|
1477
|
+
owner: meta.owner,
|
|
1478
|
+
pricing: meta.priceString,
|
|
1479
|
+
isLive: config.isLive,
|
|
1480
|
+
baseUrl: `https://api.runpod.ai/v2/${config.aiApiId}`,
|
|
1481
|
+
createdAt: config.createdAt,
|
|
1482
|
+
updatedAt: config.updatedAt
|
|
1483
|
+
}));
|
|
1484
|
+
return capListResult(result, {
|
|
1485
|
+
limit: params.limit,
|
|
1486
|
+
cursor: params.cursor
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// src/tools/endpoint-gpus.ts
|
|
1493
|
+
var import_zod6 = require("zod");
|
|
1494
|
+
function registerEndpointGpuTools(server, rt) {
|
|
1495
|
+
const { graphqlAuthed, jsonReply: jsonReply2 } = rt;
|
|
1496
|
+
server.tool(
|
|
1497
|
+
"set-endpoint-gpus",
|
|
1498
|
+
"Set which GPUs a Serverless endpoint's workers run on \u2014 including pinning specific GPU SKUs, which create-endpoint/update-endpoint cannot express. Provide either a raw gpuIds string, or pools plus optional excludeGpuTypeIds (GPU type ids from list-gpu-types) and the exclusion string is built for you: a pool allows every SKU in it, and excluding all but one SKU pins that SKU exactly (e.g. pools:['AMPERE_16'], excludeGpuTypeIds:['NVIDIA RTX 2000 Ada Generation','NVIDIA RTX 4000 Ada Generation','NVIDIA RTX A4500'] pins RTX A4000). All other endpoint settings (workers, scaling, timeouts, template) are read first and preserved. Works on any Serverless endpoint via the authenticated GraphQL API.",
|
|
1499
|
+
{
|
|
1500
|
+
endpointId: import_zod6.z.string().describe("ID of the Serverless endpoint to update"),
|
|
1501
|
+
gpuIds: import_zod6.z.string().optional().describe(
|
|
1502
|
+
"Raw gpuIds string, e.g. 'ADA_24' or 'AMPERE_16,-NVIDIA RTX A4500'. Takes precedence over pools/excludeGpuTypeIds."
|
|
1503
|
+
),
|
|
1504
|
+
pools: import_zod6.z.array(import_zod6.z.string()).optional().describe(
|
|
1505
|
+
"GPU pool names workers may use (e.g. ['ADA_80_PRO','AMPERE_80']). The pool field from list-gpu-types."
|
|
1506
|
+
),
|
|
1507
|
+
excludeGpuTypeIds: import_zod6.z.array(import_zod6.z.string()).optional().describe(
|
|
1508
|
+
"GPU type ids to exclude from the allowed pools (e.g. ['NVIDIA H100 NVL']). Use with pools to pin specific SKUs."
|
|
1509
|
+
),
|
|
1510
|
+
gpuCount: import_zod6.z.number().int().positive().optional().describe("GPUs per worker. Omit to keep the current value."),
|
|
1511
|
+
minCudaVersion: import_zod6.z.string().optional().describe(
|
|
1512
|
+
"Minimum host CUDA version workers may run on (e.g. '12.4'). Omit to keep the current value."
|
|
1513
|
+
),
|
|
1514
|
+
allowedCudaVersions: import_zod6.z.string().optional().describe(
|
|
1515
|
+
"Comma-separated allowed host CUDA versions (e.g. '12.8,12.7,12.6'). Omit to keep the current value. CUDA compatibility is part of GPU selection \u2014 a narrow list can leave an endpoint unable to schedule workers."
|
|
1516
|
+
)
|
|
1517
|
+
},
|
|
1518
|
+
{ title: "Set endpoint GPUs", ...WRITE, idempotentHint: true },
|
|
1519
|
+
async (params) => {
|
|
1520
|
+
const gpuIds = params.gpuIds ?? (params.pools && params.pools.length > 0 ? [
|
|
1521
|
+
...params.pools,
|
|
1522
|
+
...(params.excludeGpuTypeIds ?? []).map((id) => `-${id}`)
|
|
1523
|
+
].join(",") : void 0);
|
|
1524
|
+
if (!gpuIds) {
|
|
1525
|
+
return jsonReply2({
|
|
1526
|
+
error: "Provide gpuIds (raw string) or pools (with optional excludeGpuTypeIds). See list-gpu-types for pool names and GPU type ids."
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
const data = await graphqlAuthed(`
|
|
1530
|
+
query {
|
|
1531
|
+
myself {
|
|
1532
|
+
endpoints {
|
|
1533
|
+
id
|
|
1534
|
+
name
|
|
1535
|
+
gpuIds
|
|
1536
|
+
gpuCount
|
|
1537
|
+
workersMin
|
|
1538
|
+
workersMax
|
|
1539
|
+
idleTimeout
|
|
1540
|
+
scalerType
|
|
1541
|
+
scalerValue
|
|
1542
|
+
executionTimeoutMs
|
|
1543
|
+
flashBootType
|
|
1544
|
+
type
|
|
1545
|
+
locations
|
|
1546
|
+
templateId
|
|
1547
|
+
allowedCudaVersions
|
|
1548
|
+
minCudaVersion
|
|
1549
|
+
compliance
|
|
1550
|
+
modelReferences
|
|
1551
|
+
networkVolumeIds {
|
|
1552
|
+
networkVolumeId
|
|
1553
|
+
dataCenterId
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
`);
|
|
1559
|
+
const current = data.myself.endpoints.find(
|
|
1560
|
+
(e) => e.id === params.endpointId
|
|
1561
|
+
);
|
|
1562
|
+
if (!current) {
|
|
1563
|
+
return jsonReply2({
|
|
1564
|
+
error: `No Serverless endpoint found with id "${params.endpointId}". Use list-endpoints to see your endpoints.`
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
const input = {
|
|
1568
|
+
id: current.id,
|
|
1569
|
+
name: current.name,
|
|
1570
|
+
gpuIds,
|
|
1571
|
+
gpuCount: params.gpuCount ?? current.gpuCount,
|
|
1572
|
+
workersMin: current.workersMin,
|
|
1573
|
+
workersMax: current.workersMax,
|
|
1574
|
+
idleTimeout: current.idleTimeout,
|
|
1575
|
+
scalerType: current.scalerType,
|
|
1576
|
+
scalerValue: current.scalerValue,
|
|
1577
|
+
executionTimeoutMs: current.executionTimeoutMs,
|
|
1578
|
+
flashBootType: current.flashBootType,
|
|
1579
|
+
type: current.type,
|
|
1580
|
+
locations: current.locations,
|
|
1581
|
+
networkVolumeIds: current.networkVolumeIds && current.networkVolumeIds.length > 0 ? (
|
|
1582
|
+
// Drop dataCenterId: NetworkVolumeIdsInput takes networkVolumeId
|
|
1583
|
+
// ONLY, and the read shape is rejected outright.
|
|
1584
|
+
current.networkVolumeIds.map((v) => ({
|
|
1585
|
+
networkVolumeId: v.networkVolumeId
|
|
1586
|
+
}))
|
|
1587
|
+
) : null
|
|
1588
|
+
};
|
|
1589
|
+
for (const [key, value] of Object.entries({
|
|
1590
|
+
templateId: current.templateId,
|
|
1591
|
+
allowedCudaVersions: params.allowedCudaVersions ?? current.allowedCudaVersions,
|
|
1592
|
+
minCudaVersion: params.minCudaVersion ?? current.minCudaVersion,
|
|
1593
|
+
compliance: current.compliance,
|
|
1594
|
+
modelReferences: current.modelReferences
|
|
1595
|
+
})) {
|
|
1596
|
+
if (value !== null && value !== void 0) input[key] = value;
|
|
1597
|
+
}
|
|
1598
|
+
const result = await graphqlAuthed(
|
|
1599
|
+
`
|
|
1600
|
+
mutation saveEndpoint($input: EndpointInput!) {
|
|
1601
|
+
saveEndpoint(input: $input) {
|
|
1602
|
+
id
|
|
1603
|
+
name
|
|
1604
|
+
gpuIds
|
|
1605
|
+
gpuCount
|
|
1606
|
+
workersMin
|
|
1607
|
+
workersMax
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
`,
|
|
1611
|
+
{ input }
|
|
1612
|
+
);
|
|
1613
|
+
return jsonReply2({
|
|
1614
|
+
endpoint: result.saveEndpoint,
|
|
1615
|
+
previousGpuIds: current.gpuIds
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
879
1621
|
// src/tools/pods.ts
|
|
880
|
-
var
|
|
1622
|
+
var import_zod7 = require("zod");
|
|
881
1623
|
function registerPodTools(server, rt) {
|
|
882
1624
|
const { jsonReply: jsonReply2, callRestUrl, backendFor, podAction, env } = rt;
|
|
883
1625
|
server.tool(
|
|
@@ -885,12 +1627,12 @@ function registerPodTools(server, rt) {
|
|
|
885
1627
|
"List your pods (running and stopped) with optional filters \u2014 compute type, GPU type, data center, name \u2014 plus machine/network-volume expansion. Paginated via limit/cursor.",
|
|
886
1628
|
{
|
|
887
1629
|
...listPaginationParams,
|
|
888
|
-
computeType:
|
|
889
|
-
gpuTypeId:
|
|
890
|
-
dataCenterId:
|
|
891
|
-
name:
|
|
892
|
-
includeMachine:
|
|
893
|
-
includeNetworkVolume:
|
|
1630
|
+
computeType: import_zod7.z.enum(["GPU", "CPU"]).optional().describe("Filter to only GPU or only CPU Pods"),
|
|
1631
|
+
gpuTypeId: import_zod7.z.array(import_zod7.z.string()).optional().describe("Filter to Pods with any of the listed GPU types"),
|
|
1632
|
+
dataCenterId: import_zod7.z.array(import_zod7.z.string()).optional().describe("Filter to Pods in any of the provided data centers"),
|
|
1633
|
+
name: import_zod7.z.string().optional().describe("Filter to Pods with the provided name"),
|
|
1634
|
+
includeMachine: import_zod7.z.boolean().optional().describe("Include information about the machine"),
|
|
1635
|
+
includeNetworkVolume: import_zod7.z.boolean().optional().describe("Include information about attached network volumes")
|
|
894
1636
|
},
|
|
895
1637
|
{ title: "List pods", ...READ_ONLY },
|
|
896
1638
|
async (params) => {
|
|
@@ -934,9 +1676,9 @@ function registerPodTools(server, rt) {
|
|
|
934
1676
|
"get-pod",
|
|
935
1677
|
"Get full details for one pod by id, optionally expanding machine and attached network-volume info.",
|
|
936
1678
|
{
|
|
937
|
-
podId:
|
|
938
|
-
includeMachine:
|
|
939
|
-
includeNetworkVolume:
|
|
1679
|
+
podId: import_zod7.z.string().describe("ID of the pod to retrieve"),
|
|
1680
|
+
includeMachine: import_zod7.z.boolean().optional().describe("Include information about the machine"),
|
|
1681
|
+
includeNetworkVolume: import_zod7.z.boolean().optional().describe("Include information about attached network volumes")
|
|
940
1682
|
},
|
|
941
1683
|
{ title: "Get pod", ...READ_ONLY },
|
|
942
1684
|
async (params) => {
|
|
@@ -964,26 +1706,44 @@ function registerPodTools(server, rt) {
|
|
|
964
1706
|
);
|
|
965
1707
|
server.tool(
|
|
966
1708
|
"create-pod",
|
|
967
|
-
'Create a new GPU/CPU pod on Runpod. Pass gpuTypeIds for a GPU pod, or computeType:"CPU" for a CPU pod (CPU pods are served by the v1 API for now). If the user
|
|
1709
|
+
'Create a new GPU/CPU pod on Runpod. Pass gpuTypeIds for a GPU pod, or computeType:"CPU" for a CPU pod (CPU pods are served by the v1 API for now). Pass either imageName or templateId (templateId deploys from an existing template \u2014 its name, image, start command, ports, env, disk, volume, and registry credential are used as defaults, and any field you also pass explicitly replaces the template value for that field, e.g. passing env replaces the whole env set rather than merging; pass containerRegistryAuthId to override or supply a private-image credential, or an empty string to deploy with none). If the user specifies neither an image nor a template, recommend the "Runpod Pytorch 2.8.0" image (runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404) as the default \u2014 it has the most up-to-date CUDA and PyTorch versions.',
|
|
968
1710
|
{
|
|
969
|
-
name:
|
|
970
|
-
imageName:
|
|
971
|
-
|
|
1711
|
+
name: import_zod7.z.string().optional().describe("Name for the pod"),
|
|
1712
|
+
imageName: import_zod7.z.string().optional().describe("Docker image to use. Optional when templateId is provided."),
|
|
1713
|
+
templateId: import_zod7.z.string().optional().describe(
|
|
1714
|
+
"Deploy from an existing template (see list-templates / get-template). The template supplies name, image, start command, ports, env, disk, volume, and registry credential as defaults; any field you also pass replaces the template value for that field (whole-field, not a merge). Override the registry credential with containerRegistryAuthId. Requires the v2 REST API. Still pass gpuTypeIds or computeType to choose the compute \u2014 a template does not pin the pod compute."
|
|
1715
|
+
),
|
|
1716
|
+
computeType: import_zod7.z.enum(["GPU", "CPU"]).optional().describe(
|
|
972
1717
|
"Pod type. On v2, required when not passing gpuTypeIds. 'CPU' is served by the v1 API for now (v2 has no CPU pods yet)."
|
|
973
1718
|
),
|
|
974
|
-
cloudType:
|
|
975
|
-
gpuTypeIds:
|
|
976
|
-
gpuCount:
|
|
977
|
-
containerDiskInGb:
|
|
978
|
-
volumeInGb:
|
|
979
|
-
volumeMountPath:
|
|
980
|
-
ports:
|
|
981
|
-
env:
|
|
982
|
-
|
|
1719
|
+
cloudType: import_zod7.z.enum(["SECURE", "COMMUNITY"]).optional().describe("SECURE or COMMUNITY cloud"),
|
|
1720
|
+
gpuTypeIds: import_zod7.z.array(import_zod7.z.string()).optional().describe("List of acceptable GPU types"),
|
|
1721
|
+
gpuCount: import_zod7.z.number().optional().describe("Number of GPUs"),
|
|
1722
|
+
containerDiskInGb: import_zod7.z.number().optional().describe("Container disk size in GB"),
|
|
1723
|
+
volumeInGb: import_zod7.z.number().optional().describe("Volume size in GB"),
|
|
1724
|
+
volumeMountPath: import_zod7.z.string().optional().describe("Path to mount the volume"),
|
|
1725
|
+
ports: import_zod7.z.array(import_zod7.z.string()).optional().describe("Ports to expose (e.g., '8888/http', '22/tcp')"),
|
|
1726
|
+
env: import_zod7.z.record(import_zod7.z.string()).optional().describe("Environment variables"),
|
|
1727
|
+
containerRegistryAuthId: import_zod7.z.string().optional().describe(
|
|
1728
|
+
"Container registry credential ID for pulling a private image (see list-container-registry-auths). When deploying from a templateId, this overrides the credential the template carries; pass an empty string to deploy with NO credential (clears a template\u2019s inherited registry)."
|
|
1729
|
+
),
|
|
1730
|
+
dataCenterIds: import_zod7.z.array(import_zod7.z.string()).optional().describe("List of data centers")
|
|
983
1731
|
},
|
|
984
1732
|
{ title: "Create pod", ...WRITE },
|
|
985
1733
|
async (params) => {
|
|
986
1734
|
const backend = backendFor("pods");
|
|
1735
|
+
if (params.templateId && backend.version !== "v2") {
|
|
1736
|
+
return jsonReply2({
|
|
1737
|
+
error: "Deploying a pod from a templateId is only supported on the v2 REST API. Set RUNPOD_REST_VERSION=v2, or pass imageName instead.",
|
|
1738
|
+
status: 501
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
if (!params.imageName && !params.templateId) {
|
|
1742
|
+
return jsonReply2({
|
|
1743
|
+
error: "Provide imageName, or templateId to deploy from an existing template.",
|
|
1744
|
+
status: 400
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
987
1747
|
const hasGpuTypes = (params.gpuTypeIds?.length ?? 0) > 0;
|
|
988
1748
|
const hasGpuCount = (params.gpuCount ?? 0) > 0;
|
|
989
1749
|
const wantsCpu = params.computeType === "CPU";
|
|
@@ -1008,6 +1768,12 @@ function registerPodTools(server, rt) {
|
|
|
1008
1768
|
});
|
|
1009
1769
|
}
|
|
1010
1770
|
if (wantsCpu) {
|
|
1771
|
+
if (params.templateId) {
|
|
1772
|
+
return jsonReply2({
|
|
1773
|
+
error: "Template-based deploy is currently supported only for GPU pods on the v2 API. Create the CPU pod with an explicit imageName instead.",
|
|
1774
|
+
status: 400
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1011
1777
|
try {
|
|
1012
1778
|
const result = await callRestUrl(
|
|
1013
1779
|
`${restV1Base(env)}/pods`,
|
|
@@ -1032,7 +1798,24 @@ function registerPodTools(server, rt) {
|
|
|
1032
1798
|
}
|
|
1033
1799
|
}
|
|
1034
1800
|
}
|
|
1035
|
-
|
|
1801
|
+
let body = backend.mapCreate(params);
|
|
1802
|
+
if (params.templateId) {
|
|
1803
|
+
let template;
|
|
1804
|
+
try {
|
|
1805
|
+
template = await callRestUrl(
|
|
1806
|
+
`${restV2Base(env)}/templates/${params.templateId}`
|
|
1807
|
+
);
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
if (error instanceof HttpError) {
|
|
1810
|
+
return jsonReply2({
|
|
1811
|
+
error: `Failed to load template ${params.templateId}: ${error.message}`,
|
|
1812
|
+
status: error.status
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
throw error;
|
|
1816
|
+
}
|
|
1817
|
+
body = { ...podBodyFromTemplate(template), ...body };
|
|
1818
|
+
}
|
|
1036
1819
|
try {
|
|
1037
1820
|
const result = await callRestUrl(
|
|
1038
1821
|
`${backend.base}${backend.list}`,
|
|
@@ -1047,11 +1830,8 @@ function registerPodTools(server, rt) {
|
|
|
1047
1830
|
}
|
|
1048
1831
|
return jsonReply2(result);
|
|
1049
1832
|
} catch (error) {
|
|
1050
|
-
if (error instanceof HttpError
|
|
1051
|
-
return jsonReply2({
|
|
1052
|
-
error: "CPU pods are not yet supported on the v2 REST API. Use a GPU pod, or create the CPU pod via the v1 API (set RUNPOD_REST_VERSION=v1).",
|
|
1053
|
-
status: 501
|
|
1054
|
-
});
|
|
1833
|
+
if (error instanceof HttpError) {
|
|
1834
|
+
return jsonReply2({ error: error.message, status: error.status });
|
|
1055
1835
|
}
|
|
1056
1836
|
throw error;
|
|
1057
1837
|
}
|
|
@@ -1061,14 +1841,14 @@ function registerPodTools(server, rt) {
|
|
|
1061
1841
|
"update-pod",
|
|
1062
1842
|
"Update a pod's mutable fields (name, image, container disk, volume, ports, env). Only the fields you provide change.",
|
|
1063
1843
|
{
|
|
1064
|
-
podId:
|
|
1065
|
-
name:
|
|
1066
|
-
imageName:
|
|
1067
|
-
containerDiskInGb:
|
|
1068
|
-
volumeInGb:
|
|
1069
|
-
volumeMountPath:
|
|
1070
|
-
ports:
|
|
1071
|
-
env:
|
|
1844
|
+
podId: import_zod7.z.string().describe("ID of the pod to update"),
|
|
1845
|
+
name: import_zod7.z.string().optional().describe("New name for the pod"),
|
|
1846
|
+
imageName: import_zod7.z.string().optional().describe("New Docker image"),
|
|
1847
|
+
containerDiskInGb: import_zod7.z.number().optional().describe("New container disk size in GB"),
|
|
1848
|
+
volumeInGb: import_zod7.z.number().optional().describe("New volume size in GB"),
|
|
1849
|
+
volumeMountPath: import_zod7.z.string().optional().describe("New path to mount the volume"),
|
|
1850
|
+
ports: import_zod7.z.array(import_zod7.z.string()).optional().describe("New ports to expose"),
|
|
1851
|
+
env: import_zod7.z.record(import_zod7.z.string()).optional().describe("New environment variables")
|
|
1072
1852
|
},
|
|
1073
1853
|
{ title: "Update pod", ...WRITE, idempotentHint: true },
|
|
1074
1854
|
async (params) => {
|
|
@@ -1087,7 +1867,7 @@ function registerPodTools(server, rt) {
|
|
|
1087
1867
|
"start-pod",
|
|
1088
1868
|
"Start a stopped pod (resumes the same pod and its billing).",
|
|
1089
1869
|
{
|
|
1090
|
-
podId:
|
|
1870
|
+
podId: import_zod7.z.string().describe("ID of the pod to start")
|
|
1091
1871
|
},
|
|
1092
1872
|
{ title: "Start pod", ...WRITE, idempotentHint: true },
|
|
1093
1873
|
async (params) => {
|
|
@@ -1099,7 +1879,7 @@ function registerPodTools(server, rt) {
|
|
|
1099
1879
|
"stop-pod",
|
|
1100
1880
|
"Stop a running pod. The pod and its disk persist; GPU billing stops.",
|
|
1101
1881
|
{
|
|
1102
|
-
podId:
|
|
1882
|
+
podId: import_zod7.z.string().describe("ID of the pod to stop")
|
|
1103
1883
|
},
|
|
1104
1884
|
{ title: "Stop pod", ...WRITE, idempotentHint: true },
|
|
1105
1885
|
async (params) => {
|
|
@@ -1111,7 +1891,7 @@ function registerPodTools(server, rt) {
|
|
|
1111
1891
|
"restart-pod",
|
|
1112
1892
|
"Restart a running pod (single PodAction call). v2-only \u2014 on the v1 API returns a 501 notice (stop then start the pod instead).",
|
|
1113
1893
|
{
|
|
1114
|
-
podId:
|
|
1894
|
+
podId: import_zod7.z.string().describe("ID of the pod to restart")
|
|
1115
1895
|
},
|
|
1116
1896
|
{ title: "Restart pod", ...WRITE },
|
|
1117
1897
|
async (params) => {
|
|
@@ -1126,11 +1906,29 @@ function registerPodTools(server, rt) {
|
|
|
1126
1906
|
return jsonReply2(result);
|
|
1127
1907
|
}
|
|
1128
1908
|
);
|
|
1909
|
+
server.tool(
|
|
1910
|
+
"stream-pod-logs",
|
|
1911
|
+
"Fetch a bounded snapshot of a pod's live logs (container and/or system) via Server-Sent Events. v2-only \u2014 returns a 501 notice on the v1 API. Reads for up to maxWaitMs (default 5s) and returns the collected log lines; use `tail` to backfill recent lines first. Large output is truncated (see the `truncated` flag).",
|
|
1912
|
+
{
|
|
1913
|
+
podId: import_zod7.z.string().describe("ID of the pod whose logs to stream"),
|
|
1914
|
+
...logStreamParams
|
|
1915
|
+
},
|
|
1916
|
+
{ title: "Stream pod logs", ...READ_ONLY },
|
|
1917
|
+
(params) => streamLogsReply(
|
|
1918
|
+
rt,
|
|
1919
|
+
{
|
|
1920
|
+
name: "stream-pod-logs",
|
|
1921
|
+
resource: "pods",
|
|
1922
|
+
logsUrl: (backend) => `${backend.base}${backend.get(encodeURIComponent(params.podId))}/logs`
|
|
1923
|
+
},
|
|
1924
|
+
params
|
|
1925
|
+
)
|
|
1926
|
+
);
|
|
1129
1927
|
server.tool(
|
|
1130
1928
|
"delete-pod",
|
|
1131
1929
|
"Permanently delete a pod and its data. This cannot be undone.",
|
|
1132
1930
|
{
|
|
1133
|
-
podId:
|
|
1931
|
+
podId: import_zod7.z.string().describe("ID of the pod to delete")
|
|
1134
1932
|
},
|
|
1135
1933
|
{ title: "Delete pod", ...DESTRUCTIVE },
|
|
1136
1934
|
async (params) => {
|
|
@@ -1145,16 +1943,27 @@ function registerPodTools(server, rt) {
|
|
|
1145
1943
|
}
|
|
1146
1944
|
|
|
1147
1945
|
// src/tools/endpoints.ts
|
|
1148
|
-
var
|
|
1946
|
+
var import_zod8 = require("zod");
|
|
1947
|
+
var MIN_QUEUE_DELAY = 0.5;
|
|
1948
|
+
var MIN_REQUEST_COUNT = 1;
|
|
1949
|
+
var MIN_IDLE_TIMEOUT = 1;
|
|
1950
|
+
var MAX_IDLE_TIMEOUT = 3600;
|
|
1951
|
+
function scalerValueError(scalerType, scalerValue) {
|
|
1952
|
+
if (scalerType !== "REQUEST_COUNT" || scalerValue === void 0) return;
|
|
1953
|
+
if (!Number.isInteger(scalerValue) || scalerValue < MIN_REQUEST_COUNT) {
|
|
1954
|
+
return `REQUEST_COUNT scales on whole in-flight requests per worker, so scalerValue must be an integer >= ${MIN_REQUEST_COUNT} (got ${scalerValue}). For a fractional target use scalerType QUEUE_DELAY, which takes seconds >= ${MIN_QUEUE_DELAY}.`;
|
|
1955
|
+
}
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1149
1958
|
function registerEndpointTools(server, rt) {
|
|
1150
1959
|
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
1151
1960
|
server.tool(
|
|
1152
1961
|
"list-endpoints",
|
|
1153
|
-
"List your Serverless endpoints, optionally expanding template and worker details (v1 only). Paginated via limit/cursor.",
|
|
1962
|
+
"List your Serverless endpoints, optionally expanding template and worker details (v1 only). Paginated via limit/cursor. On v2 each endpoint carries its request routing in `type` and the URLs to call it in `requestUrls`.",
|
|
1154
1963
|
{
|
|
1155
1964
|
...listPaginationParams,
|
|
1156
|
-
includeTemplate:
|
|
1157
|
-
includeWorkers:
|
|
1965
|
+
includeTemplate: import_zod8.z.boolean().optional().describe("Include template information (v1 only)"),
|
|
1966
|
+
includeWorkers: import_zod8.z.boolean().optional().describe("Include information about workers (v1 only)")
|
|
1158
1967
|
},
|
|
1159
1968
|
{ title: "List endpoints", ...READ_ONLY },
|
|
1160
1969
|
async (params) => {
|
|
@@ -1185,11 +1994,11 @@ function registerEndpointTools(server, rt) {
|
|
|
1185
1994
|
);
|
|
1186
1995
|
server.tool(
|
|
1187
1996
|
"get-endpoint",
|
|
1188
|
-
"Get one Serverless endpoint by id, optionally expanding template and worker details (v1 only).",
|
|
1997
|
+
"Get one Serverless endpoint by id, optionally expanding template and worker details (v1 only). On v2 the reply carries `type` (queue-based vs load-balancing routing) and `requestUrls` \u2014 the run/runsync/status/... URLs for a queue endpoint, or the base + health URLs for a load-balancing one. Read those rather than constructing endpoint URLs by hand.",
|
|
1189
1998
|
{
|
|
1190
|
-
endpointId:
|
|
1191
|
-
includeTemplate:
|
|
1192
|
-
includeWorkers:
|
|
1999
|
+
endpointId: import_zod8.z.string().describe("ID of the endpoint to retrieve"),
|
|
2000
|
+
includeTemplate: import_zod8.z.boolean().optional().describe("Include template information (v1 only)"),
|
|
2001
|
+
includeWorkers: import_zod8.z.boolean().optional().describe("Include information about workers (v1 only)")
|
|
1193
2002
|
},
|
|
1194
2003
|
{ title: "Get endpoint", ...READ_ONLY },
|
|
1195
2004
|
async (params) => {
|
|
@@ -1217,34 +2026,43 @@ function registerEndpointTools(server, rt) {
|
|
|
1217
2026
|
);
|
|
1218
2027
|
server.tool(
|
|
1219
2028
|
"create-endpoint",
|
|
1220
|
-
"Create a Serverless endpoint. On v2 (default), pass an inline config: imageName + gpuPoolIds (GPU pool names from list-gpu-types \u2014 the `pool` field, e.g. AMPERE_80/ADA_24) plus optional workers/scaling/disk/env. On v1, pass a templateId instead. Worker min/max set autoscaling bounds (min 0 = scale to zero).",
|
|
2029
|
+
"Create a Serverless endpoint. On v2 (default), pass an inline config: imageName + gpuPoolIds (GPU pool names from list-gpu-types \u2014 the `pool` field, e.g. AMPERE_80/ADA_24) plus optional workers/scaling/disk/env. On v1, pass a templateId instead. Worker min/max set autoscaling bounds (min 0 = scale to zero). Use endpointType to pick queue-based (default) or load-balancing request routing; the response `requestUrls` carries the URLs to call the endpoint with.",
|
|
1221
2030
|
{
|
|
1222
|
-
name:
|
|
2031
|
+
name: import_zod8.z.string().optional().describe("Name for the endpoint"),
|
|
1223
2032
|
// --- v2 inline-config fields ---
|
|
1224
|
-
imageName:
|
|
1225
|
-
|
|
2033
|
+
imageName: import_zod8.z.string().optional().describe("Docker image (v2). Required on v2 instead of a templateId."),
|
|
2034
|
+
endpointType: import_zod8.z.enum(["QUEUE", "LOAD_BALANCER"]).optional().describe(
|
|
2035
|
+
"How requests reach the workers (v2). QUEUE (default) = submit jobs through the managed queue (run/runsync/status/...). LOAD_BALANCER = send HTTP requests straight to worker-defined paths, and forces scalerType REQUEST_COUNT. Fixed at creation \u2014 update-endpoint cannot change it."
|
|
2036
|
+
),
|
|
2037
|
+
gpuPoolIds: import_zod8.z.array(import_zod8.z.string()).optional().describe(
|
|
1226
2038
|
'GPU pool names (v2, required). The `pool` field from list-gpu-types, e.g. ["AMPERE_80"]. NOT GPU type ids.'
|
|
1227
2039
|
),
|
|
1228
|
-
gpuCount:
|
|
1229
|
-
args:
|
|
1230
|
-
containerDiskInGb:
|
|
1231
|
-
ports:
|
|
1232
|
-
env:
|
|
1233
|
-
containerRegistryAuthId:
|
|
1234
|
-
networkVolumeIds:
|
|
1235
|
-
executionTimeoutMs:
|
|
1236
|
-
flashboot:
|
|
2040
|
+
gpuCount: import_zod8.z.number().optional().describe("GPUs per worker (v2)"),
|
|
2041
|
+
args: import_zod8.z.string().optional().describe("Container start command/args (v2)"),
|
|
2042
|
+
containerDiskInGb: import_zod8.z.number().optional().describe("Container disk size in GB (v2)"),
|
|
2043
|
+
ports: import_zod8.z.array(import_zod8.z.string()).optional().describe("Ports to expose (v2), e.g. ['8000/http']"),
|
|
2044
|
+
env: import_zod8.z.record(import_zod8.z.string()).optional().describe("Environment variables (v2)"),
|
|
2045
|
+
containerRegistryAuthId: import_zod8.z.string().optional().describe("Container registry auth id for a private image (v2)"),
|
|
2046
|
+
networkVolumeIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("Network volume ids to attach (v2)"),
|
|
2047
|
+
executionTimeoutMs: import_zod8.z.number().optional().describe("Per-job execution timeout in ms (v2)"),
|
|
2048
|
+
flashboot: import_zod8.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("FlashBoot mode (v2)"),
|
|
1237
2049
|
// --- v1 template-based fields ---
|
|
1238
|
-
templateId:
|
|
1239
|
-
computeType:
|
|
1240
|
-
gpuTypeIds:
|
|
2050
|
+
templateId: import_zod8.z.string().optional().describe("Template ID (v1). Required on v1."),
|
|
2051
|
+
computeType: import_zod8.z.enum(["GPU", "CPU"]).optional().describe("GPU or CPU endpoint (v1)"),
|
|
2052
|
+
gpuTypeIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("List of acceptable GPU types (v1)"),
|
|
1241
2053
|
// --- shared autoscaling fields ---
|
|
1242
|
-
workersMin:
|
|
1243
|
-
workersMax:
|
|
1244
|
-
scalerType:
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
2054
|
+
workersMin: import_zod8.z.number().optional().describe("Minimum number of workers"),
|
|
2055
|
+
workersMax: import_zod8.z.number().optional().describe("Maximum number of workers"),
|
|
2056
|
+
scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe(
|
|
2057
|
+
"Autoscaler signal. QUEUE_DELAY scales on how long requests wait in the queue (queue endpoints only); REQUEST_COUNT scales on in-flight requests per worker. Defaults to QUEUE_DELAY for QUEUE endpoints and REQUEST_COUNT for LOAD_BALANCER ones."
|
|
2058
|
+
),
|
|
2059
|
+
scalerValue: import_zod8.z.number().min(MIN_QUEUE_DELAY).optional().describe(
|
|
2060
|
+
"Autoscaler target for the chosen scalerType \u2014 seconds of queue delay (min 0.5) or in-flight requests per worker (integer, min 1). Defaults to 4."
|
|
2061
|
+
),
|
|
2062
|
+
idleTimeout: import_zod8.z.number().int().min(MIN_IDLE_TIMEOUT).max(MAX_IDLE_TIMEOUT).optional().describe(
|
|
2063
|
+
"Idle timeout in seconds before scaling a worker down (1-3600). Does not apply to QUEUE endpoints scaling on REQUEST_COUNT."
|
|
2064
|
+
),
|
|
2065
|
+
dataCenterIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("List of preferred data centers")
|
|
1248
2066
|
},
|
|
1249
2067
|
{ title: "Create endpoint", ...WRITE },
|
|
1250
2068
|
async (params) => {
|
|
@@ -1268,6 +2086,19 @@ function registerEndpointTools(server, rt) {
|
|
|
1268
2086
|
status: 400
|
|
1269
2087
|
});
|
|
1270
2088
|
}
|
|
2089
|
+
if (params.endpointType === "LOAD_BALANCER" && params.scalerType === "QUEUE_DELAY") {
|
|
2090
|
+
return jsonReply2({
|
|
2091
|
+
error: "LOAD_BALANCER endpoints have no request queue, so they cannot scale on QUEUE_DELAY. Use scalerType REQUEST_COUNT (the default for this endpoint type), or create a QUEUE endpoint instead.",
|
|
2092
|
+
status: 400
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
const scalerError = scalerValueError(
|
|
2096
|
+
params.scalerType ?? (params.endpointType === "LOAD_BALANCER" ? "REQUEST_COUNT" : void 0),
|
|
2097
|
+
params.scalerValue
|
|
2098
|
+
);
|
|
2099
|
+
if (scalerError) {
|
|
2100
|
+
return jsonReply2({ error: scalerError, status: 400 });
|
|
2101
|
+
}
|
|
1271
2102
|
const body = backend.mapCreate(params);
|
|
1272
2103
|
const result2 = await callRestUrl(
|
|
1273
2104
|
`${backend.base}${backend.list}`,
|
|
@@ -1305,46 +2136,67 @@ function registerEndpointTools(server, rt) {
|
|
|
1305
2136
|
);
|
|
1306
2137
|
server.tool(
|
|
1307
2138
|
"update-endpoint",
|
|
1308
|
-
"Update a Serverless endpoint's config. On v2 you can change image/disk/env/ports/registry/workers/scaling/networkVolumes/timeout/flashboot; on v1, scaling fields (worker min/max, idle timeout, scaler type/value, name). Only provided fields change.",
|
|
2139
|
+
"Update a Serverless endpoint's config. On v2 you can change image/disk/env/ports/registry/workers/scaling/networkVolumes/timeout/flashboot; on v1, scaling fields (worker min/max, idle timeout, scaler type/value, name). Only provided fields change. An endpoint's request routing (queue vs load balancer) is fixed at creation and cannot be changed here \u2014 recreate the endpoint instead.",
|
|
1309
2140
|
{
|
|
1310
|
-
endpointId:
|
|
1311
|
-
name:
|
|
1312
|
-
workersMin:
|
|
1313
|
-
workersMax:
|
|
1314
|
-
idleTimeout:
|
|
1315
|
-
|
|
1316
|
-
|
|
2141
|
+
endpointId: import_zod8.z.string().describe("ID of the endpoint to update"),
|
|
2142
|
+
name: import_zod8.z.string().optional().describe("New name for the endpoint"),
|
|
2143
|
+
workersMin: import_zod8.z.number().optional().describe("New minimum number of workers"),
|
|
2144
|
+
workersMax: import_zod8.z.number().optional().describe("New maximum number of workers"),
|
|
2145
|
+
idleTimeout: import_zod8.z.number().int().min(MIN_IDLE_TIMEOUT).max(MAX_IDLE_TIMEOUT).optional().describe(
|
|
2146
|
+
"New idle timeout in seconds (1-3600). Does not apply to queue endpoints scaling on REQUEST_COUNT."
|
|
2147
|
+
),
|
|
2148
|
+
scalerType: import_zod8.z.enum(["QUEUE_DELAY", "REQUEST_COUNT"]).optional().describe(
|
|
2149
|
+
"New autoscaler signal. Switchable on queue endpoints; load-balancing endpoints only accept REQUEST_COUNT."
|
|
2150
|
+
),
|
|
2151
|
+
scalerValue: import_zod8.z.number().min(MIN_QUEUE_DELAY).optional().describe(
|
|
2152
|
+
"New autoscaler target \u2014 seconds of queue delay (min 0.5) or in-flight requests per worker (integer, min 1). Applies to the endpoint's current scalerType unless you also pass a new one."
|
|
2153
|
+
),
|
|
1317
2154
|
// --- v2-only inline-config fields ---
|
|
1318
|
-
imageName:
|
|
1319
|
-
gpuPoolIds:
|
|
1320
|
-
gpuCount:
|
|
1321
|
-
args:
|
|
1322
|
-
containerDiskInGb:
|
|
1323
|
-
ports:
|
|
1324
|
-
env:
|
|
1325
|
-
containerRegistryAuthId:
|
|
1326
|
-
networkVolumeIds:
|
|
1327
|
-
executionTimeoutMs:
|
|
1328
|
-
flashboot:
|
|
2155
|
+
imageName: import_zod8.z.string().optional().describe("New Docker image (v2)"),
|
|
2156
|
+
gpuPoolIds: import_zod8.z.array(import_zod8.z.string()).optional().describe('New GPU pool names (v2), e.g. ["AMPERE_80"]'),
|
|
2157
|
+
gpuCount: import_zod8.z.number().optional().describe("New GPUs per worker (v2)"),
|
|
2158
|
+
args: import_zod8.z.string().optional().describe("New container args (v2)"),
|
|
2159
|
+
containerDiskInGb: import_zod8.z.number().optional().describe("New container disk size in GB (v2)"),
|
|
2160
|
+
ports: import_zod8.z.array(import_zod8.z.string()).optional().describe("New ports (v2)"),
|
|
2161
|
+
env: import_zod8.z.record(import_zod8.z.string()).optional().describe("New environment variables (v2)"),
|
|
2162
|
+
containerRegistryAuthId: import_zod8.z.string().optional().describe("New container registry auth id (v2)"),
|
|
2163
|
+
networkVolumeIds: import_zod8.z.array(import_zod8.z.string()).optional().describe("New network volume ids (v2)"),
|
|
2164
|
+
executionTimeoutMs: import_zod8.z.number().optional().describe("New per-job execution timeout in ms (v2)"),
|
|
2165
|
+
flashboot: import_zod8.z.enum(["OFF", "FLASHBOOT", "PRIORITY_FLASHBOOT"]).optional().describe("New FlashBoot mode (v2)")
|
|
1329
2166
|
},
|
|
1330
2167
|
{ title: "Update endpoint", ...WRITE, idempotentHint: true },
|
|
1331
2168
|
async (params) => {
|
|
1332
2169
|
const { endpointId, ...updateParams } = params;
|
|
1333
2170
|
const backend = backendFor("endpoints");
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
"PATCH",
|
|
1338
|
-
|
|
2171
|
+
const url = `${backend.base}${backend.get(endpointId)}`;
|
|
2172
|
+
if (backend.version !== "v2") {
|
|
2173
|
+
const body2 = backend.mapUpdate(updateParams);
|
|
2174
|
+
return jsonReply2(await callRestUrl(url, "PATCH", body2));
|
|
2175
|
+
}
|
|
2176
|
+
let scalerType = updateParams.scalerType;
|
|
2177
|
+
if (scalerType === void 0 && updateParams.scalerValue !== void 0) {
|
|
2178
|
+
const current = await callRestUrl(url);
|
|
2179
|
+
scalerType = current?.scaling?.type === "REQUEST_COUNT" ? "REQUEST_COUNT" : "QUEUE_DELAY";
|
|
2180
|
+
}
|
|
2181
|
+
const scalerError = scalerValueError(
|
|
2182
|
+
scalerType,
|
|
2183
|
+
updateParams.scalerValue
|
|
1339
2184
|
);
|
|
1340
|
-
|
|
2185
|
+
if (scalerError) {
|
|
2186
|
+
return jsonReply2({ error: scalerError, status: 400 });
|
|
2187
|
+
}
|
|
2188
|
+
const body = backend.mapUpdate({
|
|
2189
|
+
...updateParams,
|
|
2190
|
+
scalerType
|
|
2191
|
+
});
|
|
2192
|
+
return jsonReply2(await callRestUrl(url, "PATCH", body));
|
|
1341
2193
|
}
|
|
1342
2194
|
);
|
|
1343
2195
|
server.tool(
|
|
1344
2196
|
"delete-endpoint",
|
|
1345
2197
|
"Permanently delete a Serverless endpoint. This cannot be undone.",
|
|
1346
2198
|
{
|
|
1347
|
-
endpointId:
|
|
2199
|
+
endpointId: import_zod8.z.string().describe("ID of the endpoint to delete")
|
|
1348
2200
|
},
|
|
1349
2201
|
{ title: "Delete endpoint", ...DESTRUCTIVE },
|
|
1350
2202
|
async (params) => {
|
|
@@ -1356,12 +2208,39 @@ function registerEndpointTools(server, rt) {
|
|
|
1356
2208
|
return jsonReply2(result);
|
|
1357
2209
|
}
|
|
1358
2210
|
);
|
|
2211
|
+
server.tool(
|
|
2212
|
+
"list-endpoint-releases",
|
|
2213
|
+
"List a Serverless endpoint's release history and current rollout status (workers on the latest version). v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
|
|
2214
|
+
{
|
|
2215
|
+
...listPaginationParams,
|
|
2216
|
+
endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint whose releases to list")
|
|
2217
|
+
},
|
|
2218
|
+
{ title: "List endpoint releases", ...READ_ONLY },
|
|
2219
|
+
async (params) => {
|
|
2220
|
+
const backend = backendFor("endpoints");
|
|
2221
|
+
if (backend.version === "v1") {
|
|
2222
|
+
return jsonReply2({
|
|
2223
|
+
error: "list-endpoint-releases is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.",
|
|
2224
|
+
status: 501
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
const raw = await callRestUrl(
|
|
2228
|
+
`${backend.base}/serverless/${params.endpointId}/releases`
|
|
2229
|
+
);
|
|
2230
|
+
const releases = Array.isArray(raw?.releases) ? raw.releases : [];
|
|
2231
|
+
return capListResult(
|
|
2232
|
+
releases,
|
|
2233
|
+
{ limit: params.limit, cursor: params.cursor },
|
|
2234
|
+
{ rollout: raw?.rollout, endpointVersion: raw?.endpointVersion }
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
);
|
|
1359
2238
|
server.tool(
|
|
1360
2239
|
"list-endpoint-workers",
|
|
1361
|
-
"List the workers backing a Serverless endpoint, with their status and an aggregate summary. v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
|
|
2240
|
+
"List the workers backing a Serverless endpoint, with their status and an aggregate summary. v2-only \u2014 returns a 501 notice on the v1 API. An UNHEALTHY worker usually means the container is crash-looping (repeated starts that exit before the handler runs) \u2014 jobs on the endpoint will sit IN_QUEUE even though this is a worker failure, not a capacity shortage; inspect it with stream-worker-logs. Zero workers while jobs queue means the endpoint is waiting for GPU capacity. Paginated via limit/cursor.",
|
|
1362
2241
|
{
|
|
1363
2242
|
...listPaginationParams,
|
|
1364
|
-
endpointId:
|
|
2243
|
+
endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint whose workers to list")
|
|
1365
2244
|
},
|
|
1366
2245
|
{ title: "List endpoint workers", ...READ_ONLY },
|
|
1367
2246
|
async (params) => {
|
|
@@ -1382,30 +2261,110 @@ function registerEndpointTools(server, rt) {
|
|
|
1382
2261
|
);
|
|
1383
2262
|
}
|
|
1384
2263
|
);
|
|
2264
|
+
server.tool(
|
|
2265
|
+
"stream-worker-logs",
|
|
2266
|
+
"Fetch a bounded snapshot of a serverless worker's live logs (container and/or system) via Server-Sent Events. v2-only \u2014 returns a 501 notice on the v1 API. Get the workerId from list-endpoint-workers. Use this to diagnose UNHEALTHY workers: repeated `start container` system lines with no container output is the crash-loop signature (the container exits before the handler runs). Reads for up to maxWaitMs (default 5s) and returns the collected log lines; use `tail` to backfill recent lines first. Large output is truncated (see the `truncated` flag).",
|
|
2267
|
+
{
|
|
2268
|
+
endpointId: import_zod8.z.string().describe("ID of the Serverless endpoint the worker belongs to"),
|
|
2269
|
+
workerId: import_zod8.z.string().describe(
|
|
2270
|
+
"ID of the worker whose logs to stream (from list-endpoint-workers)"
|
|
2271
|
+
),
|
|
2272
|
+
...logStreamParams
|
|
2273
|
+
},
|
|
2274
|
+
{ title: "Stream worker logs", ...READ_ONLY },
|
|
2275
|
+
(params) => streamLogsReply(
|
|
2276
|
+
rt,
|
|
2277
|
+
{
|
|
2278
|
+
name: "stream-worker-logs",
|
|
2279
|
+
resource: "workers",
|
|
2280
|
+
logsUrl: (backend) => `${backend.base}/serverless/${encodeURIComponent(params.endpointId)}/workers/${encodeURIComponent(params.workerId)}/logs`
|
|
2281
|
+
},
|
|
2282
|
+
params
|
|
2283
|
+
)
|
|
2284
|
+
);
|
|
1385
2285
|
}
|
|
1386
2286
|
|
|
1387
2287
|
// src/tools/jobs.ts
|
|
1388
|
-
var
|
|
2288
|
+
var import_zod9 = require("zod");
|
|
2289
|
+
var DIAGNOSIS_TTL_MS = 15e3;
|
|
2290
|
+
var MAX_DIAGNOSIS_ENTRIES = 500;
|
|
2291
|
+
var diagnosisCache = /* @__PURE__ */ new Map();
|
|
1389
2292
|
function registerJobTools(server, rt) {
|
|
1390
|
-
const { jsonReply: jsonReply2, serverlessRequest } = rt;
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
2293
|
+
const { jsonReply: jsonReply2, serverlessRequest, backendFor, callRestUrl } = rt;
|
|
2294
|
+
async function diagnoseQueuedJob(endpointId) {
|
|
2295
|
+
const cached = diagnosisCache.get(endpointId);
|
|
2296
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
2297
|
+
return cached.value;
|
|
2298
|
+
}
|
|
2299
|
+
diagnosisCache.delete(endpointId);
|
|
2300
|
+
try {
|
|
2301
|
+
const backend = backendFor("workers");
|
|
2302
|
+
if (backend.version !== "v2") return null;
|
|
2303
|
+
const raw = await callRestUrl(
|
|
2304
|
+
`${backend.base}/serverless/${endpointId}/workers`
|
|
2305
|
+
);
|
|
2306
|
+
const summary = raw?.summary;
|
|
2307
|
+
const workers = raw?.workers ?? [];
|
|
2308
|
+
if (!summary) return null;
|
|
2309
|
+
const remember = (value) => {
|
|
2310
|
+
if (diagnosisCache.size >= MAX_DIAGNOSIS_ENTRIES) {
|
|
2311
|
+
const oldest = diagnosisCache.keys().next().value;
|
|
2312
|
+
if (oldest !== void 0) diagnosisCache.delete(oldest);
|
|
2313
|
+
}
|
|
2314
|
+
diagnosisCache.set(endpointId, {
|
|
2315
|
+
value,
|
|
2316
|
+
expiresAt: Date.now() + DIAGNOSIS_TTL_MS
|
|
2317
|
+
});
|
|
2318
|
+
return value;
|
|
2319
|
+
};
|
|
2320
|
+
if ((summary.unhealthy ?? 0) > 0) {
|
|
2321
|
+
const badIds = workers.filter((w) => w.status === "UNHEALTHY").map((w) => w.id).join(", ");
|
|
2322
|
+
return remember({
|
|
2323
|
+
workerHealth: summary,
|
|
2324
|
+
hint: `${summary.unhealthy} of ${summary.total} worker(s) on this endpoint are UNHEALTHY \u2014 the container is likely crash-looping (repeated starts that exit before the handler runs). The job can stay IN_QUEUE indefinitely; this is a worker failure, NOT a capacity shortage. Inspect the logs with stream-worker-logs${badIds ? ` (workerId: ${badIds})` : ""}.`
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
if ((summary.total ?? 0) === 0) {
|
|
2328
|
+
return remember({
|
|
2329
|
+
workerHealth: summary,
|
|
2330
|
+
hint: "No workers are scheduled for this endpoint yet \u2014 it may simply be scaling up from zero (normal for the first seconds of a cold start), or it is waiting for GPU capacity / its GPU-CUDA constraints exclude the currently-available hosts. If this persists, check availability with list-gpu-types or widen the gpuIds/CUDA settings."
|
|
2331
|
+
});
|
|
2332
|
+
}
|
|
2333
|
+
if ((summary.throttled ?? 0) > 0 && (summary.running ?? 0) === 0) {
|
|
2334
|
+
return remember({
|
|
2335
|
+
workerHealth: summary,
|
|
2336
|
+
hint: "Workers exist but are throttled \u2014 the hosts are at capacity right now; the job should start when capacity frees up."
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
if ((summary.initializing ?? 0) > 0) {
|
|
2340
|
+
return remember({
|
|
2341
|
+
workerHealth: summary,
|
|
2342
|
+
hint: "A worker is initializing \u2014 likely a cold start (image pull / model load); the job should start soon."
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
return remember({ workerHealth: summary, hint: "" });
|
|
2346
|
+
} catch {
|
|
2347
|
+
return null;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
const endpointIdSchema = import_zod9.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid endpoint ID format");
|
|
2351
|
+
const jobIdSchema = import_zod9.z.string().regex(/^[a-zA-Z0-9_-]+$/, "Invalid job ID format");
|
|
2352
|
+
const inputSchema = import_zod9.z.record(import_zod9.z.unknown()).describe(
|
|
1394
2353
|
"Input payload for the worker handler. The expected fields depend on the deployed model or worker."
|
|
1395
2354
|
);
|
|
1396
|
-
const webhookSchema =
|
|
2355
|
+
const webhookSchema = import_zod9.z.string().url().optional().describe(
|
|
1397
2356
|
"Webhook URL to receive job completion notifications instead of polling"
|
|
1398
2357
|
);
|
|
1399
|
-
const policySchema =
|
|
1400
|
-
executionTimeout:
|
|
1401
|
-
lowPriority:
|
|
1402
|
-
ttl:
|
|
2358
|
+
const policySchema = import_zod9.z.object({
|
|
2359
|
+
executionTimeout: import_zod9.z.number().optional().describe("Maximum execution time in milliseconds"),
|
|
2360
|
+
lowPriority: import_zod9.z.boolean().optional().describe("Submit as a low-priority job"),
|
|
2361
|
+
ttl: import_zod9.z.number().optional().describe("Time-to-live for the job result in milliseconds")
|
|
1403
2362
|
}).optional().describe("Execution policy options");
|
|
1404
|
-
const s3ConfigSchema =
|
|
1405
|
-
accessId:
|
|
1406
|
-
accessSecret:
|
|
1407
|
-
bucketName:
|
|
1408
|
-
endpointUrl:
|
|
2363
|
+
const s3ConfigSchema = import_zod9.z.object({
|
|
2364
|
+
accessId: import_zod9.z.string().describe("S3 access key ID"),
|
|
2365
|
+
accessSecret: import_zod9.z.string().describe("S3 secret access key"),
|
|
2366
|
+
bucketName: import_zod9.z.string().describe("S3 bucket name"),
|
|
2367
|
+
endpointUrl: import_zod9.z.string().describe("S3 endpoint URL")
|
|
1409
2368
|
}).optional().describe("S3-compatible storage config for large outputs");
|
|
1410
2369
|
server.tool(
|
|
1411
2370
|
"run-endpoint",
|
|
@@ -1439,7 +2398,7 @@ function registerJobTools(server, rt) {
|
|
|
1439
2398
|
"ID of the Serverless endpoint to run synchronously"
|
|
1440
2399
|
),
|
|
1441
2400
|
input: inputSchema,
|
|
1442
|
-
wait:
|
|
2401
|
+
wait: import_zod9.z.number().min(1e3).max(3e5).optional().describe(
|
|
1443
2402
|
"How long in milliseconds the server should wait for a result before returning a job ID to poll (1000\u2013300000). Defaults to 90000 (90 seconds)."
|
|
1444
2403
|
),
|
|
1445
2404
|
webhook: webhookSchema,
|
|
@@ -1461,7 +2420,7 @@ function registerJobTools(server, rt) {
|
|
|
1461
2420
|
);
|
|
1462
2421
|
server.tool(
|
|
1463
2422
|
"get-job-status",
|
|
1464
|
-
"Check the status of an asynchronous Serverless job. Returns the current status and output when complete. Job statuses: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, TIMED_OUT.",
|
|
2423
|
+
"Check the status of an asynchronous Serverless job. Returns the current status and output when complete. Job statuses: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED, TIMED_OUT. IMPORTANT: a job stuck IN_QUEUE does not necessarily mean a capacity shortage \u2014 a worker may have spun up and crash-looped (the platform marks it UNHEALTHY while the job stays queued). When the job is IN_QUEUE this tool attaches a workerHealth summary and a hint; to dig deeper, call list-endpoint-workers (look for UNHEALTHY) and stream-worker-logs.",
|
|
1465
2424
|
{
|
|
1466
2425
|
endpointId: endpointIdSchema.describe(
|
|
1467
2426
|
"ID of the Serverless endpoint the job belongs to"
|
|
@@ -1474,6 +2433,16 @@ function registerJobTools(server, rt) {
|
|
|
1474
2433
|
params.endpointId,
|
|
1475
2434
|
`/status/${params.jobId}`
|
|
1476
2435
|
);
|
|
2436
|
+
if (result && typeof result === "object" && result.status === "IN_QUEUE") {
|
|
2437
|
+
const diagnosis = await diagnoseQueuedJob(params.endpointId);
|
|
2438
|
+
if (diagnosis) {
|
|
2439
|
+
return jsonReply2({
|
|
2440
|
+
...result,
|
|
2441
|
+
workerHealth: diagnosis.workerHealth,
|
|
2442
|
+
...diagnosis.hint ? { hint: diagnosis.hint } : {}
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
1477
2446
|
return jsonReply2(result);
|
|
1478
2447
|
}
|
|
1479
2448
|
);
|
|
@@ -1575,7 +2544,7 @@ function registerJobTools(server, rt) {
|
|
|
1575
2544
|
);
|
|
1576
2545
|
server.tool(
|
|
1577
2546
|
"endpoint-health",
|
|
1578
|
-
"Get the health and operational status of a Serverless endpoint, including worker counts and job statistics.",
|
|
2547
|
+
"Get the health and operational status of a Serverless endpoint, including worker counts and job statistics. Use this when jobs are stuck IN_QUEUE: unhealthy workers mean a crash-looping container (a worker failure \u2014 inspect with stream-worker-logs), while zero workers means the endpoint is waiting for GPU capacity.",
|
|
1579
2548
|
{
|
|
1580
2549
|
endpointId: endpointIdSchema.describe(
|
|
1581
2550
|
"ID of the Serverless endpoint to check health for"
|
|
@@ -1608,7 +2577,7 @@ function registerJobTools(server, rt) {
|
|
|
1608
2577
|
}
|
|
1609
2578
|
|
|
1610
2579
|
// src/tools/templates.ts
|
|
1611
|
-
var
|
|
2580
|
+
var import_zod10 = require("zod");
|
|
1612
2581
|
function registerTemplateTools(server, rt) {
|
|
1613
2582
|
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
1614
2583
|
server.tool(
|
|
@@ -1616,9 +2585,9 @@ function registerTemplateTools(server, rt) {
|
|
|
1616
2585
|
`List available templates. By default returns only the user's own templates. Use includeRunpodTemplates to also include official Runpod templates. The recommended default template for new pods is "Runpod Pytorch 2.8.0" (ID: runpod-torch-v280) \u2014 it has the latest CUDA and PyTorch versions.`,
|
|
1617
2586
|
{
|
|
1618
2587
|
...listPaginationParams,
|
|
1619
|
-
includeRunpodTemplates:
|
|
1620
|
-
includePublicTemplates:
|
|
1621
|
-
includeEndpointBoundTemplates:
|
|
2588
|
+
includeRunpodTemplates: import_zod10.z.boolean().optional().describe("Include official Runpod templates in the response"),
|
|
2589
|
+
includePublicTemplates: import_zod10.z.boolean().optional().describe("Include community-made public templates in the response"),
|
|
2590
|
+
includeEndpointBoundTemplates: import_zod10.z.boolean().optional().describe(
|
|
1622
2591
|
"Include templates bound to Serverless endpoints in the response"
|
|
1623
2592
|
)
|
|
1624
2593
|
},
|
|
@@ -1649,7 +2618,7 @@ function registerTemplateTools(server, rt) {
|
|
|
1649
2618
|
"get-template",
|
|
1650
2619
|
"Get one template by id (image, env, ports, disk/volume config).",
|
|
1651
2620
|
{
|
|
1652
|
-
templateId:
|
|
2621
|
+
templateId: import_zod10.z.string().describe("ID of the template to retrieve")
|
|
1653
2622
|
},
|
|
1654
2623
|
{ title: "Get template", ...READ_ONLY },
|
|
1655
2624
|
async (params) => {
|
|
@@ -1662,23 +2631,27 @@ function registerTemplateTools(server, rt) {
|
|
|
1662
2631
|
);
|
|
1663
2632
|
server.tool(
|
|
1664
2633
|
"create-template",
|
|
1665
|
-
"Create a reusable pod/endpoint template (image, ports, env, disk/volume).
|
|
2634
|
+
"Create a reusable pod/endpoint template (image, ports, env, disk/volume). Category is optional and defaults to NVIDIA.",
|
|
1666
2635
|
{
|
|
1667
|
-
name:
|
|
1668
|
-
imageName:
|
|
1669
|
-
isServerless:
|
|
1670
|
-
ports:
|
|
1671
|
-
dockerEntrypoint:
|
|
1672
|
-
|
|
1673
|
-
env: import_zod6.z.record(import_zod6.z.string()).optional().describe("Environment variables"),
|
|
1674
|
-
containerDiskInGb: import_zod6.z.number().optional().describe("Container disk size in GB"),
|
|
1675
|
-
volumeInGb: import_zod6.z.number().optional().describe("Volume size in GB"),
|
|
1676
|
-
volumeMountPath: import_zod6.z.string().optional().describe("Path to mount the volume"),
|
|
1677
|
-
readme: import_zod6.z.string().optional().describe("README content in markdown format"),
|
|
1678
|
-
category: import_zod6.z.enum(["CPU", "NVIDIA", "AMD"]).optional().describe(
|
|
1679
|
-
"Hardware category for the template. On v2 a category is required and defaults to NVIDIA when omitted \u2014 set CPU or AMD for non-NVIDIA templates so they are not mis-categorized."
|
|
2636
|
+
name: import_zod10.z.string().describe("Name for the template"),
|
|
2637
|
+
imageName: import_zod10.z.string().describe("Docker image to use"),
|
|
2638
|
+
isServerless: import_zod10.z.boolean().optional().describe("Is this a serverless template"),
|
|
2639
|
+
ports: import_zod10.z.array(import_zod10.z.string()).optional().describe("Ports to expose"),
|
|
2640
|
+
dockerEntrypoint: import_zod10.z.array(import_zod10.z.string()).optional().describe(
|
|
2641
|
+
"Docker entrypoint commands. Note: not persisted on the v2 REST API, which has no separate entrypoint field (only a start command)."
|
|
1680
2642
|
),
|
|
1681
|
-
|
|
2643
|
+
dockerStartCmd: import_zod10.z.array(import_zod10.z.string()).optional().describe(
|
|
2644
|
+
'Container start command. On v2 this is stored as the template\'s `args` string; a multi-element array is joined with spaces (e.g. ["python","-u","app.py"] \u2192 "python -u app.py"). The join is lossy: an element that itself contains spaces is NOT preserved as one argument \u2014 e.g. ["sh","-c","echo hi"] becomes "sh -c echo hi", so pass a shell command as a single pre-quoted element if that distinction matters. Omit or pass an empty array to leave the command unset (never overwrites with "").'
|
|
2645
|
+
),
|
|
2646
|
+
env: import_zod10.z.record(import_zod10.z.string()).optional().describe("Environment variables"),
|
|
2647
|
+
containerDiskInGb: import_zod10.z.number().optional().describe("Container disk size in GB"),
|
|
2648
|
+
volumeInGb: import_zod10.z.number().optional().describe("Volume size in GB"),
|
|
2649
|
+
volumeMountPath: import_zod10.z.string().optional().describe("Path to mount the volume"),
|
|
2650
|
+
readme: import_zod10.z.string().optional().describe("README content in markdown format"),
|
|
2651
|
+
category: import_zod10.z.enum(["CPU", "NVIDIA", "AMD"]).optional().describe(
|
|
2652
|
+
"How the template is grouped and filtered in the Runpod console \u2014 it does NOT affect hardware selection, scheduling, or billing. Optional; defaults to NVIDIA. Set CPU or AMD only to file the template correctly in the console."
|
|
2653
|
+
),
|
|
2654
|
+
containerRegistryAuthId: import_zod10.z.string().optional().describe(
|
|
1682
2655
|
"ID of a container registry credential (from create-container-registry-auth / list-container-registry-auths) used to pull a private image. Required when imageName points at a private registry; without it serverless workers fail the image pull."
|
|
1683
2656
|
)
|
|
1684
2657
|
},
|
|
@@ -1696,15 +2669,18 @@ function registerTemplateTools(server, rt) {
|
|
|
1696
2669
|
);
|
|
1697
2670
|
server.tool(
|
|
1698
2671
|
"update-template",
|
|
1699
|
-
"Update a template's mutable fields (name, image, ports, env,
|
|
2672
|
+
"Update a template's mutable fields (name, image, ports, env, start command, registry auth). Only provided fields change; omitted fields are left untouched.",
|
|
1700
2673
|
{
|
|
1701
|
-
templateId:
|
|
1702
|
-
name:
|
|
1703
|
-
imageName:
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
2674
|
+
templateId: import_zod10.z.string().describe("ID of the template to update"),
|
|
2675
|
+
name: import_zod10.z.string().optional().describe("New name for the template"),
|
|
2676
|
+
imageName: import_zod10.z.string().optional().describe("New Docker image"),
|
|
2677
|
+
dockerStartCmd: import_zod10.z.array(import_zod10.z.string()).optional().describe(
|
|
2678
|
+
"New container start command. On v2 stored as the template's `args` string; a multi-element array is joined with spaces (lossy \u2014 an element containing spaces is split into separate tokens). Note: this can only set a command, not clear one \u2014 an empty or absent array is ignored so the existing command is preserved."
|
|
2679
|
+
),
|
|
2680
|
+
ports: import_zod10.z.array(import_zod10.z.string()).optional().describe("New ports to expose"),
|
|
2681
|
+
env: import_zod10.z.record(import_zod10.z.string()).optional().describe("New environment variables"),
|
|
2682
|
+
readme: import_zod10.z.string().optional().describe("New README content in markdown format"),
|
|
2683
|
+
containerRegistryAuthId: import_zod10.z.string().optional().describe(
|
|
1708
2684
|
"ID of a container registry credential (from create-container-registry-auth / list-container-registry-auths) used to pull a private image. Set this to attach a credential to a template referencing a private registry."
|
|
1709
2685
|
)
|
|
1710
2686
|
},
|
|
@@ -1725,7 +2701,7 @@ function registerTemplateTools(server, rt) {
|
|
|
1725
2701
|
"delete-template",
|
|
1726
2702
|
"Permanently delete a template. This cannot be undone.",
|
|
1727
2703
|
{
|
|
1728
|
-
templateId:
|
|
2704
|
+
templateId: import_zod10.z.string().describe("ID of the template to delete")
|
|
1729
2705
|
},
|
|
1730
2706
|
{ title: "Delete template", ...DESTRUCTIVE },
|
|
1731
2707
|
async (params) => {
|
|
@@ -1740,7 +2716,7 @@ function registerTemplateTools(server, rt) {
|
|
|
1740
2716
|
}
|
|
1741
2717
|
|
|
1742
2718
|
// src/tools/network-volumes.ts
|
|
1743
|
-
var
|
|
2719
|
+
var import_zod11 = require("zod");
|
|
1744
2720
|
function registerNetworkVolumeTools(server, rt) {
|
|
1745
2721
|
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
1746
2722
|
server.tool(
|
|
@@ -1761,7 +2737,7 @@ function registerNetworkVolumeTools(server, rt) {
|
|
|
1761
2737
|
"get-network-volume",
|
|
1762
2738
|
"Get one network volume by id (name, size, data center).",
|
|
1763
2739
|
{
|
|
1764
|
-
networkVolumeId:
|
|
2740
|
+
networkVolumeId: import_zod11.z.string().describe("ID of the network volume to retrieve")
|
|
1765
2741
|
},
|
|
1766
2742
|
{ title: "Get network volume", ...READ_ONLY },
|
|
1767
2743
|
async (params) => {
|
|
@@ -1774,11 +2750,17 @@ function registerNetworkVolumeTools(server, rt) {
|
|
|
1774
2750
|
);
|
|
1775
2751
|
server.tool(
|
|
1776
2752
|
"create-network-volume",
|
|
1777
|
-
"Create a persistent network volume in a data center (size
|
|
2753
|
+
"Create a persistent network volume in a data center (size 10\u20134096 GB) that pods can mount. Pass volumeType to pick a storage tier; omit it to get the data center default.",
|
|
1778
2754
|
{
|
|
1779
|
-
name:
|
|
1780
|
-
|
|
1781
|
-
|
|
2755
|
+
name: import_zod11.z.string().describe("Name for the network volume"),
|
|
2756
|
+
// Bounds mirror the v2 spec (integer, minimum 10, maximum 4096). Enforced
|
|
2757
|
+
// here as well as described, so an out-of-range value fails locally
|
|
2758
|
+
// instead of costing an API roundtrip.
|
|
2759
|
+
size: import_zod11.z.number().int().min(10).max(4096).describe("Size in GB (10-4096)"),
|
|
2760
|
+
dataCenterId: import_zod11.z.string().describe("Data center ID (see list-data-centers)"),
|
|
2761
|
+
volumeType: import_zod11.z.enum(["STANDARD", "HIGH_PERFORMANCE"]).optional().describe(
|
|
2762
|
+
"Storage tier. Omit for the data center's default (primary) tier. HIGH_PERFORMANCE provisions an HPS volume. Immutable after creation \u2014 update-network-volume cannot change it."
|
|
2763
|
+
)
|
|
1782
2764
|
},
|
|
1783
2765
|
{ title: "Create network volume", ...WRITE },
|
|
1784
2766
|
async (params) => {
|
|
@@ -1796,9 +2778,11 @@ function registerNetworkVolumeTools(server, rt) {
|
|
|
1796
2778
|
"update-network-volume",
|
|
1797
2779
|
"Update a network volume (rename, or grow size \u2014 the new size must exceed the current size). Only provided fields change.",
|
|
1798
2780
|
{
|
|
1799
|
-
networkVolumeId:
|
|
1800
|
-
name:
|
|
1801
|
-
|
|
2781
|
+
networkVolumeId: import_zod11.z.string().describe("ID of the network volume to update"),
|
|
2782
|
+
name: import_zod11.z.string().optional().describe("New name for the network volume"),
|
|
2783
|
+
// Same absolute bounds as create. "Larger than current" stays server-side —
|
|
2784
|
+
// we do not hold the current size here, so only the range is checked.
|
|
2785
|
+
size: import_zod11.z.number().int().min(10).max(4096).optional().describe("New size in GB, 10-4096 (must be larger than current)")
|
|
1802
2786
|
},
|
|
1803
2787
|
{ title: "Update network volume", ...WRITE, idempotentHint: true },
|
|
1804
2788
|
async (params) => {
|
|
@@ -1817,7 +2801,7 @@ function registerNetworkVolumeTools(server, rt) {
|
|
|
1817
2801
|
"delete-network-volume",
|
|
1818
2802
|
"Permanently delete a network volume and its data. This cannot be undone.",
|
|
1819
2803
|
{
|
|
1820
|
-
networkVolumeId:
|
|
2804
|
+
networkVolumeId: import_zod11.z.string().describe("ID of the network volume to delete")
|
|
1821
2805
|
},
|
|
1822
2806
|
{ title: "Delete network volume", ...DESTRUCTIVE },
|
|
1823
2807
|
async (params) => {
|
|
@@ -1832,9 +2816,14 @@ function registerNetworkVolumeTools(server, rt) {
|
|
|
1832
2816
|
}
|
|
1833
2817
|
|
|
1834
2818
|
// src/tools/registries.ts
|
|
1835
|
-
var
|
|
2819
|
+
var import_zod12 = require("zod");
|
|
2820
|
+
var DELEGATIONS = "/delegations";
|
|
1836
2821
|
function registerRegistryTools(server, rt) {
|
|
1837
2822
|
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
2823
|
+
const delegationsV2OnlyNotice = (tool) => ({
|
|
2824
|
+
error: `${tool} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
|
|
2825
|
+
status: 501
|
|
2826
|
+
});
|
|
1838
2827
|
server.tool(
|
|
1839
2828
|
"list-container-registry-auths",
|
|
1840
2829
|
"List your saved container-registry credentials (private image-pull auth). Paginated via limit/cursor.",
|
|
@@ -1853,7 +2842,7 @@ function registerRegistryTools(server, rt) {
|
|
|
1853
2842
|
"get-container-registry-auth",
|
|
1854
2843
|
"Get one saved container-registry credential by id (the secret/password is not returned).",
|
|
1855
2844
|
{
|
|
1856
|
-
containerRegistryAuthId:
|
|
2845
|
+
containerRegistryAuthId: import_zod12.z.string().describe("ID of the container registry auth to retrieve")
|
|
1857
2846
|
},
|
|
1858
2847
|
{ title: "Get container registry auth", ...READ_ONLY },
|
|
1859
2848
|
async (params) => {
|
|
@@ -1868,9 +2857,9 @@ function registerRegistryTools(server, rt) {
|
|
|
1868
2857
|
"create-container-registry-auth",
|
|
1869
2858
|
"Save container-registry credentials (username + password/token) so pods and endpoints can pull private images.",
|
|
1870
2859
|
{
|
|
1871
|
-
name:
|
|
1872
|
-
username:
|
|
1873
|
-
password:
|
|
2860
|
+
name: import_zod12.z.string().describe("Name for the container registry auth"),
|
|
2861
|
+
username: import_zod12.z.string().describe("Registry username"),
|
|
2862
|
+
password: import_zod12.z.string().describe("Registry password or access token (stored as a secret)")
|
|
1874
2863
|
},
|
|
1875
2864
|
{ title: "Create container registry auth", ...WRITE },
|
|
1876
2865
|
async (params) => {
|
|
@@ -1888,7 +2877,7 @@ function registerRegistryTools(server, rt) {
|
|
|
1888
2877
|
"delete-container-registry-auth",
|
|
1889
2878
|
"Permanently delete a saved container-registry credential. This cannot be undone.",
|
|
1890
2879
|
{
|
|
1891
|
-
containerRegistryAuthId:
|
|
2880
|
+
containerRegistryAuthId: import_zod12.z.string().describe("ID of the container registry auth to delete")
|
|
1892
2881
|
},
|
|
1893
2882
|
{ title: "Delete container registry auth", ...DESTRUCTIVE },
|
|
1894
2883
|
async (params) => {
|
|
@@ -1900,166 +2889,69 @@ function registerRegistryTools(server, rt) {
|
|
|
1900
2889
|
return jsonReply2(result);
|
|
1901
2890
|
}
|
|
1902
2891
|
);
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
// src/tools/tags.ts
|
|
1906
|
-
var import_zod9 = require("zod");
|
|
1907
|
-
var TAG_RESOURCE_TYPES = [
|
|
1908
|
-
"POD",
|
|
1909
|
-
"NETWORK_VOLUME",
|
|
1910
|
-
"CLUSTER",
|
|
1911
|
-
"SERVERLESS_ENDPOINT"
|
|
1912
|
-
];
|
|
1913
|
-
var taggedResourceSchema = import_zod9.z.object({
|
|
1914
|
-
id: import_zod9.z.string().describe("Resource identifier (e.g. a pod or endpoint id)"),
|
|
1915
|
-
type: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to associate with the tag")
|
|
1916
|
-
});
|
|
1917
|
-
function v2OnlyNotice(tool) {
|
|
1918
|
-
return {
|
|
1919
|
-
error: `${tool} is only available on the v2 REST API. Set RUNPOD_REST_VERSION=v2.`,
|
|
1920
|
-
status: 501
|
|
1921
|
-
};
|
|
1922
|
-
}
|
|
1923
|
-
function registerTagTools(server, rt) {
|
|
1924
|
-
const { jsonReply: jsonReply2, callRestUrl, backendFor } = rt;
|
|
1925
2892
|
server.tool(
|
|
1926
|
-
"list-
|
|
1927
|
-
"List your
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
includeResources: import_zod9.z.boolean().optional().describe("Include the resources attached to each tag in the response")
|
|
1931
|
-
},
|
|
1932
|
-
{ title: "List tags", ...READ_ONLY },
|
|
2893
|
+
"list-registry-delegations",
|
|
2894
|
+
"List your AWS ECR delegations \u2014 ECR repositories Runpod has been granted scoped pull access to, so private ECR images can be pulled without storing AWS credentials. Each entry includes the `dockerRegistryUri` to pull from. v2-only \u2014 returns a 501 notice on the v1 API. Paginated via limit/cursor.",
|
|
2895
|
+
listPaginationParams,
|
|
2896
|
+
{ title: "List registry delegations", ...READ_ONLY },
|
|
1933
2897
|
async (params) => {
|
|
1934
|
-
const backend = backendFor("
|
|
1935
|
-
if (backend.version === "v1")
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
2898
|
+
const backend = backendFor("registries");
|
|
2899
|
+
if (backend.version === "v1") {
|
|
2900
|
+
return jsonReply2(delegationsV2OnlyNotice("list-registry-delegations"));
|
|
2901
|
+
}
|
|
2902
|
+
const raw = await callRestUrl(
|
|
2903
|
+
`${backend.base}${backend.list}${DELEGATIONS}`
|
|
2904
|
+
);
|
|
2905
|
+
const delegations = Array.isArray(raw?.delegations) ? raw.delegations : [];
|
|
2906
|
+
return capListResult(delegations, {
|
|
1939
2907
|
limit: params.limit,
|
|
1940
2908
|
cursor: params.cursor
|
|
1941
2909
|
});
|
|
1942
2910
|
}
|
|
1943
2911
|
);
|
|
1944
2912
|
server.tool(
|
|
1945
|
-
"
|
|
1946
|
-
"
|
|
2913
|
+
"create-registry-delegation",
|
|
2914
|
+
"Register an AWS ECR repository so Runpod can pull private images from it without stored credentials. Pass the repository ARN; the reply carries the `dockerRegistryUri`, resolved repository/tag, and AWS region. v2-only \u2014 returns a 501 notice on the v1 API. For a non-ECR private registry use create-container-registry-auth instead.",
|
|
1947
2915
|
{
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
async (params) => {
|
|
1953
|
-
const backend = backendFor("tags");
|
|
1954
|
-
if (backend.version === "v1") return jsonReply2(v2OnlyNotice("get-tag"));
|
|
1955
|
-
const query = params.includeResources ? "?include=resources" : "";
|
|
1956
|
-
const result = await callRestUrl(
|
|
1957
|
-
`${backend.base}${backend.get(params.tagId)}${query}`
|
|
1958
|
-
);
|
|
1959
|
-
return jsonReply2(result);
|
|
1960
|
-
}
|
|
1961
|
-
);
|
|
1962
|
-
server.tool(
|
|
1963
|
-
"create-tag",
|
|
1964
|
-
"Create a key/value tag, optionally attaching resources at creation. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
1965
|
-
{
|
|
1966
|
-
key: import_zod9.z.string().min(1).describe('Tag key (e.g. "project")'),
|
|
1967
|
-
value: import_zod9.z.string().min(1).describe('Tag value (e.g. "gpt-training")'),
|
|
1968
|
-
resources: import_zod9.z.array(taggedResourceSchema).optional().describe("Resources to attach to the tag at creation time")
|
|
2916
|
+
resource: import_zod12.z.string().min(1).describe(
|
|
2917
|
+
'ECR resource ARN, e.g. "arn:aws:ecr:us-east-2:123456789012:repository/my-org/my-image"'
|
|
2918
|
+
),
|
|
2919
|
+
name: import_zod12.z.string().optional().describe("Optional human-readable name for the delegation")
|
|
1969
2920
|
},
|
|
1970
|
-
{ title: "Create
|
|
2921
|
+
{ title: "Create registry delegation", ...WRITE },
|
|
1971
2922
|
async (params) => {
|
|
1972
|
-
const backend = backendFor("
|
|
1973
|
-
if (backend.version === "v1")
|
|
1974
|
-
return jsonReply2(
|
|
2923
|
+
const backend = backendFor("registries");
|
|
2924
|
+
if (backend.version === "v1") {
|
|
2925
|
+
return jsonReply2(delegationsV2OnlyNotice("create-registry-delegation"));
|
|
2926
|
+
}
|
|
2927
|
+
const body = { resource: params.resource };
|
|
2928
|
+
if (params.name !== void 0) body.name = params.name;
|
|
1975
2929
|
const result = await callRestUrl(
|
|
1976
|
-
`${backend.base}${backend.list}`,
|
|
2930
|
+
`${backend.base}${backend.list}${DELEGATIONS}`,
|
|
1977
2931
|
"POST",
|
|
1978
|
-
|
|
2932
|
+
body
|
|
1979
2933
|
);
|
|
1980
2934
|
return jsonReply2(result);
|
|
1981
2935
|
}
|
|
1982
2936
|
);
|
|
1983
2937
|
server.tool(
|
|
1984
|
-
"
|
|
1985
|
-
"
|
|
2938
|
+
"delete-registry-delegation",
|
|
2939
|
+
"Revoke an AWS ECR delegation, withdrawing Runpod's pull access to that repository. Existing workers keep running; future pulls fail. v2-only \u2014 returns a 501 notice on the v1 API. This cannot be undone.",
|
|
1986
2940
|
{
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
value: import_zod9.z.string().min(1).optional().describe("New tag value"),
|
|
1990
|
-
resources: import_zod9.z.array(taggedResourceSchema).optional().describe(
|
|
1991
|
-
"Replace the tag's resource associations with exactly these. Omit to leave unchanged; pass [] to detach all."
|
|
2941
|
+
delegationId: import_zod12.z.string().describe(
|
|
2942
|
+
"ID of the delegation to revoke (from list-registry-delegations)"
|
|
1992
2943
|
)
|
|
1993
2944
|
},
|
|
1994
|
-
|
|
1995
|
-
// retried-after-partial-failure update is not guaranteed to be a no-op.
|
|
1996
|
-
{ title: "Update tag", ...WRITE },
|
|
1997
|
-
async (params) => {
|
|
1998
|
-
const { tagId, ...updateParams } = params;
|
|
1999
|
-
const backend = backendFor("tags");
|
|
2000
|
-
if (backend.version === "v1")
|
|
2001
|
-
return jsonReply2(v2OnlyNotice("update-tag"));
|
|
2002
|
-
const result = await callRestUrl(
|
|
2003
|
-
`${backend.base}${backend.get(tagId)}`,
|
|
2004
|
-
"PATCH",
|
|
2005
|
-
updateParams
|
|
2006
|
-
);
|
|
2007
|
-
return jsonReply2(result);
|
|
2008
|
-
}
|
|
2009
|
-
);
|
|
2010
|
-
server.tool(
|
|
2011
|
-
"delete-tag",
|
|
2012
|
-
"Permanently delete a tag (removes it from all resources it is attached to). This cannot be undone. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2013
|
-
{
|
|
2014
|
-
tagId: import_zod9.z.string().describe("ID of the tag to delete")
|
|
2015
|
-
},
|
|
2016
|
-
{ title: "Delete tag", ...DESTRUCTIVE },
|
|
2017
|
-
async (params) => {
|
|
2018
|
-
const backend = backendFor("tags");
|
|
2019
|
-
if (backend.version === "v1")
|
|
2020
|
-
return jsonReply2(v2OnlyNotice("delete-tag"));
|
|
2021
|
-
const result = await callRestUrl(
|
|
2022
|
-
`${backend.base}${backend.get(params.tagId)}`,
|
|
2023
|
-
"DELETE"
|
|
2024
|
-
);
|
|
2025
|
-
return jsonReply2(result);
|
|
2026
|
-
}
|
|
2027
|
-
);
|
|
2028
|
-
server.tool(
|
|
2029
|
-
"attach-tag",
|
|
2030
|
-
"Attach a tag to a resource (pod, network volume, cluster, or serverless endpoint). Idempotent \u2014 attaching an already-associated resource is a no-op. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2031
|
-
{
|
|
2032
|
-
tagId: import_zod9.z.string().describe("ID of the tag"),
|
|
2033
|
-
resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to attach the tag to"),
|
|
2034
|
-
resourceId: import_zod9.z.string().describe("ID of the resource to attach the tag to")
|
|
2035
|
-
},
|
|
2036
|
-
{ title: "Attach tag to resource", ...WRITE, idempotentHint: true },
|
|
2945
|
+
{ title: "Revoke registry delegation", ...DESTRUCTIVE },
|
|
2037
2946
|
async (params) => {
|
|
2038
|
-
const backend = backendFor("
|
|
2039
|
-
if (backend.version === "v1")
|
|
2040
|
-
return jsonReply2(
|
|
2041
|
-
|
|
2042
|
-
`${backend.base}/tags/${encodeURIComponent(params.tagId)}/resources/${params.resourceType}/${encodeURIComponent(params.resourceId)}`,
|
|
2043
|
-
"PUT"
|
|
2044
|
-
);
|
|
2045
|
-
return jsonReply2(result);
|
|
2046
|
-
}
|
|
2047
|
-
);
|
|
2048
|
-
server.tool(
|
|
2049
|
-
"detach-tag",
|
|
2050
|
-
"Detach a tag from a resource (removes the association only \u2014 the resource and tag are not deleted). Idempotent \u2014 detaching a resource that is not associated is a no-op. v2-only \u2014 returns a 501 notice on the v1 API.",
|
|
2051
|
-
{
|
|
2052
|
-
tagId: import_zod9.z.string().describe("ID of the tag"),
|
|
2053
|
-
resourceType: import_zod9.z.enum(TAG_RESOURCE_TYPES).describe("Type of resource to detach the tag from"),
|
|
2054
|
-
resourceId: import_zod9.z.string().describe("ID of the resource to detach the tag from")
|
|
2055
|
-
},
|
|
2056
|
-
{ title: "Detach tag from resource", ...WRITE, idempotentHint: true },
|
|
2057
|
-
async (params) => {
|
|
2058
|
-
const backend = backendFor("tags");
|
|
2059
|
-
if (backend.version === "v1")
|
|
2060
|
-
return jsonReply2(v2OnlyNotice("detach-tag"));
|
|
2947
|
+
const backend = backendFor("registries");
|
|
2948
|
+
if (backend.version === "v1") {
|
|
2949
|
+
return jsonReply2(delegationsV2OnlyNotice("delete-registry-delegation"));
|
|
2950
|
+
}
|
|
2061
2951
|
const result = await callRestUrl(
|
|
2062
|
-
`${backend.base}
|
|
2952
|
+
`${backend.base}${backend.list}${DELEGATIONS}/${encodeURIComponent(
|
|
2953
|
+
params.delegationId
|
|
2954
|
+
)}`,
|
|
2063
2955
|
"DELETE"
|
|
2064
2956
|
);
|
|
2065
2957
|
return jsonReply2(result);
|
|
@@ -2068,7 +2960,7 @@ function registerTagTools(server, rt) {
|
|
|
2068
2960
|
}
|
|
2069
2961
|
|
|
2070
2962
|
// src/tools/billing.ts
|
|
2071
|
-
var
|
|
2963
|
+
var import_zod13 = require("zod");
|
|
2072
2964
|
var BILLING_SCOPE_PATH = {
|
|
2073
2965
|
all: "/billing",
|
|
2074
2966
|
pods: "/billing/pods",
|
|
@@ -2084,7 +2976,7 @@ function registerBillingTools(server, rt) {
|
|
|
2084
2976
|
"Get time-bucketed Runpod spend history, either aggregated across all resources or broken down by resource type (pods, serverless, endpoints, network volumes, clusters). v2-only \u2014 returns a 501 notice on the v1 API. Records are capped via limit/cursor; narrow the window with startTime/endTime or lastN.",
|
|
2085
2977
|
{
|
|
2086
2978
|
...listPaginationParams,
|
|
2087
|
-
scope:
|
|
2979
|
+
scope: import_zod13.z.enum([
|
|
2088
2980
|
"all",
|
|
2089
2981
|
"pods",
|
|
2090
2982
|
"serverless",
|
|
@@ -2094,14 +2986,14 @@ function registerBillingTools(server, rt) {
|
|
|
2094
2986
|
]).optional().describe(
|
|
2095
2987
|
'Which billing breakdown to fetch. "all" (default) is the aggregate across every resource.'
|
|
2096
2988
|
),
|
|
2097
|
-
startTime:
|
|
2989
|
+
startTime: import_zod13.z.string().optional().describe(
|
|
2098
2990
|
"Start of the period (RFC 3339, e.g. 2026-05-01T00:00:00Z). Defaults to 30 days ago. Mutually exclusive with lastN."
|
|
2099
2991
|
),
|
|
2100
|
-
endTime:
|
|
2992
|
+
endTime: import_zod13.z.string().optional().describe(
|
|
2101
2993
|
"End of the period (RFC 3339), exclusive. Defaults to now. Mutually exclusive with lastN."
|
|
2102
2994
|
),
|
|
2103
|
-
bucketSize:
|
|
2104
|
-
lastN:
|
|
2995
|
+
bucketSize: import_zod13.z.enum(["hour", "day", "week", "month", "year"]).optional().describe("Length of each time bucket. Defaults to day."),
|
|
2996
|
+
lastN: import_zod13.z.number().int().positive().optional().describe(
|
|
2105
2997
|
"Return the last N buckets of bucketSize, ending with the current one. Mutually exclusive with startTime/endTime."
|
|
2106
2998
|
)
|
|
2107
2999
|
},
|
|
@@ -2136,13 +3028,15 @@ function registerBillingTools(server, rt) {
|
|
|
2136
3028
|
function registerTools(server, ctx, deps = {}) {
|
|
2137
3029
|
const rt = createToolRuntime(server, ctx, deps);
|
|
2138
3030
|
registerCatalogTools(server, rt);
|
|
3031
|
+
registerHubTools(server, rt);
|
|
3032
|
+
registerPublicEndpointTools(server, rt);
|
|
2139
3033
|
registerPodTools(server, rt);
|
|
2140
3034
|
registerEndpointTools(server, rt);
|
|
3035
|
+
registerEndpointGpuTools(server, rt);
|
|
2141
3036
|
registerJobTools(server, rt);
|
|
2142
3037
|
registerTemplateTools(server, rt);
|
|
2143
3038
|
registerNetworkVolumeTools(server, rt);
|
|
2144
3039
|
registerRegistryTools(server, rt);
|
|
2145
|
-
registerTagTools(server, rt);
|
|
2146
3040
|
registerBillingTools(server, rt);
|
|
2147
3041
|
}
|
|
2148
3042
|
// Annotate the CommonJS export names for ESM import in node:
|