UNPKG

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
/** * `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