webfox 4.10.0 → 4.11.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 +2 -12
- package/dist/{adapter-UIFBJKKF.js → adapter-44ZBIK47.js} +4 -6
- package/dist/{adapter-HTMXAJTU.js → adapter-57MELLYI.js} +2 -8
- package/dist/{adapter-VMMS4YQG.js → adapter-BZO5UEI6.js} +3 -15
- package/dist/{adapter-OK2PTP6R.js → adapter-D4EXZEDL.js} +16 -48
- package/dist/{adapter-G3I7JEO5.js → adapter-DJCTEEOA.js} +3 -5
- package/dist/{adapter-764OQFKY.js → adapter-DLNMW34W.js} +8 -35
- package/dist/{adapter-HRI5ZMGK.js → adapter-GFXYSHKP.js} +9 -43
- package/dist/{adapter-2UVAARTV.js → adapter-GJOUFE2U.js} +1 -1
- package/dist/{adapter-C2VZHFUB.js → adapter-MZ7USB6Q.js} +3 -4
- package/dist/adapter-ODDFBGJG.js +167 -0
- package/dist/{adapter-K6AMSUCR.js → adapter-QQZFHUOP.js} +14 -37
- package/dist/{adapter-AUYLTDPM.js → adapter-RR55EZAW.js} +1 -1
- package/dist/{adapter-JJSMYZXG.js → adapter-SZMVKAMC.js} +1 -1
- package/dist/{adapter-6EZAIE65.js → adapter-VXT4C6RZ.js} +16 -66
- package/dist/{adapter-L6BFKWGX.js → adapter-WXEVTCM3.js} +7 -14
- package/dist/{adapter-ZYDUANQL.js → adapter-XSQX3GAI.js} +13 -5
- package/dist/{adapter-EIDSGLOP.js → adapter-XU2APKPC.js} +3 -3
- package/dist/{adapter-A5WYGGZI.js → adapter-YVZ4YBK5.js} +8 -16
- package/dist/chunk-6RQGO727.js +71 -0
- package/dist/{chunk-TGULKHB5.js → chunk-KMUCUJVN.js} +3 -3
- package/dist/{chunk-EF4VMDZL.js → chunk-LDVDVA4Z.js} +1 -4
- package/dist/{chunk-57OOEOXI.js → chunk-NENL6L6N.js} +1 -0
- package/dist/{chunk-5FP7KNMC.js → chunk-PVNWACMT.js} +132 -18
- package/dist/{chunk-YZXM4U2C.js → chunk-TYJ6RHKB.js} +10 -0
- package/dist/{chunk-3WBE6UEW.js → chunk-YILOS4E7.js} +23 -9
- package/dist/cli.js +5 -5
- package/dist/config.schema.json +187 -0
- package/dist/contents.d.ts +5 -0
- package/dist/document-schema.d.ts +17 -5
- package/dist/domain.d.ts +14 -2
- package/dist/durable.js +5 -5
- package/dist/index.js +4 -4
- package/dist/pi.js +33 -14
- package/dist/providers/contract.d.ts +11 -1
- package/dist/providers/registry.d.ts +1 -0
- package/dist/providers/sofya/adapter.d.ts +8 -0
- package/dist/providers/sofya/definition.d.ts +1 -0
- package/dist/providers/sofya/types.d.ts +4 -0
- package/dist/render.d.ts +8 -2
- package/docs/provider.md +27 -0
- package/examples/custom/README.md +9 -3
- package/package.json +1 -1
- package/dist/chunk-BTWECET5.js +0 -31
package/README.md
CHANGED
|
@@ -11,18 +11,6 @@ Search the web, extract pages, get grounded answers, and run deep research with
|
|
|
11
11
|
one command: `web`. Choose your providers, bring your API keys, and use the same
|
|
12
12
|
configuration in your terminal, TypeScript applications, and pi.
|
|
13
13
|
|
|
14
|
-
> [!IMPORTANT]
|
|
15
|
-
> **Coming from pi-web-providers?** Webfox continues the project as **v4.0.0**.
|
|
16
|
-
> Replace the old Pi package:
|
|
17
|
-
>
|
|
18
|
-
> ```sh
|
|
19
|
-
> pi remove npm:pi-web-providers
|
|
20
|
-
> pi install npm:webfox
|
|
21
|
-
> ```
|
|
22
|
-
>
|
|
23
|
-
> Recreate your provider defaults in the [Webfox configuration](./docs/reference.md#configuration)
|
|
24
|
-
> and restart pi. The old `~/.pi/agent/web-providers.json` settings aren't imported.
|
|
25
|
-
|
|
26
14
|
## 🚀 Installation
|
|
27
15
|
|
|
28
16
|
- **Terminal:** Install the `web` command with [Homebrew](https://github.com/mavam/homebrew-tap):
|
|
@@ -152,6 +140,7 @@ Use different providers for different tasks:
|
|
|
152
140
|
| [Perplexity] | ✔︎ | | ✔︎ | ✔︎ |
|
|
153
141
|
| [SerpBase] | ✔︎ | | | |
|
|
154
142
|
| [Serper] | ✔︎ | | | |
|
|
143
|
+
| [Sofya] | ✔︎ | ✔︎ | ✔︎ | ✔︎ |
|
|
155
144
|
| [Tavily] | ✔︎ | ✔︎ | | |
|
|
156
145
|
| [Valyu] | ✔︎ | ✔︎ | ✔︎ | ✔︎ |
|
|
157
146
|
| [You.com] | ✔︎ | | | |
|
|
@@ -170,6 +159,7 @@ Use different providers for different tasks:
|
|
|
170
159
|
[Perplexity]: ./docs/provider.md#perplexity
|
|
171
160
|
[SerpBase]: ./docs/provider.md#serpbase
|
|
172
161
|
[Serper]: ./docs/provider.md#serper
|
|
162
|
+
[Sofya]: ./docs/provider.md#sofya
|
|
173
163
|
[Tavily]: ./docs/provider.md#tavily
|
|
174
164
|
[Valyu]: ./docs/provider.md#valyu
|
|
175
165
|
[You.com]: ./docs/provider.md#youcom
|
|
@@ -2,8 +2,9 @@ import {
|
|
|
2
2
|
trimSnippet
|
|
3
3
|
} from "./chunk-FSTCLBSF.js";
|
|
4
4
|
import {
|
|
5
|
+
omittedContents,
|
|
5
6
|
orderedContents
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TYJ6RHKB.js";
|
|
7
8
|
import "./chunk-U4BLULLV.js";
|
|
8
9
|
|
|
9
10
|
// src/providers/tavily/adapter.ts
|
|
@@ -20,7 +21,7 @@ var tavilyImplementation = {
|
|
|
20
21
|
return {
|
|
21
22
|
provider: "tavily",
|
|
22
23
|
results: response.results.slice(0, maxResults).map((result) => ({
|
|
23
|
-
title: result.title
|
|
24
|
+
title: result.title,
|
|
24
25
|
url: result.url || "",
|
|
25
26
|
snippet: trimSnippet(result.content ?? result.rawContent),
|
|
26
27
|
score: typeof result.score === "number" ? result.score : void 0,
|
|
@@ -57,10 +58,7 @@ var tavilyImplementation = {
|
|
|
57
58
|
error: failedResult.error || "Content extraction failed."
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
|
-
return
|
|
61
|
-
url,
|
|
62
|
-
error: "No content returned for this URL."
|
|
63
|
-
};
|
|
61
|
+
return omittedContents(url);
|
|
64
62
|
})
|
|
65
63
|
};
|
|
66
64
|
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-FSTCLBSF.js";
|
|
5
5
|
import {
|
|
6
6
|
orderedContents
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TYJ6RHKB.js";
|
|
8
8
|
import {
|
|
9
9
|
asWebfoxError,
|
|
10
10
|
httpError
|
|
@@ -36,7 +36,7 @@ async function searchOllama(query, maxResults, config, context) {
|
|
|
36
36
|
return {
|
|
37
37
|
provider: "ollama",
|
|
38
38
|
results: results.slice(0, clampMaxResults(maxResults)).map((result) => ({
|
|
39
|
-
title: result.title
|
|
39
|
+
title: result.title,
|
|
40
40
|
url: result.url ?? "",
|
|
41
41
|
snippet: trimSnippet(result.content)
|
|
42
42
|
}))
|
|
@@ -69,12 +69,6 @@ async function fetchOllamaContents(urls, config, context) {
|
|
|
69
69
|
}
|
|
70
70
|
const data = await response.json();
|
|
71
71
|
const content = normalizeContentText(data.content);
|
|
72
|
-
if (!content) {
|
|
73
|
-
return {
|
|
74
|
-
url,
|
|
75
|
-
error: "No content returned for this URL."
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
72
|
const metadata = buildFetchMetadata(data);
|
|
79
73
|
return {
|
|
80
74
|
url,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAsyncResearch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LDVDVA4Z.js";
|
|
4
4
|
import "./chunk-I2UIACY3.js";
|
|
5
5
|
import "./chunk-WCTOYZVB.js";
|
|
6
6
|
import {
|
|
@@ -315,23 +315,11 @@ function ensureCompletedResponse(response, operation) {
|
|
|
315
315
|
throw new Error(`${operation} did not complete (status: ${status})`);
|
|
316
316
|
}
|
|
317
317
|
function formatResponseOutput(response, operation) {
|
|
318
|
-
const lines = [];
|
|
319
|
-
lines.push(
|
|
320
|
-
response.output_text?.trim() || `OpenAI ${operation} completed without textual output.`
|
|
321
|
-
);
|
|
322
318
|
const citations = extractUrlCitations(response);
|
|
323
|
-
if (citations.length > 0) {
|
|
324
|
-
lines.push("");
|
|
325
|
-
lines.push("Sources:");
|
|
326
|
-
for (const [index, citation] of citations.entries()) {
|
|
327
|
-
lines.push(`${index + 1}. ${citation.title}`);
|
|
328
|
-
lines.push(` ${citation.url}`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
319
|
return {
|
|
332
320
|
provider: "openai",
|
|
333
|
-
text:
|
|
334
|
-
|
|
321
|
+
text: response.output_text ?? "",
|
|
322
|
+
sources: citations.map(({ title, url }) => ({ title, url })),
|
|
335
323
|
metadata: {
|
|
336
324
|
responseId: response.id,
|
|
337
325
|
model: response.model,
|
|
@@ -38,7 +38,6 @@ var perplexityImplementation = {
|
|
|
38
38
|
config,
|
|
39
39
|
context,
|
|
40
40
|
DEFAULT_ANSWER_MODEL,
|
|
41
|
-
"Answer",
|
|
42
41
|
options
|
|
43
42
|
);
|
|
44
43
|
},
|
|
@@ -48,12 +47,11 @@ var perplexityImplementation = {
|
|
|
48
47
|
config,
|
|
49
48
|
context,
|
|
50
49
|
DEFAULT_RESEARCH_MODEL,
|
|
51
|
-
"Research",
|
|
52
50
|
options
|
|
53
51
|
);
|
|
54
52
|
}
|
|
55
53
|
};
|
|
56
|
-
async function runSilentForegroundChatTool(input, config, context, fallbackModel,
|
|
54
|
+
async function runSilentForegroundChatTool(input, config, context, fallbackModel, options) {
|
|
57
55
|
const client = createClient(config);
|
|
58
56
|
const request = {
|
|
59
57
|
...options ?? {},
|
|
@@ -67,23 +65,13 @@ async function runSilentForegroundChatTool(input, config, context, fallbackModel
|
|
|
67
65
|
);
|
|
68
66
|
const content = extractMessageText(response.choices[0]?.message?.content);
|
|
69
67
|
const sources = dedupeSources(extractSources(response));
|
|
70
|
-
const lines = [];
|
|
71
|
-
lines.push(content || `No ${label.toLowerCase()} returned.`);
|
|
72
|
-
if (sources.length > 0) {
|
|
73
|
-
lines.push("");
|
|
74
|
-
lines.push("Sources:");
|
|
75
|
-
for (const [index, source] of sources.entries()) {
|
|
76
|
-
lines.push(`${index + 1}. ${source.title}`);
|
|
77
|
-
lines.push(` ${source.url}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
68
|
return {
|
|
81
69
|
provider: "perplexity",
|
|
82
|
-
text:
|
|
83
|
-
|
|
70
|
+
text: content,
|
|
71
|
+
sources
|
|
84
72
|
};
|
|
85
73
|
}
|
|
86
|
-
async function runStreamingForegroundChatTool(input, config, context, fallbackModel,
|
|
74
|
+
async function runStreamingForegroundChatTool(input, config, context, fallbackModel, options) {
|
|
87
75
|
const client = createClient(config);
|
|
88
76
|
const request = {
|
|
89
77
|
...options ?? {},
|
|
@@ -106,21 +94,11 @@ async function runStreamingForegroundChatTool(input, config, context, fallbackMo
|
|
|
106
94
|
}
|
|
107
95
|
sources.push(...extractSources(chunk));
|
|
108
96
|
}
|
|
109
|
-
const finalText = partialText.trim() || extractMessageText(lastChunk?.choices?.[0]?.message?.content)
|
|
110
|
-
const dedupedSources = dedupeSources(sources);
|
|
111
|
-
const lines = [finalText];
|
|
112
|
-
if (dedupedSources.length > 0) {
|
|
113
|
-
lines.push("");
|
|
114
|
-
lines.push("Sources:");
|
|
115
|
-
for (const [index, source] of dedupedSources.entries()) {
|
|
116
|
-
lines.push(`${index + 1}. ${source.title}`);
|
|
117
|
-
lines.push(` ${source.url}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
97
|
+
const finalText = partialText.trim() || extractMessageText(lastChunk?.choices?.[0]?.message?.content);
|
|
120
98
|
return {
|
|
121
99
|
provider: "perplexity",
|
|
122
|
-
text:
|
|
123
|
-
|
|
100
|
+
text: finalText,
|
|
101
|
+
sources: dedupeSources(sources)
|
|
124
102
|
};
|
|
125
103
|
}
|
|
126
104
|
function createClient(config) {
|
|
@@ -182,32 +160,22 @@ function extractDeltaText(content) {
|
|
|
182
160
|
}
|
|
183
161
|
function dedupeSources(sources) {
|
|
184
162
|
const seen = /* @__PURE__ */ new Set();
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const url = source.url.trim();
|
|
189
|
-
if (!url) continue;
|
|
190
|
-
const key = `${title.toLowerCase()}::${url.toLowerCase()}`;
|
|
191
|
-
if (seen.has(key)) continue;
|
|
163
|
+
return sources.filter((source) => {
|
|
164
|
+
const key = `${source.title?.trim().toLowerCase() ?? ""}::${source.url?.trim().toLowerCase()}`;
|
|
165
|
+
if (seen.has(key)) return false;
|
|
192
166
|
seen.add(key);
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
return unique;
|
|
167
|
+
return true;
|
|
168
|
+
});
|
|
196
169
|
}
|
|
197
170
|
function extractSources(response) {
|
|
198
|
-
const searchResults = response.search_results?.flatMap(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return [];
|
|
202
|
-
}
|
|
203
|
-
return [{ title: result.title?.trim() ?? url, url }];
|
|
204
|
-
}) ?? [];
|
|
171
|
+
const searchResults = response.search_results?.flatMap(
|
|
172
|
+
(result) => result.url?.trim() ? [{ title: result.title ?? void 0, url: result.url }] : []
|
|
173
|
+
) ?? [];
|
|
205
174
|
if (searchResults.length > 0) {
|
|
206
175
|
return searchResults;
|
|
207
176
|
}
|
|
208
177
|
return response.citations?.flatMap((citation) => {
|
|
209
|
-
|
|
210
|
-
return url ? [{ title: url, url }] : [];
|
|
178
|
+
return citation?.trim() ? [{ url: citation }] : [];
|
|
211
179
|
}) ?? [];
|
|
212
180
|
}
|
|
213
181
|
function buildRequestOptions(context) {
|
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
trimSnippet
|
|
4
4
|
} from "./chunk-FSTCLBSF.js";
|
|
5
5
|
import {
|
|
6
|
+
omittedContents,
|
|
6
7
|
orderedContents
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-TYJ6RHKB.js";
|
|
8
9
|
import "./chunk-U4BLULLV.js";
|
|
9
10
|
|
|
10
11
|
// src/providers/parallel/adapter.ts
|
|
@@ -56,10 +57,7 @@ var parallelImplementation = {
|
|
|
56
57
|
return error ? {
|
|
57
58
|
url,
|
|
58
59
|
error: formatJson(error)
|
|
59
|
-
} :
|
|
60
|
-
url,
|
|
61
|
-
error: "No content returned for this URL."
|
|
62
|
-
};
|
|
60
|
+
} : omittedContents(url);
|
|
63
61
|
})
|
|
64
62
|
};
|
|
65
63
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAsyncResearch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LDVDVA4Z.js";
|
|
4
4
|
import "./chunk-I2UIACY3.js";
|
|
5
5
|
import {
|
|
6
6
|
sleep,
|
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
trimSnippet
|
|
12
12
|
} from "./chunk-FSTCLBSF.js";
|
|
13
13
|
import {
|
|
14
|
+
omittedContents,
|
|
14
15
|
orderedContents
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-TYJ6RHKB.js";
|
|
16
17
|
import "./chunk-U4BLULLV.js";
|
|
17
18
|
|
|
18
19
|
// src/providers/valyu/adapter.ts
|
|
@@ -73,10 +74,7 @@ var valyuImplementation = {
|
|
|
73
74
|
answers: urls.map((url) => {
|
|
74
75
|
const result = resultsByUrl.get(url) ?? (urls.length === 1 && finalResponse.results?.length === 1 ? finalResponse.results[0] : void 0);
|
|
75
76
|
if (!result) {
|
|
76
|
-
return
|
|
77
|
-
url,
|
|
78
|
-
error: "No content returned for this URL."
|
|
79
|
-
};
|
|
77
|
+
return omittedContents(url);
|
|
80
78
|
}
|
|
81
79
|
if (result.status === "failed" || result.status === "not_found") {
|
|
82
80
|
return {
|
|
@@ -105,22 +103,10 @@ var valyuImplementation = {
|
|
|
105
103
|
"error" in response && typeof response.error === "string" ? response.error : "answer failed"
|
|
106
104
|
);
|
|
107
105
|
}
|
|
108
|
-
const lines = [];
|
|
109
|
-
const contents = typeof response.contents === "string" ? response.contents : formatJson(response.contents);
|
|
110
|
-
lines.push(contents);
|
|
111
|
-
const sources = response.search_results ?? [];
|
|
112
|
-
if (sources.length > 0) {
|
|
113
|
-
lines.push("");
|
|
114
|
-
lines.push("Sources:");
|
|
115
|
-
for (const [index, result] of sources.entries()) {
|
|
116
|
-
lines.push(`${index + 1}. ${result.title}`);
|
|
117
|
-
lines.push(` ${result.url}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
106
|
return {
|
|
121
107
|
provider: "valyu",
|
|
122
|
-
text:
|
|
123
|
-
|
|
108
|
+
text: typeof response.contents === "string" ? response.contents : response.contents == null ? "" : formatJson(response.contents),
|
|
109
|
+
sources: response.search_results ?? []
|
|
124
110
|
};
|
|
125
111
|
},
|
|
126
112
|
async research(input, config, context, options) {
|
|
@@ -155,25 +141,12 @@ var valyuImplementation = {
|
|
|
155
141
|
throw new Error(result.error || "deep research failed");
|
|
156
142
|
}
|
|
157
143
|
if (result.status === "completed") {
|
|
158
|
-
const lines = [];
|
|
159
|
-
lines.push(
|
|
160
|
-
typeof result.output === "string" ? result.output : result.output ? formatJson(result.output) : "Valyu deep research completed without textual output."
|
|
161
|
-
);
|
|
162
|
-
const sources = result.sources ?? [];
|
|
163
|
-
if (sources.length > 0) {
|
|
164
|
-
lines.push("");
|
|
165
|
-
lines.push("Sources:");
|
|
166
|
-
for (const [index, source] of sources.entries()) {
|
|
167
|
-
lines.push(`${index + 1}. ${source.title}`);
|
|
168
|
-
lines.push(` ${source.url}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
144
|
return {
|
|
172
145
|
status: "completed",
|
|
173
146
|
output: {
|
|
174
147
|
provider: "valyu",
|
|
175
|
-
text:
|
|
176
|
-
|
|
148
|
+
text: typeof result.output === "string" ? result.output : result.output ? formatJson(result.output) : "",
|
|
149
|
+
sources: result.sources ?? []
|
|
177
150
|
}
|
|
178
151
|
};
|
|
179
152
|
}
|
|
@@ -5,11 +5,10 @@ import {
|
|
|
5
5
|
trimSnippet
|
|
6
6
|
} from "./chunk-FSTCLBSF.js";
|
|
7
7
|
import {
|
|
8
|
+
omittedContents,
|
|
8
9
|
orderedContents
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import
|
|
11
|
-
WebfoxError
|
|
12
|
-
} from "./chunk-U4BLULLV.js";
|
|
10
|
+
} from "./chunk-TYJ6RHKB.js";
|
|
11
|
+
import "./chunk-U4BLULLV.js";
|
|
13
12
|
|
|
14
13
|
// src/providers/exa/adapter.ts
|
|
15
14
|
var DEFAULT_BASE_URL = "https://api.exa.ai";
|
|
@@ -28,7 +27,7 @@ var exaImplementation = {
|
|
|
28
27
|
return {
|
|
29
28
|
provider: "exa",
|
|
30
29
|
results: (response.results ?? []).slice(0, maxResults).map((result) => ({
|
|
31
|
-
title:
|
|
30
|
+
title: typeof result.title === "string" ? result.title : void 0,
|
|
32
31
|
url: String(result.url ?? ""),
|
|
33
32
|
snippet: trimSnippet(
|
|
34
33
|
typeof result.text === "string" ? result.text : Array.isArray(result.highlights) ? result.highlights.join(" ") : typeof result.summary === "string" ? result.summary : ""
|
|
@@ -50,10 +49,7 @@ var exaImplementation = {
|
|
|
50
49
|
answers: urls.map((url, index) => {
|
|
51
50
|
const result = results[index];
|
|
52
51
|
if (!result) {
|
|
53
|
-
return
|
|
54
|
-
url,
|
|
55
|
-
error: "No content returned for this URL."
|
|
56
|
-
};
|
|
52
|
+
return omittedContents(url);
|
|
57
53
|
}
|
|
58
54
|
return {
|
|
59
55
|
url: typeof result.url === "string" ? result.url : url,
|
|
@@ -79,25 +75,10 @@ var exaImplementation = {
|
|
|
79
75
|
},
|
|
80
76
|
context
|
|
81
77
|
);
|
|
82
|
-
const lines = [];
|
|
83
|
-
lines.push(
|
|
84
|
-
typeof response.answer === "string" ? response.answer : formatJson(response.answer)
|
|
85
|
-
);
|
|
86
|
-
const citations = response.citations ?? [];
|
|
87
|
-
if (citations.length > 0) {
|
|
88
|
-
lines.push("");
|
|
89
|
-
lines.push("Sources:");
|
|
90
|
-
for (const [index, citation] of citations.entries()) {
|
|
91
|
-
lines.push(
|
|
92
|
-
`${index + 1}. ${String(citation.title ?? citation.url ?? "Untitled")}`
|
|
93
|
-
);
|
|
94
|
-
lines.push(` ${String(citation.url ?? "")}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
78
|
return {
|
|
98
79
|
provider: "exa",
|
|
99
|
-
text:
|
|
100
|
-
|
|
80
|
+
text: typeof response.answer === "string" ? response.answer : response.answer == null ? "" : formatJson(response.answer),
|
|
81
|
+
sources: response.citations ?? []
|
|
101
82
|
};
|
|
102
83
|
},
|
|
103
84
|
async research(input, config, context, options) {
|
|
@@ -117,25 +98,10 @@ var exaImplementation = {
|
|
|
117
98
|
context
|
|
118
99
|
);
|
|
119
100
|
const content = result.output?.content;
|
|
120
|
-
if (typeof content !== "string" || !content.trim())
|
|
121
|
-
throw new WebfoxError(
|
|
122
|
-
"PROVIDER_FAILURE",
|
|
123
|
-
"Exa returned no research report."
|
|
124
|
-
);
|
|
125
|
-
const sources = result.results ?? [];
|
|
126
|
-
const lines = [content.trim()];
|
|
127
|
-
if (sources.length) {
|
|
128
|
-
lines.push("", "Sources:");
|
|
129
|
-
for (const [index, source] of sources.entries())
|
|
130
|
-
lines.push(
|
|
131
|
-
`${index + 1}. ${source.title || source.url}`,
|
|
132
|
-
` ${source.url}`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
101
|
return {
|
|
136
102
|
provider: "exa",
|
|
137
|
-
text:
|
|
138
|
-
|
|
103
|
+
text: typeof content === "string" ? content : "",
|
|
104
|
+
sources: result.results ?? []
|
|
139
105
|
};
|
|
140
106
|
}
|
|
141
107
|
};
|
|
@@ -67,7 +67,7 @@ function joinUrl(baseUrl) {
|
|
|
67
67
|
function collectResults(results, section, searchMetadata) {
|
|
68
68
|
return array(results).map((entry) => asRecord(entry)).filter((entry) => Object.keys(entry).length > 0).map((entry) => {
|
|
69
69
|
const url = string(entry.url) ?? "";
|
|
70
|
-
const title = string(entry.title) ?? string(entry.name)
|
|
70
|
+
const title = string(entry.title) ?? string(entry.name);
|
|
71
71
|
return {
|
|
72
72
|
title,
|
|
73
73
|
url,
|
|
@@ -334,10 +334,9 @@ function toSearchResult(entry, searchContext, mode) {
|
|
|
334
334
|
record.query,
|
|
335
335
|
record.value,
|
|
336
336
|
user?.name,
|
|
337
|
-
formatReviewTitle(record, user)
|
|
338
|
-
|
|
339
|
-
)
|
|
340
|
-
const url = resultUrl || (mode === "autocomplete" ? toGoogleSearchUrl(title) : "");
|
|
337
|
+
formatReviewTitle(record, user)
|
|
338
|
+
);
|
|
339
|
+
const url = resultUrl || (mode === "autocomplete" && title ? toGoogleSearchUrl(title) : "");
|
|
341
340
|
const snippet = trimSnippet(
|
|
342
341
|
firstNonEmptyString(
|
|
343
342
|
record.snippet,
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
joinUrl,
|
|
3
|
+
requestJson,
|
|
4
|
+
trimSnippet
|
|
5
|
+
} from "./chunk-FSTCLBSF.js";
|
|
6
|
+
import {
|
|
7
|
+
orderedContents
|
|
8
|
+
} from "./chunk-TYJ6RHKB.js";
|
|
9
|
+
import {
|
|
10
|
+
WebfoxError,
|
|
11
|
+
asWebfoxError
|
|
12
|
+
} from "./chunk-U4BLULLV.js";
|
|
13
|
+
|
|
14
|
+
// src/providers/sofya/adapter.ts
|
|
15
|
+
var DEFAULT_BASE_URL = "https://sofya.co";
|
|
16
|
+
var MAX_RESULTS = 20;
|
|
17
|
+
var adapter = {
|
|
18
|
+
async search(request, config, context) {
|
|
19
|
+
const maxResults = Math.max(
|
|
20
|
+
1,
|
|
21
|
+
Math.min(MAX_RESULTS, Math.trunc(request.maxResults || 0))
|
|
22
|
+
);
|
|
23
|
+
const payload = await sofyaRequest(config, context, "v1/search", {
|
|
24
|
+
...asRecord(request.options),
|
|
25
|
+
query: request.query,
|
|
26
|
+
max_results: maxResults
|
|
27
|
+
});
|
|
28
|
+
const entries = payload.results;
|
|
29
|
+
if (!Array.isArray(entries)) throw invalidResponse("search");
|
|
30
|
+
const results = entries.map(toSearchResult).filter((result) => result !== void 0);
|
|
31
|
+
return { provider: "sofya", results: results.slice(0, maxResults) };
|
|
32
|
+
},
|
|
33
|
+
async contents(request, config, context) {
|
|
34
|
+
const answers = await Promise.all(
|
|
35
|
+
request.urls.map(async (url) => {
|
|
36
|
+
try {
|
|
37
|
+
const payload = await sofyaRequest(config, context, "v1/fetch", {
|
|
38
|
+
...asRecord(request.options),
|
|
39
|
+
urls: [url]
|
|
40
|
+
});
|
|
41
|
+
const result = Array.isArray(payload.results) ? asRecord(payload.results[0]) : void 0;
|
|
42
|
+
if (!result) throw invalidResponse("fetch");
|
|
43
|
+
if (result.success === false) {
|
|
44
|
+
return { url, error: text(result.error) ?? "Fetch failed." };
|
|
45
|
+
}
|
|
46
|
+
const {
|
|
47
|
+
url: finalUrl,
|
|
48
|
+
content,
|
|
49
|
+
success: _s,
|
|
50
|
+
error: _e,
|
|
51
|
+
...rest
|
|
52
|
+
} = result;
|
|
53
|
+
const metadata = Object.fromEntries(
|
|
54
|
+
Object.entries(rest).filter(
|
|
55
|
+
([, value]) => value !== null && value !== ""
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
return {
|
|
59
|
+
url: text(finalUrl) ?? url,
|
|
60
|
+
content: typeof content === "string" ? content : "",
|
|
61
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
62
|
+
};
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return { url, error: asWebfoxError(error).toJSON() };
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
return orderedContents({ provider: "sofya", answers });
|
|
69
|
+
},
|
|
70
|
+
async answer(request, config, context) {
|
|
71
|
+
const payload = await sofyaRequest(config, context, "v1/search", {
|
|
72
|
+
...asRecord(request.options),
|
|
73
|
+
query: request.query,
|
|
74
|
+
include_answer: true
|
|
75
|
+
});
|
|
76
|
+
const sources = (Array.isArray(payload.results) ? payload.results : []).map(toSearchResult).filter((result) => result !== void 0).map(({ title, url }) => ({ title, url }));
|
|
77
|
+
return output(payload.answer, sources, queryMetadata(payload));
|
|
78
|
+
},
|
|
79
|
+
async research(request, config, context) {
|
|
80
|
+
context.onProgress?.("Sofya is researching and writing the report.");
|
|
81
|
+
const payload = await sofyaRequest(config, context, "v1/research", {
|
|
82
|
+
...asRecord(request.options),
|
|
83
|
+
query: request.input
|
|
84
|
+
});
|
|
85
|
+
const sources = (Array.isArray(payload.sources) ? payload.sources : []).map(asRecord).filter((source) => source !== void 0).map((source) => ({ title: text(source.title), url: text(source.url) }));
|
|
86
|
+
return output(
|
|
87
|
+
payload.report,
|
|
88
|
+
sources,
|
|
89
|
+
Array.isArray(payload.sub_queries) ? { sub_queries: payload.sub_queries } : {}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
async function sofyaRequest(config, context, path, body) {
|
|
94
|
+
const apiKey = config.credentials?.api;
|
|
95
|
+
if (!apiKey) throw new Error("Sofya is missing an API key");
|
|
96
|
+
const payload = asRecord(
|
|
97
|
+
await requestJson(
|
|
98
|
+
joinUrl(config.baseUrl ?? DEFAULT_BASE_URL, path),
|
|
99
|
+
{
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
102
|
+
body,
|
|
103
|
+
signal: context.signal
|
|
104
|
+
},
|
|
105
|
+
describeError
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
if (!payload) throw invalidResponse(path.replace("v1/", ""));
|
|
109
|
+
return payload;
|
|
110
|
+
}
|
|
111
|
+
function output(body, sources, metadata) {
|
|
112
|
+
return {
|
|
113
|
+
provider: "sofya",
|
|
114
|
+
text: typeof body === "string" ? body : "",
|
|
115
|
+
sources,
|
|
116
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function queryMetadata(payload) {
|
|
120
|
+
return Object.fromEntries(
|
|
121
|
+
["altered_query", "relaxed_query"].flatMap((key) => {
|
|
122
|
+
const value = text(payload[key]);
|
|
123
|
+
return value ? [[key, value]] : [];
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
function invalidResponse(endpoint) {
|
|
128
|
+
return new WebfoxError(
|
|
129
|
+
"PROVIDER_FAILURE",
|
|
130
|
+
`Sofya returned an invalid ${endpoint} response.`,
|
|
131
|
+
{ retryable: false }
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
function describeError(payload, response) {
|
|
135
|
+
const record = asRecord(payload);
|
|
136
|
+
const detail = text(record?.detail) ?? (typeof payload === "string" ? text(payload) : void 0) ?? (record ? JSON.stringify(record.detail ?? record) : void 0);
|
|
137
|
+
const trimmed = detail?.trim().slice(0, 500);
|
|
138
|
+
return `Sofya request failed (HTTP ${response.status})${trimmed ? `: ${trimmed}` : "."}`;
|
|
139
|
+
}
|
|
140
|
+
function toSearchResult(entry) {
|
|
141
|
+
const record = asRecord(entry);
|
|
142
|
+
const url = text(record?.url);
|
|
143
|
+
if (!record || !url) return void 0;
|
|
144
|
+
const { title, url: _url, content, description, fetched, ...rest } = record;
|
|
145
|
+
const metadata = Object.fromEntries(
|
|
146
|
+
Object.entries(rest).filter(
|
|
147
|
+
([, value]) => value !== null && !(Array.isArray(value) && value.length === 0) && !(asRecord(value) && Object.keys(value).length === 0)
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
if (fetched === true && text(content)) metadata.content = content;
|
|
151
|
+
const excerpt = text(description) ?? text(content);
|
|
152
|
+
return {
|
|
153
|
+
title: text(title),
|
|
154
|
+
url,
|
|
155
|
+
snippet: excerpt ? trimSnippet(excerpt) : "",
|
|
156
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function asRecord(value) {
|
|
160
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
161
|
+
}
|
|
162
|
+
function text(value) {
|
|
163
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
adapter
|
|
167
|
+
};
|