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,492 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asJsonObject,
|
|
3
|
+
trimSnippet
|
|
4
|
+
} from "./chunk-X6ZBIHZC.js";
|
|
5
|
+
import {
|
|
6
|
+
httpError
|
|
7
|
+
} from "./chunk-U4BLULLV.js";
|
|
8
|
+
|
|
9
|
+
// src/providers/serper/types.ts
|
|
10
|
+
var SERPER_SEARCH_MODE_VALUES = {
|
|
11
|
+
search: "search",
|
|
12
|
+
images: "images",
|
|
13
|
+
videos: "videos",
|
|
14
|
+
places: "places",
|
|
15
|
+
maps: "maps",
|
|
16
|
+
reviews: "reviews",
|
|
17
|
+
news: "news",
|
|
18
|
+
shopping: "shopping",
|
|
19
|
+
productReviews: "product-reviews",
|
|
20
|
+
lens: "lens",
|
|
21
|
+
scholar: "scholar",
|
|
22
|
+
patents: "patents",
|
|
23
|
+
autocomplete: "autocomplete",
|
|
24
|
+
webpage: "webpage"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/providers/serper/adapter.ts
|
|
28
|
+
var DEFAULT_BASE_URL = "https://google.serper.dev";
|
|
29
|
+
var DEFAULT_SCRAPE_URL = "https://scrape.serper.dev";
|
|
30
|
+
var SERPER_SEARCH_MODES = Object.values(SERPER_SEARCH_MODE_VALUES);
|
|
31
|
+
var SERPER_SEARCH_MODE_SET = new Set(SERPER_SEARCH_MODES);
|
|
32
|
+
var RESERVED_REQUEST_OPTION_KEYS = [
|
|
33
|
+
"q",
|
|
34
|
+
"num",
|
|
35
|
+
"mode",
|
|
36
|
+
"url",
|
|
37
|
+
"productId",
|
|
38
|
+
"nextPageToken",
|
|
39
|
+
"ll",
|
|
40
|
+
"placeId",
|
|
41
|
+
"cid",
|
|
42
|
+
"fid",
|
|
43
|
+
"sortBy",
|
|
44
|
+
"topicId",
|
|
45
|
+
"includeMarkdown",
|
|
46
|
+
"includeImages",
|
|
47
|
+
"includeLinks",
|
|
48
|
+
"includeVideos",
|
|
49
|
+
"location",
|
|
50
|
+
"gl",
|
|
51
|
+
"hl",
|
|
52
|
+
"tbs",
|
|
53
|
+
"page",
|
|
54
|
+
"autocorrect"
|
|
55
|
+
];
|
|
56
|
+
var PRIMARY_RESULT_FIELDS_BY_MODE = {
|
|
57
|
+
search: ["organic"],
|
|
58
|
+
images: ["images"],
|
|
59
|
+
videos: ["videos"],
|
|
60
|
+
places: ["places"],
|
|
61
|
+
maps: ["maps", "places"],
|
|
62
|
+
reviews: ["reviews"],
|
|
63
|
+
news: ["news"],
|
|
64
|
+
shopping: ["shopping"],
|
|
65
|
+
"product-reviews": ["reviews", "productReviews"],
|
|
66
|
+
lens: ["visualMatches", "organic", "images"],
|
|
67
|
+
scholar: ["organic"],
|
|
68
|
+
patents: ["organic"],
|
|
69
|
+
autocomplete: ["suggestions"],
|
|
70
|
+
webpage: []
|
|
71
|
+
};
|
|
72
|
+
var CONTEXT_ARRAY_FIELDS = [
|
|
73
|
+
"peopleAlsoAsk",
|
|
74
|
+
"relatedSearches",
|
|
75
|
+
"topStories",
|
|
76
|
+
"news",
|
|
77
|
+
"images",
|
|
78
|
+
"videos",
|
|
79
|
+
"places",
|
|
80
|
+
"maps",
|
|
81
|
+
"shopping",
|
|
82
|
+
"reviews",
|
|
83
|
+
"productReviews",
|
|
84
|
+
"visualMatches",
|
|
85
|
+
"suggestions"
|
|
86
|
+
];
|
|
87
|
+
var serperImplementation = {
|
|
88
|
+
async search(query, maxResults, config, context, options) {
|
|
89
|
+
const apiKey = config.credentials?.api;
|
|
90
|
+
if (!apiKey) {
|
|
91
|
+
throw new Error("is missing an API key");
|
|
92
|
+
}
|
|
93
|
+
const callOptions = asJsonObject(options);
|
|
94
|
+
const requestOptions = readRequestOptions({
|
|
95
|
+
...callOptions
|
|
96
|
+
});
|
|
97
|
+
const requestBody = buildRequestBody(
|
|
98
|
+
query,
|
|
99
|
+
clampMaxResults(maxResults),
|
|
100
|
+
requestOptions
|
|
101
|
+
);
|
|
102
|
+
const response = await fetch(joinUrl(config.baseUrl, requestOptions.mode), {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: {
|
|
105
|
+
"content-type": "application/json",
|
|
106
|
+
"x-api-key": apiKey
|
|
107
|
+
},
|
|
108
|
+
body: JSON.stringify(requestBody),
|
|
109
|
+
signal: context.signal
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw httpError(response, await buildHttpError(response));
|
|
113
|
+
}
|
|
114
|
+
const payload = await response.json();
|
|
115
|
+
const responseRecord = enrichResponseRecord(
|
|
116
|
+
asRecord(payload) ?? {},
|
|
117
|
+
requestOptions.mode,
|
|
118
|
+
requestBody
|
|
119
|
+
);
|
|
120
|
+
const results = readPrimaryResults(responseRecord, requestOptions.mode);
|
|
121
|
+
const searchContext = buildSearchContext(
|
|
122
|
+
responseRecord,
|
|
123
|
+
requestOptions.mode
|
|
124
|
+
);
|
|
125
|
+
return {
|
|
126
|
+
provider: "serper",
|
|
127
|
+
results: results.map(
|
|
128
|
+
(entry) => toSearchResult(entry, searchContext, requestOptions.mode)
|
|
129
|
+
).filter(
|
|
130
|
+
(result) => result !== null
|
|
131
|
+
).slice(0, clampMaxResults(maxResults))
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
function joinUrl(baseUrl, mode = "search") {
|
|
136
|
+
const base = (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
137
|
+
if (mode === "webpage" && base === DEFAULT_BASE_URL) {
|
|
138
|
+
return DEFAULT_SCRAPE_URL;
|
|
139
|
+
}
|
|
140
|
+
return `${base}/${mode}`;
|
|
141
|
+
}
|
|
142
|
+
function readRequestOptions(options) {
|
|
143
|
+
const result = {
|
|
144
|
+
mode: readSearchMode(options.mode),
|
|
145
|
+
extra: extractExtraMetadata(options, RESERVED_REQUEST_OPTION_KEYS)
|
|
146
|
+
};
|
|
147
|
+
copyStringOption(result, "gl", options.gl);
|
|
148
|
+
copyStringOption(result, "hl", options.hl);
|
|
149
|
+
copyStringOption(result, "location", options.location);
|
|
150
|
+
copyStringOption(result, "tbs", options.tbs);
|
|
151
|
+
copyStringOption(result, "url", options.url);
|
|
152
|
+
copyStringOption(result, "ll", options.ll);
|
|
153
|
+
copyStringOption(result, "placeId", options.placeId);
|
|
154
|
+
copyStringOption(result, "cid", options.cid);
|
|
155
|
+
copyStringOption(result, "fid", options.fid);
|
|
156
|
+
copyStringOption(result, "sortBy", options.sortBy);
|
|
157
|
+
copyStringOption(result, "topicId", options.topicId);
|
|
158
|
+
copyStringOption(result, "productId", options.productId);
|
|
159
|
+
copyStringOption(result, "nextPageToken", options.nextPageToken);
|
|
160
|
+
copyBooleanOption(result, "autocorrect", options.autocorrect);
|
|
161
|
+
copyBooleanOption(result, "includeMarkdown", options.includeMarkdown);
|
|
162
|
+
copyBooleanOption(result, "includeImages", options.includeImages);
|
|
163
|
+
copyBooleanOption(result, "includeLinks", options.includeLinks);
|
|
164
|
+
copyBooleanOption(result, "includeVideos", options.includeVideos);
|
|
165
|
+
const page = readInteger(options.page);
|
|
166
|
+
if (page !== void 0) {
|
|
167
|
+
result.page = Math.max(1, page);
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
function buildRequestBody(query, maxResults, options) {
|
|
172
|
+
const common = omitUndefined({
|
|
173
|
+
location: options.location,
|
|
174
|
+
gl: options.gl,
|
|
175
|
+
hl: options.hl
|
|
176
|
+
});
|
|
177
|
+
const withExtra = (body) => ({
|
|
178
|
+
...body,
|
|
179
|
+
...options.extra
|
|
180
|
+
});
|
|
181
|
+
switch (options.mode) {
|
|
182
|
+
case "webpage":
|
|
183
|
+
return withExtra(
|
|
184
|
+
omitUndefined({
|
|
185
|
+
url: options.url ?? query,
|
|
186
|
+
includeMarkdown: options.includeMarkdown ?? true,
|
|
187
|
+
includeImages: options.includeImages,
|
|
188
|
+
includeLinks: options.includeLinks,
|
|
189
|
+
includeVideos: options.includeVideos
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
case "product-reviews":
|
|
193
|
+
return withExtra(
|
|
194
|
+
omitUndefined({
|
|
195
|
+
productId: options.productId ?? query,
|
|
196
|
+
nextPageToken: options.nextPageToken,
|
|
197
|
+
...common,
|
|
198
|
+
num: maxResults
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
case "autocomplete":
|
|
202
|
+
return withExtra({ q: query, ...common });
|
|
203
|
+
case "maps":
|
|
204
|
+
return withExtra(
|
|
205
|
+
omitUndefined({
|
|
206
|
+
q: query,
|
|
207
|
+
num: maxResults,
|
|
208
|
+
...common,
|
|
209
|
+
ll: options.ll,
|
|
210
|
+
placeId: options.placeId,
|
|
211
|
+
cid: options.cid,
|
|
212
|
+
page: options.page
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
case "reviews": {
|
|
216
|
+
const hasExplicitPlaceIdentifier = firstNonEmptyString(options.cid, options.fid, options.placeId) !== void 0;
|
|
217
|
+
return withExtra(
|
|
218
|
+
omitUndefined({
|
|
219
|
+
q: hasExplicitPlaceIdentifier ? void 0 : query,
|
|
220
|
+
cid: options.cid,
|
|
221
|
+
fid: options.fid,
|
|
222
|
+
placeId: options.placeId,
|
|
223
|
+
gl: options.gl,
|
|
224
|
+
hl: options.hl,
|
|
225
|
+
sortBy: options.sortBy,
|
|
226
|
+
topicId: options.topicId,
|
|
227
|
+
nextPageToken: options.nextPageToken
|
|
228
|
+
})
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
case "lens":
|
|
232
|
+
return withExtra(
|
|
233
|
+
omitUndefined({
|
|
234
|
+
url: options.url ?? query,
|
|
235
|
+
...common,
|
|
236
|
+
tbs: options.tbs
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
case "scholar":
|
|
240
|
+
return withExtra(
|
|
241
|
+
omitUndefined({
|
|
242
|
+
q: query,
|
|
243
|
+
...common,
|
|
244
|
+
autocorrect: options.autocorrect,
|
|
245
|
+
tbs: options.tbs,
|
|
246
|
+
page: options.page
|
|
247
|
+
})
|
|
248
|
+
);
|
|
249
|
+
default:
|
|
250
|
+
return withExtra(
|
|
251
|
+
omitUndefined({
|
|
252
|
+
q: query,
|
|
253
|
+
num: maxResults,
|
|
254
|
+
...common,
|
|
255
|
+
autocorrect: options.autocorrect,
|
|
256
|
+
tbs: options.tbs,
|
|
257
|
+
page: options.page
|
|
258
|
+
})
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function enrichResponseRecord(response, mode, requestBody) {
|
|
263
|
+
if (mode !== "webpage") {
|
|
264
|
+
return response;
|
|
265
|
+
}
|
|
266
|
+
return omitUndefined({
|
|
267
|
+
...response,
|
|
268
|
+
url: readString(response.url) ?? readString(requestBody.url)
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
function readSearchMode(value) {
|
|
272
|
+
return typeof value === "string" && SERPER_SEARCH_MODE_SET.has(value) ? value : "search";
|
|
273
|
+
}
|
|
274
|
+
function readPrimaryResults(response, mode) {
|
|
275
|
+
if (mode === "webpage") {
|
|
276
|
+
return [response];
|
|
277
|
+
}
|
|
278
|
+
for (const field of PRIMARY_RESULT_FIELDS_BY_MODE[mode]) {
|
|
279
|
+
const values = asArray(response[field]);
|
|
280
|
+
if (values) {
|
|
281
|
+
return values;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
function clampMaxResults(value) {
|
|
287
|
+
return Math.max(1, Math.min(20, Math.trunc(value || 0)));
|
|
288
|
+
}
|
|
289
|
+
async function buildHttpError(response) {
|
|
290
|
+
const detail = await readErrorDetail(response);
|
|
291
|
+
const status = `${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
|
|
292
|
+
return detail ? `Serper API request failed (${status}): ${detail}` : `Serper API request failed (${status}).`;
|
|
293
|
+
}
|
|
294
|
+
async function readErrorDetail(response) {
|
|
295
|
+
const text = (await response.text()).trim();
|
|
296
|
+
if (!text) {
|
|
297
|
+
return void 0;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const parsed = JSON.parse(text);
|
|
301
|
+
const record = asRecord(parsed);
|
|
302
|
+
const detail = readString(record?.message) ?? readString(record?.error) ?? readString(record?.detail);
|
|
303
|
+
if (detail) {
|
|
304
|
+
return detail;
|
|
305
|
+
}
|
|
306
|
+
return JSON.stringify(parsed);
|
|
307
|
+
} catch {
|
|
308
|
+
return text;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function toSearchResult(entry, searchContext, mode) {
|
|
312
|
+
if (typeof entry === "string") {
|
|
313
|
+
return {
|
|
314
|
+
title: entry,
|
|
315
|
+
url: mode === "autocomplete" ? toGoogleSearchUrl(entry) : "",
|
|
316
|
+
snippet: entry,
|
|
317
|
+
metadata: {
|
|
318
|
+
source: mode,
|
|
319
|
+
...searchContext ? { searchContext } : {}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
const record = asRecord(entry);
|
|
324
|
+
if (!record) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const responseMetadata = asRecord(record.metadata);
|
|
328
|
+
const user = asRecord(record.user);
|
|
329
|
+
const resultUrl = firstString(record.link, record.website, record.url, record.imageUrl) ?? "";
|
|
330
|
+
const title = firstNonEmptyString(
|
|
331
|
+
record.title,
|
|
332
|
+
responseMetadata?.title,
|
|
333
|
+
record.name,
|
|
334
|
+
record.query,
|
|
335
|
+
record.value,
|
|
336
|
+
user?.name,
|
|
337
|
+
formatReviewTitle(record, user),
|
|
338
|
+
resultUrl
|
|
339
|
+
) ?? "Untitled";
|
|
340
|
+
const url = resultUrl || (mode === "autocomplete" ? toGoogleSearchUrl(title) : "");
|
|
341
|
+
const snippet = trimSnippet(
|
|
342
|
+
firstNonEmptyString(
|
|
343
|
+
record.snippet,
|
|
344
|
+
record.richSnippet,
|
|
345
|
+
record.markdown,
|
|
346
|
+
record.text,
|
|
347
|
+
record.address,
|
|
348
|
+
record.price,
|
|
349
|
+
record.date,
|
|
350
|
+
record.name,
|
|
351
|
+
record.value,
|
|
352
|
+
record.url
|
|
353
|
+
) ?? ""
|
|
354
|
+
);
|
|
355
|
+
const metadata = omitUndefined({
|
|
356
|
+
source: readString(record.source) ?? (mode === "search" ? "organic" : mode),
|
|
357
|
+
position: readNumber(record.position),
|
|
358
|
+
date: readString(record.date),
|
|
359
|
+
attributes: asRecord(record.attributes),
|
|
360
|
+
sitelinks: asArray(record.sitelinks),
|
|
361
|
+
rating: readNumber(record.rating),
|
|
362
|
+
ratingCount: readNumber(record.ratingCount),
|
|
363
|
+
cid: readString(record.cid),
|
|
364
|
+
...extractExtraMetadata(record, [
|
|
365
|
+
"title",
|
|
366
|
+
"name",
|
|
367
|
+
"query",
|
|
368
|
+
"value",
|
|
369
|
+
"link",
|
|
370
|
+
"website",
|
|
371
|
+
"url",
|
|
372
|
+
"snippet"
|
|
373
|
+
]),
|
|
374
|
+
...searchContext ? { searchContext } : {}
|
|
375
|
+
});
|
|
376
|
+
return {
|
|
377
|
+
title,
|
|
378
|
+
url,
|
|
379
|
+
snippet,
|
|
380
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function buildSearchContext(response, mode) {
|
|
384
|
+
const context = omitUndefined({
|
|
385
|
+
searchParameters: asRecord(response.searchParameters),
|
|
386
|
+
searchInformation: asRecord(response.searchInformation),
|
|
387
|
+
credits: readNumber(response.credits),
|
|
388
|
+
answerBox: asRecord(response.answerBox),
|
|
389
|
+
knowledgeGraph: asRecord(response.knowledgeGraph)
|
|
390
|
+
});
|
|
391
|
+
const primaryResultFields = new Set(
|
|
392
|
+
PRIMARY_RESULT_FIELDS_BY_MODE[mode]
|
|
393
|
+
);
|
|
394
|
+
for (const field of CONTEXT_ARRAY_FIELDS) {
|
|
395
|
+
if (primaryResultFields.has(field)) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const value = asArray(response[field]);
|
|
399
|
+
if (value) {
|
|
400
|
+
context[field] = value;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return Object.keys(context).length > 0 ? context : void 0;
|
|
404
|
+
}
|
|
405
|
+
function copyStringOption(target, key, value) {
|
|
406
|
+
const text = readString(value);
|
|
407
|
+
if (text !== void 0) {
|
|
408
|
+
target[key] = text;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function copyBooleanOption(target, key, value) {
|
|
412
|
+
const flag = readBoolean(value);
|
|
413
|
+
if (flag !== void 0) {
|
|
414
|
+
target[key] = flag;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function firstString(...values) {
|
|
418
|
+
return values.find((value) => typeof value === "string");
|
|
419
|
+
}
|
|
420
|
+
function toGoogleSearchUrl(query) {
|
|
421
|
+
return `https://www.google.com/search?q=${encodeURIComponent(query)}`;
|
|
422
|
+
}
|
|
423
|
+
function formatReviewTitle(record, user) {
|
|
424
|
+
const userName = readString(user?.name);
|
|
425
|
+
const rating = readNumber(record.rating);
|
|
426
|
+
const date = readString(record.date) ?? readString(record.isoDate);
|
|
427
|
+
if (userName && rating !== void 0) {
|
|
428
|
+
return `${userName} (${rating}-star review)`;
|
|
429
|
+
}
|
|
430
|
+
if (userName) {
|
|
431
|
+
return `${userName}'s review`;
|
|
432
|
+
}
|
|
433
|
+
if (rating !== void 0 && date) {
|
|
434
|
+
return `${rating}-star review from ${date}`;
|
|
435
|
+
}
|
|
436
|
+
if (rating !== void 0) {
|
|
437
|
+
return `${rating}-star review`;
|
|
438
|
+
}
|
|
439
|
+
if (date) {
|
|
440
|
+
return `Review from ${date}`;
|
|
441
|
+
}
|
|
442
|
+
return void 0;
|
|
443
|
+
}
|
|
444
|
+
function firstNonEmptyString(...values) {
|
|
445
|
+
return values.find(
|
|
446
|
+
(value) => typeof value === "string" && value.length > 0
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
function extractExtraMetadata(record, ignoredKeys) {
|
|
450
|
+
return Object.fromEntries(
|
|
451
|
+
Object.entries(record).filter(
|
|
452
|
+
([key, value]) => !ignoredKeys.includes(key) && value !== void 0
|
|
453
|
+
)
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
function omitUndefined(value) {
|
|
457
|
+
return Object.fromEntries(
|
|
458
|
+
Object.entries(value).filter(([, entry]) => entry !== void 0)
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
function asRecord(value) {
|
|
462
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
463
|
+
}
|
|
464
|
+
function asArray(value) {
|
|
465
|
+
return Array.isArray(value) ? value : void 0;
|
|
466
|
+
}
|
|
467
|
+
function readString(value) {
|
|
468
|
+
return typeof value === "string" ? value : void 0;
|
|
469
|
+
}
|
|
470
|
+
function readNumber(value) {
|
|
471
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
472
|
+
}
|
|
473
|
+
function readInteger(value) {
|
|
474
|
+
return typeof value === "number" && Number.isInteger(value) ? value : void 0;
|
|
475
|
+
}
|
|
476
|
+
function readBoolean(value) {
|
|
477
|
+
return typeof value === "boolean" ? value : void 0;
|
|
478
|
+
}
|
|
479
|
+
var adapter = {
|
|
480
|
+
async search(input, config, context) {
|
|
481
|
+
return await serperImplementation.search(
|
|
482
|
+
input.query,
|
|
483
|
+
input.maxResults,
|
|
484
|
+
config,
|
|
485
|
+
context,
|
|
486
|
+
input.options
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
export {
|
|
491
|
+
adapter
|
|
492
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeContentText,
|
|
3
|
+
trimSnippet
|
|
4
|
+
} from "./chunk-X6ZBIHZC.js";
|
|
5
|
+
import {
|
|
6
|
+
orderedContents
|
|
7
|
+
} from "./chunk-YZXM4U2C.js";
|
|
8
|
+
import {
|
|
9
|
+
asWebfoxError,
|
|
10
|
+
httpError
|
|
11
|
+
} from "./chunk-U4BLULLV.js";
|
|
12
|
+
|
|
13
|
+
// src/providers/ollama/adapter.ts
|
|
14
|
+
var DEFAULT_BASE_URL = "https://ollama.com";
|
|
15
|
+
var WEB_SEARCH_PATH = "/api/web_search";
|
|
16
|
+
var WEB_FETCH_PATH = "/api/web_fetch";
|
|
17
|
+
async function searchOllama(query, maxResults, config, context) {
|
|
18
|
+
const apiKey = resolveApiKey(config);
|
|
19
|
+
const response = await fetch(
|
|
20
|
+
resolveEndpoint(config.baseUrl, WEB_SEARCH_PATH),
|
|
21
|
+
{
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: buildHeaders(apiKey),
|
|
24
|
+
body: JSON.stringify({
|
|
25
|
+
query,
|
|
26
|
+
max_results: clampMaxResults(maxResults)
|
|
27
|
+
}),
|
|
28
|
+
signal: context.signal
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw httpError(response, await buildHttpError(response));
|
|
33
|
+
}
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
const results = Array.isArray(data.results) ? data.results : [];
|
|
36
|
+
return {
|
|
37
|
+
provider: "ollama",
|
|
38
|
+
results: results.slice(0, clampMaxResults(maxResults)).map((result) => ({
|
|
39
|
+
title: result.title || result.url || "Untitled",
|
|
40
|
+
url: result.url ?? "",
|
|
41
|
+
snippet: trimSnippet(result.content)
|
|
42
|
+
}))
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function fetchOllamaContents(urls, config, context) {
|
|
46
|
+
const apiKey = resolveApiKey(config);
|
|
47
|
+
const endpoint = resolveEndpoint(config.baseUrl, WEB_FETCH_PATH);
|
|
48
|
+
return {
|
|
49
|
+
provider: "ollama",
|
|
50
|
+
answers: await Promise.all(
|
|
51
|
+
urls.map(async (url) => {
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(endpoint, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: buildHeaders(apiKey),
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
url
|
|
58
|
+
}),
|
|
59
|
+
signal: context.signal
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
return {
|
|
63
|
+
url,
|
|
64
|
+
error: httpError(
|
|
65
|
+
response,
|
|
66
|
+
await buildHttpError(response)
|
|
67
|
+
).toJSON()
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
const content = normalizeContentText(data.content);
|
|
72
|
+
if (!content) {
|
|
73
|
+
return {
|
|
74
|
+
url,
|
|
75
|
+
error: "No content returned for this URL."
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const metadata = buildFetchMetadata(data);
|
|
79
|
+
return {
|
|
80
|
+
url,
|
|
81
|
+
content,
|
|
82
|
+
...metadata ? { metadata } : {}
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return {
|
|
86
|
+
url,
|
|
87
|
+
error: asWebfoxError(error).toJSON()
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function resolveApiKey(config) {
|
|
95
|
+
const apiKey = config.credentials?.api;
|
|
96
|
+
if (!apiKey) {
|
|
97
|
+
throw new Error("is missing an API key");
|
|
98
|
+
}
|
|
99
|
+
return apiKey;
|
|
100
|
+
}
|
|
101
|
+
function buildHeaders(apiKey) {
|
|
102
|
+
return {
|
|
103
|
+
authorization: `Bearer ${apiKey}`,
|
|
104
|
+
"content-type": "application/json"
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function resolveEndpoint(baseUrlReference, endpointPath) {
|
|
108
|
+
const baseUrl = baseUrlReference ?? DEFAULT_BASE_URL;
|
|
109
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
110
|
+
const apiPath = endpointPath.replace(/^\/api\//, "");
|
|
111
|
+
if (base.endsWith(endpointPath)) {
|
|
112
|
+
return base;
|
|
113
|
+
}
|
|
114
|
+
if (base.endsWith("/api")) {
|
|
115
|
+
return `${base}/${apiPath}`;
|
|
116
|
+
}
|
|
117
|
+
return `${base}${endpointPath}`;
|
|
118
|
+
}
|
|
119
|
+
function clampMaxResults(value) {
|
|
120
|
+
return Math.max(1, Math.min(10, Math.trunc(value || 0)));
|
|
121
|
+
}
|
|
122
|
+
async function buildHttpError(response) {
|
|
123
|
+
const detail = await readErrorDetail(response);
|
|
124
|
+
const status = `${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
|
|
125
|
+
return detail ? `Ollama API request failed (${status}): ${detail}` : `Ollama API request failed (${status}).`;
|
|
126
|
+
}
|
|
127
|
+
async function readErrorDetail(response) {
|
|
128
|
+
const text = (await response.text()).trim();
|
|
129
|
+
if (!text) {
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const parsed = JSON.parse(text);
|
|
134
|
+
for (const key of ["message", "error", "detail"]) {
|
|
135
|
+
if (typeof parsed[key] === "string" && parsed[key].trim()) {
|
|
136
|
+
return parsed[key];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return JSON.stringify(parsed);
|
|
140
|
+
} catch {
|
|
141
|
+
return text;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function buildFetchMetadata(data) {
|
|
145
|
+
const metadata = {};
|
|
146
|
+
if (data.title) {
|
|
147
|
+
metadata.title = data.title;
|
|
148
|
+
}
|
|
149
|
+
if (data.links?.length) {
|
|
150
|
+
metadata.links = data.links;
|
|
151
|
+
}
|
|
152
|
+
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
153
|
+
}
|
|
154
|
+
var adapter = {
|
|
155
|
+
async search(input, config, context) {
|
|
156
|
+
return await searchOllama(input.query, input.maxResults, config, context);
|
|
157
|
+
},
|
|
158
|
+
async contents(input, config, context) {
|
|
159
|
+
return orderedContents(
|
|
160
|
+
await fetchOllamaContents(input.urls, config, context)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
export {
|
|
165
|
+
adapter
|
|
166
|
+
};
|