@@ -5,6 +5,57 @@ import * as Shared from '../shared';
55import { APIPromise } from '../../core/api-promise' ;
66import { RequestOptions } from '../../internal/request-options' ;
77import { path } from '../../internal/utils/path' ;
8+ import { sleep } from '../../internal/utils/sleep' ;
9+
10+ /**
11+ * Error thrown when an indexing job polling operation is aborted
12+ */
13+ export class IndexingJobAbortedError extends Error {
14+ constructor ( ) {
15+ super ( 'Indexing job polling was aborted' ) ;
16+ this . name = 'IndexingJobAbortedError' ;
17+ }
18+ }
19+
20+ /**
21+ * Error thrown when an indexing job is not found
22+ */
23+ export class IndexingJobNotFoundError extends Error {
24+ constructor ( ) {
25+ super ( 'Indexing job not found' ) ;
26+ this . name = 'IndexingJobNotFoundError' ;
27+ }
28+ }
29+
30+ /**
31+ * Error thrown when an indexing job fails
32+ */
33+ export class IndexingJobFailedError extends Error {
34+ constructor ( public readonly phase : string ) {
35+ super ( `Indexing job failed with phase: ${ phase } ` ) ;
36+ this . name = 'IndexingJobFailedError' ;
37+ }
38+ }
39+
40+ /**
41+ * Error thrown when an indexing job is cancelled
42+ */
43+ export class IndexingJobCancelledError extends Error {
44+ constructor ( ) {
45+ super ( 'Indexing job was cancelled' ) ;
46+ this . name = 'IndexingJobCancelledError' ;
47+ }
48+ }
49+
50+ /**
51+ * Error thrown when an indexing job polling times out
52+ */
53+ export class IndexingJobTimeoutError extends Error {
54+ constructor ( public readonly timeoutMs : number ) {
55+ super ( `Indexing job polling timed out after ${ timeoutMs } ms` ) ;
56+ this . name = 'IndexingJobTimeoutError' ;
57+ }
58+ }
859
960export class IndexingJobs extends APIResource {
1061 /**
@@ -113,6 +164,149 @@ export class IndexingJobs extends APIResource {
113164 ...options ,
114165 } ) ;
115166 }
167+
168+ /**
169+ * Polls for indexing job completion with configurable interval and timeout.
170+ * Uses exponential backoff to gradually increase polling intervals, reducing API load for long-running jobs.
171+ * Returns the final job state when completed, failed, or cancelled.
172+ *
173+ * **Exponential Backoff Behavior:**
174+ * - First 2 polls use the initial interval
175+ * - Starting from the 3rd poll, the interval doubles after each poll
176+ * - The interval is capped at the `maxInterval` value
177+ * - Example with default values (interval: 5000ms, maxInterval: 30000ms):
178+ * - Poll 1: 5000ms wait
179+ * - Poll 2: 5000ms wait
180+ * - Poll 3: 10000ms wait (5000 * 2)
181+ * - Poll 4: 20000ms wait (10000 * 2)
182+ * - Poll 5: 30000ms wait (20000 * 1.5, capped at maxInterval)
183+ * - Poll 6+: 30000ms wait (continues at maxInterval)
184+ *
185+ * @param uuid - The indexing job UUID to poll
186+ * @param options - Polling configuration options
187+ * @returns Promise that resolves with the final job state
188+ * @throws {IndexingJobAbortedError } When the operation is aborted via AbortSignal
189+ * @throws {IndexingJobNotFoundError } When the job is not found
190+ * @throws {IndexingJobFailedError } When the job fails with phase FAILED or ERROR
191+ * @throws {IndexingJobCancelledError } When the job is cancelled
192+ * @throws {IndexingJobTimeoutError } When polling times out
193+ *
194+ * @example
195+ * ```ts
196+ * const job = await client.knowledgeBases.indexingJobs.waitForCompletion(
197+ * '123e4567-e89b-12d3-a456-426614174000',
198+ * { interval: 5000, timeout: 300000 }
199+ * );
200+ * console.log('Job completed with phase:', job.job?.phase);
201+ * ```
202+ *
203+ * @example
204+ * ```ts
205+ * const controller = new AbortController();
206+ * const job = await client.knowledgeBases.indexingJobs.waitForCompletion(
207+ * '123e4567-e89b-12d3-a456-426614174000',
208+ * { requestOptions: { signal: controller.signal } }
209+ * );
210+ * // Cancel polling after 30 seconds
211+ * setTimeout(() => controller.abort(), 30000);
212+ * ```
213+ *
214+ * @example
215+ * ```ts
216+ * // Custom exponential backoff configuration
217+ * const job = await client.knowledgeBases.indexingJobs.waitForCompletion(uuid, {
218+ * interval: 2000, // Start with 2 second intervals
219+ * maxInterval: 60000, // Cap at 1 minute intervals
220+ * timeout: 1800000 // 30 minute timeout
221+ * });
222+ * ```
223+ *
224+ * @example
225+ * ```ts
226+ * try {
227+ * const job = await client.knowledgeBases.indexingJobs.waitForCompletion(uuid);
228+ * console.log('Job completed successfully');
229+ * } catch (error) {
230+ * if (error instanceof IndexingJobFailedError) {
231+ * console.log('Job failed with phase:', error.phase);
232+ * } else if (error instanceof IndexingJobTimeoutError) {
233+ * console.log('Job timed out after:', error.timeoutMs, 'ms');
234+ * } else if (error instanceof IndexingJobAbortedError) {
235+ * console.log('Job polling was aborted');
236+ * }
237+ * }
238+ * ```
239+ */
240+ async waitForCompletion (
241+ uuid : string ,
242+ options : {
243+ /**
244+ * Initial polling interval in milliseconds (default: 5000ms).
245+ * This interval will be used for the first few polls, then exponential backoff applies.
246+ */
247+ interval ?: number ;
248+ /**
249+ * Maximum time to wait in milliseconds (default: 600000ms = 10 minutes)
250+ */
251+ timeout ?: number ;
252+ /**
253+ * Maximum polling interval in milliseconds (default: 30000ms = 30 seconds).
254+ * Exponential backoff will not exceed this value.
255+ */
256+ maxInterval ?: number ;
257+ /**
258+ * Request options to pass to each poll request
259+ */
260+ requestOptions ?: RequestOptions ;
261+ } = { } ,
262+ ) : Promise < IndexingJobRetrieveResponse > {
263+ const { interval = 5000 , timeout = 600000 , maxInterval = 30000 , requestOptions } = options ;
264+ const startTime = Date . now ( ) ;
265+ let currentInterval = interval ;
266+ let pollCount = 0 ;
267+
268+ while ( true ) {
269+ // Check if operation was aborted
270+ if ( requestOptions ?. signal ?. aborted ) {
271+ throw new IndexingJobAbortedError ( ) ;
272+ }
273+
274+ const response = await this . retrieve ( uuid , requestOptions ) ;
275+ const job = response . job ;
276+
277+ if ( ! job ) {
278+ throw new IndexingJobNotFoundError ( ) ;
279+ }
280+
281+ // Check if job is in a terminal state
282+ if ( job . phase === 'BATCH_JOB_PHASE_SUCCEEDED' ) {
283+ return response ;
284+ }
285+
286+ if ( job . phase === 'BATCH_JOB_PHASE_FAILED' || job . phase === 'BATCH_JOB_PHASE_ERROR' ) {
287+ throw new IndexingJobFailedError ( job . phase ) ;
288+ }
289+
290+ if ( job . phase === 'BATCH_JOB_PHASE_CANCELLED' ) {
291+ throw new IndexingJobCancelledError ( ) ;
292+ }
293+
294+ // Check timeout
295+ if ( Date . now ( ) - startTime > timeout ) {
296+ throw new IndexingJobTimeoutError ( timeout ) ;
297+ }
298+
299+ // Wait before next poll with exponential backoff
300+ await sleep ( currentInterval ) ;
301+
302+ // Apply exponential backoff: double the interval after each poll, up to maxInterval
303+ pollCount ++ ;
304+ if ( pollCount > 2 ) {
305+ // Start exponential backoff after 2 polls
306+ currentInterval = Math . min ( currentInterval * 2 , maxInterval ) ;
307+ }
308+ }
309+ }
116310}
117311
118312export interface APIIndexedDataSource {
@@ -367,5 +561,10 @@ export declare namespace IndexingJobs {
367561 type IndexingJobCreateParams as IndexingJobCreateParams ,
368562 type IndexingJobListParams as IndexingJobListParams ,
369563 type IndexingJobUpdateCancelParams as IndexingJobUpdateCancelParams ,
564+ IndexingJobAbortedError ,
565+ IndexingJobNotFoundError ,
566+ IndexingJobFailedError ,
567+ IndexingJobCancelledError ,
568+ IndexingJobTimeoutError ,
370569 } ;
371570}
0 commit comments