import { readFile, writeFile, access } from 'node:fs/promises';
import { request, requestJSON, readBody } from './http-client.mjs';
try {
const mode = process.argv[2] || 'status';
if (!['submit', 'status', 'artifacts', 'head', 'download'].includes(mode)) throw new Error('Invalid mode.');
const task = process.env.API_TASK_ID;
const idPattern = /^[A-Za-z0-9_-][A-Za-z0-9._~-]{0,127}$/;
if (mode === 'submit') {
const plugin = process.env.API_PLUGIN_KEY;
if (!/^[A-Za-z0-9_-]{1,30}$/.test(plugin || '')) throw new Error('Set API_PLUGIN_KEY.');
const raw = await readFile(process.env.API_TASK_REQUEST_FILE);
if (!raw.length || raw.length > 1024 * 1024) throw new Error('Invalid request file size.');
const body = JSON.parse(raw);
if (!body || typeof body !== 'object' || Array.isArray(body)
|| typeof body.model !== 'string' || !body.model.trim()) throw new Error('Provide a plugin-specific JSON object with model.');
const result = await requestJSON(`/v1/tasks/${encodeURIComponent(plugin)}`, body);
if (typeof result.task_id !== 'string' || !idPattern.test(result.task_id)
|| result.id !== result.task_id || result.status !== 'queued') throw new Error('Invalid submission receipt.');
console.log(JSON.stringify({ task_id: result.task_id, accepted: true }));
} else {
if (!idPattern.test(task || '')) throw new Error('Set API_TASK_ID.');
const path = `/v1/tasks/${encodeURIComponent(task)}`;
const result = await requestJSON(path);
const states = ['NOT_START', 'SUBMITTED', 'QUEUED', 'IN_PROGRESS', 'SUCCESS', 'FAILURE', 'NEEDS_REVIEW', 'UNKNOWN'];
if (result.task_id !== task || !states.includes(result.status)) throw new Error('Invalid task status.');
if (mode === 'status') {
console.log(JSON.stringify({ task_id: task, status: result.status, completed: result.status === 'SUCCESS' }));
} else {
if (result.status !== 'SUCCESS') throw new Error('Task is not successful; do not resubmit automatically.');
const listing = await requestJSON(`${path}/artifacts`);
if (listing.task_id !== task || !Array.isArray(listing.artifacts) || listing.artifacts.length > 64) throw new Error('Invalid artifacts.');
const seen = new Set();
for (const item of listing.artifacts) {
if (!item || typeof item.key !== 'string' || !idPattern.test(item.key) || seen.has(item.key)
|| !['image', 'audio', 'video', 'file'].includes(item.type) || typeof item.content_url !== 'string' || !item.content_url
|| (item.mime_type !== undefined && (typeof item.mime_type !== 'string' || /[\r\n]/.test(item.mime_type)))) throw new Error('Invalid artifact entry.');
seen.add(item.key);
}
if (mode === 'artifacts') {
// Avoid logging signed URLs. Use authenticated gateway paths below.
console.log(JSON.stringify(listing.artifacts.map(({ key, type, mime_type }) => ({ key, type, mime_type }))));
} else {
const artifact = listing.artifacts.find((item) => item.key === process.env.API_ARTIFACT_KEY);
if (!artifact) throw new Error('Choose API_ARTIFACT_KEY from the listing.');
const output = process.env.API_ARTIFACT_OUTPUT || 'task-artifact.bin';
if (mode === 'download') {
try { await access(output); throw new Error('Output already exists.'); }
catch (error) { if (error.code !== 'ENOENT') throw error; }
}
// Never send the API key to a provider URL or a signed content_url.
const response = await request(`${path}/artifacts/${encodeURIComponent(artifact.key)}/content`,
{ method: mode === 'head' ? 'HEAD' : 'GET' });
if (response.status !== 200) throw new Error('Expected a complete response, not a partial file.');
if (mode === 'head') {
console.log(JSON.stringify({ available: true, content_type: response.headers.get('content-type') }));
} else {
const type = (response.headers.get('content-type') || '').split(';')[0].trim();
if (!type || type === 'text/html' || type === 'application/json' || type.endsWith('+json')
|| (artifact.mime_type && type !== artifact.mime_type.split(';')[0].trim())) throw new Error('Unexpected artifact content type.');
const bytes = await readBody(response, 20 * 1024 * 1024);
if (!bytes.length) throw new Error('Empty artifact.');
await writeFile(output, bytes, { flag: 'wx' });
console.log('Artifact saved.');
}
}
}
}
} catch {
console.error('Task request failed or returned invalid data. No automatic resubmission; check task status.');
process.exitCode = 1;
}