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
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,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">;
|