webfox 4.8.0 → 4.10.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 (37) hide show
  1. package/README.md +2 -0
  2. package/dist/{adapter-YOHLAGYM.js → adapter-2UVAARTV.js} +1 -1
  3. package/dist/{adapter-UAZNLDSS.js → adapter-764OQFKY.js} +13 -9
  4. package/dist/{adapter-67TIISDW.js → adapter-A5WYGGZI.js} +1 -1
  5. package/dist/{adapter-I6OUKBZQ.js → adapter-C2VZHFUB.js} +1 -1
  6. package/dist/{adapter-QU2G7O25.js → adapter-EIDSGLOP.js} +3 -3
  7. package/dist/{adapter-2YBFCBCZ.js → adapter-G3I7JEO5.js} +1 -1
  8. package/dist/{adapter-PADXKW43.js → adapter-HRI5ZMGK.js} +82 -26
  9. package/dist/{adapter-ISZQTZDP.js → adapter-HTMXAJTU.js} +1 -1
  10. package/dist/adapter-JJSMYZXG.js +91 -0
  11. package/dist/{adapter-HPTMLH75.js → adapter-K6AMSUCR.js} +70 -30
  12. package/dist/{adapter-XBAQSK7I.js → adapter-L6BFKWGX.js} +65 -67
  13. package/dist/{adapter-SLTIAAML.js → adapter-OK2PTP6R.js} +2 -1
  14. package/dist/{adapter-SDQX2533.js → adapter-UIFBJKKF.js} +1 -1
  15. package/dist/{adapter-HITGYUXN.js → adapter-VMMS4YQG.js} +1 -1
  16. package/dist/{adapter-VW4FMMJC.js → adapter-ZYDUANQL.js} +1 -1
  17. package/dist/{chunk-IRKSK34I.js → chunk-3WBE6UEW.js} +3 -3
  18. package/dist/{chunk-GPACLYNT.js → chunk-57OOEOXI.js} +1 -0
  19. package/dist/{chunk-QERSNL3J.js → chunk-5FP7KNMC.js} +83 -15
  20. package/dist/chunk-FSTCLBSF.js +76 -0
  21. package/dist/{chunk-G7LMBPW4.js → chunk-TGULKHB5.js} +9 -12
  22. package/dist/cli.js +4 -4
  23. package/dist/config.schema.json +112 -0
  24. package/dist/document-schema.d.ts +4 -4
  25. package/dist/domain.d.ts +1 -1
  26. package/dist/durable.js +4 -4
  27. package/dist/index.js +4 -4
  28. package/dist/pi.js +4 -4
  29. package/dist/providers/contract.d.ts +1 -0
  30. package/dist/providers/keenable/adapter.d.ts +5 -0
  31. package/dist/providers/keenable/definition.d.ts +1 -0
  32. package/dist/providers/keenable/types.d.ts +4 -0
  33. package/dist/providers/registry.d.ts +1 -0
  34. package/dist/providers/shared.d.ts +16 -0
  35. package/docs/provider.md +59 -1
  36. package/package.json +7 -10
  37. package/dist/chunk-X6ZBIHZC.js +0 -21
package/README.md CHANGED
@@ -144,6 +144,7 @@ Use different providers for different tasks:
144
144
  | [Exa] | ✔︎ | ✔︎ | ✔︎ | ✔︎ |
145
145
  | [Firecrawl] | ✔︎ | ✔︎ | ✔︎ | |
146
146
  | [Gemini] | | | ✔︎ | ✔︎ |
147
+ | [Keenable] | ✔︎ | | | |
147
148
  | [Linkup] | ✔︎ | ✔︎ | | ✔︎ |
148
149
  | [Ollama] | ✔︎ | ✔︎ | | |
149
150
  | [OpenAI] | ✔︎ | | ✔︎ | ✔︎ |
@@ -161,6 +162,7 @@ Use different providers for different tasks:
161
162
  [Exa]: ./docs/provider.md#exa
162
163
  [Firecrawl]: ./docs/provider.md#firecrawl
163
164
  [Gemini]: ./docs/provider.md#gemini
