@runpod/mcp-server 4.10.0 → 4.11.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 +32 -0
- package/README.md +2 -11
- package/dist/{chunk-FQJFKWUV.mjs → chunk-24UOECYY.mjs} +19 -1
- package/dist/chunk-24UOECYY.mjs.map +1 -0
- package/dist/{chunk-JTK6RETN.mjs → chunk-4M7QDHK4.mjs} +496 -58
- package/dist/chunk-4M7QDHK4.mjs.map +1 -0
- package/dist/hosted.cjs +510 -56
- package/dist/hosted.cjs.map +1 -1
- package/dist/hosted.mjs +2 -2
- package/dist/http.cjs +510 -56
- package/dist/http.cjs.map +1 -1
- package/dist/http.mjs +2 -2
- package/dist/specgen/profile.cjs +19 -0
- package/dist/specgen/profile.cjs.map +1 -1
- package/dist/specgen/profile.d.cts +6 -3
- package/dist/specgen/profile.d.ts +6 -3
- package/dist/specgen/profile.mjs +3 -1
- package/dist/stdio.cjs +508 -55
- package/dist/stdio.cjs.map +1 -1
- package/dist/stdio.mjs +508 -55
- package/dist/stdio.mjs.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-FQJFKWUV.mjs.map +0 -1
- package/dist/chunk-JTK6RETN.mjs.map +0 -1
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
DIRECTORY_PATH,
|
|
9
9
|
directoryIncludes,
|
|
10
10
|
directoryOmittedInputs,
|
|
11
|
+
omittedInputsIn,
|
|
11
12
|
profileForPath
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-24UOECYY.mjs";
|
|
13
14
|
|
|
14
15
|
// src/http.ts
|
|
15
16
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -266,6 +267,14 @@ function validateArguments(inputSchema, args) {
|
|
|
266
267
|
}
|
|
267
268
|
return null;
|
|
268
269
|
}
|
|
270
|
+
function withoutNullOptionals(inputSchema, args) {
|
|
271
|
+
const required = new Set(inputSchema.required ?? []);
|
|
272
|
+
return Object.fromEntries(
|
|
273
|
+
Object.entries(args).filter(
|
|
274
|
+
([name, value]) => value !== null || required.has(name)
|
|
275
|
+
)
|
|
276
|
+
);
|
|
277
|
+
}
|
|
269
278
|
async function dispatchGeneratedTool(client, tool, args) {
|
|
270
279
|
const rejected = validateArguments(tool.inputSchema, args);
|
|
271
280
|
if (rejected) return rejected;
|
|
@@ -283,25 +292,16 @@ async function dispatchGeneratedTool(client, tool, args) {
|
|
|
283
292
|
const value = args[param.name];
|
|
284
293
|
query[param.name] = param.explode === false && Array.isArray(value) ? value.join(",") : value;
|
|
285
294
|
}
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
if (tool.hasBody && requestBody !== void 0) {
|
|
301
|
-
const properties = tool.inputSchema.properties ?? {};
|
|
302
|
-
const defs = tool.inputSchema.$defs ?? {};
|
|
303
|
-
requestBody = coerceScalars(requestBody, properties.body, defs);
|
|
304
|
-
}
|
|
295
|
+
const sent = tool.hasBody ? bodyAsSent(tool, args.body) : { body: args.body };
|
|
296
|
+
if (!sent)
|
|
297
|
+
return {
|
|
298
|
+
ok: false,
|
|
299
|
+
status: 400,
|
|
300
|
+
payload: {
|
|
301
|
+
error: "The body argument arrived as a string that is not valid JSON. Send body as an object."
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const requestBody = sent.body;
|
|
305
305
|
const verbs = client;
|
|
306
306
|
const { data, error, response } = await verbs[tool.method](tool.path, {
|
|
307
307
|
params: { path: pathParams, query },
|
|
@@ -314,6 +314,32 @@ async function dispatchGeneratedTool(client, tool, args) {
|
|
|
314
314
|
payload: data ?? { status: response.status }
|
|
315
315
|
};
|
|
316
316
|
}
|
|
317
|
+
function bodyAsSent(tool, body) {
|
|
318
|
+
let parsed = body;
|
|
319
|
+
if (typeof parsed === "string") {
|
|
320
|
+
try {
|
|
321
|
+
parsed = JSON.parse(parsed);
|
|
322
|
+
} catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (parsed === void 0) return { body: parsed };
|
|
327
|
+
const properties = tool.inputSchema.properties ?? {};
|
|
328
|
+
const defs = tool.inputSchema.$defs ?? {};
|
|
329
|
+
return { body: coerceScalars(parsed, properties.body, defs) };
|
|
330
|
+
}
|
|
331
|
+
function argsAsSent(tool, args) {
|
|
332
|
+
if (!tool.hasBody || args.body === void 0) return args;
|
|
333
|
+
const sent = bodyAsSent(tool, args.body);
|
|
334
|
+
return sent ? { ...args, body: sent.body } : null;
|
|
335
|
+
}
|
|
336
|
+
function omittedEmptyQueryArg(tool) {
|
|
337
|
+
const required = new Set(tool.inputSchema.required ?? []);
|
|
338
|
+
const query = new Set(
|
|
339
|
+
tool.params.filter((p) => p.location === "query").map((p) => p.name)
|
|
340
|
+
);
|
|
341
|
+
return (name, value) => query.has(name) && !required.has(name) && isEmptyValue(value);
|
|
342
|
+
}
|
|
317
343
|
function isEmptyValue(value) {
|
|
318
344
|
return value === null || value === "" || Array.isArray(value) && !value.length;
|
|
319
345
|
}
|
|
@@ -2750,6 +2776,109 @@ var generatedTools = [
|
|
|
2750
2776
|
}
|
|
2751
2777
|
}
|
|
2752
2778
|
},
|
|
2779
|
+
{
|
|
2780
|
+
name: "exec-pod",
|
|
2781
|
+
title: "Run a command in a Pod",
|
|
2782
|
+
operationId: "execPod",
|
|
2783
|
+
description: `Run one command inside a running Pod's container and get stdout, stderr and the exit code back. No SSH key, public IP or exposed port is needed. command is an argv array run without a shell: pass ["sh", "-c", "..."] for pipes, redirects, globs or &&. One call waits at most timeoutSeconds (1-25, default 20). timedOut true means the call stopped waiting, NOT that the command stopped: it keeps running in the container. For anything longer (model downloads, pip installs, builds), start it in the background and log to a file, e.g. ["sh", "-c", "nohup wget -c -O /workspace/m.safetensors URL > /tmp/dl.log 2>&1 &"], then check progress with later calls (ls -la, du -h, tail /tmp/dl.log). stdout and stderr are each capped at 1 MiB (truncated true). Right after a create, the Pod can answer 404 "Pod not found" for about 10 seconds even with the right id, then 409 while its container starts: retry in about 10 seconds, a few times at most. A Pod that is not running answers 409. A host not yet updated for exec answers 503. Reaches only Pods owned by the API key's own account. Commands can change or delete data in the Pod: run write commands only for what the user asked.`,
|
|
2784
|
+
directoryDescription: "Run one command inside a running Pod's container and return stdout, stderr and the exit code. The command is an argv array run without a shell. One call waits at most timeoutSeconds (1-25, default 20); a command still running then keeps running in the container, and the result says timedOut. stdout and stderr are each capped at 1 MiB. A Pod that is not running answers 409, and a host not yet updated for exec answers 503. Reaches only Pods owned by the caller's own account. The command can change or delete data in the Pod.",
|
|
2785
|
+
method: "POST",
|
|
2786
|
+
path: "/v2/pods/{id}/exec",
|
|
2787
|
+
params: [
|
|
2788
|
+
{
|
|
2789
|
+
name: "id",
|
|
2790
|
+
location: "path"
|
|
2791
|
+
}
|
|
2792
|
+
],
|
|
2793
|
+
hasBody: true,
|
|
2794
|
+
annotations: {
|
|
2795
|
+
readOnlyHint: false,
|
|
2796
|
+
destructiveHint: true,
|
|
2797
|
+
idempotentHint: false,
|
|
2798
|
+
openWorldHint: true
|
|
2799
|
+
},
|
|
2800
|
+
inputSchema: {
|
|
2801
|
+
type: "object",
|
|
2802
|
+
properties: {
|
|
2803
|
+
id: {
|
|
2804
|
+
type: "string",
|
|
2805
|
+
description: "Pod identifier"
|
|
2806
|
+
},
|
|
2807
|
+
body: {
|
|
2808
|
+
$ref: "#/$defs/PodExecRequest",
|
|
2809
|
+
type: "object"
|
|
2810
|
+
}
|
|
2811
|
+
},
|
|
2812
|
+
required: ["id", "body"],
|
|
2813
|
+
$defs: {
|
|
2814
|
+
PodExecRequest: {
|
|
2815
|
+
type: "object",
|
|
2816
|
+
additionalProperties: false,
|
|
2817
|
+
required: ["command"],
|
|
2818
|
+
properties: {
|
|
2819
|
+
command: {
|
|
2820
|
+
type: "array",
|
|
2821
|
+
minItems: 1,
|
|
2822
|
+
maxItems: 256,
|
|
2823
|
+
prefixItems: [
|
|
2824
|
+
{
|
|
2825
|
+
type: "string",
|
|
2826
|
+
minLength: 1,
|
|
2827
|
+
maxLength: 4096,
|
|
2828
|
+
pattern: "^[^\\x00]*[^\\t-\\r \x85\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\\x00][^\\x00]*$"
|
|
2829
|
+
}
|
|
2830
|
+
],
|
|
2831
|
+
items: {
|
|
2832
|
+
type: "string",
|
|
2833
|
+
maxLength: 4096,
|
|
2834
|
+
pattern: "^[^\\x00]*$"
|
|
2835
|
+
},
|
|
2836
|
+
description: 'Command to run, as an argv array of 1-256 arguments of at most 4096 characters each. It is run directly, never through a shell; for pipes, redirects or globbing pass `["sh", "-c", "..."]`.',
|
|
2837
|
+
examples: [
|
|
2838
|
+
["nvidia-smi", "--query-gpu=name,memory.used", "--format=csv"]
|
|
2839
|
+
]
|
|
2840
|
+
},
|
|
2841
|
+
timeoutSeconds: {
|
|
2842
|
+
type: "integer",
|
|
2843
|
+
minimum: 1,
|
|
2844
|
+
maximum: 25,
|
|
2845
|
+
default: 20,
|
|
2846
|
+
description: "How long the call waits for the command to finish, in seconds. A command still running at this point returns with `timedOut: true`.",
|
|
2847
|
+
examples: [10]
|
|
2848
|
+
},
|
|
2849
|
+
cwd: {
|
|
2850
|
+
type: "string",
|
|
2851
|
+
pattern: "^/[^\\x00-\\x1f\\x7f]*$",
|
|
2852
|
+
maxLength: 1024,
|
|
2853
|
+
description: "Absolute working directory for the command, at most 1024 characters and without control characters. Defaults to the image's `WORKDIR`.",
|
|
2854
|
+
examples: ["/workspace"]
|
|
2855
|
+
},
|
|
2856
|
+
env: {
|
|
2857
|
+
type: "object",
|
|
2858
|
+
maxProperties: 64,
|
|
2859
|
+
propertyNames: {
|
|
2860
|
+
type: "string",
|
|
2861
|
+
minLength: 1,
|
|
2862
|
+
maxLength: 256,
|
|
2863
|
+
pattern: "^[^=\\x00]+$"
|
|
2864
|
+
},
|
|
2865
|
+
additionalProperties: {
|
|
2866
|
+
type: "string",
|
|
2867
|
+
maxLength: 16384,
|
|
2868
|
+
pattern: "^[^\\x00]*$"
|
|
2869
|
+
},
|
|
2870
|
+
description: "Up to 64 environment variables added to the container's environment for this command only. A name is 1-256 characters and must not contain `=` or a NUL character; a value is at most 16384 characters and must not contain a NUL character.",
|
|
2871
|
+
examples: [
|
|
2872
|
+
{
|
|
2873
|
+
HF_HOME: "/workspace/hf"
|
|
2874
|
+
}
|
|
2875
|
+
]
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
},
|
|
2753
2882
|
{
|
|
2754
2883
|
name: "get-cluster",
|
|
2755
2884
|
title: "Get cluster",
|
|
@@ -6812,6 +6941,122 @@ var generatedTools = [
|
|
|
6812
6941
|
}
|
|
6813
6942
|
];
|
|
6814
6943
|
|
|
6944
|
+
// src/specgen/validate.ts
|
|
6945
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
6946
|
+
function schemaValidationMode(value = process.env.MCP_SCHEMA_VALIDATION) {
|
|
6947
|
+
const mode = value?.trim().toLowerCase();
|
|
6948
|
+
return mode === "off" || mode === "enforce" ? mode : "shadow";
|
|
6949
|
+
}
|
|
6950
|
+
var ajv = new Ajv2020({
|
|
6951
|
+
strict: false,
|
|
6952
|
+
allErrors: true,
|
|
6953
|
+
validateFormats: false
|
|
6954
|
+
});
|
|
6955
|
+
var compiled = /* @__PURE__ */ new WeakMap();
|
|
6956
|
+
function validatorFor(schema) {
|
|
6957
|
+
let validate = compiled.get(schema);
|
|
6958
|
+
if (!validate) {
|
|
6959
|
+
validate = ajv.compile(structuredClone(schema));
|
|
6960
|
+
compiled.set(schema, validate);
|
|
6961
|
+
}
|
|
6962
|
+
return validate;
|
|
6963
|
+
}
|
|
6964
|
+
var MAX_NULL_PASSES = 8;
|
|
6965
|
+
function isNullTypeError(error, data) {
|
|
6966
|
+
return error.keyword === "type" && error.instancePath !== "" && valueAt(data, error.instancePath) === null;
|
|
6967
|
+
}
|
|
6968
|
+
function pointerSegments(pointer) {
|
|
6969
|
+
return pointer.split("/").slice(1).map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
6970
|
+
}
|
|
6971
|
+
function valueAt(data, pointer) {
|
|
6972
|
+
let node = data;
|
|
6973
|
+
for (const key of pointerSegments(pointer)) {
|
|
6974
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
6975
|
+
node = node[key];
|
|
6976
|
+
}
|
|
6977
|
+
return node;
|
|
6978
|
+
}
|
|
6979
|
+
function removeAt(data, pointer) {
|
|
6980
|
+
const segments = pointerSegments(pointer);
|
|
6981
|
+
const last = segments.pop();
|
|
6982
|
+
const parent = valueAt(data, segments.length ? `/${segments.join("/")}` : "");
|
|
6983
|
+
if (last === void 0 || parent === null || typeof parent !== "object")
|
|
6984
|
+
return false;
|
|
6985
|
+
if (Array.isArray(parent)) return false;
|
|
6986
|
+
delete parent[last];
|
|
6987
|
+
return true;
|
|
6988
|
+
}
|
|
6989
|
+
var WRAPPER_KEYWORDS = /* @__PURE__ */ new Set(["if"]);
|
|
6990
|
+
function toViolation(error) {
|
|
6991
|
+
const base = pointerSegments(error.instancePath);
|
|
6992
|
+
let path = base.join(".");
|
|
6993
|
+
let message = error.message ?? "is invalid";
|
|
6994
|
+
const params = error.params;
|
|
6995
|
+
if (error.keyword === "required" && typeof params.missingProperty === "string") {
|
|
6996
|
+
path = [...base, params.missingProperty].join(".");
|
|
6997
|
+
message = "is required";
|
|
6998
|
+
} else if (error.keyword === "additionalProperties" && typeof params.additionalProperty === "string") {
|
|
6999
|
+
path = [...base, params.additionalProperty].join(".");
|
|
7000
|
+
message = "is not an accepted argument here";
|
|
7001
|
+
} else if (error.keyword === "pattern") {
|
|
7002
|
+
message = "is not in an accepted format";
|
|
7003
|
+
}
|
|
7004
|
+
const allowed = error.keyword === "enum" ? params.allowedValues : error.keyword === "const" ? [params.allowedValue] : void 0;
|
|
7005
|
+
return {
|
|
7006
|
+
path,
|
|
7007
|
+
keyword: error.keyword,
|
|
7008
|
+
message,
|
|
7009
|
+
...allowed ? { allowed } : {},
|
|
7010
|
+
schemaPath: error.schemaPath
|
|
7011
|
+
};
|
|
7012
|
+
}
|
|
7013
|
+
function schemaViolations(schema, args, omitted = () => false) {
|
|
7014
|
+
const validate = validatorFor(schema);
|
|
7015
|
+
const data = structuredClone(
|
|
7016
|
+
Object.fromEntries(
|
|
7017
|
+
Object.entries(args).filter(([name, value]) => !omitted(name, value))
|
|
7018
|
+
)
|
|
7019
|
+
);
|
|
7020
|
+
for (let pass = 0; ; pass++) {
|
|
7021
|
+
if (validate(data)) return [];
|
|
7022
|
+
const errors = validate.errors ?? [];
|
|
7023
|
+
let removed = false;
|
|
7024
|
+
if (pass < MAX_NULL_PASSES) {
|
|
7025
|
+
for (const error of errors.filter((e) => isNullTypeError(e, data)))
|
|
7026
|
+
removed = removeAt(data, error.instancePath) || removed;
|
|
7027
|
+
}
|
|
7028
|
+
if (!removed) {
|
|
7029
|
+
return errors.filter((e) => !WRAPPER_KEYWORDS.has(e.keyword)).map(toViolation);
|
|
7030
|
+
}
|
|
7031
|
+
}
|
|
7032
|
+
}
|
|
7033
|
+
var INVALID_ARGUMENTS_HINT = "Fix the listed fields and call again.";
|
|
7034
|
+
function invalidArgumentsPayload(violations) {
|
|
7035
|
+
return {
|
|
7036
|
+
error: "Invalid arguments",
|
|
7037
|
+
code: "invalid_arguments",
|
|
7038
|
+
fields: violations.map(({ path, message, allowed }) => ({
|
|
7039
|
+
path,
|
|
7040
|
+
problem: message,
|
|
7041
|
+
...allowed ? { allowed } : {}
|
|
7042
|
+
})),
|
|
7043
|
+
hint: INVALID_ARGUMENTS_HINT
|
|
7044
|
+
};
|
|
7045
|
+
}
|
|
7046
|
+
function logSchemaViolations(tool, mode, violations) {
|
|
7047
|
+
console.error(
|
|
7048
|
+
"schema_violation",
|
|
7049
|
+
JSON.stringify({
|
|
7050
|
+
tool,
|
|
7051
|
+
mode,
|
|
7052
|
+
violations: violations.map(({ keyword, schemaPath }) => ({
|
|
7053
|
+
keyword,
|
|
7054
|
+
schemaPath
|
|
7055
|
+
}))
|
|
7056
|
+
})
|
|
7057
|
+
);
|
|
7058
|
+
}
|
|
7059
|
+
|
|
6815
7060
|
// src/specgen/tools/annotations.ts
|
|
6816
7061
|
var readOnly = {
|
|
6817
7062
|
readOnlyHint: true,
|
|
@@ -9761,23 +10006,40 @@ function createRunpodClient(options = {}) {
|
|
|
9761
10006
|
// src/specgen/clients/sse.ts
|
|
9762
10007
|
var LOG_STREAM_DEFAULT_WAIT_MS = 5e3;
|
|
9763
10008
|
var LOG_STREAM_MAX_BYTES = 256 * 1024;
|
|
10009
|
+
var LOG_STREAM_CONNECT_TIMEOUT_MS = 1e4;
|
|
10010
|
+
var LOG_SNAPSHOT_MAX_CHARS = 32e3;
|
|
9764
10011
|
function createSseReader(options = {}) {
|
|
9765
10012
|
const apiKey = options.apiKey ?? process.env.RUNPOD_API_KEY;
|
|
9766
10013
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
10014
|
+
const connectTimeoutMs = options.connectTimeoutMs ?? LOG_STREAM_CONNECT_TIMEOUT_MS;
|
|
9767
10015
|
return async (url, opts) => {
|
|
9768
10016
|
if (!apiKey) throw missingKeyError();
|
|
9769
10017
|
const chunks = [];
|
|
9770
10018
|
let bytes = 0;
|
|
9771
10019
|
let truncated = false;
|
|
9772
10020
|
let streamEstablished = false;
|
|
10021
|
+
const connect = new AbortController();
|
|
10022
|
+
const connectTimer = setTimeout(
|
|
10023
|
+
() => connect.abort(
|
|
10024
|
+
new DOMException(
|
|
10025
|
+
`Runpod log stream sent no headers within ${connectTimeoutMs}ms`,
|
|
10026
|
+
"TimeoutError"
|
|
10027
|
+
)
|
|
10028
|
+
),
|
|
10029
|
+
connectTimeoutMs
|
|
10030
|
+
);
|
|
9773
10031
|
try {
|
|
9774
|
-
const response = await createDeadlineFetch(
|
|
10032
|
+
const response = await createDeadlineFetch(
|
|
10033
|
+
fetchImpl,
|
|
10034
|
+
connectTimeoutMs + opts.maxWaitMs
|
|
10035
|
+
)(url, {
|
|
9775
10036
|
method: "GET",
|
|
9776
10037
|
headers: {
|
|
9777
10038
|
Authorization: `Bearer ${apiKey}`,
|
|
9778
10039
|
Accept: "text/event-stream"
|
|
9779
|
-
}
|
|
9780
|
-
|
|
10040
|
+
},
|
|
10041
|
+
signal: connect.signal
|
|
10042
|
+
}).finally(() => clearTimeout(connectTimer));
|
|
9781
10043
|
if (!response.ok) {
|
|
9782
10044
|
const body = await response.text().catch(() => "");
|
|
9783
10045
|
throw new HttpError(
|
|
@@ -9788,14 +10050,31 @@ function createSseReader(options = {}) {
|
|
|
9788
10050
|
}
|
|
9789
10051
|
streamEstablished = true;
|
|
9790
10052
|
if (response.body) {
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
10053
|
+
const reader = response.body.getReader();
|
|
10054
|
+
let timer;
|
|
10055
|
+
const windowClosed = new Promise((resolve) => {
|
|
10056
|
+
timer = setTimeout(() => resolve(null), opts.maxWaitMs);
|
|
10057
|
+
});
|
|
10058
|
+
try {
|
|
10059
|
+
for (; ; ) {
|
|
10060
|
+
const read = reader.read();
|
|
10061
|
+
read.catch(() => {
|
|
10062
|
+
});
|
|
10063
|
+
const next = await Promise.race([read, windowClosed]);
|
|
10064
|
+
if (next === null || next.done) break;
|
|
10065
|
+
const boundedChunk = next.value.subarray(0, opts.maxBytes - bytes);
|
|
10066
|
+
chunks.push(boundedChunk);
|
|
10067
|
+
bytes += boundedChunk.length;
|
|
10068
|
+
if (bytes >= opts.maxBytes) {
|
|
10069
|
+
truncated = true;
|
|
10070
|
+
break;
|
|
10071
|
+
}
|
|
9798
10072
|
}
|
|
10073
|
+
} finally {
|
|
10074
|
+
clearTimeout(timer);
|
|
10075
|
+
void reader.cancel().catch(() => {
|
|
10076
|
+
});
|
|
10077
|
+
reader.releaseLock();
|
|
9799
10078
|
}
|
|
9800
10079
|
}
|
|
9801
10080
|
} catch (err) {
|
|
@@ -9821,7 +10100,80 @@ async function collectLogSnapshot(reader, logsUrl, params) {
|
|
|
9821
10100
|
for await (const event of iterateLogEvents(new Response(raw).body)) {
|
|
9822
10101
|
items.push(event.data);
|
|
9823
10102
|
}
|
|
9824
|
-
return
|
|
10103
|
+
return fitSnapshot(items, truncated);
|
|
10104
|
+
}
|
|
10105
|
+
var serializedLength = (snapshot) => JSON.stringify(snapshot, null, 2).length;
|
|
10106
|
+
var LOG_ENTRY_MAX_CHARS = 16e3;
|
|
10107
|
+
var CLIPPABLE_KEYS = ["line", "raw"];
|
|
10108
|
+
function clipText(text, keep) {
|
|
10109
|
+
const code = text.charCodeAt(keep - 1);
|
|
10110
|
+
if (keep > 0 && code >= 55296 && code <= 56319) keep--;
|
|
10111
|
+
return `${text.slice(0, keep)}\u2026 [clipped ${text.length - keep} chars]`;
|
|
10112
|
+
}
|
|
10113
|
+
function clipEntry(entry) {
|
|
10114
|
+
let clipped = false;
|
|
10115
|
+
const out = { ...entry };
|
|
10116
|
+
for (const key of CLIPPABLE_KEYS) {
|
|
10117
|
+
const text = out[key];
|
|
10118
|
+
if (typeof text === "string" && text.length > LOG_ENTRY_MAX_CHARS) {
|
|
10119
|
+
out[key] = clipText(text, LOG_ENTRY_MAX_CHARS);
|
|
10120
|
+
clipped = true;
|
|
10121
|
+
}
|
|
10122
|
+
}
|
|
10123
|
+
return { entry: out, clipped };
|
|
10124
|
+
}
|
|
10125
|
+
function fitEntry(entry, maxChars) {
|
|
10126
|
+
const size = (e) => serializedLength({ items: [e], count: 1, truncated: false });
|
|
10127
|
+
const out = { ...entry };
|
|
10128
|
+
const keys = Object.keys(out).filter((key) => key !== "ts" && typeof out[key] === "string").sort((a, b) => out[b].length - out[a].length);
|
|
10129
|
+
for (const key of keys) {
|
|
10130
|
+
if (size(out) <= maxChars) break;
|
|
10131
|
+
const text = entry[key];
|
|
10132
|
+
let lo = 0;
|
|
10133
|
+
let hi = Math.min(text.length, LOG_ENTRY_MAX_CHARS);
|
|
10134
|
+
while (lo < hi) {
|
|
10135
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
10136
|
+
if (size({ ...out, [key]: clipText(text, mid) }) <= maxChars)
|
|
10137
|
+
lo = mid;
|
|
10138
|
+
else hi = mid - 1;
|
|
10139
|
+
}
|
|
10140
|
+
out[key] = clipText(text, lo);
|
|
10141
|
+
}
|
|
10142
|
+
return out;
|
|
10143
|
+
}
|
|
10144
|
+
var entryTs = (entry) => entry?.ts;
|
|
10145
|
+
function fitSnapshot(items, truncated, maxChars = LOG_SNAPSHOT_MAX_CHARS) {
|
|
10146
|
+
let cut = truncated;
|
|
10147
|
+
const entries = items.map((item) => {
|
|
10148
|
+
const { entry, clipped } = clipEntry(item);
|
|
10149
|
+
if (clipped) cut = true;
|
|
10150
|
+
return entry;
|
|
10151
|
+
});
|
|
10152
|
+
const snapshot = (n, isCut) => ({
|
|
10153
|
+
items: entries.slice(0, n),
|
|
10154
|
+
count: n,
|
|
10155
|
+
truncated: isCut
|
|
10156
|
+
});
|
|
10157
|
+
const whole = snapshot(entries.length, cut);
|
|
10158
|
+
if (entries.length === 0 || serializedLength(whole) <= maxChars) return whole;
|
|
10159
|
+
if (serializedLength(snapshot(1, true)) > maxChars) {
|
|
10160
|
+
entries[0] = fitEntry(items[0], maxChars);
|
|
10161
|
+
if (entries.length === 1) return snapshot(1, true);
|
|
10162
|
+
}
|
|
10163
|
+
let lo = 1;
|
|
10164
|
+
let hi = entries.length - 1;
|
|
10165
|
+
while (lo < hi) {
|
|
10166
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
10167
|
+
if (serializedLength(snapshot(mid, true)) <= maxChars) lo = mid;
|
|
10168
|
+
else hi = mid - 1;
|
|
10169
|
+
}
|
|
10170
|
+
let keep = lo;
|
|
10171
|
+
const omittedTs = entryTs(entries[keep]);
|
|
10172
|
+
if (omittedTs !== void 0) {
|
|
10173
|
+
while (keep > 0 && entryTs(entries[keep - 1]) === omittedTs) keep--;
|
|
10174
|
+
if (keep === 0) keep = lo;
|
|
10175
|
+
}
|
|
10176
|
+
return snapshot(keep, true);
|
|
9825
10177
|
}
|
|
9826
10178
|
|
|
9827
10179
|
// src/specgen/tools/logs.ts
|
|
@@ -9839,13 +10191,13 @@ var logStreamProperties = {
|
|
|
9839
10191
|
},
|
|
9840
10192
|
since: {
|
|
9841
10193
|
type: "string",
|
|
9842
|
-
description: "RFC3339 timestamp to resume
|
|
10194
|
+
description: "RFC3339 timestamp to resume after (exclusive). Replays container lines only; system lines stream live from the call. When set, `tail` is ignored."
|
|
9843
10195
|
},
|
|
9844
10196
|
maxWaitMs: {
|
|
9845
10197
|
type: "integer",
|
|
9846
10198
|
minimum: 500,
|
|
9847
10199
|
maximum: 3e4,
|
|
9848
|
-
description: "How long to read the stream, in ms (default 5000, max 30000)"
|
|
10200
|
+
description: "How long to read the stream once it opens, in ms (default 5000, max 30000)"
|
|
9849
10201
|
}
|
|
9850
10202
|
};
|
|
9851
10203
|
function baseUrl() {
|
|
@@ -9871,7 +10223,7 @@ var streamPodLogs = {
|
|
|
9871
10223
|
title: "Stream Pod logs",
|
|
9872
10224
|
holdsResponse: true,
|
|
9873
10225
|
annotations: readOnly,
|
|
9874
|
-
description: "Read a bounded snapshot of a Pod's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the
|
|
10226
|
+
description: "Read a bounded snapshot of a Pod's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the size cap cut the output. For later container lines, call again with `since` set to the last container line's `ts`; `since` does not replay system lines, so for those request a smaller `tail`.",
|
|
9875
10227
|
inputSchema: {
|
|
9876
10228
|
type: "object",
|
|
9877
10229
|
properties: {
|
|
@@ -9893,10 +10245,10 @@ var streamPodLogs = {
|
|
|
9893
10245
|
var streamWorkerLogs = {
|
|
9894
10246
|
name: "stream-worker-logs",
|
|
9895
10247
|
title: "Stream worker logs",
|
|
9896
|
-
directoryDescription: "Read a bounded snapshot of a Serverless worker's logs (container, system, or both). Holds the live stream open for maxWaitMs and returns the parsed log lines; truncated: true means the
|
|
10248
|
+
directoryDescription: "Read a bounded snapshot of a Serverless worker's logs (container, system, or both). Holds the live stream open for maxWaitMs and returns the parsed log lines; truncated: true means the size cap cut the output. A worker whose container keeps failing to start can report RUNNING or THROTTLED while its logs show the failure.",
|
|
9897
10249
|
holdsResponse: true,
|
|
9898
10250
|
annotations: readOnly,
|
|
9899
|
-
description: "Read a bounded snapshot of a Serverless worker's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the
|
|
10251
|
+
description: "Read a bounded snapshot of a Serverless worker's logs (container and/or system source). Holds the live stream open for maxWaitMs and returns the parsed log lines; `truncated: true` means the size cap cut the output. For later container lines, call again with `since` set to the last container line's `ts`; `since` does not replay system lines, so for those request a smaller `tail`. Get worker IDs from list-endpoint-workers. Use this whenever a job stays IN_QUEUE while workers exist, whatever status they report \u2014 a crash-looping container often still shows RUNNING or THROTTLED with unhealthy 0, so these logs, not the worker status, are what identify a crash loop.",
|
|
9900
10252
|
inputSchema: {
|
|
9901
10253
|
type: "object",
|
|
9902
10254
|
properties: {
|
|
@@ -10042,6 +10394,7 @@ import {
|
|
|
10042
10394
|
loadKnowledge
|
|
10043
10395
|
} from "@runpod/plugin-knowledge";
|
|
10044
10396
|
var knowledge = loadKnowledge();
|
|
10397
|
+
var resolveGuideId = (id) => knowledge.aliases?.[id] ?? id;
|
|
10045
10398
|
var hiddenRules = new Set(
|
|
10046
10399
|
(process.env.KNOWLEDGE_HIDDEN_RULES ?? "").split(",").map((id) => id.trim()).filter(Boolean)
|
|
10047
10400
|
);
|
|
@@ -10218,7 +10571,9 @@ var readGuide = {
|
|
|
10218
10571
|
title: "Read guide",
|
|
10219
10572
|
annotations: bundledRead,
|
|
10220
10573
|
recordedArguments: ["id"],
|
|
10221
|
-
recordedGuideKind: (args) => knowledge.guides.find(
|
|
10574
|
+
recordedGuideKind: (args) => knowledge.guides.find(
|
|
10575
|
+
(guide) => guide.id === resolveGuideId(String(args.id).trim())
|
|
10576
|
+
)?.kind,
|
|
10222
10577
|
description: 'Read one official Runpod guide in full, by the id list-guides returns (for example "runpod-usage/storage" or "golden-path/06-dev-pod").',
|
|
10223
10578
|
inputSchema: {
|
|
10224
10579
|
type: "object",
|
|
@@ -10230,7 +10585,9 @@ var readGuide = {
|
|
|
10230
10585
|
},
|
|
10231
10586
|
handler: async (_ctx, args) => {
|
|
10232
10587
|
const id = String(args.id).trim();
|
|
10233
|
-
const guide = knowledge.guides.find(
|
|
10588
|
+
const guide = knowledge.guides.find(
|
|
10589
|
+
(candidate) => candidate.id === resolveGuideId(id)
|
|
10590
|
+
);
|
|
10234
10591
|
if (!guide) {
|
|
10235
10592
|
const close = knowledge.guides.filter(
|
|
10236
10593
|
(candidate) => candidate.id.includes(id) || id.includes(candidate.id)
|
|
@@ -10385,6 +10742,12 @@ var guideResources = knowledge.guides.map((guide) => ({
|
|
|
10385
10742
|
mimeType: "text/markdown",
|
|
10386
10743
|
text: guide.body
|
|
10387
10744
|
}));
|
|
10745
|
+
var findGuideResource = (resources, uri) => {
|
|
10746
|
+
const id = uri.startsWith(GUIDE_URI_PREFIX) ? resolveGuideId(uri.slice(GUIDE_URI_PREFIX.length)) : void 0;
|
|
10747
|
+
return resources.find(
|
|
10748
|
+
(resource) => resource.uri === uri || resource.uri === GUIDE_URI_PREFIX + id
|
|
10749
|
+
);
|
|
10750
|
+
};
|
|
10388
10751
|
|
|
10389
10752
|
// src/specgen/prose.ts
|
|
10390
10753
|
var ABBREVIATIONS = ["e.g.", "i.e.", "etc.", "vs."];
|
|
@@ -10460,13 +10823,40 @@ function withoutCredentialExamples(schema) {
|
|
|
10460
10823
|
};
|
|
10461
10824
|
return strip(schema);
|
|
10462
10825
|
}
|
|
10463
|
-
function
|
|
10464
|
-
if (!omit.length) return schema;
|
|
10826
|
+
function withoutKeys(schema, omit) {
|
|
10465
10827
|
const properties = { ...schema.properties ?? {} };
|
|
10466
10828
|
for (const key of omit) delete properties[key];
|
|
10467
10829
|
const required = Array.isArray(schema.required) ? schema.required.filter((key) => !omit.includes(key)) : schema.required;
|
|
10468
10830
|
return { ...schema, properties, ...required ? { required } : {} };
|
|
10469
10831
|
}
|
|
10832
|
+
function withoutInputs(schema, omit) {
|
|
10833
|
+
if (!omit.length) return schema;
|
|
10834
|
+
let out = withoutKeys(
|
|
10835
|
+
schema,
|
|
10836
|
+
omit.filter((key) => !key.includes("."))
|
|
10837
|
+
);
|
|
10838
|
+
for (const key of omit.filter((k) => k.includes("."))) {
|
|
10839
|
+
const [parent, child] = key.split(".");
|
|
10840
|
+
const properties = out.properties ?? {};
|
|
10841
|
+
const node = properties[parent];
|
|
10842
|
+
if (!node) continue;
|
|
10843
|
+
const ref = typeof node.$ref === "string" ? node.$ref : void 0;
|
|
10844
|
+
const defName = ref?.startsWith("#/$defs/") ? ref.slice(8) : void 0;
|
|
10845
|
+
const defs = out.$defs ?? {};
|
|
10846
|
+
if (defName && defs[defName]) {
|
|
10847
|
+
out = {
|
|
10848
|
+
...out,
|
|
10849
|
+
$defs: { ...defs, [defName]: withoutKeys(defs[defName], [child]) }
|
|
10850
|
+
};
|
|
10851
|
+
} else {
|
|
10852
|
+
out = {
|
|
10853
|
+
...out,
|
|
10854
|
+
properties: { ...properties, [parent]: withoutKeys(node, [child]) }
|
|
10855
|
+
};
|
|
10856
|
+
}
|
|
10857
|
+
}
|
|
10858
|
+
return out;
|
|
10859
|
+
}
|
|
10470
10860
|
function callShape(schema) {
|
|
10471
10861
|
const defs = schema.$defs ?? {};
|
|
10472
10862
|
const resolve = (node) => {
|
|
@@ -10568,7 +10958,7 @@ function words2(name) {
|
|
|
10568
10958
|
|
|
10569
10959
|
// src/server.ts
|
|
10570
10960
|
var SERVER_NAME = "Runpod API Server";
|
|
10571
|
-
var SERVER_VERSION = true ? "4.
|
|
10961
|
+
var SERVER_VERSION = true ? "4.11.0" : "dev";
|
|
10572
10962
|
|
|
10573
10963
|
// src/specgen/analytics.ts
|
|
10574
10964
|
import { createHmac } from "crypto";
|
|
@@ -10636,6 +11026,7 @@ function captureToolCall(apiKey, event) {
|
|
|
10636
11026
|
server_version: event.serverVersion,
|
|
10637
11027
|
...event.clientName ? { client_name: event.clientName } : {},
|
|
10638
11028
|
...event.guideKind ? { guide_kind: event.guideKind } : {},
|
|
11029
|
+
...event.errorClass ? { error_class: event.errorClass } : {},
|
|
10639
11030
|
...event.blockedBy ? { blocked_by: event.blockedBy } : {},
|
|
10640
11031
|
...event.profile ? { profile: event.profile } : {},
|
|
10641
11032
|
...args
|
|
@@ -11259,7 +11650,7 @@ These tools manage infrastructure only. They do not do SSH sessions, file transf
|
|
|
11259
11650
|
|
|
11260
11651
|
OFFICIAL GUIDES AND FACTS. list-guides and read-guide serve the official Runpod plugin's skills, reference docs and live-verified golden paths, so you have them even without the plugin installed. For a multi-step or billable task, look for a matching golden path first. lookup-concept and search-concepts answer exact questions about how Runpod works (what a stopped pod keeps, where a volume lives) with the public source for each fact.
|
|
11261
11652
|
|
|
11262
|
-
PLAYBOOKS \u2014 READ BEFORE ACTING. Each Runpod task has a journey playbook carrying its procedure, pitfalls and report format. Before the FIRST Runpod tool call of a session, load the plugin's runpod-mcp skill if you have it, otherwise read-guide runpod-mcp (also the runpod://guides/runpod-mcp resource): it routes the request to a journey guide \u2014 deploying an endpoint reads serverless-deploy, diagnosing a broken pod reads pod-doctor, cost questions read cost-audit, and so on \u2014 and states the answer contract. A reply produced without the routed playbook loaded is out of contract.
|
|
11653
|
+
PLAYBOOKS \u2014 READ BEFORE ACTING. Each Runpod task has a journey playbook carrying its procedure, pitfalls and report format. Before the FIRST Runpod tool call of a session, load the plugin's runpod-mcp skill if you have it, otherwise read-guide runpod-mcp (also the runpod://guides/runpod-mcp resource): it routes the request to a journey guide \u2014 deploying an endpoint reads runpod-serverless-deploy, diagnosing a broken pod reads runpod-pod-doctor, cost questions read runpod-cost-audit, and so on \u2014 and states the answer contract. A reply produced without the routed playbook loaded is out of contract.
|
|
11263
11654
|
|
|
11264
11655
|
THE OFFICIAL RUNPOD PLUGIN. Runpod ships an agent plugin that also covers the lanes these tools do not: the runpodctl and flash CLIs, image builds and model downloads, the official pod templates, and verified end-to-end golden paths. Check once per session whether skills named runpod, runpod-usage, or runpodctl are available to you \u2014 read what you already have, never shell out to look. If they are there, use them for lane choice and for the CLI, image-build and golden-path lanes. For work done with these tools, the journey playbook is the procedure, wherever it is served from: the plugin's runpod-mcp skill and the journey skills it routes to when you have them, otherwise the same playbooks through read-guide here. If you do not see them, recommend the plugin to the user, and recommend updating to the latest if what they have is missing something you need. Point them at https://github.com/runpod/runpod-plugins-official \u2014 its README carries the install, update, and clone commands for each client, so read it yourself when you need the exact one rather than guessing a command. This is a recommendation, never a prerequisite: make it once, briefly, and get on with the task. Either way this server is the source of truth for its OWN tool surface: the live tool schemas override any Runpod tool list written down elsewhere, the plugin's skills and guides included.
|
|
11265
11656
|
|
|
@@ -11278,6 +11669,7 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
|
|
|
11278
11669
|
const caller = callerId(ctx.apiKey);
|
|
11279
11670
|
const profile = opts.profile ?? "default";
|
|
11280
11671
|
const directory = profile === "directory";
|
|
11672
|
+
const schemaMode = schemaValidationMode();
|
|
11281
11673
|
const alp = directory ? void 0 : opts.alp;
|
|
11282
11674
|
const servedGeneratedTools = directory ? generatedTools.filter((tool) => directoryIncludes(tool.name)) : generatedTools;
|
|
11283
11675
|
const servedCuratedTools = [
|
|
@@ -11320,9 +11712,7 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
|
|
|
11320
11712
|
resourceTemplates: []
|
|
11321
11713
|
}));
|
|
11322
11714
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
11323
|
-
const guide = servedGuides.
|
|
11324
|
-
(candidate) => candidate.uri === request.params.uri
|
|
11325
|
-
);
|
|
11715
|
+
const guide = findGuideResource(servedGuides, request.params.uri);
|
|
11326
11716
|
if (opts.analytics) {
|
|
11327
11717
|
captureResourceCall(ctx.apiKey, {
|
|
11328
11718
|
method: "resources/read",
|
|
@@ -11359,6 +11749,7 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
|
|
|
11359
11749
|
ok: false,
|
|
11360
11750
|
status: 429,
|
|
11361
11751
|
durationMs: 0,
|
|
11752
|
+
errorClass: "rate_limited",
|
|
11362
11753
|
...verdict.reason ? { blockedBy: verdict.reason } : {},
|
|
11363
11754
|
profile,
|
|
11364
11755
|
...opts.analytics
|
|
@@ -11384,7 +11775,8 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
|
|
|
11384
11775
|
const generated = servedGeneratedTools.find(
|
|
11385
11776
|
(tool) => tool.name === request.params.name
|
|
11386
11777
|
);
|
|
11387
|
-
const
|
|
11778
|
+
const curatedArgs = curated ? withoutNullOptionals(curated.inputSchema, args) : args;
|
|
11779
|
+
const omitted = directory ? omittedInputsIn(directoryOmittedInputs(request.params.name), args) : [];
|
|
11388
11780
|
const rejected = omitted.length ? {
|
|
11389
11781
|
ok: false,
|
|
11390
11782
|
status: 400,
|
|
@@ -11393,10 +11785,28 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
|
|
|
11393
11785
|
}
|
|
11394
11786
|
} : curated && !curated.lenientArguments ? validateArguments(
|
|
11395
11787
|
curated.inputSchema,
|
|
11396
|
-
|
|
11397
|
-
) : null;
|
|
11398
|
-
|
|
11399
|
-
|
|
11788
|
+
curatedArgs
|
|
11789
|
+
) : generated ? validateArguments(generated.inputSchema, args) : null;
|
|
11790
|
+
let schemaRejected = null;
|
|
11791
|
+
if (!rejected && schemaMode !== "off") {
|
|
11792
|
+
const sent = generated ? argsAsSent(generated, args) : null;
|
|
11793
|
+
const violations = curated ? curated.lenientArguments ? [] : schemaViolations(curated.inputSchema, curatedArgs) : generated && sent ? schemaViolations(
|
|
11794
|
+
generated.inputSchema,
|
|
11795
|
+
sent,
|
|
11796
|
+
omittedEmptyQueryArg(generated)
|
|
11797
|
+
) : [];
|
|
11798
|
+
if (violations.length) {
|
|
11799
|
+
logSchemaViolations(request.params.name, schemaMode, violations);
|
|
11800
|
+
if (schemaMode === "enforce")
|
|
11801
|
+
schemaRejected = {
|
|
11802
|
+
ok: false,
|
|
11803
|
+
status: 400,
|
|
11804
|
+
payload: invalidArgumentsPayload(violations)
|
|
11805
|
+
};
|
|
11806
|
+
}
|
|
11807
|
+
}
|
|
11808
|
+
const result = rejected ?? schemaRejected ?? await runTool(
|
|
11809
|
+
() => curated ? curated.handler(ctx, curatedArgs) : dispatchTool(generated, request.params.name, args)
|
|
11400
11810
|
);
|
|
11401
11811
|
logToolCall({
|
|
11402
11812
|
tool: request.params.name,
|
|
@@ -11411,6 +11821,7 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
|
|
|
11411
11821
|
ok: result.ok,
|
|
11412
11822
|
status: result.status,
|
|
11413
11823
|
durationMs: Date.now() - startedAt,
|
|
11824
|
+
...result.ok ? {} : { errorClass: errorClass(result) },
|
|
11414
11825
|
// The directory profile records which tool ran, never what was asked.
|
|
11415
11826
|
...directory ? {} : {
|
|
11416
11827
|
args: recordedArgumentValues(curated?.recordedArguments, args),
|
|
@@ -11450,24 +11861,50 @@ function createSpecgenServer(ctx, version = "0.1.0", opts = {}) {
|
|
|
11450
11861
|
var SHAPE_HINT = "Input shape mismatch: check this tool's schema for required fields. For job payloads, the worker's release config defines the expected input object \u2014 fix the payload and retry; don't abandon the task.";
|
|
11451
11862
|
var DETAIL_HINTS = [
|
|
11452
11863
|
[
|
|
11453
|
-
/no longer any instances available|not enough free GPUs/i,
|
|
11454
|
-
"Capacity, not input: the request is valid but no machine can take it right now. Don't change the payload shape. Retry with another GPU type, data center, cloud, CUDA version or count, or try again later. For a stopped Pod, its host's GPUs are taken; that Pod can only start once they free up."
|
|
11864
|
+
/no longer any instances available|no instances currently available|not enough free GPUs/i,
|
|
11865
|
+
"Capacity, not input: the request is valid but no machine can take it right now. Don't change the payload shape. Retry with another GPU type, data center, cloud, CUDA version or count, or try again later. For a stopped Pod, its host's GPUs are taken; that Pod can only start once they free up.",
|
|
11866
|
+
"capacity"
|
|
11455
11867
|
],
|
|
11456
11868
|
[
|
|
11457
11869
|
/at least \$\d|insufficient (?:funds|balance)|\bbalance\b/i,
|
|
11458
|
-
"Account balance, not input: this operation needs funds on the account. Ask the user to add credit in the Runpod console; list-billing shows recent spend."
|
|
11870
|
+
"Account balance, not input: this operation needs funds on the account. Ask the user to add credit in the Runpod console; list-billing shows recent spend.",
|
|
11871
|
+
"balance"
|
|
11459
11872
|
],
|
|
11460
11873
|
[
|
|
11461
11874
|
/mutually exclusive|cannot be combined with/i,
|
|
11462
|
-
"Two arguments conflict: send only one of them and leave the other out entirely. Do not change anything else."
|
|
11875
|
+
"Two arguments conflict: send only one of them and leave the other out entirely. Do not change anything else.",
|
|
11876
|
+
"conflicting_args"
|
|
11877
|
+
],
|
|
11878
|
+
// A 400 with a bare "Unauthorized" detail from a write the same key can
|
|
11879
|
+
// otherwise make is upstream's answer for an unfunded account.
|
|
11880
|
+
[
|
|
11881
|
+
/^\s*unauthorized\s*$/i,
|
|
11882
|
+
"Upstream refused this write. With a key that can read, this usually means the account has no balance: ask the user to check their balance in the Runpod console before retrying.",
|
|
11883
|
+
"balance"
|
|
11463
11884
|
]
|
|
11464
11885
|
];
|
|
11886
|
+
function errorClass(result) {
|
|
11887
|
+
const { status } = result;
|
|
11888
|
+
const payload = asRecord(result.payload) ?? { error: result.payload };
|
|
11889
|
+
const detail = detailText(payload);
|
|
11890
|
+
if (status === 400 && detail) {
|
|
11891
|
+
for (const [pattern, , cls] of DETAIL_HINTS)
|
|
11892
|
+
if (pattern.test(detail)) return cls;
|
|
11893
|
+
}
|
|
11894
|
+
if (status === 400 || status === 422) return "validation";
|
|
11895
|
+
if (status === 401) return "auth";
|
|
11896
|
+
if (status === 402) return "balance";
|
|
11897
|
+
if (status === 403) return "permission";
|
|
11898
|
+
if (status === 404) return "not_found";
|
|
11899
|
+
if (status === 429) return "rate_limited";
|
|
11900
|
+
if (status === 504) return "timeout";
|
|
11901
|
+
if (status >= 500) return "server";
|
|
11902
|
+
return "other";
|
|
11903
|
+
}
|
|
11465
11904
|
function errorHint(status, detail) {
|
|
11466
11905
|
if (status === 400 && detail) {
|
|
11467
11906
|
for (const [pattern, hint] of DETAIL_HINTS)
|
|
11468
11907
|
if (pattern.test(detail)) return hint;
|
|
11469
|
-
if (/^unauthorized$/i.test(detail.trim()))
|
|
11470
|
-
return "Upstream refused this write. With a key that can read, this usually means the account has no balance: ask the user to check their balance in the Runpod console before retrying.";
|
|
11471
11908
|
}
|
|
11472
11909
|
if (status === 400 || status === 422) return SHAPE_HINT;
|
|
11473
11910
|
if (status === 401) return "The Runpod API key is missing or invalid.";
|
|
@@ -11707,6 +12144,7 @@ function createToolContext(options = {}) {
|
|
|
11707
12144
|
|
|
11708
12145
|
// src/specgen/not-found-limiter.ts
|
|
11709
12146
|
var SINK_TIMEOUT_MS2 = 3e3;
|
|
12147
|
+
var LOOP_LIMITED_TOOLS = /* @__PURE__ */ new Set(["get-pod"]);
|
|
11710
12148
|
var NOT_FOUND_LOOP = "not_found_loop";
|
|
11711
12149
|
var blocked = /* @__PURE__ */ new Map();
|
|
11712
12150
|
function notFoundSinkUrl(submitUrl) {
|
|
@@ -11731,7 +12169,7 @@ function createNotFoundLimiter(opts) {
|
|
|
11731
12169
|
};
|
|
11732
12170
|
},
|
|
11733
12171
|
onToolResult: (tool, status) => {
|
|
11734
|
-
if (status !== 404) return;
|
|
12172
|
+
if (status !== 404 || !LOOP_LIMITED_TOOLS.has(tool)) return;
|
|
11735
12173
|
const pair = `${opts.key}|${tool}`;
|
|
11736
12174
|
void fetchImpl(opts.sinkUrl, {
|
|
11737
12175
|
method: "POST",
|
|
@@ -12224,4 +12662,4 @@ export {
|
|
|
12224
12662
|
summarizeRpc,
|
|
12225
12663
|
handleMcpRequest
|
|
12226
12664
|
};
|
|
12227
|
-
//# sourceMappingURL=chunk-
|
|
12665
|
+
//# sourceMappingURL=chunk-4M7QDHK4.mjs.map
|