← Files Atlas CloudARCHIVED FILE
skills/seedance-skill/scripts/providers/atlas-rest.mjs
6.63 KB · Sep 30, 2026 · 23:11 UTC
import { readFileSync } from "node:fs";
import { basename, resolve } from "node:path";
const POLL_INTERVAL_SECONDS = 2;
const POLL_INTERVAL_MS = POLL_INTERVAL_SECONDS * 1000;
function unwrap(response) {
return response?.data && response.code !== undefined ? response.data : response;
}
function imageMime(buffer, path) {
if (buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) return "image/png";
if (buffer.subarray(0, 3).equals(Buffer.from([0xff, 0xd8, 0xff]))) return "image/jpeg";
if (buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") return "image/webp";
throw new Error(`unsupported image format: ${path} (use PNG, JPEG, or WebP)`);
}
function audioMime(path) {
const suffix = path.toLowerCase().split(".").pop();
return ({ mp3: "audio/mpeg", wav: "audio/wav", m4a: "audio/mp4", aac: "audio/aac", ogg: "audio/ogg", opus: "audio/ogg", flac: "audio/flac" })[suffix] || "application/octet-stream";
}
function collectModelIds(value, ids = new Set()) {
if (Array.isArray(value)) {
for (const entry of value) collectModelIds(entry, ids);
} else if (value && typeof value === "object") {
if (typeof value.id === "string") ids.add(value.id);
if (typeof value.model === "string") ids.add(value.model);
for (const child of Object.values(value)) collectModelIds(child, ids);
}
return ids;
}
export function createAtlasRestExecutor({ config, root, log = console.log }) {
const execution = config.execution || {};
const base = execution.base || config.base || "https://api.atlascloud.ai";
const apiKeyEnv = execution.apiKeyEnv || config.apiKeyEnv || "ATLASCLOUD_API_KEY";
const requestTimeoutMs = execution.requestTimeoutMs ?? 45_000;
const key = process.env[apiKeyEnv]
|| process.env.ATLASCLOUD_API_KEY
|| process.env.ATLAS_CLOUD_API_KEY;
if (!key) {
throw new Error(
`no Atlas Cloud API key is visible in this process. Get one at https://www.atlascloud.ai/console/api-keys?utm_source=github&utm_campaign=awesome-seedance-2.5-prompts-skills and set ${apiKeyEnv}. `
+ "If it is already configured in a parent process or host application, refresh or restart the execution session so this process inherits it. Do not paste the key into chat.",
);
}
async function request(path, body, { auth = true } = {}) {
const headers = body ? { "Content-Type": "application/json" } : {};
if (auth) headers.Authorization = `Bearer ${key}`;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), requestTimeoutMs);
let response;
try {
response = await fetch(base + path, {
method: body ? "POST" : "GET",
headers,
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
} catch (error) {
const message = error.name === "AbortError"
? `${path} fetch failed: timed out after ${requestTimeoutMs}ms`
: `${path} fetch failed: ${error.message}`;
throw new Error(message);
} finally {
clearTimeout(timer);
}
const text = await response.text();
let json;
try { json = JSON.parse(text); } catch { throw new Error(`${path} HTTP ${response.status}: ${text.slice(0, 200)}`); }
if (!response.ok) throw new Error(`${path} HTTP ${response.status}: ${text.slice(0, 200)}`);
return unwrap(json);
}
async function poll(id, label, maxPollSeconds = 900) {
for (let elapsed = 0; ; elapsed += POLL_INTERVAL_SECONDS) {
if (elapsed > maxPollSeconds) {
throw new Error(`[${label}] local polling window ended after ${maxPollSeconds}s for task ${id}; task status is not a terminal failure, so resume this ID instead of resubmitting`);
}
let prediction;
let error;
try { prediction = await request(`/api/v1/model/prediction/${id}`); }
catch (firstError) {
try { prediction = await request(`/api/v1/model/result/${id}`); }
catch (secondError) { error = `${firstError.message} | ${secondError.message}`; }
}
if (!prediction) {
if (/HTTP 50[234]|fetch failed|HTTP 404/.test(error)) {
await new Promise(resolveDelay => setTimeout(resolveDelay, POLL_INTERVAL_MS));
continue;
}
throw new Error(error);
}
const status = String(prediction.status || "unknown").toLowerCase();
if (elapsed % 32 === 0) log(`[${label}] ${status} … ${elapsed}s`);
if (status === "completed" || status === "succeeded") return prediction;
if (status === "failed" || status === "timeout" || status === "canceled") {
throw new Error(`[${label}] ${status}: ${JSON.stringify(prediction.error ?? prediction.meta_info ?? "").slice(0, 300)}`);
}
await new Promise(resolveDelay => setTimeout(resolveDelay, POLL_INTERVAL_MS));
}
}
async function download(url, destination) {
const response = await fetch(url);
if (!response.ok) throw new Error(`download HTTP ${response.status}`);
const { writeFileSync } = await import("node:fs");
writeFileSync(destination, Buffer.from(await response.arrayBuffer()));
}
async function uploadMedia(source) {
if (/^https?:\/\//.test(source)) return source;
const path = resolve(root, source);
const buffer = readFileSync(path);
const mime = /\.(png|jpe?g|webp)$/i.test(path) ? imageMime(buffer, path) : audioMime(path);
const form = new FormData();
form.append("file", new Blob([buffer], { type: mime }), basename(path));
const response = await fetch(`${base}/api/v1/model/uploadMedia`, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const text = await response.text();
let json;
try { json = JSON.parse(text); } catch { throw new Error(`HTTP ${response.status}: ${text.slice(0, 200)}`); }
const data = unwrap(json);
const url = data?.download_url || data?.url;
if (!response.ok || !url) throw new Error(text.slice(0, 300));
return url;
}
async function verifyModels(modelIds) {
const catalog = await request("/api/v1/models", undefined, { auth: false });
const available = collectModelIds(catalog);
const missing = modelIds.filter(model => !available.has(model));
if (missing.length) throw new Error(`configured model is not in Atlas Cloud's live catalog: ${missing.join(", ")}`);
log(`[executor] atlas-rest verified ${modelIds.join(", ")}`);
}
return {
id: "atlas-rest",
maxReferenceAspectRatio: 2.5,
submitImage: body => request("/api/v1/model/generateImage", body),
submitVideo: body => request("/api/v1/model/generateVideo", body),
poll,
download,
uploadMedia,
verifyModels,
};
}
SHA-256: a79b476772547c0f6c70aab689b4032a36e5a7951e203dd700baa936a41fc725