165
+ [Keenable]: ./docs/provider.md#keenable
164
166
  [Linkup]: ./docs/provider.md#linkup
165
167
  [Ollama]: ./docs/provider.md#ollama
166
168
  [OpenAI]: ./docs/provider.md#openai
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  trimSnippet
3
- } from "./chunk-X6ZBIHZC.js";
3
+ } from "./chunk-FSTCLBSF.js";
4
4
  import {
5
5
  httpError
6
6
  } from "./chunk-U4BLULLV.js";
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  formatJson,
11
11
  trimSnippet
12
- } from "./chunk-X6ZBIHZC.js";
12
+ } from "./chunk-FSTCLBSF.js";
13
13
  import {
14
14
  orderedContents
15
15
  } from "./chunk-YZXM4U2C.js";
@@ -78,14 +78,18 @@ var valyuImplementation = {
78
78
  error: "No content returned for this URL."
79
79
  };
80
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
81
+ if (result.status === "failed" || result.status === "not_found") {
82
+ return {
83
+ url,
84
+ error: result.error ?? formatJson(result)
85
+ };
86
+ }
87
+ const page = result;
88
+ return {
89
+ url: page.url,
90
+ ...typeof page.content === "string" || typeof page.content === "number" ? { content: String(page.content) } : {},
91
+ ...page.summary !== void 0 ? { summary: page.summary } : {},
92
+ metadata: page
89
93
  };
90
94
  })
91
95
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  trimSnippet
3
- } from "./chunk-X6ZBIHZC.js";
3
+ } from "./chunk-FSTCLBSF.js";
4
4
  import {
5
5
  httpError
6
6
  } from "./chunk-U4BLULLV.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  asJsonObject,
3
3
  trimSnippet
4
- } from "./chunk-X6ZBIHZC.js";
4
+ } from "./chunk-FSTCLBSF.js";
5
5
  import {
6
6
  httpError
7
7
  } from "./chunk-U4BLULLV.js";
@@ -2,11 +2,11 @@ import {
2
2
  SERPBASE_MODES,
3
3
  serpbaseProvider,
4
4
  validateOptions
5
- } from "./chunk-QERSNL3J.js";
5
+ } from "./chunk-5FP7KNMC.js";
6
6
  import {
7
7
  trimSnippet
8
- } from "./chunk-X6ZBIHZC.js";
9
- import "./chunk-GPACLYNT.js";
8
+ } from "./chunk-FSTCLBSF.js";
9
+ import "./chunk-57OOEOXI.js";
10
10
  import {
11
11
  WebfoxError,
12
12
  httpError
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  formatJson,
3
3
  trimSnippet
4
- } from "./chunk-X6ZBIHZC.js";
4
+ } from "./chunk-FSTCLBSF.js";
5
5
  import {
6
6
  orderedContents
7
7
  } from "./chunk-YZXM4U2C.js";
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  formatJson,
3
+ joinUrl,
4
+ requestJson,
3
5
  trimSnippet
4
- } from "./chunk-X6ZBIHZC.js";
6
+ } from "./chunk-FSTCLBSF.js";
5
7
  import {
6
8
  orderedContents
7
9
  } from "./chunk-YZXM4U2C.js";
@@ -10,15 +12,19 @@ import {
10
12
  } from "./chunk-U4BLULLV.js";
11
13
 
12
14
  // src/providers/exa/adapter.ts
