webfox 0.0.0-test.0 → 4.0.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +256 -3
  3. package/assets/webfox-lockup-dark.png +0 -0
  4. package/assets/webfox-lockup.png +0 -0
  5. package/dist/adapter-2YBFCBCZ.js +161 -0
  6. package/dist/adapter-67TIISDW.js +753 -0
  7. package/dist/adapter-AB7LKV7N.js +324 -0
  8. package/dist/adapter-AUYLTDPM.js +73 -0
  9. package/dist/adapter-HITGYUXN.js +494 -0
  10. package/dist/adapter-HPTMLH75.js +334 -0
  11. package/dist/adapter-I6OUKBZQ.js +492 -0
  12. package/dist/adapter-ISZQTZDP.js +166 -0
  13. package/dist/adapter-MAG7X663.js +230 -0
  14. package/dist/adapter-NEXSOHGT.js +232 -0
  15. package/dist/adapter-PADXKW43.js +162 -0
  16. package/dist/adapter-SDQX2533.js +123 -0
  17. package/dist/adapter-UAZNLDSS.js +237 -0
  18. package/dist/adapter-XBAQSK7I.js +282 -0
  19. package/dist/application-types.d.ts +34 -0
  20. package/dist/application.d.ts +2 -0
  21. package/dist/chunk-EF4VMDZL.js +85 -0
  22. package/dist/chunk-G2YAQEET.js +38 -0
  23. package/dist/chunk-I2UIACY3.js +12 -0
  24. package/dist/chunk-L425U55P.js +3051 -0
  25. package/dist/chunk-QRHDXM2L.js +31 -0
  26. package/dist/chunk-SMF5ALX7.js +762 -0
  27. package/dist/chunk-U4BLULLV.js +52 -0
  28. package/dist/chunk-WCTOYZVB.js +85 -0
  29. package/dist/chunk-X6ZBIHZC.js +21 -0
  30. package/dist/chunk-YZXM4U2C.js +21 -0
  31. package/dist/chunk-ZVTVERW5.js +20 -0
  32. package/dist/cli.js +641 -0
  33. package/dist/config.schema.json +4270 -0
  34. package/dist/configuration/defaults.d.ts +3 -0
  35. package/dist/configuration/file.d.ts +17 -0
  36. package/dist/configuration/options-schema.d.ts +2 -0
  37. package/dist/configuration/planning.d.ts +10 -0
  38. package/dist/configuration/schema.d.ts +9 -0
  39. package/dist/configuration/types.d.ts +37 -0
  40. package/dist/contents.d.ts +19 -0
  41. package/dist/domain.d.ts +89 -0
  42. package/dist/errors.d.ts +17 -0
  43. package/dist/index.d.ts +8 -0
  44. package/dist/index.js +38 -0
  45. package/dist/package-metadata.d.ts +3 -0
  46. package/dist/pi-keybindings.d.ts +1 -0
  47. package/dist/pi-params.d.ts +3 -0
  48. package/dist/pi-render.d.ts +50 -0
  49. package/dist/pi-search-render.d.ts +4 -0
  50. package/dist/pi-text.d.ts +4 -0
  51. package/dist/pi-validation.d.ts +4 -0
  52. package/dist/pi.d.ts +2 -0
  53. package/dist/pi.js +563 -0
  54. package/dist/providers/brave/adapter.d.ts +7 -0
  55. package/dist/providers/brave/definition.d.ts +1 -0
  56. package/dist/providers/brave/types.d.ts +4 -0
  57. package/dist/providers/cli-json.d.ts +9 -0
  58. package/dist/providers/cloudflare/adapter.d.ts +5 -0
  59. package/dist/providers/cloudflare/definition.d.ts +1 -0
  60. package/dist/providers/cloudflare/types.d.ts +4 -0
  61. package/dist/providers/contract.d.ts +88 -0
  62. package/dist/providers/custom/adapter.d.ts +9 -0
  63. package/dist/providers/custom/definition.d.ts +1 -0
  64. package/dist/providers/custom/types.d.ts +9 -0
  65. package/dist/providers/definition.d.ts +32 -0
  66. package/dist/providers/exa/adapter.d.ts +8 -0
  67. package/dist/providers/exa/definition.d.ts +1 -0
  68. package/dist/providers/exa/types.d.ts +4 -0
  69. package/dist/providers/firecrawl/adapter.d.ts +7 -0
  70. package/dist/providers/firecrawl/definition.d.ts +1 -0
  71. package/dist/providers/firecrawl/types.d.ts +4 -0
  72. package/dist/providers/gemini/adapter.d.ts +14 -0
  73. package/dist/providers/gemini/definition.d.ts +1 -0
  74. package/dist/providers/gemini/types.d.ts +3 -0
  75. package/dist/providers/linkup/adapter.d.ts +7 -0
  76. package/dist/providers/linkup/definition.d.ts +1 -0
  77. package/dist/providers/linkup/types.d.ts +4 -0
  78. package/dist/providers/metadata.d.ts +15 -0
  79. package/dist/providers/ollama/adapter.d.ts +6 -0
  80. package/dist/providers/ollama/definition.d.ts +1 -0
  81. package/dist/providers/ollama/types.d.ts +4 -0
  82. package/dist/providers/openai/adapter.d.ts +7 -0
  83. package/dist/providers/openai/definition.d.ts +1 -0
  84. package/dist/providers/openai/types.d.ts +28 -0
  85. package/dist/providers/parallel/adapter.d.ts +6 -0
  86. package/dist/providers/parallel/definition.d.ts +1 -0
  87. package/dist/providers/parallel/types.d.ts +4 -0
  88. package/dist/providers/perplexity/adapter.d.ts +7 -0
  89. package/dist/providers/perplexity/definition.d.ts +1 -0
  90. package/dist/providers/perplexity/types.d.ts +4 -0
  91. package/dist/providers/registry.d.ts +16 -0
  92. package/dist/providers/serper/adapter.d.ts +5 -0
  93. package/dist/providers/serper/definition.d.ts +1 -0
  94. package/dist/providers/serper/types.d.ts +21 -0
  95. package/dist/providers/shared.d.ts +4 -0
  96. package/dist/providers/tavily/adapter.d.ts +6 -0
  97. package/dist/providers/tavily/definition.d.ts +1 -0
  98. package/dist/providers/tavily/types.d.ts +4 -0
  99. package/dist/providers/valyu/adapter.d.ts +8 -0
  100. package/dist/providers/valyu/definition.d.ts +1 -0
  101. package/dist/providers/valyu/types.d.ts +4 -0
  102. package/dist/render.d.ts +5 -0
  103. package/dist/runtime/credentials.d.ts +15 -0
  104. package/dist/runtime/duration.d.ts +2 -0
  105. package/dist/runtime/execute.d.ts +20 -0
  106. package/dist/runtime/lifecycle.d.ts +8 -0
  107. package/dist/runtime/outward.d.ts +11 -0
  108. package/dist/runtime/polling.d.ts +13 -0
  109. package/dist/runtime/process.d.ts +4 -0
  110. package/docs/cli-experience.md +177 -0
  111. package/docs/provider.md +177 -0
  112. package/docs/reference.md +181 -0
  113. package/example-config.yaml +36 -0
  114. package/examples/custom/README.md +57 -0
  115. package/examples/custom/provider.mjs +54 -0
  116. package/examples/custom/webfox.json +18 -0
  117. package/package.json +122 -8
  118. package/index.js +0 -2
