@mendable/firecrawl-js 4.46.0 → 4.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,6 @@ import {
6
6
  throwForBadResponse,
7
7
  normalizeAxiosError,
8
8
  } from "../utils/errorHandler";
9
- import { getVersion } from "../utils/getVersion";
10
9
 
11
10
  function toUploadBlob(input: ParseFile["data"], contentType?: string): Blob {
12
11
  if (typeof Blob !== "undefined" && input instanceof Blob) {
@@ -57,13 +56,9 @@ export async function parse(
57
56
 
58
57
  if (options) ensureValidParseOptions(options);
59
58
 
60
- const version = getVersion();
61
59
  const normalizedOptions: ParseOptions = {
62
60
  ...(options ?? {}),
63
- origin:
64
- typeof options?.origin === "string" && options.origin.includes("mcp")
65
- ? options.origin
66
- : (options?.origin ?? `js-sdk@${version}`),
61
+ origin: options?.origin || http.getOrigin(),
67
62
  };
68
63
 
69
64
  const formData = new FormData();
@@ -12,7 +12,6 @@ import type {
12
12
  import { SdkError } from "../types";
13
13
  import { HttpClient } from "../utils/httpClient";
14
14
  import { throwForBadResponse } from "../utils/errorHandler";
15
- import { getVersion } from "../utils/getVersion";
16
15
 
17
16
  const BASE = "/v2/search/research";
18
17
 
@@ -32,10 +31,14 @@ function appendParam(
32
31
  }
33
32
  }
34
33
 
35
- function withQuery(path: string, params: URLSearchParams): string {
34
+ function withQuery(
35
+ origin: string,
36
+ path: string,
37
+ params: URLSearchParams,
38
+ ): string {
36
39
  // Research endpoints are GETs, so the POST-body origin injection in
37
40
  // HttpClient never applies — attach it as a query param instead.
38
- params.append("origin", `js-sdk@${getVersion()}`);
41
+ params.append("origin", origin);
39
42
  const qs = params.toString();
40
43
  return qs ? `${path}?${qs}` : path;
41
44
  }
@@ -121,7 +124,7 @@ export class ResearchClient {
121
124
  appendParam(params, "to", options.to);
122
125
  try {
123
126
  const res = await this.http.get<SearchPapersResponse>(
124
- withQuery(`${BASE}/papers`, params),
127
+ withQuery(this.http.getOrigin(), `${BASE}/papers`, params),
125
128
  );
126
129
  if (res.status !== 200) throwForBadResponse(res, "search papers");
127
130
  return res.data;
@@ -161,7 +164,13 @@ export class ResearchClient {
161
164
  try {
162
165
  const res = await this.http.get<
163
166
  PaperMetadataResponse | ReadPaperResponse
164
- >(withQuery(`${BASE}/papers/${encodeURIComponent(id)}`, params));
167
+ >(
168
+ withQuery(
169
+ this.http.getOrigin(),
170
+ `${BASE}/papers/${encodeURIComponent(id)}`,
171
+ params,
172
+ ),
173
+ );
165
174
  if (res.status !== 200) throwForBadResponse(res, "get paper");
166
175
  return res.data;
167
176
  } catch (err) {
@@ -191,7 +200,11 @@ export class ResearchClient {
191
200
  appendParam(params, "anchor", options.anchor);
192
201
  try {
193
202
  const res = await this.http.get<SimilarPapersResponse>(
194
- withQuery(`${BASE}/papers/${encodeURIComponent(id)}/similar`, params),
203
+ withQuery(
204
+ this.http.getOrigin(),
205
+ `${BASE}/papers/${encodeURIComponent(id)}/similar`,
206
+ params,
207
+ ),
195
208
  );
196
209
  if (res.status !== 200) throwForBadResponse(res, "find similar papers");
197
210
  return res.data;
@@ -226,7 +239,7 @@ export class ResearchClient {
226
239
  appendParam(params, "k", options.k);
227
240
  try {
228
241
  const res = await this.http.get<GitHubSearchResponse>(
229
- withQuery(`${BASE}/github`, params),
242
+ withQuery(this.http.getOrigin(), `${BASE}/github`, params),
230
243
  );
231
244
  if (res.status !== 200) throwForBadResponse(res, "search github");
232
245
  return res.data;
@@ -9,7 +9,7 @@ import {
9
9
  } from "../types";
10
10
  import { HttpClient } from "../utils/httpClient";
11
11
  import { agentHintMetadata } from "../utils/agentHints";
12
- import { ensureValidScrapeOptions } from "../utils/validation";
12
+ import { ensureNoV1Options, ensureValidScrapeOptions } from "../utils/validation";
13
13
  import {
14
14
  throwForBadResponse,
15
15
  normalizeAxiosError,
@@ -17,6 +17,7 @@ import {
17
17
 
18
18
  function prepareSearchPayload(req: SearchRequest): Record<string, unknown> {
19
19
  if (!req.query || !req.query.trim()) throw new Error("Query cannot be empty");
20
+ ensureNoV1Options(req, "search");
20
21
  if (req.limit != null && req.limit <= 0)
21
22
  throw new Error("limit must be positive");
22
23
  if (req.timeout != null && req.timeout <= 0)
package/src/v2/types.ts CHANGED
@@ -915,10 +915,10 @@ export interface FindToolsOptions {
915
915
  }
916
916
 
917
917
  export interface FindToolsData {
918
- level: "providers" | "groups" | "tools";
918
+ level: "categories" | "providers" | "groups" | "tools";
919
919
  items: Array<{
920
920
  id: string;
921
- name: string;
921
+ name?: string;
922
922
  next?: AlexandriaCall;
923
923
  execute?: Pick<AlexandriaCall, "provider" | "capability">;
924
924
  [key: string]: unknown;
@@ -2,12 +2,33 @@ import { type AxiosError, type AxiosResponse } from "axios";
2
2
  import { SdkError, JobTimeoutError, parseRequiresAction } from "../types";
3
3
  import { agentHintMetadata } from "./agentHints";
4
4
 
5
+ /** Names the fields a validation error rejected, so a bare "Bad Request" says what to change. */
6
+ function describeValidationIssues(details: unknown): string | undefined {
7
+ if (!Array.isArray(details)) return undefined;
8
+ const parts = details.slice(0, 3).flatMap((issue) => {
9
+ if (!issue || typeof issue !== "object") return [];
10
+ const { path, message, keys } = issue as { path?: unknown; message?: unknown; keys?: unknown };
11
+ const what = Array.isArray(keys) && keys.length > 0
12
+ ? `unrecognized key(s) ${keys.join(", ")}`
13
+ : typeof message === "string" ? message : undefined;
14
+ if (!what) return [];
15
+ return Array.isArray(path) && path.length > 0 ? [`${path.join(".")}: ${what}`] : [what];
16
+ });
17
+ return parts.length > 0 ? parts.join("; ") : undefined;
18
+ }
19
+
20
+ function withIssues(message: unknown, details: unknown): string {
21
+ const text = typeof message === "string" ? message : JSON.stringify(message);
22
+ const issues = describeValidationIssues(details);
23
+ return issues && !text.includes(issues) ? `${text} (${issues})` : text;
24
+ }
25
+
5
26
  export function throwForBadResponse(resp: AxiosResponse, action: string): never {
6
27
  const status = resp.status;
7
28
  const body = resp.data || {};
8
29
  const msg = body?.error || body?.message || `Request failed (${status}) while trying to ${action}`;
9
30
  throw Object.assign(new SdkError(
10
- msg,
31
+ withIssues(msg, body?.details),
11
32
  status,
12
33
  body?.code,
13
34
  body?.details,
@@ -23,7 +44,7 @@ export function normalizeAxiosError(err: AxiosError, action: string): never {
23
44
  const message = body?.error || err.message || `Request failed${status ? ` (${status})` : ""} while trying to ${action}`;
24
45
  const code = (body?.code as string) || err.code;
25
46
  throw Object.assign(new SdkError(
26
- message,
47
+ withIssues(message, body?.details),
27
48
  status,
28
49
  code,
29
50
  body?.details ?? body,
@@ -1,14 +1,6 @@
1
- export function getVersion(): string {
2
- try {
3
- if (typeof process !== "undefined" && process.env && process.env.npm_package_version) {
4
- return process.env.npm_package_version as string;
5
- }
1
+ import { version } from "../../../package.json";
6
2
 
7
- // eslint-disable-next-line @typescript-eslint/no-var-requires
8
- const pkg = require("../../../package.json");
9
- return (pkg?.version as string) || "3.x.x";
10
- } catch {
11
- return "3.x.x";
12
- }
3
+ /** The SDK's own version, inlined at build time so a host app's npm_package_version never replaces it. */
4
+ export function getVersion(): string {
5
+ return version;
13
6
  }
14
-
@@ -12,6 +12,7 @@ export interface HttpClientOptions {
12
12
  timeoutMs?: number;
13
13
  maxRetries?: number;
14
14
  backoffFactor?: number; // seconds factor for 0.5, 1, 2...
15
+ origin?: string;
15
16
  }
16
17
 
17
18
  export interface RequestOptions {
@@ -25,12 +26,14 @@ export class HttpClient {
25
26
  private readonly apiUrl: string;
26
27
  private readonly maxRetries: number;
27
28
  private readonly backoffFactor: number;
29
+ private readonly origin: string;
28
30
 
29
31
  constructor(options: HttpClientOptions) {
30
32
  this.apiKey = options.apiKey;
31
33
  this.apiUrl = options.apiUrl.replace(/\/$/, "");
32
34
  this.maxRetries = options.maxRetries ?? 3;
33
35
  this.backoffFactor = options.backoffFactor ?? 0.5;
36
+ this.origin = options.origin || `js-sdk@${getVersion()}`;
34
37
  this.instance = axios.create({
35
38
  baseURL: this.apiUrl,
36
39
  timeout: options.timeoutMs ?? 300000,
@@ -52,6 +55,11 @@ export class HttpClient {
52
55
  return this.apiKey;
53
56
  }
54
57
 
58
+ /** The attribution origin stamped into requests that do not set their own. */
59
+ getOrigin(): string {
60
+ return this.origin;
61
+ }
62
+
55
63
  /** The client-wide default request timeout, for callers that need to
56
64
  * extend a specific request beyond it (e.g. scrape auto-resume). */
57
65
  getTimeoutMs(): number {
@@ -61,7 +69,6 @@ export class HttpClient {
61
69
  private async request<T = any>(
62
70
  config: AxiosRequestConfig,
63
71
  ): Promise<AxiosResponse<T>> {
64
- const version = getVersion();
65
72
  config.headers = {
66
73
  ...(config.headers || {}),
67
74
  };
@@ -89,9 +96,9 @@ export class HttpClient {
89
96
  cfg.data = {
90
97
  ...data,
91
98
  origin:
92
- typeof data.origin === "string" && data.origin.includes("mcp")
99
+ typeof data.origin === "string" && data.origin
93
100
  ? data.origin
94
- : `js-sdk@${version}`,
101
+ : this.origin,
95
102
  };
96
103
  }
97
104
 
@@ -39,7 +39,10 @@ export async function fetchAllPages<T = Document>(
39
39
  docs.push(d as T);
40
40
  }
41
41
  if (maxResults != null && docs.length >= maxResults) break;
42
- current = (payload.next ?? (Array.isArray(payload.data) ? null : payload.data?.next) ?? null) as string | null;
42
+ const next = (payload.next ?? (Array.isArray(payload.data) ? null : payload.data?.next) ?? null) as string | null;
43
+ // A running job's cursor can point back at itself until new documents land; re-requesting it here only spins.
44
+ if (next === current) break;
45
+ current = next;
43
46
  pageCount += 1;
44
47
  }
45
48
  return docs;
@@ -86,6 +86,34 @@ export function ensureValidFormats(formats?: FormatOption[]): void {
86
86
  }
87
87
  }
88
88
 
89
+ const V1_SCRAPE_OPTION_REPLACEMENTS: Record<string, string> = {
90
+ pageOptions: "use scrapeOptions",
91
+ includeLinks: "add 'links' to scrapeOptions.formats",
92
+ parsePDF: "use scrapeOptions.parsers",
93
+ };
94
+
95
+ const V1_OPTION_REPLACEMENTS: Record<"crawl" | "search", Record<string, string>> = {
96
+ crawl: {
97
+ crawlerOptions: "move its fields to the top level",
98
+ maxDepth: "use maxDiscoveryDepth",
99
+ allowBackwardLinks: "use crawlEntireDomain",
100
+ ignoreSitemap: "use sitemap: 'skip'",
101
+ ...V1_SCRAPE_OPTION_REPLACEMENTS,
102
+ },
103
+ search: V1_SCRAPE_OPTION_REPLACEMENTS,
104
+ };
105
+
106
+ /** Throws on v1 option names, which the v2 payload builders would otherwise drop without a word. */
107
+ export function ensureNoV1Options(options: object | undefined, method: "crawl" | "search"): void {
108
+ if (!options) return;
109
+ const replacements = V1_OPTION_REPLACEMENTS[method];
110
+ const found = Object.keys(replacements).filter((key) => (options as Record<string, unknown>)[key] != null);
111
+ if (found.length === 0) return;
112
+ throw new Error(
113
+ `${method}: ${found.map((key) => `${key} is a v1 option, ${replacements[key]}`).join("; ")}`,
114
+ );
115
+ }
116
+
89
117
  export function ensureValidScrapeOptions(options?: ScrapeOptions): void {
90
118
  if (!options) return;
91
119
  if (options.timeout != null && options.timeout <= 0) {
@@ -1,93 +0,0 @@
1
- var __getOwnPropNames = Object.getOwnPropertyNames;
2
- var __commonJS = (cb, mod) => function __require() {
3
- try {
4
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5
- } catch (e) {
6
- throw mod = 0, e;
7
- }
8
- };
9
-
10
- // package.json
11
- var require_package = __commonJS({
12
- "package.json"(exports, module) {
13
- module.exports = {
14
- name: "@mendable/firecrawl-js",
15
- version: "4.46.0",
16
- description: "JavaScript SDK for the Firecrawl API: web scraping, crawling, web search, and scientific literature search over a research paper index of PubMed, bioRxiv, medRxiv and arXiv abstracts",
17
- main: "dist/index.js",
18
- types: "dist/index.d.ts",
19
- exports: {
20
- "./package.json": "./package.json",
21
- ".": {
22
- import: "./dist/index.js",
23
- default: "./dist/index.cjs"
24
- }
25
- },
26
- type: "module",
27
- scripts: {
28
- build: "tsup",
29
- "build-and-publish": "pnpm run build && pnpm publish --access public",
30
- "publish-beta": "pnpm run build && pnpm publish --access public --tag beta",
31
- test: "vitest run --reporter=verbose src/__tests__/e2e/v2",
32
- "test:unit": "vitest run --reporter=verbose src/__tests__/unit"
33
- },
34
- repository: {
35
- type: "git",
36
- url: "git+https://github.com/firecrawl/firecrawl.git"
37
- },
38
- author: "Mendable.ai",
39
- license: "MIT",
40
- dependencies: {
41
- axios: "1.20.0",
42
- firecrawl: "4.30.1",
43
- "typescript-event-target": "^1.1.1",
44
- zod: "^3.23.8",
45
- "zod-to-json-schema": "^3.23.0"
46
- },
47
- bugs: {
48
- url: "https://github.com/firecrawl/firecrawl/issues"
49
- },
50
- homepage: "https://github.com/firecrawl/firecrawl#readme",
51
- devDependencies: {
52
- "@types/dotenv": "^8.2.0",
53
- "@types/node": "^20.12.12",
54
- "@types/uuid": "^9.0.8",
55
- dotenv: "^16.4.5",
56
- tsup: "^8.5.0",
57
- typescript: "^5.4.5",
58
- uuid: "^14.0.0",
59
- vitest: "^4.1.11"
60
- },
61
- keywords: [
62
- "firecrawl",
63
- "mendable",
64
- "crawler",
65
- "web",
66
- "scraper",
67
- "api",
68
- "sdk",
69
- "web-search",
70
- "literature-search",
71
- "research",
72
- "scientific-papers",
73
- "academic-search",
74
- "biomedical",
75
- "life-sciences",
76
- "pubmed",
77
- "biorxiv",
78
- "medrxiv",
79
- "arxiv",
80
- "preprints",
81
- "citations",
82
- "bioinformatics"
83
- ],
84
- engines: {
85
- node: ">=22.0.0"
86
- }
87
- };
88
- }
89
- });
90
-
91
- export {
92
- require_package
93
- };
@@ -1,4 +0,0 @@
1
- import {
2
- require_package
3
- } from "./chunk-R4XT7PME.js";
4
- export default require_package();