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
package/dist/pi.js ADDED
@@ -0,0 +1,563 @@
1
+ import {
2
+ renderTextDocument
3
+ } from "./chunk-QRHDXM2L.js";
4
+ import {
5
+ createWebfox
6
+ } from "./chunk-MVCCEPDF.js";
7
+ import "./chunk-WCTOYZVB.js";
8
+ import "./chunk-GAWPRXAU.js";
9
+ import "./chunk-ZVTVERW5.js";
10
+ import {
11
+ CAPABILITIES
12
+ } from "./chunk-G2YAQEET.js";
13
+ import "./chunk-U4BLULLV.js";
14
+
15
+ // src/pi.ts
16
+ import { mkdtemp, writeFile } from "node:fs/promises";
17
+ import { tmpdir } from "node:os";
18
+ import { join as join2 } from "node:path";
19
+ import {
20
+ truncateHead,
21
+ withFileMutationQueue
22
+ } from "@earendil-works/pi-coding-agent";
23
+ import { Type } from "typebox";
24
+
25
+ // src/pi-render.ts
26
+ import { stripVTControlCharacters as stripVTControlCharacters3 } from "node:util";
27
+ import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
28
+ import {
29
+ Text as Text2,
30
+ Container,
31
+ Markdown,
32
+ visibleWidth as visibleWidth2
33
+ } from "@earendil-works/pi-tui";
34
+
35
+ // src/pi-params.ts
36
+ import { stripVTControlCharacters } from "node:util";
37
+ function isRecord(value) {
38
+ return value !== null && typeof value === "object" && !Array.isArray(value);
39
+ }
40
+ function safeOptions(value, ancestors = /* @__PURE__ */ new WeakSet()) {
41
+ if (typeof value === "string") return stripVTControlCharacters(value);
42
+ if (typeof value === "number")
43
+ return Number.isFinite(value) ? value : void 0;
44
+ if (value === null || typeof value === "boolean") return value;
45
+ if (typeof value !== "object") return void 0;
46
+ if (ancestors.has(value)) return "[Circular]";
47
+ ancestors.add(value);
48
+ const safe = Array.isArray(value) ? value.map((child) => safeOptions(child, ancestors)) : Object.fromEntries(
49
+ Object.entries(value).map(([key, child]) => {
50
+ const normalized = stripVTControlCharacters(key).replace(
51
+ /[^a-z0-9]/gi,
52
+ ""
53
+ );
54
+ const sensitive = /authorization|apikey|credential|password|secret|headers?|cookie|privatekey|token$|^(auth|bearer)$/i.test(
55
+ normalized
56
+ );
57
+ return [
58
+ key,
59
+ sensitive ? "[redacted]" : safeOptions(child, ancestors)
60
+ ];
61
+ })
62
+ );
63
+ ancestors.delete(value);
64
+ return safe;
65
+ }
66
+ function keySegment(key) {
67
+ const safe = stripVTControlCharacters(key);
68
+ return /^[a-zA-Z_][\w-]*$/.test(safe) ? safe : JSON.stringify(safe);
69
+ }
70
+ function parameterValue(value) {
71
+ if (typeof value === "string" && /^[a-zA-Z_][\w./:@+-]*$/.test(value) && !/^(true|false|null)$/.test(value))
72
+ return value;
73
+ return JSON.stringify(value);
74
+ }
75
+ function callParameters(capability, args, formatKey = (key) => key) {
76
+ const pairs = [];
77
+ if (capability === "search" && typeof args.maxResults === "number" && Number.isFinite(args.maxResults))
78
+ pairs.push(`${formatKey("limit")}=${args.maxResults}`);
79
+ const append = (value, path) => {
80
+ if (isRecord(value) && Object.keys(value).length) {
81
+ for (const [key, child] of Object.entries(value))
82
+ append(child, path ? `${path}.${keySegment(key)}` : keySegment(key));
83
+ } else if (path) {
84
+ const formatted = parameterValue(value);
85
+ if (formatted !== void 0)
86
+ pairs.push(`${formatKey(path)}=${formatted}`);
87
+ }
88
+ };
89
+ if (isRecord(args.options)) {
90
+ const options = safeOptions(args.options);
91
+ for (const [key, value] of Object.entries(options)) {
92
+ append(value, key === "limit" ? "options.limit" : keySegment(key));
93
+ }
94
+ }
95
+ return pairs.join(" ");
96
+ }
97
+
98
+ // src/pi-keybindings.ts
99
+ import { readFileSync } from "node:fs";
100
+ import { join } from "node:path";
101
+ import { getAgentDir, keyText } from "@earendil-works/pi-coding-agent";
102
+ import {
103
+ getKeybindings,
104
+ KeybindingsManager
105
+ } from "@earendil-works/pi-tui";
106
+ function expansionKey() {
107
+ const id = "app.tools.expand";
108
+ const key = keyText(id);
109
+ if (key) return key;
110
+ const manager = getKeybindings();
111
+ if (manager.getDefinition(id)) return formatKeys(manager.getKeys(id));
112
+ let configured;
113
+ try {
114
+ const settings = JSON.parse(
115
+ readFileSync(join(getAgentDir(), "keybindings.json"), "utf8")
116
+ );
117
+ configured = settings?.[id] ?? settings?.expandTools;
118
+ } catch {
119
+ }
120
+ const binding = typeof configured === "string" || Array.isArray(configured) && configured.every((key2) => typeof key2 === "string") ? configured : "ctrl+o";
121
+ const keys = new KeybindingsManager({ [id]: { defaultKeys: "ctrl+o" } }, {
122
+ [id]: binding
123
+ }).getKeys(id);
124
+ return formatKeys(keys);
125
+ }
126
+ function formatKeys(keys) {
127
+ return keys.map(
128
+ (key) => key.split("+").map(
129
+ (part) => process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part
130
+ ).join("+")
131
+ ).join("/");
132
+ }
133
+
134
+ // src/pi-text.ts
135
+ import { stripVTControlCharacters as stripVTControlCharacters2 } from "node:util";
136
+ import { truncateToWidth } from "@earendil-works/pi-tui";
137
+ function truncateLine(text, width) {
138
+ return truncateToWidth(text, width).replaceAll(
139
+ "\x1B[0m",
140
+ "\x1B[22;23;24;25;27;28;29;39m"
141
+ );
142
+ }
143
+ function plainText(value) {
144
+ return stripVTControlCharacters2(value).replace(
145
+ /[\x00-\x08\x0b-\x1f\x7f-\x9f]/g,
146
+ ""
147
+ );
148
+ }
149
+
150
+ // src/pi-search-render.ts
151
+ import {
152
+ Text,
153
+ getCapabilities,
154
+ hyperlink,
155
+ visibleWidth
156
+ } from "@earendil-works/pi-tui";
157
+ function isRecord2(value) {
158
+ return !!value && typeof value === "object" && !Array.isArray(value);
159
+ }
160
+ function searchDocument(value) {
161
+ if (!isRecord2(value) || value.capability !== "search" || !Array.isArray(value.results))
162
+ return false;
163
+ return value.results.every((entry) => {
164
+ if (!isRecord2(entry) || typeof entry.input !== "string") return false;
165
+ if (entry.ok === false)
166
+ return isRecord2(entry.error) && typeof entry.error.message === "string";
167
+ return entry.ok === true && isRecord2(entry.value) && Array.isArray(entry.value.results) && entry.value.results.every(
168
+ (result) => isRecord2(result) && typeof result.title === "string" && typeof result.url === "string" && typeof result.snippet === "string"
169
+ );
170
+ });
171
+ }
172
+ function link(url, theme) {
173
+ const label = theme.fg("accent", theme.underline(plainText(url)));
174
+ if (!getCapabilities().hyperlinks || /[\x00-\x20\x7f-\x9f]/.test(url))
175
+ return label;
176
+ try {
177
+ const target = new URL(url);
178
+ if (target.protocol === "https:" || target.protocol === "http:")
179
+ return hyperlink(label, target.href);
180
+ } catch {
181
+ }
182
+ return label;
183
+ }
184
+ function renderSearchResult(details, fallback, theme) {
185
+ const document = isRecord2(details) && searchDocument(details.result) ? details.result : void 0;
186
+ return {
187
+ render(width) {
188
+ if (width <= 0) return [];
189
+ if (!document) {
190
+ const text = plainText(fallback).split("\n").map((line) => {
191
+ const urlLine = /^(\s*)(https?:\/\/\S+)(\s*)$/.exec(line);
192
+ return urlLine ? urlLine[1] + link(urlLine[2], theme) + urlLine[3] : theme.fg("toolOutput", line);
193
+ }).join("\n");
194
+ return new Text(text, 0, 0).render(width).map((line) => truncateLine(line, width));
195
+ }
196
+ const lines = [];
197
+ const append = (text, prefix = "") => {
198
+ const indent = visibleWidth(prefix);
199
+ const wrapped = new Text(text, 0, 0).render(
200
+ Math.max(1, width - indent)
201
+ );
202
+ lines.push(
203
+ ...wrapped.map(
204
+ (line, index) => truncateLine((index ? " ".repeat(indent) : prefix) + line, width)
205
+ )
206
+ );
207
+ };
208
+ document.results.forEach((entry, index) => {
209
+ if (index) lines.push("");
210
+ if (document.results.length > 1) {
211
+ append(
212
+ theme.fg(
213
+ "mdHeading",
214
+ theme.bold(`${index + 1}. ${plainText(entry.input)}`)
215
+ )
216
+ );
217
+ lines.push("");
218
+ }
219
+ if (!entry.ok) {
220
+ append(theme.fg("error", `Error: ${plainText(entry.error.message)}`));
221
+ return;
222
+ }
223
+ if (!entry.value.results.length)
224
+ append(theme.fg("toolOutput", "No results found."));
225
+ entry.value.results.forEach((result, resultIndex) => {
226
+ if (resultIndex) lines.push("");
227
+ const number = `${resultIndex + 1}. `;
228
+ const indent = " ".repeat(number.length);
229
+ append(
230
+ theme.fg("toolOutput", plainText(result.title)),
231
+ theme.fg("accent", number)
232
+ );
233
+ append(link(result.url, theme), indent);
234
+ if (result.snippet)
235
+ append(theme.fg("toolOutput", plainText(result.snippet)), indent);
236
+ });
237
+ });
238
+ return lines;
239
+ },
240
+ invalidate() {
241
+ }
242
+ };
243
+ }
244
+
245
+ // src/pi-render.ts
246
+ var inputStates = {
247
+ queued: { glyph: "\u25CF", color: "dim" },
248
+ running: { glyph: "\u25B6\uFE0E", color: "muted" },
249
+ done: { glyph: "\u2714\uFE0E", color: "success" },
250
+ failed: { glyph: "\u2718\uFE0E", color: "error" },
251
+ cancelled: { glyph: "\u25A0", color: "dim" }
252
+ };
253
+ function statusRows(details) {
254
+ if (!details || typeof details !== "object") return;
255
+ let rows;
256
+ if ("webInputStatus" in details && details.webInputStatus === true && "inputs" in details && Array.isArray(details.inputs)) {
257
+ rows = details.inputs;
258
+ } else if ("webContentsStatus" in details && details.webContentsStatus === true && "urls" in details && Array.isArray(details.urls)) {
259
+ rows = details.urls.map((row) => ({ input: row?.url, state: row?.state }));
260
+ } else return;
261
+ const inputs = rows.filter(
262
+ (row) => !!row && typeof row === "object" && "input" in row && typeof row.input === "string" && "state" in row && typeof row.state === "string" && Object.hasOwn(inputStates, row.state)
263
+ );
264
+ return inputs;
265
+ }
266
+ function displayInput(value) {
267
+ return JSON.stringify(plainText(value)).slice(1, -1);
268
+ }
269
+ var WebCall = class {
270
+ constructor(capability) {
271
+ this.capability = capability;
272
+ }
273
+ capability;
274
+ args = {};
275
+ theme;
276
+ expanded = false;
277
+ update(args, theme, expanded) {
278
+ this.args = args && typeof args === "object" ? args : {};
279
+ this.theme = theme;
280
+ this.expanded = expanded;
281
+ }
282
+ render(width) {
283
+ if (width <= 0) return [];
284
+ const { args, theme, capability } = this;
285
+ const title = theme.fg("toolTitle", theme.bold(`web ${capability}`));
286
+ let text = title;
287
+ const parameters = callParameters(
288
+ capability,
289
+ args,
290
+ (key2) => theme.bold(key2)
291
+ );
292
+ if (parameters) text += theme.fg("dim", ` ${parameters}`);
293
+ if (this.expanded)
294
+ return visibleWidth2(text) <= width ? [text] : new Text2(text, 0, 0).render(width).map((line) => truncateLine(line, width));
295
+ const key = expansionKey();
296
+ const hint = key ? theme.fg("muted", " (") + theme.fg("dim", key) + theme.fg("muted", " to expand)") : "";
297
+ if (width >= visibleWidth2(title) + visibleWidth2(hint) + 3)
298
+ return [truncateLine(text, width - visibleWidth2(hint)) + hint];
299
+ return [truncateLine(text, width)];
300
+ }
301
+ // Rendering is stateless so width and theme changes never retain stale styling.
302
+ invalidate() {
303
+ }
304
+ };
305
+ function renderWebResult(result, options, theme, isError, capability) {
306
+ const text = result.content.filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n");
307
+ const metadata = result.details;
308
+ const isSearch = (capability ?? metadata?.capability ?? metadata?.result?.capability) === "search";
309
+ const body = () => isSearch ? renderSearchResult(result.details, text, theme) : new Markdown(text, 0, 0, getMarkdownTheme());
310
+ const rows = statusRows(result.details);
311
+ if (rows?.length) {
312
+ const container = new Container();
313
+ container.addChild({
314
+ render: (width) => width > 0 ? rows.flatMap((row) => {
315
+ const { glyph, color } = inputStates[row.state];
316
+ const line = theme.fg(color, glyph) + " " + theme.fg("accent", displayInput(row.input));
317
+ return options.expanded ? new Text2(line, 0, 0).render(width).map((wrapped) => truncateLine(wrapped, width)) : [truncateLine(line, width)];
318
+ }) : [],
319
+ invalidate() {
320
+ }
321
+ });
322
+ if (options.expanded && !options.isPartial) container.addChild(body());
323
+ return container;
324
+ }
325
+ if (options.expanded) return body();
326
+ const partialFailure = result.details && typeof result.details === "object" && "status" in result.details && result.details.status === "partial";
327
+ if (!options.isPartial && !isError && !partialFailure) return new Container();
328
+ const summary = partialFailure ? "One or more inputs failed." : stripVTControlCharacters3(text).split(/\r?\n/).find((line) => line.trim()) ?? (options.isPartial ? "Working\u2026" : "Tool failed.");
329
+ const safe = JSON.stringify(summary).slice(1, -1);
330
+ const status = inputStates[isError || partialFailure ? "failed" : "running"];
331
+ return {
332
+ render: (width) => width > 0 ? [
333
+ truncateLine(
334
+ theme.fg(status.color, status.glyph) + " " + theme.fg(isError || partialFailure ? "error" : "muted", safe),
335
+ width
336
+ )
337
+ ] : [],
338
+ invalidate() {
339
+ }
340
+ };
341
+ }
342
+
343
+ // src/pi-validation.ts
344
+ import { stripVTControlCharacters as stripVTControlCharacters4 } from "node:util";
345
+ import { Check, Errors } from "typebox/value";
346
+ function isSchema(value) {
347
+ return !!value && typeof value === "object" && !Array.isArray(value);
348
+ }
349
+ function alternatives(schema) {
350
+ return ["anyOf", "oneOf", "allOf"].flatMap(
351
+ (key) => Array.isArray(schema[key]) ? schema[key].filter(isSchema) : []
352
+ );
353
+ }
354
+ function acceptsPath(schema, path) {
355
+ if (schema === true) return true;
356
+ if (!isSchema(schema)) return false;
357
+ if (!path.length) return true;
358
+ if (alternatives(schema).some((branch) => acceptsPath(branch, path)))
359
+ return true;
360
+ const [key, ...rest] = path;
361
+ if (schema.type === "array") return acceptsPath(schema.items, rest);
362
+ if (schema.properties && Object.hasOwn(schema.properties, key))
363
+ return acceptsPath(schema.properties[key], rest);
364
+ if (isSchema(schema.patternProperties)) {
365
+ for (const [pattern, child] of Object.entries(schema.patternProperties))
366
+ if (new RegExp(pattern).test(key) && acceptsPath(child, rest))
367
+ return true;
368
+ }
369
+ if (schema.additionalProperties === false) return false;
370
+ if (schema.type && schema.type !== "object") return false;
371
+ if (alternatives(schema).length && !schema.properties && schema.additionalProperties === void 0)
372
+ return false;
373
+ return schema.additionalProperties === void 0 || acceptsPath(schema.additionalProperties, rest);
374
+ }
375
+ function displayPath(path) {
376
+ return path.map((part, index) => {
377
+ const safe = stripVTControlCharacters4(part);
378
+ if (/^[a-zA-Z_][\w-]*$/.test(safe)) return `${index ? "." : ""}${safe}`;
379
+ if (/^\d+$/.test(safe)) return `[${safe}]`;
380
+ return `[${JSON.stringify(safe)}]`;
381
+ }).join("");
382
+ }
383
+ function locations(schema, name, path = []) {
384
+ if (!isSchema(schema)) return [];
385
+ const found = alternatives(schema).flatMap(
386
+ (branch) => locations(branch, name, path)
387
+ );
388
+ if (isSchema(schema.properties)) {
389
+ for (const [key, child] of Object.entries(schema.properties)) {
390
+ const next = [...path, key];
391
+ if (key === name) found.push(next);
392
+ found.push(...locations(child, name, next));
393
+ }
394
+ }
395
+ return found;
396
+ }
397
+ function prepareToolArguments(schema, args) {
398
+ if (Check(schema, args)) return args;
399
+ const messages = /* @__PURE__ */ new Set();
400
+ for (const error of Errors(schema, args)) {
401
+ if (error.keyword !== "additionalProperties") continue;
402
+ const names = error.params.additionalProperties;
403
+ if (!Array.isArray(names)) continue;
404
+ const parent = error.instancePath.split("/").slice(1).map((part) => part.replaceAll("~1", "/").replaceAll("~0", "~"));
405
+ for (const name of names) {
406
+ if (typeof name !== "string") continue;
407
+ const path = [...parent, name];
408
+ if (acceptsPath(schema, path)) continue;
409
+ const targets = [...new Set(locations(schema, name).map(displayPath))];
410
+ const suggestion = targets.length === 1 ? ` Use ${targets[0]} instead.` : " Check the tool's parameter schema.";
411
+ messages.add(`Invalid parameter: ${displayPath(path)}.${suggestion}`);
412
+ if (messages.size === 3) break;
413
+ }
414
+ if (messages.size === 3) break;
415
+ }
416
+ if (messages.size) throw new Error([...messages].join("\n"));
417
+ return args;
418
+ }
419
+
420
+ // src/pi.ts
421
+ function webExtension(pi) {
422
+ const clients = /* @__PURE__ */ new Map();
423
+ const clientFor = (cwd) => {
424
+ let client = clients.get(cwd);
425
+ if (!client) {
426
+ client = createWebfox({ cwd });
427
+ clients.set(cwd, client);
428
+ }
429
+ return client;
430
+ };
431
+ const initial = clientFor(process.cwd());
432
+ const selected = CAPABILITIES.filter(
433
+ (capability) => initial.inspectCapability(capability).provider
434
+ );
435
+ if (!selected.length)
436
+ pi.on("session_start", (_event, context) => {
437
+ if (context.hasUI)
438
+ context.ui.notify(
439
+ "Web registered no tools. Select a default provider in the shared configuration, then restart pi.",
440
+ "warning"
441
+ );
442
+ });
443
+ for (const capability of selected) {
444
+ const inspection = initial.inspectCapability(capability);
445
+ const provider = inspection.provider;
446
+ const fields = capability === "contents" ? { urls: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }) } : capability === "research" ? { input: Type.String({ minLength: 1 }) } : {
447
+ queries: Type.Array(Type.String({ minLength: 1 }), {
448
+ minItems: 1,
449
+ maxItems: 10
450
+ }),
451
+ ...capability === "search" ? { maxResults: Type.Optional(Type.Integer({ minimum: 1 })) } : {}
452
+ };
453
+ const parameters = Type.Object(
454
+ {
455
+ ...fields,
456
+ ...inspection.optionSchema ? {
457
+ options: Type.Optional(
458
+ inspection.optionSchema
459
+ )
460
+ } : {}
461
+ },
462
+ { additionalProperties: false }
463
+ );
464
+ pi.registerTool({
465
+ name: `web_${capability}`,
466
+ label: `Web ${capability[0].toUpperCase()}${capability.slice(1)}`,
467
+ description: `${descriptions[capability]} Output is truncated to 2000 lines or 50 KiB; full results are saved to a file when truncated.`,
468
+ parameters,
469
+ prepareArguments(args) {
470
+ return prepareToolArguments(parameters, args);
471
+ },
472
+ renderCall(args, theme, context) {
473
+ const call = context.lastComponent instanceof WebCall ? context.lastComponent : new WebCall(capability);
474
+ call.update(args, theme, context.expanded);
475
+ return call;
476
+ },
477
+ renderResult(result, options, theme, context) {
478
+ return renderWebResult(
479
+ result,
480
+ options,
481
+ theme,
482
+ context.isError,
483
+ capability
484
+ );
485
+ },
486
+ async execute(_id, values, signal, onUpdate, ctx) {
487
+ const params = values;
488
+ const client = clientFor(ctx.cwd);
489
+ const inputStatuses = [];
490
+ const request = {
491
+ provider,
492
+ signal: signal ?? ctx.signal,
493
+ options: params.options,
494
+ onProgress: (event) => {
495
+ if (event.inputIndex !== void 0 && event.input !== void 0 && event.state) {
496
+ inputStatuses[event.inputIndex] = {
497
+ input: event.input,
498
+ state: event.state
499
+ };
500
+ }
501
+ onUpdate?.({
502
+ content: [{ type: "text", text: event.message }],
503
+ details: {
504
+ webInputStatus: true,
505
+ capability,
506
+ inputs: inputStatuses.filter(Boolean).map((row) => ({ ...row }))
507
+ }
508
+ });
509
+ }
510
+ };
511
+ const result = capability === "search" ? await client.search({
512
+ ...request,
513
+ queries: params.queries,
514
+ maxResults: params.maxResults
515
+ }) : capability === "answer" ? await client.answer({ ...request, queries: params.queries }) : capability === "contents" ? await client.contents({ ...request, urls: params.urls }) : await client.research({ ...request, input: params.input });
516
+ const text = renderTextDocument(result);
517
+ const truncated = truncateHead(text);
518
+ let body = truncated.content;
519
+ let fullOutputPath;
520
+ if (truncated.truncated) {
521
+ fullOutputPath = join2(
522
+ await mkdtemp(join2(tmpdir(), "web-")),
523
+ "result.json"
524
+ );
525
+ await withFileMutationQueue(
526
+ fullOutputPath,
527
+ () => writeFile(fullOutputPath, JSON.stringify(result), { mode: 384 })
528
+ );
529
+ body += `
530
+
531
+ Full results: ${fullOutputPath}`;
532
+ }
533
+ return {
534
+ content: [{ type: "text", text: body }],
535
+ details: {
536
+ webProviderResult: true,
537
+ status: result.status,
538
+ webInputStatus: true,
539
+ capability,
540
+ inputs: result.results.map((entry) => ({
541
+ input: entry.input,
542
+ state: entry.ok ? "done" : entry.error.code === "CANCELLED" ? "cancelled" : "failed"
543
+ })),
544
+ ...fullOutputPath ? { fullOutputPath } : { result }
545
+ }
546
+ };
547
+ }
548
+ });
549
+ }
550
+ pi.on("tool_result", (event) => {
551
+ if (event.details && typeof event.details === "object" && "webProviderResult" in event.details && event.details.webProviderResult === true && "status" in event.details && event.details.status === "partial")
552
+ return { isError: true };
553
+ });
554
+ }
555
+ var descriptions = {
556
+ search: "Search up to ten queries and return titles, URLs, and snippets in input order.",
557
+ contents: "Fetch and extract readable contents from web URLs.",
558
+ answer: "Answer up to ten questions using web-grounded evidence.",
559
+ research: "Run foreground multi-step web research and return the final report."
560
+ };
561
+ export {
562
+ webExtension as default
563
+ };
@@ -0,0 +1,7 @@
1
+ import type { ProviderContext, SearchResponse, ToolOutput } from "../contract.js";
2
+ import type { Brave } from "./types.js";
3
+ export declare const adapter: {
4
+ search(input: import("../contract.js").ProviderRequest<"search">, config: Brave, context: ProviderContext): Promise<SearchResponse>;
5
+ answer(input: import("../contract.js").ProviderRequest<"answer">, config: Brave, context: ProviderContext): Promise<ToolOutput>;
6
+ research(input: import("../contract.js").ProviderRequest<"research">, config: Brave, context: ProviderContext): Promise<ToolOutput>;
7
+ };
@@ -0,0 +1 @@
1
+ export declare const braveProvider: import("../definition.js").ProviderDefinitionFor<"brave">;
@@ -0,0 +1,4 @@
1
+ import type { Provider } from "../contract.js";
2
+ export interface Brave extends Provider {
3
+ baseUrl?: string;
4
+ }
@@ -0,0 +1,9 @@
1
+ import type { CustomCommandConfig } from "./custom/types.js";
2
+ import type { ProviderContext } from "./contract.js";
3
+ /** Subprocess mechanics only; lifecycle and outward redaction belong to runtime. */
4
+ export declare function runCliJsonCommand<TOutput>({ command, payload, context, label, }: {
5
+ command: CustomCommandConfig;
6
+ payload: Record<string, unknown>;
7
+ context: ProviderContext;
8
+ label: string;
9
+ }): Promise<TOutput>;
@@ -0,0 +1,5 @@
1
+ import type { ProviderContext } from "../contract.js";
2
+ import type { Cloudflare } from "./types.js";
3
+ export declare const adapter: {
4
+ contents(input: import("../contract.js").ProviderRequest<"contents">, config: Cloudflare, context: ProviderContext): Promise<import("../../contents.js").IndexedContentsResponse>;
5
+ };
@@ -0,0 +1 @@
1
+ export declare const cloudflareProvider: import("../definition.js").ProviderDefinitionFor<"cloudflare">;
@@ -0,0 +1,4 @@
1
+ import type { Provider } from "../contract.js";
2
+ export interface Cloudflare extends Provider {
3
+ accountId?: string;
4
+ }