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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +256 -3
  3. package/assets/webfox-lockup-dark.png +0 -0
  4. package/assets/webfox-lockup.png +0 -0
  5. package/dist/adapter-2YBFCBCZ.js +161 -0
  6. package/dist/adapter-67TIISDW.js +753 -0
  7. package/dist/adapter-AB7LKV7N.js +324 -0
  8. package/dist/adapter-AUYLTDPM.js +73 -0
  9. package/dist/adapter-HITGYUXN.js +494 -0
  10. package/dist/adapter-HPTMLH75.js +334 -0
  11. package/dist/adapter-I6OUKBZQ.js +492 -0
  12. package/dist/adapter-ISZQTZDP.js +166 -0
  13. package/dist/adapter-MAG7X663.js +230 -0
  14. package/dist/adapter-NEXSOHGT.js +232 -0
  15. package/dist/adapter-PADXKW43.js +162 -0
  16. package/dist/adapter-SDQX2533.js +123 -0
  17. package/dist/adapter-UAZNLDSS.js +237 -0
  18. package/dist/adapter-XBAQSK7I.js +282 -0
  19. package/dist/application-types.d.ts +34 -0
  20. package/dist/application.d.ts +2 -0
  21. package/dist/chunk-EF4VMDZL.js +85 -0
  22. package/dist/chunk-G2YAQEET.js +38 -0
  23. package/dist/chunk-GAWPRXAU.js +3051 -0
  24. package/dist/chunk-I2UIACY3.js +12 -0
  25. package/dist/chunk-MVCCEPDF.js +762 -0
  26. package/dist/chunk-QRHDXM2L.js +31 -0
  27. package/dist/chunk-U4BLULLV.js +52 -0
  28. package/dist/chunk-WCTOYZVB.js +85 -0
  29. package/dist/chunk-X6ZBIHZC.js +21 -0
  30. package/dist/chunk-YZXM4U2C.js +21 -0
  31. package/dist/chunk-ZVTVERW5.js +20 -0
  32. package/dist/cli.js +641 -0
  33. package/dist/config.schema.json +4270 -0
  34. package/dist/configuration/defaults.d.ts +3 -0
  35. package/dist/configuration/file.d.ts +17 -0
  36. package/dist/configuration/options-schema.d.ts +2 -0
  37. package/dist/configuration/planning.d.ts +10 -0
  38. package/dist/configuration/schema.d.ts +9 -0
  39. package/dist/configuration/types.d.ts +37 -0
  40. package/dist/contents.d.ts +19 -0
  41. package/dist/domain.d.ts +89 -0
  42. package/dist/errors.d.ts +17 -0
  43. package/dist/index.d.ts +8 -0
  44. package/dist/index.js +38 -0
  45. package/dist/package-metadata.d.ts +3 -0
  46. package/dist/pi-keybindings.d.ts +1 -0
  47. package/dist/pi-params.d.ts +3 -0
  48. package/dist/pi-render.d.ts +50 -0
  49. package/dist/pi-search-render.d.ts +4 -0
  50. package/dist/pi-text.d.ts +4 -0
  51. package/dist/pi-validation.d.ts +4 -0
  52. package/dist/pi.d.ts +2 -0
  53. package/dist/pi.js +563 -0
  54. package/dist/providers/brave/adapter.d.ts +7 -0
  55. package/dist/providers/brave/definition.d.ts +1 -0
  56. package/dist/providers/brave/types.d.ts +4 -0
  57. package/dist/providers/cli-json.d.ts +9 -0
  58. package/dist/providers/cloudflare/adapter.d.ts +5 -0
  59. package/dist/providers/cloudflare/definition.d.ts +1 -0
  60. package/dist/providers/cloudflare/types.d.ts +4 -0
  61. package/dist/providers/contract.d.ts +88 -0
  62. package/dist/providers/custom/adapter.d.ts +9 -0
  63. package/dist/providers/custom/definition.d.ts +1 -0
  64. package/dist/providers/custom/types.d.ts +9 -0
  65. package/dist/providers/definition.d.ts +32 -0
  66. package/dist/providers/exa/adapter.d.ts +8 -0
  67. package/dist/providers/exa/definition.d.ts +1 -0
  68. package/dist/providers/exa/types.d.ts +4 -0
  69. package/dist/providers/firecrawl/adapter.d.ts +7 -0
  70. package/dist/providers/firecrawl/definition.d.ts +1 -0
  71. package/dist/providers/firecrawl/types.d.ts +4 -0
  72. package/dist/providers/gemini/adapter.d.ts +14 -0
  73. package/dist/providers/gemini/definition.d.ts +1 -0
  74. package/dist/providers/gemini/types.d.ts +3 -0
  75. package/dist/providers/linkup/adapter.d.ts +7 -0
  76. package/dist/providers/linkup/definition.d.ts +1 -0
  77. package/dist/providers/linkup/types.d.ts +4 -0
  78. package/dist/providers/metadata.d.ts +15 -0
  79. package/dist/providers/ollama/adapter.d.ts +6 -0
  80. package/dist/providers/ollama/definition.d.ts +1 -0
  81. package/dist/providers/ollama/types.d.ts +4 -0
  82. package/dist/providers/openai/adapter.d.ts +7 -0
  83. package/dist/providers/openai/definition.d.ts +1 -0
  84. package/dist/providers/openai/types.d.ts +28 -0
  85. package/dist/providers/parallel/adapter.d.ts +6 -0
  86. package/dist/providers/parallel/definition.d.ts +1 -0
  87. package/dist/providers/parallel/types.d.ts +4 -0
  88. package/dist/providers/perplexity/adapter.d.ts +7 -0
  89. package/dist/providers/perplexity/definition.d.ts +1 -0
  90. package/dist/providers/perplexity/types.d.ts +4 -0
  91. package/dist/providers/registry.d.ts +16 -0
  92. package/dist/providers/serper/adapter.d.ts +5 -0
  93. package/dist/providers/serper/definition.d.ts +1 -0
  94. package/dist/providers/serper/types.d.ts +21 -0
  95. package/dist/providers/shared.d.ts +4 -0
  96. package/dist/providers/tavily/adapter.d.ts +6 -0
  97. package/dist/providers/tavily/definition.d.ts +1 -0
  98. package/dist/providers/tavily/types.d.ts +4 -0
  99. package/dist/providers/valyu/adapter.d.ts +8 -0
  100. package/dist/providers/valyu/definition.d.ts +1 -0
  101. package/dist/providers/valyu/types.d.ts +4 -0
  102. package/dist/render.d.ts +5 -0
  103. package/dist/runtime/credentials.d.ts +15 -0
  104. package/dist/runtime/duration.d.ts +2 -0
  105. package/dist/runtime/execute.d.ts +20 -0
  106. package/dist/runtime/lifecycle.d.ts +8 -0
  107. package/dist/runtime/outward.d.ts +11 -0
  108. package/dist/runtime/polling.d.ts +13 -0
  109. package/dist/runtime/process.d.ts +4 -0
  110. package/docs/cli-experience.md +177 -0
  111. package/docs/provider.md +177 -0
  112. package/docs/reference.md +181 -0
  113. package/example-config.yaml +36 -0
  114. package/examples/custom/README.md +57 -0
  115. package/examples/custom/provider.mjs +54 -0
  116. package/examples/custom/webfox.json +18 -0
  117. package/package.json +122 -8
  118. package/index.js +0 -2
@@ -0,0 +1,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
+ };