import type { MarketClient } from './client.js' import type { AssetAccess, AssetType, GenerateJobStatus, GenerateResponse } from './schemas.js' export class GenerateError extends Error { constructor(message: string) { super(message) this.name = 'GenerateError' } } export interface GenerateInput { description: string type?: AssetType /** Reference images (https URL or data URI) the result should match. */ referenceImages?: string[] /** Requested visibility; omitted lets the server resolve it from entitlement. */ access?: AssetAccess } // How often the CLI polls a running job. const POLL_INTERVAL_MS = 4_000 // Wait through normal provider latency while staying below OpenCode's ten-minute Bash ceiling. // An unusually long job remains resumable with `generate install `. const WINDOW_MS = 8 * 60_000 // Start a generation. Fast providers answer `completed` inline; slow, job-based providers answer // `pending` with a jobId to poll (see `waitForGeneration` / `generate install`). export function startGeneration( client: MarketClient, input: GenerateInput, ): Promise { return client.asset.generate(input) } // One poll of a generation job. export function pollGeneration(client: MarketClient, jobId: string): Promise { return client.asset.generateStatus({ jobId }) } // The bounded outcome of waiting on a job: it settled (completed/failed), or the window elapsed while // it was still running. export type PollOutcome = | { status: 'completed'; assetName: string; version: string } | { status: 'failed'; error: string } | { status: 'running' } // Poll a job until it settles or `windowMs` elapses. Bounded so a single call stays under a harness // timeout; on timeout it returns `running` (the job keeps going server-side — run again to continue). export async function waitForGeneration( client: MarketClient, jobId: string, opts: { windowMs?: number; onProgress?: (message: string) => void } = {}, ): Promise { const windowMs = opts.windowMs ?? WINDOW_MS const report = opts.onProgress ?? (() => {}) const startedAt = Date.now() for (;;) { const status = await pollGeneration(client, jobId) if (status.status === 'completed') { return { status: 'completed', assetName: status.assetName, version: status.version } } if (status.status === 'failed') { return { status: 'failed', error: status.error } } const remaining = windowMs - (Date.now() - startedAt) if (remaining <= 0) { return { status: 'running' } } report('Generating asset') // Trim the last sleep so the call returns as soon as the window passes, not a poll-interval later. await delay(Math.min(POLL_INTERVAL_MS, remaining)) } } function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)) }