exa-js
Version:
Exa SDK for Node.js and the browser
1,580 lines (1,563 loc) • 57.2 kB
JavaScript
"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