firecrawl 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.
- package/README.md +18 -8
- package/dist/chunk-GD463K4D.js +114 -0
- package/dist/index.cjs +163 -39
- package/dist/index.d.cts +24 -5
- package/dist/index.d.ts +24 -5
- package/dist/index.js +164 -41
- package/dist/package-WONXBIJS.js +40 -0
- package/package.json +1 -1
- package/src/__tests__/unit/v2/agent-hints.test.ts +1 -0
- package/src/__tests__/unit/v2/browser.unit.test.ts +28 -0
- package/src/__tests__/unit/v2/errorHandler.test.ts +23 -0
- package/src/__tests__/unit/v2/origin.test.ts +132 -0
- package/src/__tests__/unit/v2/pagination.test.ts +20 -0
- package/src/__tests__/unit/v2/research.test.ts +1 -0
- package/src/__tests__/unit/v2/validation.test.ts +39 -1
- package/src/index.ts +1 -0
- package/src/utils/callerOrigin.ts +74 -0
- package/src/v1/index.ts +28 -13
- package/src/v2/client.ts +11 -2
- package/src/v2/methods/browser.ts +3 -0
- package/src/v2/methods/crawl.ts +2 -1
- package/src/v2/methods/parse.ts +1 -6
- package/src/v2/methods/research.ts +20 -7
- package/src/v2/methods/search.ts +2 -1
- package/src/v2/types.ts +2 -2
- package/src/v2/utils/errorHandler.ts +23 -2
- package/src/v2/utils/getVersion.ts +4 -12
- package/src/v2/utils/httpClient.ts +10 -3
- package/src/v2/utils/pagination.ts +4 -1
- package/src/v2/utils/validation.ts +28 -0
- package/dist/chunk-R4XT7PME.js +0 -93
- package/dist/package-EI4Q33HD.js +0 -4
package/src/v2/methods/parse.ts
CHANGED
|
@@ -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(
|
|
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",
|
|
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
|
-
>(
|
|
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(
|
|
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;
|
package/src/v2/methods/search.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
99
|
+
typeof data.origin === "string" && data.origin
|
|
93
100
|
? data.origin
|
|
94
|
-
:
|
|
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
|
-
|
|
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) {
|
package/dist/chunk-R4XT7PME.js
DELETED
|
@@ -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
|
-
};
|
package/dist/package-EI4Q33HD.js
DELETED