13
- import { Exa as ExaClient } from "exa-js";
15
+ var DEFAULT_BASE_URL = "https://api.exa.ai";
16
+ var DEFAULT_MAX_CHARACTERS = 1e4;
14
17
  var exaImplementation = {
15
- async search(query, maxResults, config, _context, searchOptions) {
16
- const client = createClient(config);
17
- const options = {
18
- ...searchOptions ?? {},
19
- numResults: maxResults
20
- };
21
- const response = await client.search(query, options);
18
+ async search(query, maxResults, config, context, searchOptions) {
19
+ const response = await exaRequest(
20
+ config,
21
+ "/search",
22
+ buildSearchBody(query, {
23
+ ...searchOptions ?? {},
24
+ numResults: maxResults
25
+ }),
26
+ context
27
+ );
22
28
  return {
23
29
  provider: "exa",
24
30
  results: (response.results ?? []).slice(0, maxResults).map((result) => ({
@@ -31,9 +37,13 @@ var exaImplementation = {
31
37
  }))
32
38
  };
33
39
  },
34
- async contents(urls, config, _context, options) {
35
- const client = createClient(config);
36
- const response = await client.getContents(urls, options);
40
+ async contents(urls, config, context, options) {
41
+ const response = await exaRequest(
42
+ config,
43
+ "/contents",
44
+ { urls, ...options ?? {} },
45
+ context
46
+ );
37
47
  const results = response.results ?? [];
38
48
  return {
39
49
  provider: "exa",
@@ -54,9 +64,21 @@ var exaImplementation = {
54
64
  })
55
65
  };
56
66
  },
57
- async answer(query, config, _context, options) {
58
- const client = createClient(config);
59
- const response = await client.answer(query, options);
67
+ async answer(query, config, context, options) {
68
+ const response = await exaRequest(
69
+ config,
70
+ "/answer",
71
+ {
72
+ query,
73
+ stream: false,
74
+ text: options?.text ?? false,
75
+ model: options?.model ?? "exa",
76
+ systemPrompt: options?.systemPrompt,
77
+ outputSchema: options?.outputSchema,
78
+ userLocation: options?.userLocation
79
+ },
80
+ context
81
+ );
60
82
  const lines = [];
61
83
  lines.push(
62
84
  typeof response.answer === "string" ? response.answer : formatJson(response.answer)
@@ -81,15 +103,19 @@ var exaImplementation = {
81
103
  async research(input, config, context, options) {
82
104
  context.signal?.throwIfAborted();
83
105
  context.onProgress?.("Exa is researching and synthesizing the report.");
84
- const client = createClient(config);
85
- const result = await client.search(input, {
86
- ...options ?? {},
87
- type: "deep-reasoning",
88
- outputSchema: {
89
- type: "text",
90
- description: "A detailed Markdown research report answering the query. Cite sources with links, distinguish evidence from uncertainty, and include a conclusion."
91
- }
92
- });
106
+ const result = await exaRequest(
107
+ config,
108
+ "/search",
109
+ buildSearchBody(input, {
110
+ ...options ?? {},
111
+ type: "deep-reasoning",
112
+ outputSchema: {
113
+ type: "text",
114
+ description: "A detailed Markdown research report answering the query. Cite sources with links, distinguish evidence from uncertainty, and include a conclusion."
115
+ }
116
+ }),
117
+ context
118
+ );
93
119
  const content = result.output?.content;
94
120
  if (typeof content !== "string" || !content.trim())
95
121
  throw new WebfoxError(
@@ -113,12 +139,42 @@ var exaImplementation = {
113
139
  };
114
140
  }
115
141
  };
116
- function createClient(config) {
142
+ function buildSearchBody(query, options) {
143
+ const { stream: _stream, ...rest } = options;
144
+ if (!("contents" in rest)) {
145
+ return {
146
+ query,
147
+ ...rest,
148
+ contents: { text: { maxCharacters: DEFAULT_MAX_CHARACTERS } }
149
+ };
150
+ }
151
+ if (rest.contents === false || rest.contents == null) {
152
+ const { contents: _contents, ...withoutContents } = rest;
153
+ return { query, ...withoutContents };
154
+ }
155
+ return { query, ...rest };
156
+ }
157
+ async function exaRequest(config, path, body, context) {
117
158
  const apiKey = config.credentials?.api;
118
159
  if (!apiKey) {
119
160
  throw new Error("is missing an API key");
120
161
  }
121
- return new ExaClient(apiKey, config.baseUrl);
162
+ return await requestJson(
163
+ joinUrl(config.baseUrl ?? DEFAULT_BASE_URL, path),
164
+ { headers: { "x-api-key": apiKey }, body, signal: context.signal },
165
+ readExaError
166
+ );
167
+ }
168
+ function readExaError(payload, response) {
169
+ if (typeof payload !== "object" || payload === null) {
170
+ const text = typeof payload === "string" ? payload.trim().slice(0, 500) : "";
171
+ return text ? `Request failed with status ${response.status}: ${text}` : `Request failed with status ${response.status}.`;
172
+ }
173
+ const record = payload;
174
+ const envelope = typeof record.error === "object" && record.error !== null ? record.error : void 0;
175
+ const parts = envelope ? [envelope.message ?? record.message] : [record.error, record.message];
176
+ const message = parts.filter((part) => typeof part === "string" && part !== "").join(". ");
177
+ return message || `Request failed with status ${response.status}.`;
122
178
  }
123
179
  var adapter = {
124
180
  async search(input, config, context) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  normalizeContentText,
3
3
  trimSnippet
4
- } from "./chunk-X6ZBIHZC.js";
4
+ } from "./chunk-FSTCLBSF.js";
5
5
  import {
6
6
  orderedContents
7
7
  } from "./chunk-YZXM4U2C.js";
@@ -0,0 +1,91 @@
1
+ import {
2
+ joinUrl,
3
+ requestJson,
4
+ trimSnippet
5
+ } from "./chunk-FSTCLBSF.js";
6
+ import {
7
+ WebfoxError
8
+ } from "./chunk-U4BLULLV.js";
9
+
10
+ // src/providers/keenable/adapter.ts
11
+ var DEFAULT_BASE_URL = "https://api.keenable.ai";
12
+ var MAX_RESULTS = 50;
13
+ var FORWARDED_OPTIONS = [
14
+ "mode",
15
+ "site",
16
+ "published_after",
17
+ "published_before",
18
+ "acquired_after",
19
+ "acquired_before",
20
+ "query_time",
21
+ "snippet_max_length"
22
+ ];
23
+ var adapter = {
24
+ async search(request, config, context) {
25
+ const apiKey = config.credentials?.api;
26
+ if (!apiKey) throw new Error("Keenable search is missing an API key");
27
+ const maxResults = Math.max(
28
+ 1,
29
+ Math.min(MAX_RESULTS, Math.trunc(request.maxResults || 0))
30
+ );
31
+ const options = asRecord(request.options) ?? {};
32
+ const payload = asRecord(
33
+ await requestJson(
34
+ joinUrl(config.baseUrl ?? DEFAULT_BASE_URL, "v1/search"),
35
+ {
36
+ method: "POST",
37
+ headers: { "X-API-Key": apiKey, "X-Keenable-Title": "webfox" },
38
+ body: {
39
+ query: request.query,
40
+ max_results: maxResults,
41
+ ...Object.fromEntries(
42
+ FORWARDED_OPTIONS.flatMap(
43
+ (key) => options[key] === void 0 ? [] : [[key, options[key]]]
44
+ )
45
+ )
46
+ },
47
+ signal: context.signal
48
+ },
49
+ describeError
50
+ )
51
+ );
52
+ const entries = payload?.results;
53
+ if (!payload || !Array.isArray(entries)) {
54
+ throw new WebfoxError(
55
+ "PROVIDER_FAILURE",
56
+ "Keenable returned an invalid search response.",
57
+ { retryable: false }
58
+ );
59
+ }
60
+ const results = entries.map(toSearchResult).filter((result) => result !== void 0);
61
+ return { provider: "keenable", results: results.slice(0, maxResults) };
62
+ }
63
+ };
64
+ function describeError(payload, response) {
65
+ const record = asRecord(payload);
66
+ const detail = text(record?.detail) ?? text(record?.error) ?? text(record?.message) ?? (typeof payload === "string" ? text(payload) : void 0) ?? (record ? JSON.stringify(record) : void 0);
67
+ const trimmed = detail?.trim().slice(0, 500);
68
+ return `Keenable search failed (HTTP ${response.status})${trimmed ? `: ${trimmed}` : "."}`;
69
+ }
70
+ function toSearchResult(entry) {
71
+ const record = asRecord(entry);
72
+ const url = text(record?.url);
73
+ if (!record || !url) return void 0;
74
+ const { title, url: _url, description, snippet, ...metadata } = record;
75
+ const excerpt = text(snippet) ?? text(description);
76
+ return {
77
+ title: text(title) ?? url,
78
+ url,
79
+ snippet: excerpt ? trimSnippet(excerpt) : "",
80
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
81
+ };
82
+ }
83
+ function asRecord(value) {
84
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
85
+ }
86
+ function text(value) {
87
+ return typeof value === "string" && value.trim() ? value : void 0;
88
+ }
89
+ export {
90
+ adapter
91
+ };
@@ -4,45 +4,45 @@ import {
4
4
  import "./chunk-I2UIACY3.js";
5
5
  import "./chunk-WCTOYZVB.js";
6
6
  import {
7
+ expectJsonBody,
7
8
  formatJson,
9
+ joinUrl,
10
+ readResponseBody,
8
11
  trimSnippet
9
- } from "./chunk-X6ZBIHZC.js";
12
+ } from "./chunk-FSTCLBSF.js";
10
13
  import {
11
14
  orderedContents
12
15
  } from "./chunk-YZXM4U2C.js";
13
16
  import {
17
+ WebfoxError,
14
18
  asWebfoxError
15
19
  } from "./chunk-U4BLULLV.js";
16
20
 
17
21
  // src/providers/linkup/adapter.ts
18
- import {
19
- LinkupClient
20
- } from "linkup-sdk";
22
+ var DEFAULT_BASE_URL = "https://api.linkup.so/v1";
21
23
  var linkupImplementation = {
22
- async search(query, maxResults, config, _context, options) {
23
- const client = createClient(config);
24
- const response = await client.search(
25
- buildSearchParams(query, maxResults, {
24
+ async search(query, maxResults, config, context, options) {
25
+ const response = await linkupRequest(config, context, "/search", {
26
+ body: buildSearchParams(query, maxResults, {
26
27
  ...options ?? {}
27
28
  })
28
- );
29
+ });
29
30
  return {
30
31
  provider: "linkup",
31
32
  results: (response.results ?? []).map(toSearchResult).filter((result) => result !== null).slice(0, maxResults)
32
33
  };
33
34
  },
34
- async contents(urls, config, _context, options) {
35
- const client = createClient(config);
35
+ async contents(urls, config, context, options) {
36
36
  return {
37
37
  provider: "linkup",
38
38
  answers: await Promise.all(
39
39
  urls.map(async (url) => {
40
40
  try {
41
- const response = await client.fetch(
42
- buildFetchParams(url, {
41
+ const response = await linkupRequest(config, context, "/fetch", {
42
+ body: buildFetchParams(url, {
43
43
  ...options ?? {}
44
44
  })
45
- );
45
+ });
46
46
  const metadata = Object.fromEntries(
47
47
  Object.entries(response).filter(
48
48
  ([key, value]) => [
@@ -86,18 +86,20 @@ var linkupImplementation = {
86
86
  poll: (id, researchContext) => linkupImplementation.pollResearch(id, config, researchContext)
87
87
  });
88
88
  },
89
- async startResearch(input, config, _context, options) {
90
- const client = createClient(config);
91
- const task = await client.research(
92
- buildResearchParams(input, {
89
+ async startResearch(input, config, context, options) {
90
+ const task = await linkupRequest(config, context, "/research", {
91
+ body: buildResearchParams(input, {
93
92
  ...options ?? {}
94
93
  })
95
- );
94
+ });
96
95
  return { id: task.id };
97
96
  },
98
- async pollResearch(id, config, _context) {
99
- const client = createClient(config);
100
- const task = await client.getResearch(id);
97
+ async pollResearch(id, config, context) {
98
+ const task = await linkupRequest(
99
+ config,
100
+ context,
101
+ `/research/${encodeURIComponent(id)}`
102
+ );
101
103
  if (task.status === "completed") {
102
104
  return {
103
105
  status: "completed",
@@ -135,7 +137,7 @@ function buildSearchParams(query, maxResults, options) {
135
137
  );
136
138
  }
137
139
  return {
138
- query,
140
+ q: query,
139
141
  depth: searchOptions.depth ?? "standard",
140
142
  outputType: "searchResults",
141
143
  maxResults,
@@ -179,7 +181,7 @@ function buildResearchParams(input, options) {
179
181
  );
180
182
  }
181
183
  const commonParams = {
182
- query: input,
184
+ q: input,
183
185
  ...researchOptions.includeDomains !== void 0 ? { includeDomains: researchOptions.includeDomains } : {},
184
186
  ...researchOptions.excludeDomains !== void 0 ? { excludeDomains: researchOptions.excludeDomains } : {},
185
187
  ...researchOptions.fromDate !== void 0 ? { fromDate: toDate(researchOptions.fromDate, "fromDate") } : {},
@@ -191,7 +193,9 @@ function buildResearchParams(input, options) {
191
193
  return {
192
194
  ...commonParams,
193
195
  outputType,
194
- structuredOutputSchema: researchOptions.structuredOutputSchema
196
+ structuredOutputSchema: serializeSchema(
197
+ researchOptions.structuredOutputSchema
198
+ )
195
199
  };
196
200
  }
197
201
  return {
@@ -199,14 +203,50 @@ function buildResearchParams(input, options) {
199
203
  outputType
200
204
  };
201
205
  }
202
- function createClient(config) {
206
+ function serializeSchema(schema) {
207
+ return typeof schema === "string" ? schema : JSON.stringify(schema);
208
+ }
209
+ async function linkupRequest(config, context, path, init = {}) {
203
210
  const apiKey = config.credentials?.api;
204
211
  if (!apiKey) {
205
212
  throw new Error("is missing an API key");
206
213
  }
207
- return new LinkupClient({
208
- apiKey,
209
- baseUrl: config.baseUrl
214
+ const response = await fetch(
215
+ joinUrl(config.baseUrl ?? DEFAULT_BASE_URL, path),
216
+ {
217
+ method: init.body === void 0 ? "GET" : "POST",
218
+ headers: {
219
+ Accept: "application/json",
220
+ Authorization: `Bearer ${apiKey}`,
221
+ ...init.body === void 0 ? {} : { "Content-Type": "application/json" }
222
+ },
223
+ ...init.body === void 0 ? {} : { body: JSON.stringify(init.body) },
224
+ signal: context.signal
225
+ }
226
+ );
227
+ const payload = await readResponseBody(response);
228
+ if (!response.ok) {
229
+ throw linkupError(response, payload);
230
+ }
231
+ return expectJsonBody(payload, response);
232
+ }
233
+ var NON_RETRYABLE_CODES = /* @__PURE__ */ new Set([
234
+ "INSUFFICIENT_FUNDS_CREDITS",
235
+ "EXCEED_BUDGET_LIMIT"
236
+ ]);
237
+ function linkupError(response, payload) {
238
+ const error = asRecord(asRecord(payload)?.error);
239
+ const code = readString(error?.code);
240
+ const details = Array.isArray(error?.details) ? error.details : [];
241
+ const message = [
242
+ readString(error?.message) ?? (typeof payload === "string" && payload.trim() ? payload.trim().slice(0, 500) : `Linkup request failed with status ${response.status}.`),
243
+ ...details.flatMap((detail) => {
244
+ const text = readString(asRecord(detail)?.message);
245
+ return text ? [text] : [];
246
+ })
247
+ ].join(" ");
248
+ return new WebfoxError("PROVIDER_FAILURE", message, {
249
+ retryable: [408, 429, 500, 502, 503, 504].includes(response.status) && !(code && NON_RETRYABLE_CODES.has(code))
210
250
  });
211
251
  }
212
252
  function formatResearchTaskOutput(task) {
@@ -298,7 +338,7 @@ function toDate(value, name) {
298
338
  `Linkup option '${name}' must be a valid date string, timestamp, or Date.`
299
339
  );
300
340
  }
301
- return date;
341
+ return date.toISOString().slice(0, 10);
302
342
  }
303
343
  var adapter = {
304
344
  async search(input, config, context) {