@@ -0,0 +1,123 @@
1
+ import {
2
+ trimSnippet
3
+ } from "./chunk-X6ZBIHZC.js";
4
+ import {
5
+ orderedContents
6
+ } from "./chunk-YZXM4U2C.js";
7
+ import "./chunk-U4BLULLV.js";
8
+
9
+ // src/providers/tavily/adapter.ts
10
+ import {
11
+ tavily
12
+ } from "@tavily/core";
13
+ var tavilyImplementation = {
14
+ async search(query, maxResults, config, _context, options) {
15
+ const client = createClient(config);
16
+ const response = await client.search(query, {
17
+ ...options ?? {},
18
+ maxResults
19
+ });
20
+ return {
21
+ provider: "tavily",
22
+ results: response.results.slice(0, maxResults).map((result) => ({
23
+ title: result.title || result.url || "Untitled",
24
+ url: result.url || "",
25
+ snippet: trimSnippet(result.content ?? result.rawContent),
26
+ score: typeof result.score === "number" ? result.score : void 0,
27
+ metadata: buildSearchMetadata(response, result)
28
+ }))
29
+ };
30
+ },
31
+ async contents(urls, config, _context, options) {
32
+ const client = createClient(config);
33
+ const response = await client.extract(urls, {
34
+ ...options ?? {}
35
+ });
36
+ const resultsByUrl = new Map(
37
+ response.results.map((result) => [result.url, result])
38
+ );
39
+ const failedResultsByUrl = new Map(
40
+ response.failedResults.map((result) => [result.url, result])
41
+ );
42
+ return {
43
+ provider: "tavily",
44
+ answers: urls.map((url) => {
45
+ const result = resultsByUrl.get(url) ?? (urls.length === 1 && response.results.length === 1 ? response.results[0] : void 0);
46
+ if (result) {
47
+ return {
48
+ url: result.url,
49
+ ...typeof result.rawContent === "string" ? { content: result.rawContent } : {},
50
+ metadata: buildExtractMetadata(response, result)
51
+ };
52
+ }
53
+ const failedResult = failedResultsByUrl.get(url) ?? (urls.length === 1 && response.failedResults.length === 1 ? response.failedResults[0] : void 0);
54
+ if (failedResult) {
55
+ return {
56
+ url,
57
+ error: failedResult.error || "Content extraction failed."
58
+ };
59
+ }
60
+ return {
61
+ url,
62
+ error: "No content returned for this URL."
63
+ };
64
+ })
65
+ };
66
+ }
67
+ };
68
+ function createClient(config) {
69
+ const apiKey = config.credentials?.api;
70
+ if (!apiKey) {
71
+ throw new Error("is missing an API key");
72
+ }
73
+ return tavily({
74
+ apiKey,
75
+ apiBaseURL: config.baseUrl
76
+ });
77
+ }
78
+ function buildSearchMetadata(response, result) {
79
+ const metadata = {
80
+ ...response.answer ? { answer: response.answer } : {},
81
+ ...response.images?.length ? { images: response.images } : {},
82
+ ...result.publishedDate ? { publishedDate: result.publishedDate } : {},
83
+ ...result.favicon ? { favicon: result.favicon } : {},
84
+ ...result.rawContent ? { rawContent: result.rawContent } : {},
85
+ ...response.requestId ? { requestId: response.requestId } : {},
86
+ ...typeof response.responseTime === "number" ? { responseTime: response.responseTime } : {}
87
+ };
88
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
89
+ }
90
+ function buildExtractMetadata(response, result) {
91
+ const metadata = {
92
+ ...result.title ? { title: result.title } : {},
93
+ ...Array.isArray(result.images) ? { images: result.images } : {},
94
+ ...result.favicon ? { favicon: result.favicon } : {},
95
+ ...response.requestId ? { requestId: response.requestId } : {},
96
+ ...typeof response.responseTime === "number" ? { responseTime: response.responseTime } : {}
97
+ };
98
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
99
+ }
100
+ var adapter = {
101
+ async search(input, config, context) {
102
+ return await tavilyImplementation.search(
103
+ input.query,
104
+ input.maxResults,
105
+ config,
106
+ context,
107
+ input.options
108
+ );
109
+ },
110
+ async contents(input, config, context) {
111
+ return orderedContents(
112
+ await tavilyImplementation.contents(
113
+ input.urls,
114
+ config,
115
+ context,
116
+ input.options
117
+ )
118
+ );
119
+ }
120
+ };
121
+ export {
122
+ adapter
123
+ };
@@ -0,0 +1,237 @@
1
+ import {
2
+ executeAsyncResearch
3
+ } from "./chunk-EF4VMDZL.js";
4
+ import "./chunk-I2UIACY3.js";
5
+ import {
6
+ sleep,
7
+ withSignal
8
+ } from "./chunk-WCTOYZVB.js";
9
+ import {
10
+ formatJson,
11
+ trimSnippet
12
+ } from "./chunk-X6ZBIHZC.js";
13
+ import {
14
+ orderedContents
15
+ } from "./chunk-YZXM4U2C.js";
16
+ import "./chunk-U4BLULLV.js";
17
+
18
+ // src/providers/valyu/adapter.ts
19
+ import { Valyu as ValyuClient } from "valyu-js";
20
+ var valyuImplementation = {
21
+ async search(query, maxResults, config, _context, searchOptions) {
22
+ const client = createClient(config);
23
+ const options = {
24
+ ...searchOptions ?? {},
25
+ maxNumResults: maxResults
26
+ };
27
+ const response = await client.search(query, options);
28
+ if (!response.success) {
29
+ throw new Error(response.error || "search failed");
30
+ }
31
+ return {
32
+ provider: "valyu",
33
+ results: (response.results ?? []).slice(0, maxResults).map((result) => ({
34
+ title: result.title,
35
+ url: result.url,
36
+ snippet: trimSnippet(
37
+ result.description ?? (typeof result.content === "string" ? result.content : "")
38
+ ),
39
+ score: result.relevance_score
40
+ }))
41
+ };
42
+ },
43
+ async contents(urls, config, context, options) {
44
+ const client = createClient(config);
45
+ const response = await client.contents(urls, {
46
+ ...options ?? {}
47
+ });
48
+ let finalResponse;
49
+ if ("jobId" in response) {
50
+ while (true) {
51
+ context.signal?.throwIfAborted();
52
+ const status = await withSignal(
53
+ client.getContentsJob(response.jobId),
54
+ context.signal
55
+ );
56
+ if (!status.success || ["completed", "partial", "failed"].includes(status.status)) {
57
+ finalResponse = status;
58
+ break;
59
+ }
60
+ await sleep(3e3, context.signal);
61
+ }
62
+ } else finalResponse = response;
63
+ if (!finalResponse.success) {
64
+ throw new Error(finalResponse.error || "contents failed");
65
+ }
66
+ const resultsByUrl = new Map(
67
+ (finalResponse.results ?? []).map(
68
+ (result) => [result.url, result]
69
+ )
70
+ );
71
+ return {
72
+ provider: "valyu",
73
+ answers: urls.map((url) => {
74
+ const result = resultsByUrl.get(url) ?? (urls.length === 1 && finalResponse.results?.length === 1 ? finalResponse.results[0] : void 0);
75
+ if (!result) {
76
+ return {
77
+ url,
78
+ error: "No content returned for this URL."
79
+ };
80
+ }
81
+ return result.status === "failed" ? {
82
+ url,
83
+ error: result.error ?? formatJson(result)
84
+ } : {
85
+ url: result.url,
86
+ ...typeof result.content === "string" || typeof result.content === "number" ? { content: String(result.content) } : {},
87
+ ...result.summary !== void 0 ? { summary: result.summary } : {},
88
+ metadata: result
89
+ };
90
+ })
91
+ };
92
+ },
93
+ async answer(query, config, _context, options) {
94
+ const client = createClient(config);
95
+ const response = await client.answer(query, {
96
+ ...options ?? {},
97
+ streaming: false
98
+ });
99
+ if (!("success" in response) || !response.success) {
100
+ throw new Error(
101
+ "error" in response && typeof response.error === "string" ? response.error : "answer failed"
102
+ );
103
+ }
104
+ const lines = [];
105
+ const contents = typeof response.contents === "string" ? response.contents : formatJson(response.contents);
106
+ lines.push(contents);
107
+ const sources = response.search_results ?? [];
108
+ if (sources.length > 0) {
109
+ lines.push("");
110
+ lines.push("Sources:");
111
+ for (const [index, result] of sources.entries()) {
112
+ lines.push(`${index + 1}. ${result.title}`);
113
+ lines.push(` ${result.url}`);
114
+ }
115
+ }
116
+ return {
117
+ provider: "valyu",
118
+ text: lines.join("\n").trimEnd(),
119
+ itemCount: sources.length
120
+ };
121
+ },
122
+ async research(input, config, context, options) {
123
+ return await executeAsyncResearch({
124
+ providerLabel: "Valyu",
125
+ providerId: "valyu",
126
+ context,
127
+ start: (researchContext) => valyuImplementation.startResearch(
128
+ input,
129
+ config,
130
+ researchContext,
131
+ options
132
+ ),
133
+ poll: (id, researchContext) => valyuImplementation.pollResearch(id, config, researchContext, options)
134
+ });
135
+ },
136
+ async startResearch(input, config, _context, options) {
137
+ const client = createClient(config);
138
+ const task = await client.deepresearch.create({
139
+ input,
140
+ ...options ?? {}
141
+ });
142
+ if (!task.success || !task.deepresearch_id) {
143
+ throw new Error(task.error || "deep research creation failed");
144
+ }
145
+ return { id: task.deepresearch_id };
146
+ },
147
+ async pollResearch(id, config, _context, _options) {
148
+ const client = createClient(config);
149
+ const result = await client.deepresearch.status(id);
150
+ if (!result.success) {
151
+ throw new Error(result.error || "deep research failed");
152
+ }
153
+ if (result.status === "completed") {
154
+ const lines = [];
155
+ lines.push(
156
+ typeof result.output === "string" ? result.output : result.output ? formatJson(result.output) : "Valyu deep research completed without textual output."
157
+ );
158
+ const sources = result.sources ?? [];
159
+ if (sources.length > 0) {
160
+ lines.push("");
161
+ lines.push("Sources:");
162
+ for (const [index, source] of sources.entries()) {
163
+ lines.push(`${index + 1}. ${source.title}`);
164
+ lines.push(` ${source.url}`);
165
+ }
166
+ }
167
+ return {
168
+ status: "completed",
169
+ output: {
170
+ provider: "valyu",
171
+ text: lines.join("\n").trimEnd(),
172
+ itemCount: sources.length
173
+ }
174
+ };
175
+ }
176
+ if (result.status === "failed") {
177
+ return {
178
+ status: "failed",
179
+ error: result.error || "research failed"
180
+ };
181
+ }
182
+ if (result.status === "cancelled") {
183
+ return {
184
+ status: "cancelled",
185
+ error: result.error || "research was canceled"
186
+ };
187
+ }
188
+ return { status: "in_progress" };
189
+ }
190
+ };
191
+ function createClient(config) {
192
+ const apiKey = config.credentials?.api;
193
+ if (!apiKey) {
194
+ throw new Error("is missing an API key");
195
+ }
196
+ return new ValyuClient(apiKey, config.baseUrl);
197
+ }
198
+ var adapter = {
199
+ async search(input, config, context) {
200
+ return await valyuImplementation.search(
201
+ input.query,
202
+ input.maxResults,
203
+ config,
204
+ context,
205
+ input.options
206
+ );
207
+ },
208
+ async contents(input, config, context) {
209
+ return orderedContents(
210
+ await valyuImplementation.contents(
211
+ input.urls,
212
+ config,
213
+ context,
214
+ input.options
215
+ )
216
+ );
217
+ },
218
+ async answer(input, config, context) {
219
+ return await valyuImplementation.answer(
220
+ input.query,
221
+ config,
222
+ context,
223
+ input.options
224
+ );
225
+ },
226
+ async research(input, config, context) {
227
+ return await valyuImplementation.research(
228
+ input.input,
229
+ config,
230
+ context,
231
+ input.options
232
+ );
233
+ }
234
+ };
235
+ export {
236
+ adapter
237
+ };
@@ -0,0 +1,282 @@
1
+ import {
2
+ formatJson,
3
+ trimSnippet
4
+ } from "./chunk-X6ZBIHZC.js";
5
+ import {
6
+ orderedContents
7
+ } from "./chunk-YZXM4U2C.js";
8
+ import {
9
+ WebfoxError,
10
+ asWebfoxError,
11
+ httpError
12
+ } from "./chunk-U4BLULLV.js";
13
+
14
+ // src/providers/firecrawl/adapter.ts
15
+ import FirecrawlClient from "@mendable/firecrawl-js";
16
+ var FIRECRAWL_CLOUD_HOST = "api.firecrawl.dev";
17
+ var FIRECRAWL_DEFAULT_API_URL = "https://api.firecrawl.dev";
18
+ var FIRECRAWL_QUESTION_LIMIT = 1e4;
19
+ var firecrawlImplementation = {
20
+ async search(query, maxResults, config, _context, options) {
21
+ const client = createClient(config);
22
+ const response = await client.search(query, {
23
+ ...options ?? {},
24
+ limit: maxResults
25
+ });
26
+ return {
27
+ provider: "firecrawl",
28
+ results: flattenSearchResults(response).slice(0, maxResults)
29
+ };
30
+ },
31
+ async contents(urls, config, _context, options) {
32
+ const client = createClient(config);
33
+ const scrapeOptions = {
34
+ formats: ["markdown"],
35
+ onlyMainContent: true,
36
+ ...options ?? {},
37
+ autoResume: false
38
+ };
39
+ return {
40
+ provider: "firecrawl",
41
+ answers: await Promise.all(
42
+ urls.map(async (url) => {
43
+ try {
44
+ const document = await client.scrape(url, scrapeOptions);
45
+ const content = getDocumentContent(document);
46
+ return content ? {
47
+ url,
48
+ content,
49
+ ...document.metadata ? {
50
+ metadata: document.metadata
51
+ } : {}
52
+ } : {
53
+ url,
54
+ error: "No content returned for this URL."
55
+ };
56
+ } catch (error) {
57
+ return {
58
+ url,
59
+ error: asWebfoxError(error).toJSON()
60
+ };
61
+ }
62
+ })
63
+ )
64
+ };
65
+ },
66
+ async answer(query, config, context, options) {
67
+ const question = validateQuestion(query);
68
+ const mergedOptions = {
69
+ onlyMainContent: true,
70
+ ...options ?? {}
71
+ };
72
+ const url = validateUrl(mergedOptions.url);
73
+ const scrapeOptions = stripAnswerOnlyOptions(mergedOptions);
74
+ const response = await scrapeQuestion(
75
+ config,
76
+ url,
77
+ question,
78
+ scrapeOptions,
79
+ context.signal
80
+ );
81
+ const document = getFirecrawlDocument(response);
82
+ const answer = readString(document.answer);
83
+ if (!answer?.trim()) {
84
+ throw new Error("No answer returned for this URL.");
85
+ }
86
+ return {
87
+ provider: "firecrawl",
88
+ text: answer.trim(),
89
+ itemCount: 1,
90
+ metadata: {
91
+ url,
92
+ ...asRecord(document.metadata) ? { metadata: document.metadata } : {}
93
+ }
94
+ };
95
+ }
96
+ };
97
+ function createClient(config) {
98
+ const apiUrl = config.baseUrl;
99
+ const apiKey = config.credentials?.api;
100
+ if (isFirecrawlCloudApiUrl(apiUrl) && !apiKey) {
101
+ throw new WebfoxError(
102
+ "PROVIDER_UNAVAILABLE",
103
+ "Firecrawl cloud requires FIRECRAWL_API_KEY; self-hosted instances can use baseUrl without a key."
104
+ );
105
+ }
106
+ return new FirecrawlClient({
107
+ // Firecrawl counts attempts, unlike SDKs whose maxRetries excludes the first call.
108
+ maxRetries: 1,
109
+ apiKey,
110
+ apiUrl
111
+ });
112
+ }
113
+ function isFirecrawlCloudApiUrl(apiUrl) {
114
+ return !apiUrl || apiUrl.includes(FIRECRAWL_CLOUD_HOST);
115
+ }
116
+ function validateQuestion(query) {
117
+ const question = query.trim();
118
+ if (!question) {
119
+ throw new Error("question must be a non-empty string.");
120
+ }
121
+ if (question.length > FIRECRAWL_QUESTION_LIMIT) {
122
+ throw new Error(
123
+ `Firecrawl question must be at most ${FIRECRAWL_QUESTION_LIMIT} characters.`
124
+ );
125
+ }
126
+ return question;
127
+ }
128
+ function validateUrl(value) {
129
+ if (typeof value !== "string" || !value.trim()) {
130
+ throw new Error("Firecrawl answer requires options.url.");
131
+ }
132
+ return value.trim();
133
+ }
134
+ function stripAnswerOnlyOptions(options) {
135
+ const { url: _url, formats: _formats, ...scrapeOptions } = options;
136
+ return scrapeOptions;
137
+ }
138
+ async function scrapeQuestion(config, url, question, options, signal) {
139
+ const apiUrl = config.baseUrl ?? FIRECRAWL_DEFAULT_API_URL;
140
+ const apiKey = config.credentials?.api;
141
+ if (isFirecrawlCloudApiUrl(apiUrl) && !apiKey) {
142
+ throw new WebfoxError(
143
+ "PROVIDER_UNAVAILABLE",
144
+ "Firecrawl cloud requires FIRECRAWL_API_KEY; self-hosted instances can use baseUrl without a key."
145
+ );
146
+ }
147
+ const response = await fetch(joinUrl(apiUrl, "/v2/scrape"), {
148
+ signal,
149
+ method: "POST",
150
+ headers: {
151
+ "Content-Type": "application/json",
152
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
153
+ },
154
+ body: JSON.stringify({
155
+ ...options,
156
+ url,
157
+ formats: [{ type: "question", question }]
158
+ })
159
+ });
160
+ const payload = await readJsonResponse(response);
161
+ if (!response.ok) {
162
+ throw httpError(response, readFirecrawlError(payload, response.statusText));
163
+ }
164
+ if (isFirecrawlFailure(payload)) {
165
+ throw new Error(readFirecrawlError(payload, "Firecrawl scrape failed."));
166
+ }
167
+ return payload;
168
+ }
169
+ function joinUrl(baseUrl, path) {
170
+ return `${baseUrl.replace(/\/+$/g, "")}/${path.replace(/^\/+/g, "")}`;
171
+ }
172
+ async function readJsonResponse(response) {
173
+ const text = await response.text();
174
+ if (!text) {
175
+ return {};
176
+ }
177
+ try {
178
+ return JSON.parse(text);
179
+ } catch {
180
+ return text;
181
+ }
182
+ }
183
+ function isFirecrawlFailure(value) {
184
+ const record = asRecord(value);
185
+ return record?.success === false || record?.error !== void 0;
186
+ }
187
+ function readFirecrawlError(value, fallback) {
188
+ const record = asRecord(value);
189
+ return readString(record?.error) ?? readString(record?.message) ?? (typeof value === "string" ? value : void 0) ?? fallback;
190
+ }
191
+ function getFirecrawlDocument(value) {
192
+ const record = asRecord(value);
193
+ const data = asRecord(record?.data);
194
+ if (data) {
195
+ return data;
196
+ }
197
+ if (record) {
198
+ return record;
199
+ }
200
+ throw new Error(`Unexpected Firecrawl response: ${formatJson(value)}`);
201
+ }
202
+ function flattenSearchResults(response) {
203
+ return ["web", "news", "images"].flatMap(
204
+ (source) => (response[source] ?? []).map((entry) => toSearchResult(source, entry)).filter((entry) => entry !== null)
205
+ );
206
+ }
207
+ function toSearchResult(source, value) {
208
+ const entry = asRecord(value);
209
+ if (!entry) {
210
+ return null;
211
+ }
212
+ const metadata = asRecord(entry.metadata);
213
+ const url = readString(entry.url) ?? readString(metadata?.sourceURL) ?? readString(entry.imageUrl) ?? "";
214
+ const title = readString(entry.title) ?? readString(metadata?.title) ?? url;
215
+ const snippet = trimSnippet(
216
+ readString(entry.description) ?? readString(entry.snippet) ?? readString(entry.markdown) ?? readString(metadata?.description) ?? ""
217
+ );
218
+ const resultMetadata = {
219
+ source,
220
+ ...readString(entry.category) ? { category: entry.category } : {},
221
+ ...readString(entry.date) ? { date: entry.date } : {},
222
+ ...readString(entry.imageUrl) ? { imageUrl: entry.imageUrl } : {},
223
+ ...typeof entry.position === "number" ? { position: entry.position } : {},
224
+ ...metadata ?? {}
225
+ };
226
+ return {
227
+ title: title || "Untitled",
228
+ url,
229
+ snippet,
230
+ metadata: Object.keys(resultMetadata).length > 1 ? resultMetadata : void 0
231
+ };
232
+ }
233
+ function getDocumentContent(document) {
234
+ if (typeof document.markdown === "string" && document.markdown.trim()) {
235
+ return document.markdown;
236
+ }
237
+ if (typeof document.html === "string" && document.html.trim()) {
238
+ return document.html;
239
+ }
240
+ if (typeof document.rawHtml === "string" && document.rawHtml.trim()) {
241
+ return document.rawHtml;
242
+ }
243
+ return document.json !== void 0 ? JSON.stringify(document.json, null, 2) : void 0;
244
+ }
245
+ function asRecord(value) {
246
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
247
+ }
248
+ function readString(value) {
249
+ return typeof value === "string" ? value : void 0;
250
+ }
251
+ var adapter = {
252
+ async search(input, config, context) {
253
+ return await firecrawlImplementation.search(
254
+ input.query,
255
+ input.maxResults,
256
+ config,
257
+ context,
258
+ input.options
259
+ );
260
+ },
261
+ async contents(input, config, context) {
262
+ return orderedContents(
263
+ await firecrawlImplementation.contents(
264
+ input.urls,
265
+ config,
266
+ context,
267
+ input.options
268
+ )
269
+ );
270
+ },
271
+ async answer(input, config, context) {
272
+ return await firecrawlImplementation.answer(
273
+ input.query,
274
+ config,
275
+ context,
276
+ input.options
277
+ );
278
+ }
279
+ };
280
+ export {
281
+ adapter
282
+ };
@@ -0,0 +1,34 @@
1
+ import type { AnswerDocument, AnswerOptions, Capability, ContentsDocument, ContentsOptions, ProviderId, ResearchDocument, ResearchOptions, SearchDocument, SearchOptions } from "./domain.js";
2
+ import type { WebfoxConfig } from "./configuration/types.js";
3
+ import type { ProviderMetadata } from "./providers/metadata.js";
4
+ export type { ProviderMetadata } from "./providers/metadata.js";
5
+ export interface CreateWebfoxOptions {
6
+ config?: WebfoxConfig;
7
+ configPath?: string;
8
+ cwd?: string;
9
+ env?: Record<string, string | undefined>;
10
+ }
11
+ export interface ProviderInspection extends ProviderMetadata {
12
+ configurationRequirements: Record<string, string>;
13
+ configured: readonly Capability[];
14
+ selectedDefaults: readonly Capability[];
15
+ }
16
+ export interface CapabilityInspection {
17
+ capability: Capability;
18
+ provider?: ProviderId;
19
+ configured: boolean;
20
+ optionSchema?: Record<string, unknown>;
21
+ defaults: {
22
+ maxResults?: number;
23
+ options: Record<string, unknown>;
24
+ };
25
+ }
26
+ export interface WebfoxClient {
27
+ search(options: SearchOptions): Promise<SearchDocument>;
28
+ contents(options: ContentsOptions): Promise<ContentsDocument>;
29
+ answer(options: AnswerOptions): Promise<AnswerDocument>;
30
+ research(options: ResearchOptions): Promise<ResearchDocument>;
31
+ listProviders(): readonly ProviderInspection[];
32
+ getProvider(id: ProviderId): ProviderInspection | undefined;
33
+ inspectCapability(capability: Capability, provider?: ProviderId): CapabilityInspection;
34
+ }
@@ -0,0 +1,2 @@
1
+ import type { CreateWebfoxOptions, WebfoxClient } from "./application-types.js";
2
+ export declare function createWebfox(options?: CreateWebfoxOptions): WebfoxClient;