run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
125 lines • 5.27 kB
JavaScript
/**
* `jobs` namespace — platform-managed job runs.
* All methods require the project's service key.
*/
import { ApiError } from "../errors.js";
import { requireProjectCredentials } from "../project-credentials.js";
export class Jobs {
client;
constructor(client) {
this.client = client;
}
/** Submit a platform-managed job run for a run402-configured job type. */
async submit(projectId, request) {
const project = await requireProjectCredentials(this.client, projectId, "submitting job");
const body = managedJobSubmitRequestToWire(request);
return this.client.request("/jobs/v1/runs", {
method: "POST",
headers: {
Authorization: `Bearer ${project.service_key}`,
"Idempotency-Key": createIdempotencyKey(),
},
body,
context: "submitting job",
});
}
/** Get a job run by id. */
async get(projectId, jobId) {
const project = await requireProjectCredentials(this.client, projectId, "getting job");
return this.client.request(jobRunPath(jobId), {
headers: { Authorization: `Bearer ${project.service_key}` },
context: "getting job",
});
}
/**
* Download a completed job's artifact by filename. Returns the raw `Response`
* so callers can stream to disk or buffer with `.bytes()` / `.text()` —
* this avoids forcing large artifacts through a JS string.
*
* Discover the available filenames from the `artifacts` map on {@link get}.
*
* @throws {ProjectNotFound} if `projectId` is not in the provider.
* @throws {ApiError} on non-2xx — notably `404` when the job has not
* completed or the filename was not recorded for the run.
*/
async downloadArtifact(projectId, jobId, filename) {
const project = await requireProjectCredentials(this.client, projectId, "downloading job artifact");
const url = `${this.client.apiBase}${jobRunPath(jobId)}/artifacts/${encodeURIComponent(filename)}`;
const res = await this.client.fetch(url, {
headers: { Authorization: `Bearer ${project.service_key}` },
});
if (!res.ok) {
const errText = await res.text().catch(() => "");
throw new ApiError(`Downloading job artifact failed (HTTP ${res.status})`, res.status, errText, "downloading job artifact");
}
return res;
}
/** Read job runner logs. */
async logs(projectId, jobId, opts = {}) {
const project = await requireProjectCredentials(this.client, projectId, "getting job logs");
const query = new URLSearchParams();
if (opts.tail !== undefined)
query.set("tail", String(opts.tail));
if (opts.since !== undefined)
query.set("since", String(opts.since));
const suffix = query.toString() ? `?${query.toString()}` : "";
return this.client.request(`${jobRunPath(jobId)}/logs${suffix}`, {
headers: { Authorization: `Bearer ${project.service_key}` },
context: "getting job logs",
});
}
/** Cancel a queued or running job. */
async cancel(projectId, jobId) {
const project = await requireProjectCredentials(this.client, projectId, "cancelling job");
return this.client.request(jobRunPath(jobId), {
method: "DELETE",
headers: { Authorization: `Bearer ${project.service_key}` },
context: "cancelling job",
});
}
/** Purge all managed job runs for a project. Active runners are terminated when known. */
async purge(projectId) {
const project = await requireProjectCredentials(this.client, projectId, "purging jobs");
return this.client.request("/jobs/v1/runs", {
method: "DELETE",
headers: { Authorization: `Bearer ${project.service_key}` },
context: "purging jobs",
});
}
}
function jobRunPath(jobId) {
return `/jobs/v1/runs/${encodeURIComponent(jobId)}`;
}
function managedJobSubmitRequestToWire(request) {
if ("jobType" in request) {
return {
job_type: request.jobType,
input: { input_json: request.input.inputJson },
max_cost_usd_micros: request.maxCostUsdMicros,
...(request.callbackUrl ? { callback_url: request.callbackUrl } : {}),
};
}
const inputJson = request.input.input_json ?? request.input["input.json"];
return {
job_type: request.job_type,
input: { input_json: inputJson ?? {} },
max_cost_usd_micros: request.max_cost_usd_micros,
...(request.callback_url ? { callback_url: request.callback_url } : {}),
};
}
function createIdempotencyKey() {
const crypto = globalThis.crypto;
if (crypto?.randomUUID)
return `job-${crypto.randomUUID()}`;
const bytes = new Uint8Array(16);
if (crypto?.getRandomValues) {
crypto.getRandomValues(bytes);
}
else {
for (let i = 0; i < bytes.length; i += 1) {
bytes[i] = Math.floor(Math.random() * 256);
}
}
return `job-${[...bytes].map((b) => b.toString(16).padStart(2, "0")).join("")}`;
}
//# sourceMappingURL=jobs.js.map