diff --git a/README.md b/README.md
index 950a8b2..797ac88 100644
--- a/README.md
+++ b/README.md
@@ -401,7 +401,6 @@ If you are interested in other runtime environments, please open or upvote an is
See [the contributing documentation](./CONTRIBUTING.md).
-
## License
-Licensed under the Apache License 2.0. See [LICENSE.](./LICENSE)
\ No newline at end of file
+Licensed under the Apache License 2.0. See [LICENSE.](./LICENSE)
diff --git a/api.md b/api.md
index bc4367a..2167eda 100644
--- a/api.md
+++ b/api.md
@@ -733,6 +733,7 @@ Methods:
- client.knowledgeBases.indexingJobs.list({ ...params }) -> IndexingJobListResponse
- client.knowledgeBases.indexingJobs.retrieveDataSources(indexingJobUuid) -> IndexingJobRetrieveDataSourcesResponse
- client.knowledgeBases.indexingJobs.updateCancel(pathUuid, { ...params }) -> IndexingJobUpdateCancelResponse
+- client.knowledgeBases.indexingJobs.waitForCompletion(uuid, { ...options }) -> IndexingJobRetrieveResponse
# Models
diff --git a/src/client.ts b/src/client.ts
index 24c18a0..bdb9d87 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -810,6 +810,13 @@ export class Gradient {
static PermissionDeniedError = Errors.PermissionDeniedError;
static UnprocessableEntityError = Errors.UnprocessableEntityError;
+ // Indexing Job Error Types
+ static IndexingJobAbortedError = API.KnowledgeBases.IndexingJobs.IndexingJobAbortedError;
+ static IndexingJobNotFoundError = API.KnowledgeBases.IndexingJobs.IndexingJobNotFoundError;
+ static IndexingJobFailedError = API.KnowledgeBases.IndexingJobs.IndexingJobFailedError;
+ static IndexingJobCancelledError = API.KnowledgeBases.IndexingJobs.IndexingJobCancelledError;
+ static IndexingJobTimeoutError = API.KnowledgeBases.IndexingJobs.IndexingJobTimeoutError;
+
static toFile = Uploads.toFile;
agents: API.Agents = new API.Agents(this);
diff --git a/src/index.ts b/src/index.ts
index 0223275..a068645 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -20,3 +20,10 @@ export {
PermissionDeniedError,
UnprocessableEntityError,
} from './core/error';
+export {
+ IndexingJobAbortedError,
+ IndexingJobNotFoundError,
+ IndexingJobFailedError,
+ IndexingJobCancelledError,
+ IndexingJobTimeoutError,
+} from './resources/knowledge-bases/indexing-jobs';
diff --git a/src/resources/knowledge-bases/indexing-jobs.ts b/src/resources/knowledge-bases/indexing-jobs.ts
index 0a54512..25fdd8b 100644
--- a/src/resources/knowledge-bases/indexing-jobs.ts
+++ b/src/resources/knowledge-bases/indexing-jobs.ts
@@ -5,6 +5,57 @@ import * as Shared from '../shared';
import { APIPromise } from '../../core/api-promise';
import { RequestOptions } from '../../internal/request-options';
import { path } from '../../internal/utils/path';
+import { sleep } from '../../internal/utils/sleep';
+
+/**
+ * Error thrown when an indexing job polling operation is aborted
+ */
+export class IndexingJobAbortedError extends Error {
+ constructor() {
+ super('Indexing job polling was aborted');
+ this.name = 'IndexingJobAbortedError';
+ }
+}
+
+/**
+ * Error thrown when an indexing job is not found
+ */
+export class IndexingJobNotFoundError extends Error {
+ constructor() {
+ super('Indexing job not found');
+ this.name = 'IndexingJobNotFoundError';
+ }
+}
+
+/**
+ * Error thrown when an indexing job fails
+ */
+export class IndexingJobFailedError extends Error {
+ constructor(public readonly phase: string) {
+ super(`Indexing job failed with phase: ${phase}`);
+ this.name = 'IndexingJobFailedError';
+ }
+}
+
+/**
+ * Error thrown when an indexing job is cancelled
+ */
+export class IndexingJobCancelledError extends Error {
+ constructor() {
+ super('Indexing job was cancelled');
+ this.name = 'IndexingJobCancelledError';
+ }
+}
+
+/**
+ * Error thrown when an indexing job polling times out
+ */
+export class IndexingJobTimeoutError extends Error {
+ constructor(public readonly timeoutMs: number) {
+ super(`Indexing job polling timed out after ${timeoutMs}ms`);
+ this.name = 'IndexingJobTimeoutError';
+ }
+}
export class IndexingJobs extends APIResource {
/**
@@ -113,6 +164,149 @@ export class IndexingJobs extends APIResource {
...options,
});
}
+
+ /**
+ * Polls for indexing job completion with configurable interval and timeout.
+ * Uses exponential backoff to gradually increase polling intervals, reducing API load for long-running jobs.
+ * Returns the final job state when completed, failed, or cancelled.
+ *
+ * **Exponential Backoff Behavior:**
+ * - First 2 polls use the initial interval
+ * - Starting from the 3rd poll, the interval doubles after each poll
+ * - The interval is capped at the `maxInterval` value
+ * - Example with default values (interval: 5000ms, maxInterval: 30000ms):
+ * - Poll 1: 5000ms wait
+ * - Poll 2: 5000ms wait
+ * - Poll 3: 10000ms wait (5000 * 2)
+ * - Poll 4: 20000ms wait (10000 * 2)
+ * - Poll 5: 30000ms wait (20000 * 1.5, capped at maxInterval)
+ * - Poll 6+: 30000ms wait (continues at maxInterval)
+ *
+ * @param uuid - The indexing job UUID to poll
+ * @param options - Polling configuration options
+ * @returns Promise that resolves with the final job state
+ * @throws {IndexingJobAbortedError} When the operation is aborted via AbortSignal
+ * @throws {IndexingJobNotFoundError} When the job is not found
+ * @throws {IndexingJobFailedError} When the job fails with phase FAILED or ERROR
+ * @throws {IndexingJobCancelledError} When the job is cancelled
+ * @throws {IndexingJobTimeoutError} When polling times out
+ *
+ * @example
+ * ```ts
+ * const job = await client.knowledgeBases.indexingJobs.waitForCompletion(
+ * '123e4567-e89b-12d3-a456-426614174000',
+ * { interval: 5000, timeout: 300000 }
+ * );
+ * console.log('Job completed with phase:', job.job?.phase);
+ * ```
+ *
+ * @example
+ * ```ts
+ * const controller = new AbortController();
+ * const job = await client.knowledgeBases.indexingJobs.waitForCompletion(
+ * '123e4567-e89b-12d3-a456-426614174000',
+ * { requestOptions: { signal: controller.signal } }
+ * );
+ * // Cancel polling after 30 seconds
+ * setTimeout(() => controller.abort(), 30000);
+ * ```
+ *
+ * @example
+ * ```ts
+ * // Custom exponential backoff configuration
+ * const job = await client.knowledgeBases.indexingJobs.waitForCompletion(uuid, {
+ * interval: 2000, // Start with 2 second intervals
+ * maxInterval: 60000, // Cap at 1 minute intervals
+ * timeout: 1800000 // 30 minute timeout
+ * });
+ * ```
+ *
+ * @example
+ * ```ts
+ * try {
+ * const job = await client.knowledgeBases.indexingJobs.waitForCompletion(uuid);
+ * console.log('Job completed successfully');
+ * } catch (error) {
+ * if (error instanceof IndexingJobFailedError) {
+ * console.log('Job failed with phase:', error.phase);
+ * } else if (error instanceof IndexingJobTimeoutError) {
+ * console.log('Job timed out after:', error.timeoutMs, 'ms');
+ * } else if (error instanceof IndexingJobAbortedError) {
+ * console.log('Job polling was aborted');
+ * }
+ * }
+ * ```
+ */
+ async waitForCompletion(
+ uuid: string,
+ options: {
+ /**
+ * Initial polling interval in milliseconds (default: 5000ms).
+ * This interval will be used for the first few polls, then exponential backoff applies.
+ */
+ interval?: number;
+ /**
+ * Maximum time to wait in milliseconds (default: 600000ms = 10 minutes)
+ */
+ timeout?: number;
+ /**
+ * Maximum polling interval in milliseconds (default: 30000ms = 30 seconds).
+ * Exponential backoff will not exceed this value.
+ */
+ maxInterval?: number;
+ /**
+ * Request options to pass to each poll request
+ */
+ requestOptions?: RequestOptions;
+ } = {},
+ ): Promise {
+ const { interval = 5000, timeout = 600000, maxInterval = 30000, requestOptions } = options;
+ const startTime = Date.now();
+ let currentInterval = interval;
+ let pollCount = 0;
+
+ while (true) {
+ // Check if operation was aborted
+ if (requestOptions?.signal?.aborted) {
+ throw new IndexingJobAbortedError();
+ }
+
+ const response = await this.retrieve(uuid, requestOptions);
+ const job = response.job;
+
+ if (!job) {
+ throw new IndexingJobNotFoundError();
+ }
+
+ // Check if job is in a terminal state
+ if (job.phase === 'BATCH_JOB_PHASE_SUCCEEDED') {
+ return response;
+ }
+
+ if (job.phase === 'BATCH_JOB_PHASE_FAILED' || job.phase === 'BATCH_JOB_PHASE_ERROR') {
+ throw new IndexingJobFailedError(job.phase);
+ }
+
+ if (job.phase === 'BATCH_JOB_PHASE_CANCELLED') {
+ throw new IndexingJobCancelledError();
+ }
+
+ // Check timeout
+ if (Date.now() - startTime > timeout) {
+ throw new IndexingJobTimeoutError(timeout);
+ }
+
+ // Wait before next poll with exponential backoff
+ await sleep(currentInterval);
+
+ // Apply exponential backoff: double the interval after each poll, up to maxInterval
+ pollCount++;
+ if (pollCount > 2) {
+ // Start exponential backoff after 2 polls
+ currentInterval = Math.min(currentInterval * 2, maxInterval);
+ }
+ }
+ }
}
export interface APIIndexedDataSource {
@@ -367,5 +561,10 @@ export declare namespace IndexingJobs {
type IndexingJobCreateParams as IndexingJobCreateParams,
type IndexingJobListParams as IndexingJobListParams,
type IndexingJobUpdateCancelParams as IndexingJobUpdateCancelParams,
+ IndexingJobAbortedError,
+ IndexingJobNotFoundError,
+ IndexingJobFailedError,
+ IndexingJobCancelledError,
+ IndexingJobTimeoutError,
};
}
diff --git a/tests/api-resources/knowledge-bases/indexing-jobs.test.ts b/tests/api-resources/knowledge-bases/indexing-jobs.test.ts
index ff34604..fc03c5c 100644
--- a/tests/api-resources/knowledge-bases/indexing-jobs.test.ts
+++ b/tests/api-resources/knowledge-bases/indexing-jobs.test.ts
@@ -109,4 +109,62 @@ describe('resource indexingJobs', () => {
),
).rejects.toThrow(Gradient.NotFoundError);
});
+
+ describe('waitForCompletion', () => {
+ // Prism tests are disabled
+ test.skip('waits for job completion successfully', async () => {
+ const jobUuid = '123e4567-e89b-12d3-a456-426614174000';
+ const responsePromise = client.knowledgeBases.indexingJobs.waitForCompletion(jobUuid, {
+ interval: 100,
+ timeout: 1000,
+ });
+ const response = await responsePromise;
+ expect(response).toBeDefined();
+ expect(response.job).toBeDefined();
+ });
+
+ // Prism tests are disabled
+ test.skip('throws error when job fails', async () => {
+ const jobUuid = '123e4567-e89b-12d3-a456-426614174000';
+ await expect(
+ client.knowledgeBases.indexingJobs.waitForCompletion(jobUuid, {
+ interval: 100,
+ timeout: 1000,
+ }),
+ ).rejects.toThrow();
+ });
+
+ // Prism tests are disabled
+ test.skip('throws error when job is cancelled', async () => {
+ const jobUuid = '123e4567-e89b-12d3-a456-426614174000';
+ await expect(
+ client.knowledgeBases.indexingJobs.waitForCompletion(jobUuid, {
+ interval: 100,
+ timeout: 1000,
+ }),
+ ).rejects.toThrow('Indexing job was cancelled');
+ });
+
+ // Prism tests are disabled
+ test.skip('throws error when timeout is reached', async () => {
+ const jobUuid = '123e4567-e89b-12d3-a456-426614174000';
+ await expect(
+ client.knowledgeBases.indexingJobs.waitForCompletion(jobUuid, {
+ interval: 100,
+ timeout: 50, // Very short timeout
+ }),
+ ).rejects.toThrow('Indexing job polling timed out');
+ });
+
+ // Prism tests are disabled
+ test.skip('throws error when job is not found', async () => {
+ const jobUuid = 'nonexistent-job-uuid';
+ await expect(
+ client.knowledgeBases.indexingJobs.waitForCompletion(jobUuid, {
+ interval: 100,
+ timeout: 1000,
+ }),
+ ).rejects.toThrow('Job not found');
+ });
+ });
});