webfox 0.0.0-test.0 → 4.0.1
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/LICENSE +21 -0
- package/README.md +256 -3
- package/assets/webfox-lockup-dark.png +0 -0
- package/assets/webfox-lockup.png +0 -0
- package/dist/adapter-2YBFCBCZ.js +161 -0
- package/dist/adapter-67TIISDW.js +753 -0
- package/dist/adapter-AB7LKV7N.js +324 -0
- package/dist/adapter-AUYLTDPM.js +73 -0
- package/dist/adapter-HITGYUXN.js +494 -0
- package/dist/adapter-HPTMLH75.js +334 -0
- package/dist/adapter-I6OUKBZQ.js +492 -0
- package/dist/adapter-ISZQTZDP.js +166 -0
- package/dist/adapter-MAG7X663.js +230 -0
- package/dist/adapter-NEXSOHGT.js +232 -0
- package/dist/adapter-PADXKW43.js +162 -0
- package/dist/adapter-SDQX2533.js +123 -0
- package/dist/adapter-UAZNLDSS.js +237 -0
- package/dist/adapter-XBAQSK7I.js +282 -0
- package/dist/application-types.d.ts +34 -0
- package/dist/application.d.ts +2 -0
- package/dist/chunk-EF4VMDZL.js +85 -0
- package/dist/chunk-G2YAQEET.js +38 -0
- package/dist/chunk-GAWPRXAU.js +3051 -0
- package/dist/chunk-I2UIACY3.js +12 -0
- package/dist/chunk-MVCCEPDF.js +762 -0
- package/dist/chunk-QRHDXM2L.js +31 -0
- package/dist/chunk-U4BLULLV.js +52 -0
- package/dist/chunk-WCTOYZVB.js +85 -0
- package/dist/chunk-X6ZBIHZC.js +21 -0
- package/dist/chunk-YZXM4U2C.js +21 -0
- package/dist/chunk-ZVTVERW5.js +20 -0
- package/dist/cli.js +641 -0
- package/dist/config.schema.json +4270 -0
- package/dist/configuration/defaults.d.ts +3 -0
- package/dist/configuration/file.d.ts +17 -0
- package/dist/configuration/options-schema.d.ts +2 -0
- package/dist/configuration/planning.d.ts +10 -0
- package/dist/configuration/schema.d.ts +9 -0
- package/dist/configuration/types.d.ts +37 -0
- package/dist/contents.d.ts +19 -0
- package/dist/domain.d.ts +89 -0
- package/dist/errors.d.ts +17 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +38 -0
- package/dist/package-metadata.d.ts +3 -0
- package/dist/pi-keybindings.d.ts +1 -0
- package/dist/pi-params.d.ts +3 -0
- package/dist/pi-render.d.ts +50 -0
- package/dist/pi-search-render.d.ts +4 -0
- package/dist/pi-text.d.ts +4 -0
- package/dist/pi-validation.d.ts +4 -0
- package/dist/pi.d.ts +2 -0
- package/dist/pi.js +563 -0
- package/dist/providers/brave/adapter.d.ts +7 -0
- package/dist/providers/brave/definition.d.ts +1 -0
- package/dist/providers/brave/types.d.ts +4 -0
- package/dist/providers/cli-json.d.ts +9 -0
- package/dist/providers/cloudflare/adapter.d.ts +5 -0
- package/dist/providers/cloudflare/definition.d.ts +1 -0
- package/dist/providers/cloudflare/types.d.ts +4 -0
- package/dist/providers/contract.d.ts +88 -0
- package/dist/providers/custom/adapter.d.ts +9 -0
- package/dist/providers/custom/definition.d.ts +1 -0
- package/dist/providers/custom/types.d.ts +9 -0
- package/dist/providers/definition.d.ts +32 -0
- package/dist/providers/exa/adapter.d.ts +8 -0
- package/dist/providers/exa/definition.d.ts +1 -0
- package/dist/providers/exa/types.d.ts +4 -0
- package/dist/providers/firecrawl/adapter.d.ts +7 -0
- package/dist/providers/firecrawl/definition.d.ts +1 -0
- package/dist/providers/firecrawl/types.d.ts +4 -0
- package/dist/providers/gemini/adapter.d.ts +14 -0
- package/dist/providers/gemini/definition.d.ts +1 -0
- package/dist/providers/gemini/types.d.ts +3 -0
- package/dist/providers/linkup/adapter.d.ts +7 -0
- package/dist/providers/linkup/definition.d.ts +1 -0
- package/dist/providers/linkup/types.d.ts +4 -0
- package/dist/providers/metadata.d.ts +15 -0
- package/dist/providers/ollama/adapter.d.ts +6 -0
- package/dist/providers/ollama/definition.d.ts +1 -0
- package/dist/providers/ollama/types.d.ts +4 -0
- package/dist/providers/openai/adapter.d.ts +7 -0
- package/dist/providers/openai/definition.d.ts +1 -0
- package/dist/providers/openai/types.d.ts +28 -0
- package/dist/providers/parallel/adapter.d.ts +6 -0
- package/dist/providers/parallel/definition.d.ts +1 -0
- package/dist/providers/parallel/types.d.ts +4 -0
- package/dist/providers/perplexity/adapter.d.ts +7 -0
- package/dist/providers/perplexity/definition.d.ts +1 -0
- package/dist/providers/perplexity/types.d.ts +4 -0
- package/dist/providers/registry.d.ts +16 -0
- package/dist/providers/serper/adapter.d.ts +5 -0
- package/dist/providers/serper/definition.d.ts +1 -0
- package/dist/providers/serper/types.d.ts +21 -0
- package/dist/providers/shared.d.ts +4 -0
- package/dist/providers/tavily/adapter.d.ts +6 -0
- package/dist/providers/tavily/definition.d.ts +1 -0
- package/dist/providers/tavily/types.d.ts +4 -0
- package/dist/providers/valyu/adapter.d.ts +8 -0
- package/dist/providers/valyu/definition.d.ts +1 -0
- package/dist/providers/valyu/types.d.ts +4 -0
- package/dist/render.d.ts +5 -0
- package/dist/runtime/credentials.d.ts +15 -0
- package/dist/runtime/duration.d.ts +2 -0
- package/dist/runtime/execute.d.ts +20 -0
- package/dist/runtime/lifecycle.d.ts +8 -0
- package/dist/runtime/outward.d.ts +11 -0
- package/dist/runtime/polling.d.ts +13 -0
- package/dist/runtime/process.d.ts +4 -0
- package/docs/cli-experience.md +177 -0
- package/docs/provider.md +177 -0
- package/docs/reference.md +181 -0
- package/example-config.yaml +36 -0
- package/examples/custom/README.md +57 -0
- package/examples/custom/provider.mjs +54 -0
- package/examples/custom/webfox.json +18 -0
- package/package.json +122 -8
- 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
|
+
}
|