import type { Scenario } from '../client'; import type { APIPromise } from '../core/api-promise'; import type { RequestOptions } from '../internal/request-options'; import { Jobs, type JobRetrieveParams, type JobRetrieveResponse, type JobTriggerActionParams, type JobTriggerActionResponse, } from '../resources/jobs'; import { type Scope, clientProjectId, effectiveScope, withScope } from './scope'; export interface WaitOptions { /** Polling interval in milliseconds. Default: 3000 */ intervalMs?: number; /** Maximum wait time in milliseconds. Default: 120000 */ timeoutMs?: number; /** * Override the project scope used for the polling requests. Defaults to * the scope captured on the job (per-call override on the originating * call, else the client's default project). */ projectId?: string; } type JobStatus = JobRetrieveResponse.Job['status']; const TERMINAL_STATUSES: Set = new Set(['success', 'failure', 'canceled']); /** Job methods added on top of the original job fields. */ export class JobMethods { /** @internal */ declare readonly _client: Scenario; /** @internal scope captured from the originating call — replayed on every `.wait()` poll. */ declare readonly _scope?: Scope; /** * Poll until the job reaches a terminal status (success, failure, or canceled). * * @example * ```ts * const response = await client.workflows.run(workflowId, { body }); * const completed = await response.job.wait(); * console.log(completed.status); * ``` */ async wait(this: Job, options?: WaitOptions): Promise { const intervalMs = options?.intervalMs ?? 3_000; const timeoutMs = options?.timeoutMs ?? 120_000; const scope = waitOverrideScope(options) ?? this._scope; let elapsed = 0; while (elapsed < timeoutMs) { const response = await this._client.jobs.retrieve(this.jobId, undefined, withScope(scope)); if (TERMINAL_STATUSES.has(response.job.status)) { return Job.from(this._client, response.job, scope); } await new Promise((resolve) => setTimeout(resolve, intervalMs)); elapsed += intervalMs; } throw new Error(`Job ${this.jobId} did not complete within ${timeoutMs / 1_000}s`); } /** @internal Create a Job from raw job data, optionally remembering the originating scope. */ static from(client: Scenario, data: JobRetrieveResponse['job'], scope?: Scope): Job { const job = Object.assign(Object.create(JobMethods.prototype), data) as Job; Object.defineProperty(job, '_client', { value: client, enumerable: false }); if (scope) Object.defineProperty(job, '_scope', { value: scope, enumerable: false }); return job; } } /** A job with all original fields plus `.wait()`. */ export type Job = JobRetrieveResponse.Job & JobMethods; export const Job = JobMethods; /** * @internal Helper type: adds `.wait()` to the `job` field of a response. * Uses intersection so the original response type is preserved — a `WithJob` is still assignable to `T`. */ export type WithJob = T & { job: JobMethods }; /** * @internal Wrap `_thenUnwrap` to replace `response.job` with an enhanced Job. * Captures the effective scope (per-call override or client default) so * `.wait()` can replay it on every poll — keeping follow-up calls in sync * with the project the job was originally created in. */ export function enhanceJob( client: Scenario, promise: APIPromise, options?: RequestOptions, ): APIPromise> { const scope = effectiveScope(options, clientProjectId(client)); return promise._thenUnwrap((data) => ({ ...data, job: Job.from(client, data.job as JobRetrieveResponse['job'], scope), })); } /** * Enhanced Jobs resource. `retrieve` and `triggerAction` responses' `job` * field gains a `.wait()` helper — previously only job-creating endpoints * (workflow.run, models.trigger, generate.*) had it. `list` is not enhanced: * its items use a different (but structurally similar) type, and users * typically fetch individual jobs via `retrieve` before acting on them. */ export class EnhancedJobs extends Jobs { override retrieve( jobID: string, query: JobRetrieveParams | null | undefined = {}, options?: RequestOptions, ): APIPromise> { return enhanceJob(this._client, super.retrieve(jobID, query, options), options); } override triggerAction( jobID: string, params: JobTriggerActionParams, options?: RequestOptions, ): APIPromise> { return enhanceJob(this._client, super.triggerAction(jobID, params, options), options); } } /** Resolve the scope override carried by a `WaitOptions`, if any. */ function waitOverrideScope(options?: WaitOptions): Scope | undefined { if (!options?.projectId) return undefined; return { projectId: options.projectId }; }