UNPKG

exa-js

Version:

Exa SDK for Node.js and the browser

1,580 lines (1,563 loc) 57.2 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { CreateEnrichmentParametersFormat: () => CreateEnrichmentParametersFormat, CreateImportParametersFormat: () => CreateImportParametersFormat, EventType: () => EventType, EventsClient: () => EventsClient, Exa: () => Exa2, ExaError: () => ExaError, HttpStatusCode: () => HttpStatusCode, ImportFailedReason: () => ImportFailedReason, ImportFormat: () => ImportFormat, ImportObject: () => ImportObject, ImportStatus: () => ImportStatus, ImportsClient: () => ImportsClient, MonitorObject: () => MonitorObject, MonitorRunObject: () => MonitorRunObject, MonitorRunStatus: () => MonitorRunStatus, MonitorRunType: () => MonitorRunType, MonitorStatus: () => MonitorStatus, UpdateMonitorStatus: () => UpdateMonitorStatus, WebhookStatus: () => WebhookStatus, WebsetEnrichmentFormat: () => WebsetEnrichmentFormat, WebsetEnrichmentStatus: () => WebsetEnrichmentStatus, WebsetEnrichmentsClient: () => WebsetEnrichmentsClient, WebsetExcludeSource: () => WebsetExcludeSource, WebsetImportSource: () => WebsetImportSource, WebsetItemEvaluationSatisfied: () => WebsetItemEvaluationSatisfied, WebsetItemSource: () => WebsetItemSource, WebsetItemsClient: () => WebsetItemsClient, WebsetMonitorsClient: () => WebsetMonitorsClient, WebsetSearchBehavior: () => WebsetSearchBehavior, WebsetSearchCanceledReason: () => WebsetSearchCanceledReason, WebsetSearchScopeSource: () => WebsetSearchScopeSource, WebsetSearchStatus: () => WebsetSearchStatus, WebsetSearchesClient: () => WebsetSearchesClient, WebsetStatus: () => WebsetStatus, WebsetWebhooksClient: () => WebsetWebhooksClient, WebsetsClient: () => WebsetsClient, default: () => index_default }); module.exports = __toCommonJS(index_exports); var import_cross_fetch = __toESM(require("cross-fetch")); // package.json var package_default = { name: "exa-js", version: "2.0.0", description: "Exa SDK for Node.js and the browser", publishConfig: { access: "public" }, files: [ "dist" ], main: "./dist/index.js", module: "./dist/index.mjs", exports: { ".": { types: "./dist/index.d.ts", require: "./dist/index.js", module: "./dist/index.mjs", import: "./dist/index.mjs" }, "./package.json": "./package.json" }, types: "./dist/index.d.ts", scripts: { "build-fast": "tsup src/index.ts --format cjs,esm", build: "tsup", test: "vitest run", "test:unit": "vitest run --config vitest.unit.config.ts", "test:integration": "vitest run --config vitest.integration.config.ts", typecheck: "tsc --noEmit", "typecheck:examples": "tsc --noEmit examples/**/*.ts", "generate:types:websets": "openapi-typescript https://raw.githubusercontent.com/exa-labs/openapi-spec/refs/heads/master/exa-websets-spec.yaml --enum --root-types --alphabetize --root-types-no-schema-prefix --output ./src/websets/openapi.ts && npm run format:websets", format: 'prettier --write "src/**/*.ts" "examples/**/*.ts"', "format:websets": "prettier --write src/websets/openapi.ts", "build:beta": "cross-env NPM_CONFIG_TAG=beta npm run build", "version:beta": "npm version prerelease --preid=beta", "version:stable": "npm version patch", "publish:beta": "npm run version:beta && npm run build:beta && npm publish --tag beta", "publish:stable": "npm run version:stable && npm run build && npm publish", prepublishOnly: "npm run build" }, license: "MIT", devDependencies: { "@types/node": "~22.14.0", "cross-env": "~7.0.3", "openapi-typescript": "~7.6.1", prettier: "~3.5.3", "ts-node": "~10.9.2", tsup: "~8.4.0", typescript: "~5.8.3", vitest: "~3.1.1" }, dependencies: { "cross-fetch": "~4.1.0", dotenv: "~16.4.7", openai: "^5.0.1", zod: "^3.22.0", "zod-to-json-schema": "^3.20.0" }, directories: { test: "test" }, repository: { type: "git", url: "git+https://github.com/exa-labs/exa-js.git" }, keywords: [ "exa", "metaphor", "search", "AI", "LLMs", "RAG", "retrieval", "augmented", "generation" ], author: "jeffzwang", bugs: { url: "https://github.com/exa-labs/exa-js/issues" }, homepage: "https://github.com/exa-labs/exa-js#readme" }; // src/errors.ts var HttpStatusCode = /* @__PURE__ */ ((HttpStatusCode2) => { HttpStatusCode2[HttpStatusCode2["BadRequest"] = 400] = "BadRequest"; HttpStatusCode2[HttpStatusCode2["NotFound"] = 404] = "NotFound"; HttpStatusCode2[HttpStatusCode2["Unauthorized"] = 401] = "Unauthorized"; HttpStatusCode2[HttpStatusCode2["Forbidden"] = 403] = "Forbidden"; HttpStatusCode2[HttpStatusCode2["TooManyRequests"] = 429] = "TooManyRequests"; HttpStatusCode2[HttpStatusCode2["RequestTimeout"] = 408] = "RequestTimeout"; HttpStatusCode2[HttpStatusCode2["InternalServerError"] = 500] = "InternalServerError"; HttpStatusCode2[HttpStatusCode2["ServiceUnavailable"] = 503] = "ServiceUnavailable"; return HttpStatusCode2; })(HttpStatusCode || {}); var ExaError = class extends Error { /** * Create a new ExaError * @param message Error message * @param statusCode HTTP status code * @param timestamp ISO timestamp from API * @param path Path that caused the error */ constructor(message, statusCode, timestamp, path) { super(message); this.name = "ExaError"; this.statusCode = statusCode; this.timestamp = timestamp ?? (/* @__PURE__ */ new Date()).toISOString(); this.path = path; } }; // src/zod-utils.ts var import_zod = require("zod"); var import_zod_to_json_schema = require("zod-to-json-schema"); function isZodSchema(obj) { return obj instanceof import_zod.ZodType; } function zodToJsonSchema(schema) { return (0, import_zod_to_json_schema.zodToJsonSchema)(schema, { $refStrategy: "none" }); } // src/research/base.ts var ResearchBaseClient = class { constructor(client) { this.client = client; } async request(endpoint, method = "POST", data, params) { return this.client.request( `/research/v1${endpoint}`, method, data, params ); } async rawRequest(endpoint, method = "POST", data, params) { return this.client.rawRequest( `/research/v1${endpoint}`, method, data, params ); } buildPaginationParams(pagination) { const params = {}; if (!pagination) return params; if (pagination.cursor) params.cursor = pagination.cursor; if (pagination.limit) params.limit = pagination.limit; return params; } }; // src/research/client.ts var ResearchClient = class extends ResearchBaseClient { constructor(client) { super(client); } async create(params) { const { instructions, model, outputSchema } = params; let schema = outputSchema; if (schema && isZodSchema(schema)) { schema = zodToJsonSchema(schema); } const payload = { instructions, model: model ?? "exa-research-fast" }; if (schema) { payload.outputSchema = schema; } return this.request("", "POST", payload); } get(researchId, options) { if (options?.stream) { const promise = async () => { const params = { stream: "true" }; if (options.events !== void 0) { params.events = options.events.toString(); } const resp = await this.rawRequest( `/${researchId}`, "GET", void 0, params ); if (!resp.body) { throw new Error("No response body for SSE stream"); } const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; function processPart(part) { const lines = part.split("\n"); let data = lines.slice(1).join("\n"); if (data.startsWith("data:")) { data = data.slice(5).trimStart(); } try { return JSON.parse(data); } catch (e) { return null; } } async function* streamEvents() { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); let parts = buffer.split("\n\n"); buffer = parts.pop() ?? ""; for (const part of parts) { const processed = processPart(part); if (processed) { yield processed; } } } if (buffer.trim()) { const processed = processPart(buffer.trim()); if (processed) { yield processed; } } } return streamEvents(); }; return promise(); } else { const params = { stream: "false" }; if (options?.events !== void 0) { params.events = options.events.toString(); } return this.request( `/${researchId}`, "GET", void 0, params ); } } async list(options) { const params = this.buildPaginationParams(options); return this.request("", "GET", void 0, params); } async pollUntilFinished(researchId, options) { const pollInterval = options?.pollInterval ?? 1e3; const timeoutMs = options?.timeoutMs ?? 10 * 60 * 1e3; const maxConsecutiveFailures = 5; const startTime = Date.now(); let consecutiveFailures = 0; while (true) { try { const research = await this.get(researchId, { events: options?.events }); consecutiveFailures = 0; if (research.status === "completed" || research.status === "failed" || research.status === "canceled") { return research; } } catch (err) { consecutiveFailures += 1; if (consecutiveFailures >= maxConsecutiveFailures) { throw new Error( `Polling failed ${maxConsecutiveFailures} times in a row for research ${researchId}: ${err}` ); } } if (Date.now() - startTime > timeoutMs) { throw new Error( `Polling timeout: Research ${researchId} did not complete within ${timeoutMs}ms` ); } await new Promise((resolve) => setTimeout(resolve, pollInterval)); } } }; // src/websets/base.ts var WebsetsBaseClient = class { /** * Initialize a new Websets base client * @param client The Exa client instance */ constructor(client) { this.client = client; } /** * Make a request to the Websets API * @param endpoint The endpoint path * @param method The HTTP method * @param data Optional request body data * @param params Optional query parameters * @returns The response JSON * @throws ExaError with API error details if the request fails */ async request(endpoint, method = "POST", data, params, headers) { return this.client.request( `/websets${endpoint}`, method, data, params, headers ); } /** * Helper to build pagination parameters * @param pagination The pagination parameters * @returns QueryParams object with pagination parameters */ buildPaginationParams(pagination) { const params = {}; if (!pagination) return params; if (pagination.cursor) params.cursor = pagination.cursor; if (pagination.limit) params.limit = pagination.limit; return params; } }; // src/websets/enrichments.ts var WebsetEnrichmentsClient = class extends WebsetsBaseClient { /** * Create an Enrichment for a Webset * @param websetId The ID of the Webset * @param params The enrichment parameters * @returns The created Webset Enrichment */ async create(websetId, params) { return this.request( `/v0/websets/${websetId}/enrichments`, "POST", params ); } /** * Get an Enrichment by ID * @param websetId The ID of the Webset * @param id The ID of the Enrichment * @returns The Webset Enrichment */ async get(websetId, id) { return this.request( `/v0/websets/${websetId}/enrichments/${id}`, "GET" ); } /** * Delete an Enrichment * @param websetId The ID of the Webset * @param id The ID of the Enrichment * @returns The deleted Webset Enrichment */ async delete(websetId, id) { return this.request( `/v0/websets/${websetId}/enrichments/${id}`, "DELETE" ); } /** * Update an Enrichment * @param websetId The ID of the Webset * @param id The ID of the Enrichment * @param params The enrichment update parameters * @returns Promise that resolves when the update is complete */ async update(websetId, id, params) { return this.request( `/v0/websets/${websetId}/enrichments/${id}`, "PATCH", params ); } /** * Cancel a running Enrichment * @param websetId The ID of the Webset * @param id The ID of the Enrichment * @returns The canceled Webset Enrichment */ async cancel(websetId, id) { return this.request( `/v0/websets/${websetId}/enrichments/${id}/cancel`, "POST" ); } }; // src/websets/events.ts var EventsClient = class extends WebsetsBaseClient { /** * Initialize a new Events client * @param client The Exa client instance */ constructor(client) { super(client); } /** * List all Events * @param options Optional filtering and pagination options * @returns The list of Events */ async list(options) { const params = { cursor: options?.cursor, limit: options?.limit, types: options?.types }; return this.request( "/v0/events", "GET", void 0, params ); } /** * Get an Event by ID * @param id The ID of the Event * @returns The Event */ async get(id) { return this.request(`/v0/events/${id}`, "GET"); } }; // src/websets/openapi.ts var CreateEnrichmentParametersFormat = /* @__PURE__ */ ((CreateEnrichmentParametersFormat2) => { CreateEnrichmentParametersFormat2["text"] = "text"; CreateEnrichmentParametersFormat2["date"] = "date"; CreateEnrichmentParametersFormat2["number"] = "number"; CreateEnrichmentParametersFormat2["options"] = "options"; CreateEnrichmentParametersFormat2["email"] = "email"; CreateEnrichmentParametersFormat2["phone"] = "phone"; CreateEnrichmentParametersFormat2["url"] = "url"; return CreateEnrichmentParametersFormat2; })(CreateEnrichmentParametersFormat || {}); var CreateImportParametersFormat = /* @__PURE__ */ ((CreateImportParametersFormat2) => { CreateImportParametersFormat2["csv"] = "csv"; return CreateImportParametersFormat2; })(CreateImportParametersFormat || {}); var WebsetImportSource = /* @__PURE__ */ ((WebsetImportSource2) => { WebsetImportSource2["import"] = "import"; WebsetImportSource2["webset"] = "webset"; return WebsetImportSource2; })(WebsetImportSource || {}); var EventType = /* @__PURE__ */ ((EventType2) => { EventType2["webset_created"] = "webset.created"; EventType2["webset_deleted"] = "webset.deleted"; EventType2["webset_paused"] = "webset.paused"; EventType2["webset_idle"] = "webset.idle"; EventType2["webset_search_created"] = "webset.search.created"; EventType2["webset_search_canceled"] = "webset.search.canceled"; EventType2["webset_search_completed"] = "webset.search.completed"; EventType2["webset_search_updated"] = "webset.search.updated"; EventType2["import_created"] = "import.created"; EventType2["import_completed"] = "import.completed"; EventType2["webset_item_created"] = "webset.item.created"; EventType2["webset_item_enriched"] = "webset.item.enriched"; EventType2["monitor_created"] = "monitor.created"; EventType2["monitor_updated"] = "monitor.updated"; EventType2["monitor_deleted"] = "monitor.deleted"; EventType2["monitor_run_created"] = "monitor.run.created"; EventType2["monitor_run_completed"] = "monitor.run.completed"; EventType2["webset_export_created"] = "webset.export.created"; EventType2["webset_export_completed"] = "webset.export.completed"; return EventType2; })(EventType || {}); var ImportFailedReason = /* @__PURE__ */ ((ImportFailedReason2) => { ImportFailedReason2["invalid_format"] = "invalid_format"; ImportFailedReason2["invalid_file_content"] = "invalid_file_content"; ImportFailedReason2["missing_identifier"] = "missing_identifier"; return ImportFailedReason2; })(ImportFailedReason || {}); var ImportFormat = /* @__PURE__ */ ((ImportFormat2) => { ImportFormat2["csv"] = "csv"; ImportFormat2["webset"] = "webset"; return ImportFormat2; })(ImportFormat || {}); var ImportObject = /* @__PURE__ */ ((ImportObject2) => { ImportObject2["import"] = "import"; return ImportObject2; })(ImportObject || {}); var ImportStatus = /* @__PURE__ */ ((ImportStatus2) => { ImportStatus2["pending"] = "pending"; ImportStatus2["processing"] = "processing"; ImportStatus2["completed"] = "completed"; ImportStatus2["failed"] = "failed"; return ImportStatus2; })(ImportStatus || {}); var MonitorObject = /* @__PURE__ */ ((MonitorObject2) => { MonitorObject2["monitor"] = "monitor"; return MonitorObject2; })(MonitorObject || {}); var MonitorStatus = /* @__PURE__ */ ((MonitorStatus2) => { MonitorStatus2["enabled"] = "enabled"; MonitorStatus2["disabled"] = "disabled"; return MonitorStatus2; })(MonitorStatus || {}); var MonitorRunObject = /* @__PURE__ */ ((MonitorRunObject2) => { MonitorRunObject2["monitor_run"] = "monitor_run"; return MonitorRunObject2; })(MonitorRunObject || {}); var MonitorRunStatus = /* @__PURE__ */ ((MonitorRunStatus2) => { MonitorRunStatus2["created"] = "created"; MonitorRunStatus2["running"] = "running"; MonitorRunStatus2["completed"] = "completed"; MonitorRunStatus2["canceled"] = "canceled"; MonitorRunStatus2["failed"] = "failed"; return MonitorRunStatus2; })(MonitorRunStatus || {}); var MonitorRunType = /* @__PURE__ */ ((MonitorRunType2) => { MonitorRunType2["search"] = "search"; MonitorRunType2["refresh"] = "refresh"; return MonitorRunType2; })(MonitorRunType || {}); var UpdateMonitorStatus = /* @__PURE__ */ ((UpdateMonitorStatus2) => { UpdateMonitorStatus2["enabled"] = "enabled"; UpdateMonitorStatus2["disabled"] = "disabled"; return UpdateMonitorStatus2; })(UpdateMonitorStatus || {}); var WebhookStatus = /* @__PURE__ */ ((WebhookStatus2) => { WebhookStatus2["active"] = "active"; WebhookStatus2["inactive"] = "inactive"; return WebhookStatus2; })(WebhookStatus || {}); var WebsetStatus = /* @__PURE__ */ ((WebsetStatus2) => { WebsetStatus2["idle"] = "idle"; WebsetStatus2["pending"] = "pending"; WebsetStatus2["running"] = "running"; WebsetStatus2["paused"] = "paused"; return WebsetStatus2; })(WebsetStatus || {}); var WebsetEnrichmentStatus = /* @__PURE__ */ ((WebsetEnrichmentStatus2) => { WebsetEnrichmentStatus2["pending"] = "pending"; WebsetEnrichmentStatus2["canceled"] = "canceled"; WebsetEnrichmentStatus2["completed"] = "completed"; return WebsetEnrichmentStatus2; })(WebsetEnrichmentStatus || {}); var WebsetEnrichmentFormat = /* @__PURE__ */ ((WebsetEnrichmentFormat2) => { WebsetEnrichmentFormat2["text"] = "text"; WebsetEnrichmentFormat2["date"] = "date"; WebsetEnrichmentFormat2["number"] = "number"; WebsetEnrichmentFormat2["options"] = "options"; WebsetEnrichmentFormat2["email"] = "email"; WebsetEnrichmentFormat2["phone"] = "phone"; WebsetEnrichmentFormat2["url"] = "url"; return WebsetEnrichmentFormat2; })(WebsetEnrichmentFormat || {}); var WebsetItemSource = /* @__PURE__ */ ((WebsetItemSource2) => { WebsetItemSource2["search"] = "search"; WebsetItemSource2["import"] = "import"; return WebsetItemSource2; })(WebsetItemSource || {}); var WebsetItemEvaluationSatisfied = /* @__PURE__ */ ((WebsetItemEvaluationSatisfied2) => { WebsetItemEvaluationSatisfied2["yes"] = "yes"; WebsetItemEvaluationSatisfied2["no"] = "no"; WebsetItemEvaluationSatisfied2["unclear"] = "unclear"; return WebsetItemEvaluationSatisfied2; })(WebsetItemEvaluationSatisfied || {}); var WebsetExcludeSource = /* @__PURE__ */ ((WebsetExcludeSource2) => { WebsetExcludeSource2["import"] = "import"; WebsetExcludeSource2["webset"] = "webset"; return WebsetExcludeSource2; })(WebsetExcludeSource || {}); var WebsetSearchScopeSource = /* @__PURE__ */ ((WebsetSearchScopeSource2) => { WebsetSearchScopeSource2["import"] = "import"; WebsetSearchScopeSource2["webset"] = "webset"; return WebsetSearchScopeSource2; })(WebsetSearchScopeSource || {}); var WebsetSearchStatus = /* @__PURE__ */ ((WebsetSearchStatus2) => { WebsetSearchStatus2["created"] = "created"; WebsetSearchStatus2["pending"] = "pending"; WebsetSearchStatus2["running"] = "running"; WebsetSearchStatus2["completed"] = "completed"; WebsetSearchStatus2["canceled"] = "canceled"; return WebsetSearchStatus2; })(WebsetSearchStatus || {}); var WebsetSearchBehavior = /* @__PURE__ */ ((WebsetSearchBehavior2) => { WebsetSearchBehavior2["override"] = "override"; WebsetSearchBehavior2["append"] = "append"; return WebsetSearchBehavior2; })(WebsetSearchBehavior || {}); var WebsetSearchCanceledReason = /* @__PURE__ */ ((WebsetSearchCanceledReason2) => { WebsetSearchCanceledReason2["webset_deleted"] = "webset_deleted"; WebsetSearchCanceledReason2["webset_canceled"] = "webset_canceled"; return WebsetSearchCanceledReason2; })(WebsetSearchCanceledReason || {}); // src/websets/imports.ts var ImportsClient = class extends WebsetsBaseClient { async create(params, csv) { if (csv === void 0) { return this.request( "/v0/imports", "POST", params ); } let csvBuffer; if (typeof csv === "string") { csvBuffer = Buffer.from(csv, "utf8"); } else if (Buffer.isBuffer(csv)) { csvBuffer = csv; } else { throw new ExaError( "Invalid CSV data input. Must be string or Buffer", 400 /* BadRequest */ ); } const sizeInBytes = csvBuffer.length; const sizeInMB = Math.max(1, Math.ceil(sizeInBytes / (1024 * 1024))); const csvText = csvBuffer.toString("utf8"); const lines = csvText.split("\n").filter((line) => line.trim().length > 0); const recordCount = Math.max(0, lines.length - 1); if (sizeInMB > 50) { throw new ExaError( `CSV file too large: ${sizeInMB}MB. Maximum size is 50MB.`, 400 /* BadRequest */ ); } if (recordCount === 0) { throw new ExaError( "CSV file appears to have no data records (only header or empty)", 400 /* BadRequest */ ); } const createParams = { title: params.title, format: "csv" /* csv */, entity: params.entity, size: sizeInBytes, count: recordCount, metadata: params.metadata, csv: params.csv }; const importResponse = await this.request( "/v0/imports", "POST", createParams ); try { const uploadResponse = await fetch(importResponse.uploadUrl, { method: "PUT", body: csvBuffer }); if (!uploadResponse.ok) { const errorText = await uploadResponse.text(); throw new ExaError( `Upload failed: ${uploadResponse.status} ${uploadResponse.statusText}. ${errorText}`, 400 /* BadRequest */ ); } } catch (error) { if (error instanceof ExaError) { throw error; } throw new ExaError( `Failed to upload CSV data: ${error.message}`, 400 /* BadRequest */ ); } return importResponse; } /** * Get an Import by ID * @param id The ID of the Import * @returns The Import */ async get(id) { return this.request(`/v0/imports/${id}`, "GET"); } /** * List all Imports * @param options Pagination options * @returns The list of Imports */ async list(options) { const params = this.buildPaginationParams(options); return this.request( "/v0/imports", "GET", void 0, params ); } /** * Update an Import * @param id The ID of the Import * @param params The import update parameters * @returns The updated Import */ async update(id, params) { return this.request(`/v0/imports/${id}`, "PATCH", params); } /** * Delete an Import * @param id The ID of the Import * @returns The deleted Import */ async delete(id) { return this.request(`/v0/imports/${id}`, "DELETE"); } /** * Wait until an Import is completed or failed * @param id The ID of the Import * @param options Configuration options for timeout and polling * @returns The Import once it reaches a final state (completed or failed) * @throws Error if the Import does not complete within the timeout or fails */ async waitUntilCompleted(id, options) { const timeout = options?.timeout ?? 30 * 60 * 1e3; const pollInterval = options?.pollInterval ?? 2e3; const onPoll = options?.onPoll; const startTime = Date.now(); while (true) { const importItem = await this.get(id); if (onPoll) { onPoll(importItem.status); } if (importItem.status === "completed" /* completed */) { return importItem; } if (importItem.status === "failed" /* failed */) { throw new ExaError( `Import ${id} failed: ${importItem.failedMessage || "Unknown error"}`, 400 /* BadRequest */ ); } if (Date.now() - startTime > timeout) { throw new ExaError( `Import ${id} did not complete within ${timeout}ms. Current status: ${importItem.status}`, 408 /* RequestTimeout */ ); } await new Promise((resolve) => setTimeout(resolve, pollInterval)); } } }; // src/websets/items.ts var WebsetItemsClient = class extends WebsetsBaseClient { /** * List all Items for a Webset * @param websetId The ID of the Webset * @param params - Optional pagination and filtering parameters * @returns A promise that resolves with the list of Items */ list(websetId, params) { const queryParams = { ...this.buildPaginationParams(params), sourceId: params?.sourceId }; return this.request( `/v0/websets/${websetId}/items`, "GET", void 0, queryParams ); } /** * Iterate through all Items in a Webset, handling pagination automatically * @param websetId The ID of the Webset * @param options Pagination options * @returns Async generator of Webset Items */ async *listAll(websetId, options) { let cursor = void 0; const pageOptions = options ? { ...options } : {}; while (true) { pageOptions.cursor = cursor; const response = await this.list(websetId, pageOptions); for (const item of response.data) { yield item; } if (!response.hasMore || !response.nextCursor) { break; } cursor = response.nextCursor; } } /** * Collect all items from a Webset into an array * @param websetId The ID of the Webset * @param options Pagination options * @returns Promise resolving to an array of all Webset Items */ async getAll(websetId, options) { const items = []; for await (const item of this.listAll(websetId, options)) { items.push(item); } return items; } /** * Get an Item by ID * @param websetId The ID of the Webset * @param id The ID of the Item * @returns The Webset Item */ async get(websetId, id) { return this.request( `/v0/websets/${websetId}/items/${id}`, "GET" ); } /** * Delete an Item * @param websetId The ID of the Webset * @param id The ID of the Item * @returns The deleted Webset Item */ async delete(websetId, id) { return this.request( `/v0/websets/${websetId}/items/${id}`, "DELETE" ); } }; // src/websets/monitors.ts var WebsetMonitorRunsClient = class extends WebsetsBaseClient { /** * List all runs for a Monitor * @param monitorId The ID of the Monitor * @param options Pagination options * @returns The list of Monitor runs */ async list(monitorId, options) { const params = this.buildPaginationParams(options); return this.request( `/v0/monitors/${monitorId}/runs`, "GET", void 0, params ); } /** * Get a specific Monitor run * @param monitorId The ID of the Monitor * @param runId The ID of the Monitor run * @returns The Monitor run */ async get(monitorId, runId) { return this.request( `/v0/monitors/${monitorId}/runs/${runId}`, "GET" ); } }; var WebsetMonitorsClient = class extends WebsetsBaseClient { constructor(client) { super(client); this.runs = new WebsetMonitorRunsClient(client); } /** * Create a Monitor * @param params The monitor parameters * @returns The created Monitor */ async create(params) { return this.request("/v0/monitors", "POST", params); } /** * Get a Monitor by ID * @param id The ID of the Monitor * @returns The Monitor */ async get(id) { return this.request(`/v0/monitors/${id}`, "GET"); } /** * List all Monitors * @param options Pagination and filtering options * @returns The list of Monitors */ async list(options) { const params = { cursor: options?.cursor, limit: options?.limit, websetId: options?.websetId }; return this.request( "/v0/monitors", "GET", void 0, params ); } /** * Update a Monitor * @param id The ID of the Monitor * @param params The monitor update parameters (status, metadata) * @returns The updated Monitor */ async update(id, params) { return this.request(`/v0/monitors/${id}`, "PATCH", params); } /** * Delete a Monitor * @param id The ID of the Monitor * @returns The deleted Monitor */ async delete(id) { return this.request(`/v0/monitors/${id}`, "DELETE"); } }; // src/websets/searches.ts var WebsetSearchesClient = class extends WebsetsBaseClient { /** * Create a new Search for the Webset * @param websetId The ID of the Webset * @param params The search parameters * @returns The created Webset Search */ async create(websetId, params, options) { return this.request( `/v0/websets/${websetId}/searches`, "POST", params, void 0, options?.headers ); } /** * Get a Search by ID * @param websetId The ID of the Webset * @param id The ID of the Search * @returns The Webset Search */ async get(websetId, id) { return this.request( `/v0/websets/${websetId}/searches/${id}`, "GET" ); } /** * Cancel a running Search * @param websetId The ID of the Webset * @param id The ID of the Search * @returns The canceled Webset Search */ async cancel(websetId, id) { return this.request( `/v0/websets/${websetId}/searches/${id}/cancel`, "POST" ); } }; // src/websets/webhooks.ts var WebsetWebhooksClient = class extends WebsetsBaseClient { /** * Create a Webhook * @param params The webhook parameters * @returns The created Webhook */ async create(params) { return this.request("/v0/webhooks", "POST", params); } /** * Get a Webhook by ID * @param id The ID of the Webhook * @returns The Webhook */ async get(id) { return this.request(`/v0/webhooks/${id}`, "GET"); } /** * List all Webhooks * @param options Pagination options * @returns The list of Webhooks */ async list(options) { const params = this.buildPaginationParams(options); return this.request( "/v0/webhooks", "GET", void 0, params ); } /** * Iterate through all Webhooks, handling pagination automatically * @param options Pagination options * @returns Async generator of Webhooks */ async *listAll(options) { let cursor = void 0; const pageOptions = options ? { ...options } : {}; while (true) { pageOptions.cursor = cursor; const response = await this.list(pageOptions); for (const webhook of response.data) { yield webhook; } if (!response.hasMore || !response.nextCursor) { break; } cursor = response.nextCursor; } } /** * Collect all Webhooks into an array * @param options Pagination options * @returns Promise resolving to an array of all Webhooks */ async getAll(options) { const webhooks = []; for await (const webhook of this.listAll(options)) { webhooks.push(webhook); } return webhooks; } /** * Update a Webhook * @param id The ID of the Webhook * @param params The webhook update parameters (events, metadata, url) * @returns The updated Webhook */ async update(id, params) { return this.request(`/v0/webhooks/${id}`, "PATCH", params); } /** * Delete a Webhook * @param id The ID of the Webhook * @returns The deleted Webhook */ async delete(id) { return this.request(`/v0/webhooks/${id}`, "DELETE"); } /** * List all attempts for a Webhook * @param id The ID of the Webhook * @param options Pagination and filtering options * @returns The list of Webhook attempts */ async listAttempts(id, options) { const params = { cursor: options?.cursor, limit: options?.limit, eventType: options?.eventType, successful: options?.successful }; return this.request( `/v0/webhooks/${id}/attempts`, "GET", void 0, params ); } /** * Iterate through all attempts for a Webhook, handling pagination automatically * @param id The ID of the Webhook * @param options Pagination and filtering options * @returns Async generator of Webhook attempts */ async *listAllAttempts(id, options) { let cursor = void 0; const pageOptions = options ? { ...options } : {}; while (true) { pageOptions.cursor = cursor; const response = await this.listAttempts(id, pageOptions); for (const attempt of response.data) { yield attempt; } if (!response.hasMore || !response.nextCursor) { break; } cursor = response.nextCursor; } } /** * Collect all attempts for a Webhook into an array * @param id The ID of the Webhook * @param options Pagination and filtering options * @returns Promise resolving to an array of all Webhook attempts */ async getAllAttempts(id, options) { const attempts = []; for await (const attempt of this.listAllAttempts(id, options)) { attempts.push(attempt); } return attempts; } }; // src/websets/client.ts var WebsetsClient = class extends WebsetsBaseClient { /** * Initialize a new Websets client * @param client The Exa client instance */ constructor(client) { super(client); this.events = new EventsClient(client); this.imports = new ImportsClient(client); this.items = new WebsetItemsClient(client); this.searches = new WebsetSearchesClient(client); this.enrichments = new WebsetEnrichmentsClient(client); this.monitors = new WebsetMonitorsClient(client); this.webhooks = new WebsetWebhooksClient(client); } /** * Create a new Webset * @param params The Webset creation parameters * @returns The created Webset */ async create(params, options) { return this.request( "/v0/websets", "POST", params, void 0, options?.headers ); } /** * Preview a webset * @param params The preview parameters * @returns The preview response showing how the query will be decomposed */ async preview(params) { return this.request( "/v0/websets/preview", "POST", params ); } /** * Get a Webset by ID * @param id The ID of the Webset * @param expand Optional array of relations to expand * @returns The Webset */ async get(id, expand) { const params = {}; if (expand) { params.expand = expand; } return this.request( `/v0/websets/${id}`, "GET", void 0, params ); } /** * List all Websets * @param options Pagination options (filtering by status is not supported by API) * @returns The list of Websets */ async list(options) { const params = this.buildPaginationParams(options); return this.request( "/v0/websets", "GET", void 0, params ); } /** * Iterate through all Websets, handling pagination automatically * @param options Pagination options * @returns Async generator of Websets */ async *listAll(options) { let cursor = void 0; const pageOptions = options ? { ...options } : {}; while (true) { pageOptions.cursor = cursor; const response = await this.list(pageOptions); for (const webset of response.data) { yield webset; } if (!response.hasMore || !response.nextCursor) { break; } cursor = response.nextCursor; } } /** * Collect all Websets into an array * @param options Pagination options * @returns Promise resolving to an array of all Websets */ async getAll(options) { const websets = []; for await (const webset of this.listAll(options)) { websets.push(webset); } return websets; } /** * Update a Webset * @param id The ID of the Webset * @param params The Webset update parameters * @returns The updated Webset */ async update(id, params) { return this.request(`/v0/websets/${id}`, "POST", params); } /** * Delete a Webset * @param id The ID of the Webset * @returns The deleted Webset */ async delete(id) { return this.request(`/v0/websets/${id}`, "DELETE"); } /** * Cancel a running Webset * @param id The ID or external ID of the Webset * @returns The canceled Webset (as returned by the API) */ async cancel(id) { return this.request(`/v0/websets/${id}/cancel`, "POST"); } /** * Wait until a Webset is idle * @param id The ID of the Webset * @param options Configuration options for timeout and polling * @returns The Webset once it becomes idle * @throws Error if the Webset does not become idle within the timeout */ async waitUntilIdle(id, options) { let timeout; let pollInterval = 1e3; let onPoll; if (typeof options === "number") { timeout = options; } else if (options) { timeout = options.timeout; pollInterval = options.pollInterval || 1e3; onPoll = options.onPoll; } const startTime = Date.now(); while (true) { const webset = await this.get(id); if (onPoll) { onPoll(webset.status); } if (webset.status === "idle" /* idle */) { return webset; } if (timeout && Date.now() - startTime > timeout) { throw new ExaError( `Webset ${id} did not reach idle state within ${timeout}ms. Current status: ${webset.status}`, 408 /* RequestTimeout */ ); } await new Promise((resolve) => setTimeout(resolve, pollInterval)); } } }; // src/index.ts var fetchImpl = typeof global !== "undefined" && global.fetch ? global.fetch : import_cross_fetch.default; var HeadersImpl = typeof global !== "undefined" && global.Headers ? global.Headers : import_cross_fetch.Headers; var DEFAULT_MAX_CHARACTERS = 1e4; var Exa2 = class { /** * Helper method to separate out the contents-specific options from the rest. */ extractContentsOptions(options) { const { text, summary, subpages, subpageTarget, extras, livecrawl, livecrawlTimeout, context, ...rest } = options; const contentsOptions = {}; if (text === void 0 && summary === void 0 && extras === void 0) { contentsOptions.text = true; } if (text !== void 0) contentsOptions.text = text; if (summary !== void 0) { if (typeof summary === "object" && summary !== null && "schema" in summary && summary.schema && isZodSchema(summary.schema)) { contentsOptions.summary = { ...summary, schema: zodToJsonSchema(summary.schema) }; } else { contentsOptions.summary = summary; } } if (subpages !== void 0) contentsOptions.subpages = subpages; if (subpageTarget !== void 0) contentsOptions.subpageTarget = subpageTarget; if (extras !== void 0) contentsOptions.extras = extras; if (livecrawl !== void 0) contentsOptions.livecrawl = livecrawl; if (livecrawlTimeout !== void 0) contentsOptions.livecrawlTimeout = livecrawlTimeout; if (context !== void 0) contentsOptions.context = context; return { contentsOptions, restOptions: rest }; } /** * Constructs the Exa API client. * @param {string} apiKey - The API key for authentication. * @param {string} [baseURL] - The base URL of the Exa API. */ constructor(apiKey, baseURL = "https://api.exa.ai") { this.baseURL = baseURL; if (!apiKey) { apiKey = process.env.EXA_API_KEY; if (!apiKey) { throw new ExaError( "API key must be provided as an argument or as an environment variable (EXA_API_KEY)", 401 /* Unauthorized */ ); } } this.headers = new HeadersImpl({ "x-api-key": apiKey, "Content-Type": "application/json", "User-Agent": `exa-node ${package_default.version}` }); this.websets = new WebsetsClient(this); this.research = new ResearchClient(this); } /** * Makes a request to the Exa API. * @param {string} endpoint - The API endpoint to call. * @param {string} method - The HTTP method to use. * @param {any} [body] - The request body for POST requests. * @param {Record<string, any>} [params] - The query parameters. * @returns {Promise<any>} The response from the API. * @throws {ExaError} When any API request fails with structured error information */ async request(endpoint, method, body, params, headers) { let url = this.baseURL + endpoint; if (params && Object.keys(params).length > 0) { const searchParams = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { if (Array.isArray(value)) { for (const item of value) { searchParams.append(key, item); } } else if (value !== void 0) { searchParams.append(key, String(value)); } } url += `?${searchParams.toString()}`; } let combinedHeaders = {}; if (this.headers instanceof HeadersImpl) { this.headers.forEach((value, key) => { combinedHeaders[key] = value; }); } else { combinedHeaders = { ...this.headers }; } if (headers) { combinedHeaders = { ...combinedHeaders, ...headers }; } const response = await fetchImpl(url, { method, headers: combinedHeaders, body: body ? JSON.stringify(body) : void 0 }); if (!response.ok) { const errorData = await response.json(); if (!errorData.statusCode) { errorData.statusCode = response.status; } if (!errorData.timestamp) { errorData.timestamp = (/* @__PURE__ */ new Date()).toISOString(); } if (!errorData.path) { errorData.path = endpoint; } let message = errorData.error || "Unknown error"; if (errorData.message) { message += (message.length > 0 ? ". " : "") + errorData.message; } throw new ExaError( message, response.status, errorData.timestamp, errorData.path ); } const contentType = response.headers.get("content-type") || ""; if (contentType.includes("text/event-stream")) { return await this.parseSSEStream(response); } return await response.json(); } async rawRequest(endpoint, method = "POST", body, queryParams) { let url = this.baseURL + endpoint; if (queryParams) { const searchParams = new URLSearchParams(); for (const [key, value] of Object.entries(queryParams)) { if (Array.isArray(value)) { for (const item of value) { searchParams.append(key, String(item)); } } else if (value !== void 0) { searchParams.append(key, String(value)); } } url += `?${searchParams.toString()}`; } const response = await fetchImpl(url, { method, headers: this.headers, body: body ? JSON.stringify(body) : void 0 }); return response; } async search(query, options) { if (options === void 0 || !("contents" in options)) { return await this.request("/search", "POST", { query, ...options, contents: { text: { maxCharacters: DEFAULT_MAX_CHARACTERS } } }); } if (options.contents === false || options.contents === null || options.contents === void 0) { const { contents, ...restOptions } = options; return await this.request("/search", "POST", { query, ...restOptions }); } return await this.request("/search", "POST", { query, ...options }); } /** * @deprecated Use `search()` instead. The search method now returns text contents by default. * * Migration examples: * - `searchAndContents(query)` → `search(query)` * - `searchAndContents(query, { text: true })` → `search(query, { contents: { text: true } })` * - `searchAndContents(query, { summary: true })` → `search(query, { contents: { summary: true } })` * * Performs a search with an Exa prompt-engineered query and returns the contents of the documents. * * @param {string} query - The query string. * @param {RegularSearchOptions & T} [options] - Additional search + contents options * @returns {Promise<SearchResponse<T>>} A list of relevant search results with requested contents. */ async searchAndContents(query, options) { const { contentsOptions, restOptions } = options === void 0 ? { contentsOptions: { text: { maxCharacters: DEFAULT_MAX_CHARACTERS } }, restOptions: {} } : this.extractContentsOptions(options); return await this.request("/search", "POST", { query, contents: contentsOptions, ...restOptions }); } async findSimilar(url, options) { if (options === void 0 || !("contents" in options)) { return await this.request("/findSimilar", "POST", { url, ...options, contents: { text: { maxCharacters: DEFAULT_MAX_CHARACTERS } } }); } if (options.contents === false || options.contents === null || options.contents === void 0) { const { contents, ...restOptions } = options; return await this.request("/findSimilar", "POST", { url, ...restOptions }); } return await this.request("/findSimilar", "POST", { url, ...options }); } /** * @deprecated Use `findSimilar()` instead. The findSimilar method now returns text contents by default. * * Migration examples: * - `findSimilarAndContents(url)` → `findSimilar(url)` * - `findSimilarAndContents(url, { text: true })` → `findSimilar(url, { contents: { text: true } })` * - `findSimilarAndContents(url, { summary: true })` → `findSimilar(url, { contents: { summary: true } })` * * Finds similar links to the provided URL and returns the contents of the documents. * @param {string} url - The URL for which to find similar links. * @param {FindSimilarOptions & T} [options] - Additional options for finding similar links + contents. * @returns {Promise<SearchResponse<T>>} A list of similar search results, including requested contents. */ async findSimilarAndContents(url, options) { const { contentsOptions, restOptions } = options === void 0 ? { contentsOptions: { text: { maxCharacters: DEFAULT_MAX_CHARACTERS } }, restOptions: {} } : this.extractContentsOptions(options); return await this.request("/findSimilar", "POST", { url, contents: contentsOptions, ...restOptions }); } /** * Retrieves contents of documents based on URLs. * @param {string | string[] | SearchResult[]} urls - A URL or array of URLs, or an array of SearchResult objects. * @param {ContentsOptions} [options] - Additional options for retrieving document contents. * @returns {Promise<SearchResponse<T>>} A list of documen