@mendable/firecrawl-js 4.38.0 → 4.40.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 +53 -2
- package/dist/{chunk-KSTNOGOD.js → chunk-OJ4YJQ4J.js} +1 -1
- package/dist/index.cjs +204 -17
- package/dist/index.d.cts +289 -7
- package/dist/index.d.ts +289 -7
- package/dist/index.js +203 -17
- package/dist/{package-OS7RONXY.js → package-F6O7J2XI.js} +1 -1
- package/package.json +1 -1
- package/pnpm-workspace.yaml +3 -2
- package/src/__tests__/e2e/v2/batch.test.ts +13 -11
- package/src/__tests__/e2e/v2/crawl.test.ts +24 -20
- package/src/__tests__/e2e/v2/extract.test.ts +32 -7
- package/src/__tests__/e2e/v2/map.test.ts +4 -3
- package/src/__tests__/e2e/v2/parse.test.ts +4 -3
- package/src/__tests__/e2e/v2/scrape.test.ts +10 -9
- package/src/__tests__/e2e/v2/search.test.ts +9 -8
- package/src/__tests__/e2e/v2/usage.test.ts +6 -5
- package/src/__tests__/e2e/v2/utils/rateLimit.ts +257 -0
- package/src/__tests__/e2e/v2/watcher.test.ts +4 -3
- package/src/__tests__/unit/v2/agent-effort-trace.unit.test.ts +1 -1
- package/src/__tests__/unit/v2/agent-thread.unit.test.ts +164 -0
- package/src/__tests__/unit/v2/e2eRateLimitRetry.test.ts +501 -0
- package/src/__tests__/unit/v2/extract.unit.test.ts +1 -1
- package/src/__tests__/unit/v2/scrapeAutoResume.test.ts +1 -1
- package/src/__tests__/unit/v2/search-country.unit.test.ts +32 -0
- package/src/__tests__/unit/v2/search-data-property.test.ts +1 -0
- package/src/__tests__/unit/v2/threat-protection.unit.test.ts +18 -1
- package/src/__tests__/unit/v2/tools.unit.test.ts +240 -0
- package/src/__tests__/unit/v2/v1-aliases.test.ts +1 -0
- package/src/v2/client.ts +52 -4
- package/src/v2/methods/agent.ts +23 -1
- package/src/v2/methods/research.ts +14 -8
- package/src/v2/methods/scrape.ts +3 -1
- package/src/v2/methods/search.ts +8 -1
- package/src/v2/methods/tools.ts +137 -0
- package/src/v2/types.ts +307 -35
- package/src/v2/utils/errorHandler.ts +19 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import { search } from "../../../v2/methods/search";
|
|
3
|
+
|
|
4
|
+
function httpMock() {
|
|
5
|
+
return {
|
|
6
|
+
post: jest.fn(async () => ({ status: 200, data: { success: true } })),
|
|
7
|
+
} as any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe("v2 search country", () => {
|
|
11
|
+
test("forwards the country option", async () => {
|
|
12
|
+
const http = httpMock();
|
|
13
|
+
|
|
14
|
+
await search(http, { query: "firecrawl", country: "de" });
|
|
15
|
+
|
|
16
|
+
expect(http.post).toHaveBeenCalledWith(
|
|
17
|
+
"/v2/search",
|
|
18
|
+
{ query: "firecrawl", country: "de" },
|
|
19
|
+
{},
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("omits country when the caller does not set it", async () => {
|
|
24
|
+
const http = httpMock();
|
|
25
|
+
|
|
26
|
+
await search(http, { query: "firecrawl" });
|
|
27
|
+
|
|
28
|
+
expect(http.post).toHaveBeenCalledWith("/v2/search", { query: "firecrawl" }, {});
|
|
29
|
+
const payload = http.post.mock.calls[0][1] as Record<string, unknown>;
|
|
30
|
+
expect(payload).not.toHaveProperty("country");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -32,7 +32,17 @@ describe("v2 threatProtection request serialization", () => {
|
|
|
32
32
|
expect(http.post).toHaveBeenCalledWith(
|
|
33
33
|
"/v2/scrape",
|
|
34
34
|
expect.objectContaining({ url: "https://example.com", threatProtection }),
|
|
35
|
-
{},
|
|
35
|
+
{ timeoutMs: 330000 },
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("scrape sends domainTools when true", async () => {
|
|
40
|
+
const http = makeHttp({ success: true, data: {} });
|
|
41
|
+
await scrape(http, "https://example.com", { domainTools: true });
|
|
42
|
+
expect(http.post).toHaveBeenCalledWith(
|
|
43
|
+
"/v2/scrape",
|
|
44
|
+
expect.objectContaining({ url: "https://example.com", domainTools: true }),
|
|
45
|
+
expect.anything(),
|
|
36
46
|
);
|
|
37
47
|
});
|
|
38
48
|
|
|
@@ -121,3 +131,10 @@ describe("v2 threatProtection request serialization", () => {
|
|
|
121
131
|
expect(body).not.toHaveProperty("threatProtection");
|
|
122
132
|
});
|
|
123
133
|
});
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
test.each([false, undefined])("scrape omits domainTools when %s", async (domainTools) => {
|
|
137
|
+
const http = makeHttp({ success: true, data: {} });
|
|
138
|
+
await scrape(http, "https://example.com", { domainTools });
|
|
139
|
+
expect(http.post.mock.calls[0][1]).not.toHaveProperty("domainTools");
|
|
140
|
+
});
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
import { createServer, type Server } from "node:http";
|
|
3
|
+
import { FirecrawlClient } from "../../../v2/client";
|
|
4
|
+
|
|
5
|
+
import { scrapeAlexandria } from "../../../v2/methods/tools";
|
|
6
|
+
|
|
7
|
+
const tool = {
|
|
8
|
+
id: "particle/podcasts/episodes/search",
|
|
9
|
+
provider: "particle",
|
|
10
|
+
capability: "podcasts/episodes/search",
|
|
11
|
+
name: "Episode search",
|
|
12
|
+
description: "Find episodes",
|
|
13
|
+
creditsCost: 15,
|
|
14
|
+
perRecord: false,
|
|
15
|
+
options: [{ name: "semantic_search", type: "string" }],
|
|
16
|
+
response: { about: "Episodes", key: "data", fields: [] },
|
|
17
|
+
examples: { javascript: "example", python: "example", curl: "example" },
|
|
18
|
+
matchedBy: ["semantic", "domain"],
|
|
19
|
+
matchedUrls: ["https://podcasts.apple.com"],
|
|
20
|
+
};
|
|
21
|
+
const productionTool = {
|
|
22
|
+
id: "benzinga/calendar/ratings",
|
|
23
|
+
provider: "benzinga",
|
|
24
|
+
capability: "calendar/ratings",
|
|
25
|
+
name: "Analyst ratings",
|
|
26
|
+
description: "Ratings",
|
|
27
|
+
creditsCost: 5,
|
|
28
|
+
perRecord: false,
|
|
29
|
+
label: "Ratings",
|
|
30
|
+
whenToUse: "Analyst ratings for a ticker",
|
|
31
|
+
returns: { about: "Ratings" },
|
|
32
|
+
discovery: { urls: [] },
|
|
33
|
+
attribution: { required: true },
|
|
34
|
+
options: [{ name: "tickers", type: "string" }],
|
|
35
|
+
response: { fields: [] },
|
|
36
|
+
matchedBy: ["semantic"],
|
|
37
|
+
matchedUrls: [],
|
|
38
|
+
};
|
|
39
|
+
const termsRequired = {
|
|
40
|
+
success: false,
|
|
41
|
+
code: "THIRD_PARTY_DATA_TERMS_REQUIRED",
|
|
42
|
+
error: "An organization admin must accept the benzinga provider's terms",
|
|
43
|
+
requiresAction: {
|
|
44
|
+
type: "accept_terms",
|
|
45
|
+
terms: "benzinga",
|
|
46
|
+
version: "C-1.0.0-draft",
|
|
47
|
+
url: "https://www.firecrawl.dev/app/alexandria/benzinga",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const next = {
|
|
51
|
+
provider: "firecrawl",
|
|
52
|
+
capability: "find-tools",
|
|
53
|
+
options: { providers: ["particle"], level: "tools" },
|
|
54
|
+
};
|
|
55
|
+
let server: Server;
|
|
56
|
+
let client: FirecrawlClient;
|
|
57
|
+
const sent: Array<{ body: any; id: string | undefined }> = [];
|
|
58
|
+
let attempts = 0;
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
sent.length = 0;
|
|
62
|
+
attempts = 0;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
beforeAll(async () => {
|
|
66
|
+
server = createServer(async (req, res) => {
|
|
67
|
+
let raw = "";
|
|
68
|
+
for await (const chunk of req) raw += chunk;
|
|
69
|
+
const body = JSON.parse(raw);
|
|
70
|
+
sent.push({ body, id: req.headers["x-request-id"] as string | undefined });
|
|
71
|
+
res.setHeader("content-type", "application/json");
|
|
72
|
+
if (req.url === "/v2/search")
|
|
73
|
+
return res.end(
|
|
74
|
+
JSON.stringify({
|
|
75
|
+
success: true,
|
|
76
|
+
warning: "Example warning",
|
|
77
|
+
data: {
|
|
78
|
+
web: [{ url: "https://podcasts.apple.com" }],
|
|
79
|
+
tools: [tool, productionTool],
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
if (body.alexandria[0].provider === "retry" && attempts++ === 0) {
|
|
84
|
+
res.statusCode = 502;
|
|
85
|
+
return res.end("{}");
|
|
86
|
+
}
|
|
87
|
+
if (body.alexandria[0].provider === "benzinga") {
|
|
88
|
+
res.statusCode = 403;
|
|
89
|
+
return res.end(JSON.stringify(termsRequired));
|
|
90
|
+
}
|
|
91
|
+
if (body.alexandria[0].provider === "denied") {
|
|
92
|
+
res.statusCode = 402;
|
|
93
|
+
return res.end(
|
|
94
|
+
JSON.stringify({
|
|
95
|
+
success: false,
|
|
96
|
+
code: "insufficient_credits",
|
|
97
|
+
error: "Insufficient credits",
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const alexandria =
|
|
102
|
+
body.alexandria[0].provider === "firecrawl"
|
|
103
|
+
? [
|
|
104
|
+
{
|
|
105
|
+
...next,
|
|
106
|
+
creditsCost: 0,
|
|
107
|
+
data: {
|
|
108
|
+
level: "providers",
|
|
109
|
+
items: [{ id: "particle", name: "Particle", next }],
|
|
110
|
+
total: 1,
|
|
111
|
+
next: null,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
]
|
|
115
|
+
: [
|
|
116
|
+
{
|
|
117
|
+
provider: "retry",
|
|
118
|
+
capability: "a/b",
|
|
119
|
+
creditsCost: 15,
|
|
120
|
+
data: { nested: { value: 1 } },
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
provider: "x",
|
|
124
|
+
capability: "b",
|
|
125
|
+
error: {
|
|
126
|
+
code: "unavailable",
|
|
127
|
+
message: "Unavailable",
|
|
128
|
+
status: 503,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
res.end(
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
success: true,
|
|
135
|
+
scrape_id: "scrape-1",
|
|
136
|
+
data: { alexandria, creditsCost: alexandria.length === 1 ? 0 : 15 },
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
141
|
+
const port = (server.address() as { port: number }).port;
|
|
142
|
+
client = new FirecrawlClient({
|
|
143
|
+
apiKey: "fc-test",
|
|
144
|
+
apiUrl: `http://127.0.0.1:${port}`,
|
|
145
|
+
backoffFactor: 0,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
afterAll(async () => {
|
|
149
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("Alexandria contracts and execution", () => {
|
|
153
|
+
test("returns complete unified tools and warning beside web results", async () => {
|
|
154
|
+
const result = await client.search("podcasts", {
|
|
155
|
+
sources: ["web", { type: "alexandria" }],
|
|
156
|
+
domainTools: true,
|
|
157
|
+
limit: 2,
|
|
158
|
+
});
|
|
159
|
+
expect(result.tools).toEqual([tool, productionTool]);
|
|
160
|
+
expect(result.tools?.[1].examples).toBeUndefined();
|
|
161
|
+
expect(result.tools?.[1].whenToUse).toBe("Analyst ratings for a ticker");
|
|
162
|
+
expect(result.warning).toBe("Example warning");
|
|
163
|
+
expect(sent.at(-1)?.body).toMatchObject({
|
|
164
|
+
domainTools: true,
|
|
165
|
+
sources: ["web", { type: "alexandria" }],
|
|
166
|
+
});
|
|
167
|
+
expect(result).not.toHaveProperty("alexandria");
|
|
168
|
+
});
|
|
169
|
+
test("retains one ID through transport retries and per-tool failures", async () => {
|
|
170
|
+
const result = await client.scrape({
|
|
171
|
+
alexandria: [{ provider: "retry", capability: "a/b" }],
|
|
172
|
+
requestId: "same-request",
|
|
173
|
+
});
|
|
174
|
+
const retries = sent.filter(
|
|
175
|
+
(r) => r.body.alexandria?.[0].provider === "retry",
|
|
176
|
+
);
|
|
177
|
+
expect(retries).toHaveLength(2);
|
|
178
|
+
expect(retries.map((r) => r.id)).toEqual(["same-request", "same-request"]);
|
|
179
|
+
expect(retries[0].body).not.toHaveProperty("requestId");
|
|
180
|
+
expect(result.requestId).toBe("same-request");
|
|
181
|
+
expect(result.creditsCost).toBe(15);
|
|
182
|
+
expect(result.alexandria[1].error?.code).toBe("unavailable");
|
|
183
|
+
});
|
|
184
|
+
test("returns an error code and retry identity on failed execution", async () => {
|
|
185
|
+
await expect(
|
|
186
|
+
client.scrape({
|
|
187
|
+
alexandria: { provider: "denied", capability: "a/b" },
|
|
188
|
+
requestId: "denied-1",
|
|
189
|
+
}),
|
|
190
|
+
).rejects.toMatchObject({
|
|
191
|
+
status: 402,
|
|
192
|
+
code: "insufficient_credits",
|
|
193
|
+
requestId: "denied-1",
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
test("exposes requiresAction on a provider terms rejection", async () => {
|
|
197
|
+
await expect(
|
|
198
|
+
client.scrape({
|
|
199
|
+
alexandria: { provider: "benzinga", capability: "calendar/ratings" },
|
|
200
|
+
requestId: "terms-1",
|
|
201
|
+
}),
|
|
202
|
+
).rejects.toMatchObject({
|
|
203
|
+
status: 403,
|
|
204
|
+
code: "THIRD_PARTY_DATA_TERMS_REQUIRED",
|
|
205
|
+
message: termsRequired.error,
|
|
206
|
+
requestId: "terms-1",
|
|
207
|
+
requiresAction: termsRequired.requiresAction,
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
test("walks with Find Tools and feeds next directly into scrape", async () => {
|
|
211
|
+
const found = await client.findTools({ providers: ["particle"], limit: 2 });
|
|
212
|
+
const result = await client.scrape({ alexandria: found.items[0].next! });
|
|
213
|
+
expect(result.creditsCost).toBe(0);
|
|
214
|
+
expect(sent.at(-1)?.id).toBeTruthy();
|
|
215
|
+
expect(sent.at(-1)?.body.alexandria).toEqual([next]);
|
|
216
|
+
});
|
|
217
|
+
test("rejects URL options and queryless browsing before dispatch", async () => {
|
|
218
|
+
const count = sent.length;
|
|
219
|
+
await expect(
|
|
220
|
+
client.scrape({ alexandria: next, url: "https://example.com" } as any),
|
|
221
|
+
).rejects.toThrow();
|
|
222
|
+
await expect(
|
|
223
|
+
client.search("", { sources: ["alexandria"] }),
|
|
224
|
+
).rejects.toThrow("Query cannot be empty");
|
|
225
|
+
expect(sent).toHaveLength(count);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
test.each([[undefined, 80000], [1000, 31000], [100000, 80000]])(
|
|
231
|
+
"Alexandria timeout %s allows response delivery (%s ms)",
|
|
232
|
+
async (timeout, timeoutMs) => {
|
|
233
|
+
const http = { post: jest.fn(async () => ({ status: 200, data: {
|
|
234
|
+
success: true, data: { alexandria: [], creditsCost: 0 },
|
|
235
|
+
} })) };
|
|
236
|
+
await scrapeAlexandria(http as any, [next], { timeout });
|
|
237
|
+
expect(http.post).toHaveBeenCalledWith("/v2/scrape", expect.anything(),
|
|
238
|
+
expect.objectContaining({ timeoutMs }));
|
|
239
|
+
},
|
|
240
|
+
);
|
package/src/v2/client.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "./methods/scrape";
|
|
7
7
|
import { parse as parseMethod } from "./methods/parse";
|
|
8
8
|
import { search } from "./methods/search";
|
|
9
|
+
import { scrapeAlexandria, findTools } from "./methods/tools";
|
|
9
10
|
import { developerSearch as developerSearchMethod } from "./methods/developer";
|
|
10
11
|
import { map as mapMethod } from "./methods/map";
|
|
11
12
|
import { feedback as feedbackMethod, searchFeedback as searchFeedbackMethod } from "./methods/feedback";
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
batchScrape as batchWaiter,
|
|
27
28
|
} from "./methods/batch";
|
|
28
29
|
import { startExtract, getExtractStatus, extract as extractWaiter } from "./methods/extract";
|
|
29
|
-
import { startAgent, getAgentStatus, getAgentTrace, getAgentSnapshot, cancelAgent, listAgents, agent as agentWaiter } from "./methods/agent";
|
|
30
|
+
import { startAgent, getAgentStatus, getAgentThread, getAgentTrace, getAgentSnapshot, cancelAgent, listAgents, agent as agentWaiter } from "./methods/agent";
|
|
30
31
|
import {
|
|
31
32
|
browser as browserMethod,
|
|
32
33
|
browserExecute,
|
|
@@ -47,6 +48,10 @@ import {
|
|
|
47
48
|
} from "./methods/monitor";
|
|
48
49
|
import type {
|
|
49
50
|
Document,
|
|
51
|
+
AlexandriaScrapeRequest,
|
|
52
|
+
FindToolsOptions,
|
|
53
|
+
FindToolsData,
|
|
54
|
+
AlexandriaScrapeData,
|
|
50
55
|
ParseFile,
|
|
51
56
|
ParseOptions,
|
|
52
57
|
ScrapeOptions,
|
|
@@ -70,6 +75,7 @@ import type {
|
|
|
70
75
|
AgentStatusResponse,
|
|
71
76
|
AgentTraceResponse,
|
|
72
77
|
AgentSnapshotResponse,
|
|
78
|
+
AgentThreadResponse,
|
|
73
79
|
AgentListOptions,
|
|
74
80
|
AgentListResponse,
|
|
75
81
|
CrawlOptions,
|
|
@@ -170,11 +176,42 @@ export class FirecrawlClient {
|
|
|
170
176
|
*/
|
|
171
177
|
async scrape<Opts extends ScrapeOptions>(
|
|
172
178
|
url: string,
|
|
173
|
-
options: Opts
|
|
179
|
+
options: Opts,
|
|
174
180
|
): Promise<Omit<Document, "json"> & { json?: InferredJsonFromOptions<Opts> }>;
|
|
175
181
|
async scrape(url: string, options?: ScrapeCallOptions): Promise<Document>;
|
|
176
|
-
async scrape(
|
|
177
|
-
|
|
182
|
+
async scrape(request: AlexandriaScrapeRequest): Promise<AlexandriaScrapeData>;
|
|
183
|
+
async scrape(
|
|
184
|
+
url: string | AlexandriaScrapeRequest,
|
|
185
|
+
options?: ScrapeCallOptions,
|
|
186
|
+
): Promise<Document | AlexandriaScrapeData> {
|
|
187
|
+
if (typeof url === "string") return scrape(this.http, url, options);
|
|
188
|
+
if (
|
|
189
|
+
!url ||
|
|
190
|
+
options !== undefined ||
|
|
191
|
+
Object.keys(url).some(
|
|
192
|
+
(key) =>
|
|
193
|
+
![
|
|
194
|
+
"alexandria",
|
|
195
|
+
"requestId",
|
|
196
|
+
"timeout",
|
|
197
|
+
"integration",
|
|
198
|
+
"origin",
|
|
199
|
+
].includes(key),
|
|
200
|
+
)
|
|
201
|
+
) {
|
|
202
|
+
throw new Error("Provide an alexandria request without URL scrape options");
|
|
203
|
+
}
|
|
204
|
+
const { alexandria, ...opts } = url;
|
|
205
|
+
return scrapeAlexandria(
|
|
206
|
+
this.http,
|
|
207
|
+
Array.isArray(alexandria) ? alexandria : [alexandria],
|
|
208
|
+
opts,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Explore the catalogue without executing the tools it returns. */
|
|
213
|
+
async findTools(options?: FindToolsOptions): Promise<FindToolsData> {
|
|
214
|
+
return findTools(this.http, options);
|
|
178
215
|
}
|
|
179
216
|
/**
|
|
180
217
|
* Interact with the browser session associated with a scrape job.
|
|
@@ -284,6 +321,9 @@ export class FirecrawlClient {
|
|
|
284
321
|
* Access the v2 research endpoints — Firecrawl's **research paper index**
|
|
285
322
|
* (~43M paper abstracts) plus GitHub history/readmes.
|
|
286
323
|
*
|
|
324
|
+
* `research.searchGithub()` is deprecated and stops responding after
|
|
325
|
+
* 2026-11-03. Use `developerSearch()` instead.
|
|
326
|
+
*
|
|
287
327
|
* The paper corpus is roughly 90% biomedical and life sciences — PubMed,
|
|
288
328
|
* bioRxiv and medRxiv — with arXiv covering physics, mathematics and
|
|
289
329
|
* computer science.
|
|
@@ -566,6 +606,14 @@ export class FirecrawlClient {
|
|
|
566
606
|
async getAgentSnapshot(jobId: string, snapshotId: string): Promise<AgentSnapshotResponse> {
|
|
567
607
|
return getAgentSnapshot(this.http, jobId, snapshotId);
|
|
568
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* Get a thread and its runs, oldest turn first.
|
|
611
|
+
* @param threadId Thread id, as returned by startAgent or getAgentStatus.
|
|
612
|
+
* @param options.includeData Inline each succeeded run's data.
|
|
613
|
+
*/
|
|
614
|
+
async getAgentThread(threadId: string, options?: { includeData?: boolean }): Promise<AgentThreadResponse> {
|
|
615
|
+
return getAgentThread(this.http, threadId, options);
|
|
616
|
+
}
|
|
569
617
|
|
|
570
618
|
// Browser
|
|
571
619
|
/**
|
package/src/v2/methods/agent.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentEffort, type AgentListOptions, type AgentListResponse, type AgentResponse, type AgentSnapshotResponse, type AgentStatusResponse, type AgentTraceResponse, type AgentWebhookConfig, type AuditMetadata, type ThreatProtectionOptions } from "../types";
|
|
1
|
+
import { type AgentEffort, type AgentExchangeOptions, type AgentListOptions, type AgentListResponse, type AgentMode, type AgentResponse, type AgentSnapshotResponse, type AgentStatusResponse, type AgentThreadResponse, type AgentTraceResponse, type AgentWebhookConfig, type AuditMetadata, type ThreatProtectionOptions } from "../types";
|
|
2
2
|
import { HttpClient } from "../utils/httpClient";
|
|
3
3
|
import { normalizeAxiosError, throwForBadResponse } from "../utils/errorHandler";
|
|
4
4
|
import { isZodSchema, zodSchemaToJsonSchema } from "../../utils/zodSchemaToJson";
|
|
@@ -17,6 +17,9 @@ function prepareAgentPayload(args: {
|
|
|
17
17
|
webhook?: string | AgentWebhookConfig;
|
|
18
18
|
threatProtection?: ThreatProtectionOptions;
|
|
19
19
|
auditMetadata?: AuditMetadata;
|
|
20
|
+
threadId?: string;
|
|
21
|
+
mode?: AgentMode;
|
|
22
|
+
exchange?: AgentExchangeOptions;
|
|
20
23
|
}): Record<string, unknown> {
|
|
21
24
|
const body: Record<string, unknown> = {};
|
|
22
25
|
if (args.urls) body.urls = args.urls;
|
|
@@ -35,6 +38,9 @@ function prepareAgentPayload(args: {
|
|
|
35
38
|
body.threatProtection = args.threatProtection;
|
|
36
39
|
if (args.auditMetadata != null)
|
|
37
40
|
body.auditMetadata = args.auditMetadata;
|
|
41
|
+
if (args.threadId != null) body.threadId = args.threadId;
|
|
42
|
+
if (args.mode != null) body.mode = args.mode;
|
|
43
|
+
if (args.exchange != null) body.exchange = args.exchange;
|
|
38
44
|
return body;
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -129,6 +135,22 @@ export async function getAgentSnapshot(
|
|
|
129
135
|
}
|
|
130
136
|
}
|
|
131
137
|
|
|
138
|
+
export async function getAgentThread(
|
|
139
|
+
http: HttpClient,
|
|
140
|
+
threadId: string,
|
|
141
|
+
options?: { includeData?: boolean }
|
|
142
|
+
): Promise<AgentThreadResponse> {
|
|
143
|
+
try {
|
|
144
|
+
const query = options?.includeData ? "?includeData=true" : "";
|
|
145
|
+
const res = await http.get<AgentThreadResponse>(`/v2/agent/threads/${threadId}${query}`);
|
|
146
|
+
if (res.status !== 200) throwForBadResponse(res, "agent thread");
|
|
147
|
+
return res.data;
|
|
148
|
+
} catch (err: any) {
|
|
149
|
+
if (err?.isAxiosError) return normalizeAxiosError(err, "agent thread");
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
132
154
|
export async function cancelAgent(http: HttpClient, jobId: string): Promise<boolean> {
|
|
133
155
|
try {
|
|
134
156
|
const res = await http.delete<{ success: boolean }>(`/v2/agent/${jobId}`);
|
|
@@ -159,9 +159,9 @@ export class ResearchClient {
|
|
|
159
159
|
appendParam(params, "query", options.query);
|
|
160
160
|
appendParam(params, "k", options.k);
|
|
161
161
|
try {
|
|
162
|
-
const res = await this.http.get<
|
|
163
|
-
|
|
164
|
-
);
|
|
162
|
+
const res = await this.http.get<
|
|
163
|
+
PaperMetadataResponse | ReadPaperResponse
|
|
164
|
+
>(withQuery(`${BASE}/papers/${encodeURIComponent(id)}`, params));
|
|
165
165
|
if (res.status !== 200) throwForBadResponse(res, "get paper");
|
|
166
166
|
return res.data;
|
|
167
167
|
} catch (err) {
|
|
@@ -191,10 +191,7 @@ export class ResearchClient {
|
|
|
191
191
|
appendParam(params, "anchor", options.anchor);
|
|
192
192
|
try {
|
|
193
193
|
const res = await this.http.get<SimilarPapersResponse>(
|
|
194
|
-
withQuery(
|
|
195
|
-
`${BASE}/papers/${encodeURIComponent(id)}/similar`,
|
|
196
|
-
params,
|
|
197
|
-
),
|
|
194
|
+
withQuery(`${BASE}/papers/${encodeURIComponent(id)}/similar`, params),
|
|
198
195
|
);
|
|
199
196
|
if (res.status !== 200) throwForBadResponse(res, "find similar papers");
|
|
200
197
|
return res.data;
|
|
@@ -204,7 +201,16 @@ export class ResearchClient {
|
|
|
204
201
|
}
|
|
205
202
|
|
|
206
203
|
/**
|
|
207
|
-
* Search GitHub issue/PR history and repository
|
|
204
|
+
* Search the research index GitHub slice: issue/PR history and repository
|
|
205
|
+
* readmes.
|
|
206
|
+
*
|
|
207
|
+
* @deprecated Use `developerSearch()` on the client instead. This
|
|
208
|
+
* endpoint stops responding after 2026-11-03. The developer index searches
|
|
209
|
+
* GitHub issues, pull requests and readmes plus curated documentation
|
|
210
|
+
* sources, returns matched passages, and adds filters for repo, language,
|
|
211
|
+
* license and stars. It does not carry over the `scores` breakdown or the
|
|
212
|
+
* `resultType: "web"` fallback results. See
|
|
213
|
+
* https://docs.firecrawl.dev/features/developer.
|
|
208
214
|
* @param query Search query.
|
|
209
215
|
* @param options Optional `k`.
|
|
210
216
|
*/
|
package/src/v2/methods/scrape.ts
CHANGED
|
@@ -88,9 +88,11 @@ export async function scrape(
|
|
|
88
88
|
if (options) ensureValidScrapeOptions(options);
|
|
89
89
|
|
|
90
90
|
// autoResume is SDK behavior, never part of the wire payload.
|
|
91
|
-
const { autoResume, ...requestOptions } = options ?? {};
|
|
91
|
+
const { autoResume, domainTools, ...requestOptions } = options ?? {};
|
|
92
92
|
const payload: Record<string, unknown> = { url: url.trim() };
|
|
93
93
|
Object.assign(payload, requestOptions);
|
|
94
|
+
// Default off: omit entirely rather than send `false`.
|
|
95
|
+
if (domainTools) payload.domainTools = domainTools;
|
|
94
96
|
|
|
95
97
|
// Per-request timeout. With auto-resume disabled this is exactly the
|
|
96
98
|
// pre-existing behavior (explicit timeout +5s, else the client's own
|
package/src/v2/methods/search.ts
CHANGED
|
@@ -27,6 +27,7 @@ function prepareSearchPayload(req: SearchRequest): Record<string, unknown> {
|
|
|
27
27
|
const payload: Record<string, unknown> = {
|
|
28
28
|
query: req.query,
|
|
29
29
|
};
|
|
30
|
+
if (req.domainTools != null) payload.domainTools = req.domainTools;
|
|
30
31
|
if (req.sources) payload.sources = req.sources;
|
|
31
32
|
if (req.categories) payload.categories = req.categories;
|
|
32
33
|
if (req.includeDomains) payload.includeDomains = req.includeDomains;
|
|
@@ -34,6 +35,7 @@ function prepareSearchPayload(req: SearchRequest): Record<string, unknown> {
|
|
|
34
35
|
if (req.limit != null) payload.limit = req.limit;
|
|
35
36
|
if (req.tbs != null) payload.tbs = req.tbs;
|
|
36
37
|
if (req.location != null) payload.location = req.location;
|
|
38
|
+
if (req.country != null) payload.country = req.country;
|
|
37
39
|
if (req.ignoreInvalidURLs != null)
|
|
38
40
|
payload.ignoreInvalidURLs = req.ignoreInvalidURLs;
|
|
39
41
|
if (req.timeout != null) payload.timeout = req.timeout;
|
|
@@ -86,6 +88,7 @@ export async function search(
|
|
|
86
88
|
success: boolean;
|
|
87
89
|
data?: Record<string, unknown>;
|
|
88
90
|
error?: string;
|
|
91
|
+
warning?: string;
|
|
89
92
|
}>(
|
|
90
93
|
"/v2/search",
|
|
91
94
|
payload,
|
|
@@ -98,10 +101,12 @@ export async function search(
|
|
|
98
101
|
}
|
|
99
102
|
const data = (res.data.data || {}) as Record<string, any>;
|
|
100
103
|
const out: SearchData = {};
|
|
104
|
+
if (res.data.warning) out.warning = res.data.warning;
|
|
101
105
|
if (data.web) out.web = transformArray<SearchResultWeb>(data.web);
|
|
102
106
|
if (data.news) out.news = transformArray<SearchResultNews>(data.news);
|
|
103
107
|
if (data.images)
|
|
104
108
|
out.images = transformArray<SearchResultImages>(data.images);
|
|
109
|
+
if (data.tools) out.tools = data.tools;
|
|
105
110
|
Object.defineProperty(out, "data", {
|
|
106
111
|
get() {
|
|
107
112
|
const parts: string[] = [];
|
|
@@ -109,9 +114,11 @@ export async function search(
|
|
|
109
114
|
if (out.news?.length) parts.push(`.news (${out.news.length} results)`);
|
|
110
115
|
if (out.images?.length)
|
|
111
116
|
parts.push(`.images (${out.images.length} results)`);
|
|
117
|
+
if (out.tools?.length)
|
|
118
|
+
parts.push(`.tools (${out.tools.length} results)`);
|
|
112
119
|
const available = parts.length
|
|
113
120
|
? parts.join(", ")
|
|
114
|
-
: ".web, .news, or .
|
|
121
|
+
: ".web, .news, .images, or .tools";
|
|
115
122
|
throw new Error(
|
|
116
123
|
`SearchData has no '.data'. Results are grouped by source: ${available}`,
|
|
117
124
|
);
|