@asterxsk/kiln 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +170 -170
- package/agent/AGENTS.md +67 -67
- package/agent/README.md +5 -5
- package/agent/extensions/AGENTS.md +68 -68
- package/agent/extensions/ask-user/index.ts +418 -418
- package/agent/extensions/ask-user/package-lock.json +769 -769
- package/agent/extensions/ask-user/package.json +19 -19
- package/agent/extensions/ask-user/prompt.ts +45 -45
- package/agent/extensions/ask-user/tsconfig.json +7 -7
- package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
- package/agent/extensions/background-terminals/index.ts +627 -627
- package/agent/extensions/background-terminals/manager.test.ts +735 -735
- package/agent/extensions/background-terminals/output.test.ts +109 -109
- package/agent/extensions/background-terminals/package-lock.json +769 -769
- package/agent/extensions/background-terminals/package.json +17 -17
- package/agent/extensions/background-terminals/prompt.test.ts +125 -125
- package/agent/extensions/background-terminals/ps.test.ts +82 -82
- package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
- package/agent/extensions/background-terminals/src/domain.ts +87 -87
- package/agent/extensions/background-terminals/src/manager.ts +907 -907
- package/agent/extensions/background-terminals/src/output.ts +84 -84
- package/agent/extensions/background-terminals/src/prompt.ts +142 -142
- package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
- package/agent/extensions/background-terminals/src/runtime.ts +36 -36
- package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
- package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
- package/agent/extensions/background-terminals/tsconfig.json +7 -7
- package/agent/extensions/file-search/index.spec.ts +443 -443
- package/agent/extensions/file-search/index.ts +459 -459
- package/agent/extensions/file-search/package-lock.json +2253 -2253
- package/agent/extensions/file-search/package.json +23 -23
- package/agent/extensions/file-search/src/args.ts +122 -122
- package/agent/extensions/file-search/src/binaries.ts +422 -422
- package/agent/extensions/file-search/src/output.ts +126 -126
- package/agent/extensions/file-search/src/process.ts +146 -146
- package/agent/extensions/file-search/src/prompt.ts +52 -52
- package/agent/extensions/file-search/tsconfig.json +7 -7
- package/agent/extensions/modelconf/PLAN.md +915 -915
- package/agent/extensions/modelconf/index.ts +296 -296
- package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
- package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
- package/agent/extensions/pi-web-access/LICENSE +21 -21
- package/agent/extensions/pi-web-access/README.md +470 -470
- package/agent/extensions/pi-web-access/SECURITY.md +5 -5
- package/agent/extensions/pi-web-access/activity.ts +101 -101
- package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
- package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
- package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
- package/agent/extensions/pi-web-access/content-find.ts +139 -139
- package/agent/extensions/pi-web-access/credential-source.ts +191 -191
- package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
- package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
- package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
- package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
- package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
- package/agent/extensions/pi-web-access/exa.ts +526 -526
- package/agent/extensions/pi-web-access/extract.ts +1196 -1196
- package/agent/extensions/pi-web-access/feature-config.ts +29 -29
- package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
- package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
- package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
- package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
- package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
- package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
- package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
- package/agent/extensions/pi-web-access/github-api.ts +197 -197
- package/agent/extensions/pi-web-access/github-extract.ts +746 -746
- package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
- package/agent/extensions/pi-web-access/index.ts +1737 -1737
- package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
- package/agent/extensions/pi-web-access/package.json +64 -64
- package/agent/extensions/pi-web-access/page-query.ts +96 -96
- package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
- package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
- package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
- package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
- package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
- package/agent/extensions/pi-web-access/source-check.ts +282 -282
- package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
- package/agent/extensions/pi-web-access/storage.ts +521 -521
- package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
- package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
- package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
- package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
- package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
- package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
- package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
- package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
- package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
- package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
- package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
- package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
- package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
- package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
- package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
- package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
- package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
- package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
- package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
- package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
- package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
- package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
- package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
- package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
- package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
- package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
- package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
- package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
- package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
- package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
- package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
- package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
- package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
- package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
- package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
- package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
- package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
- package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
- package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
- package/agent/extensions/pi-web-access/tsconfig.json +11 -11
- package/agent/extensions/pi-web-access/utils.ts +451 -451
- package/agent/extensions/pi-web-access/video-extract.ts +392 -392
- package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
- package/agent/extensions/shared/activity-status.ts +31 -31
- package/agent/extensions/shared/child-session.test.ts +270 -270
- package/agent/extensions/shared/child-session.ts +148 -148
- package/agent/extensions/shared/context-utilization.test.ts +48 -48
- package/agent/extensions/shared/context-utilization.ts +47 -47
- package/agent/extensions/shared/dashboard-state.ts +99 -99
- package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
- package/agent/extensions/shared/tool-call-timeout.ts +104 -104
- package/agent/extensions/subagents/by-the-way.test.ts +29 -29
- package/agent/extensions/subagents/claude.test.ts +119 -119
- package/agent/extensions/subagents/codex.test.ts +102 -102
- package/agent/extensions/subagents/context-usage.test.ts +107 -107
- package/agent/extensions/subagents/docs/design-plan.md +568 -568
- package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
- package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
- package/agent/extensions/subagents/index.ts +779 -779
- package/agent/extensions/subagents/manager.test.ts +276 -276
- package/agent/extensions/subagents/package-lock.json +2244 -2244
- package/agent/extensions/subagents/package.json +19 -19
- package/agent/extensions/subagents/result-delivery.test.ts +27 -27
- package/agent/extensions/subagents/src/backend.ts +73 -73
- package/agent/extensions/subagents/src/backends/claude.ts +701 -701
- package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
- package/agent/extensions/subagents/src/backends/pi.ts +575 -575
- package/agent/extensions/subagents/src/backends/stub.ts +300 -300
- package/agent/extensions/subagents/src/by-the-way.ts +21 -21
- package/agent/extensions/subagents/src/domain.ts +253 -253
- package/agent/extensions/subagents/src/format.ts +74 -74
- package/agent/extensions/subagents/src/manager.ts +736 -736
- package/agent/extensions/subagents/src/prompt.ts +92 -92
- package/agent/extensions/subagents/src/result-delivery.ts +20 -20
- package/agent/extensions/subagents/src/runtime.ts +53 -53
- package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
- package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
- package/agent/extensions/subagents/takeover.test.ts +29 -29
- package/agent/extensions/subagents/tsconfig.json +7 -7
- package/agent/extensions/todo/AGENTS.md +38 -38
- package/agent/extensions/todo/LICENSE +21 -21
- package/agent/extensions/todo/config.ts +55 -55
- package/agent/extensions/todo/index.ts +151 -151
- package/agent/extensions/todo/locales/de.json +17 -17
- package/agent/extensions/todo/locales/en.json +15 -15
- package/agent/extensions/todo/locales/es.json +17 -17
- package/agent/extensions/todo/locales/fr.json +17 -17
- package/agent/extensions/todo/locales/pt-BR.json +17 -17
- package/agent/extensions/todo/locales/pt.json +17 -17
- package/agent/extensions/todo/locales/ru.json +17 -17
- package/agent/extensions/todo/locales/uk.json +17 -17
- package/agent/extensions/todo/locales/zh.json +17 -17
- package/agent/extensions/todo/package-lock.json +3358 -3358
- package/agent/extensions/todo/package.json +67 -67
- package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
- package/agent/extensions/todo/state/invariants.ts +20 -20
- package/agent/extensions/todo/state/replay.ts +38 -38
- package/agent/extensions/todo/state/selectors.ts +107 -107
- package/agent/extensions/todo/state/state-reducer.ts +326 -326
- package/agent/extensions/todo/state/state.ts +18 -18
- package/agent/extensions/todo/state/store.ts +82 -82
- package/agent/extensions/todo/state/task-graph.ts +57 -57
- package/agent/extensions/todo/todo-overlay.ts +200 -200
- package/agent/extensions/todo/todo.ts +155 -155
- package/agent/extensions/todo/tool/response-envelope.ts +109 -109
- package/agent/extensions/todo/tool/types.ts +206 -206
- package/agent/extensions/todo/view/format.ts +177 -177
- package/agent/install.ps1 +637 -527
- package/agent/install.sh +620 -511
- package/agent/keybindings.json +7 -7
- package/bin/kiln.js +124 -11
- package/package.json +8 -2
- package/agent/extensions/taste/index.ts +0 -443
- package/agent/extensions/taste/install.ps1 +0 -23
- package/agent/extensions/taste/install.sh +0 -21
- /package/agent/extensions/{status line → statusline}/index.ts +0 -0
- /package/agent/extensions/{status line → statusline}/install.ps1 +0 -0
- /package/agent/extensions/{status line → statusline}/install.sh +0 -0
|
@@ -1,182 +1,182 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { test } from "node:test";
|
|
7
|
-
import { Value } from "typebox/value";
|
|
8
|
-
|
|
9
|
-
import initializeExtension from "../index.ts";
|
|
10
|
-
|
|
11
|
-
const indexUrl = new URL("../index.ts", import.meta.url).href;
|
|
12
|
-
const indexSrc = readFileSync(new URL("../index.ts", import.meta.url), "utf8");
|
|
13
|
-
const readmeSrc = readFileSync(new URL("../README.md", import.meta.url), "utf8");
|
|
14
|
-
|
|
15
|
-
function runRegistration(config) {
|
|
16
|
-
return runRegistrationWithConfig(JSON.stringify(config) + "\n");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function runRegistrationWithConfig(configText) {
|
|
20
|
-
const root = mkdtempSync(join(tmpdir(), "pi-web-access-tool-names-"));
|
|
21
|
-
writeFileSync(join(root, "web-search.json"), configText, "utf8");
|
|
22
|
-
return spawnSync(process.execPath, ["--input-type=module"], {
|
|
23
|
-
input: `
|
|
24
|
-
const { default: initializeExtension } = await import(${JSON.stringify(indexUrl)});
|
|
25
|
-
const tools = [];
|
|
26
|
-
const commands = [];
|
|
27
|
-
initializeExtension({
|
|
28
|
-
registerTool(tool) { tools.push({ name: tool.name, description: tool.description, promptSnippet: tool.promptSnippet, parameters: tool.parameters }); },
|
|
29
|
-
registerCommand(name) { commands.push(name); },
|
|
30
|
-
registerShortcut() {},
|
|
31
|
-
on() {},
|
|
32
|
-
});
|
|
33
|
-
console.log(JSON.stringify({ tools, commands }));
|
|
34
|
-
`,
|
|
35
|
-
encoding: "utf8",
|
|
36
|
-
env: { ...process.env, PI_CODING_AGENT_DIR: root, XDG_CONFIG_HOME: "", HOME: join(root, "home"), USERPROFILE: join(root, "home") },
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function registered(config) {
|
|
41
|
-
const child = runRegistration(config);
|
|
42
|
-
assert.equal(child.status, 0, child.stderr);
|
|
43
|
-
return JSON.parse(child.stdout);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function registeredToolNames(config) {
|
|
47
|
-
return registered(config).tools.map(tool => tool.name);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function registeredTool(config, name) {
|
|
51
|
-
return registered(config).tools.find(tool => tool.name === name);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function registeredCommandNames(config) {
|
|
55
|
-
return registered(config).commands;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function registrationError(config) {
|
|
59
|
-
const child = runRegistration(config);
|
|
60
|
-
assert.notEqual(child.status, 0, child.stdout);
|
|
61
|
-
return child.stderr;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
test("malformed config falls back during extension registration", () => {
|
|
65
|
-
const child = runRegistrationWithConfig("{");
|
|
66
|
-
assert.equal(child.status, 0, child.stderr);
|
|
67
|
-
const registered = JSON.parse(child.stdout);
|
|
68
|
-
assert.deepEqual(registered.tools.map(tool => tool.name), ["web_search", "source_check", "fetch_content", "get_search_content"]);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("search tools constrain numResults to integer values from 1 through 20", () => {
|
|
72
|
-
for (const name of ["web_search", "source_check"]) {
|
|
73
|
-
const schema = registeredTool({}, name).parameters.properties.numResults;
|
|
74
|
-
assert.equal(schema.type, "integer");
|
|
75
|
-
assert.equal(schema.minimum, 1);
|
|
76
|
-
assert.equal(schema.maximum, 20);
|
|
77
|
-
for (const value of [0, -1, 1.5, 21, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
78
|
-
assert.equal(Value.Check(schema, value), false, `${name} accepts ${value}`);
|
|
79
|
-
}
|
|
80
|
-
for (const value of [1, 5, 20]) {
|
|
81
|
-
assert.equal(Value.Check(schema, value), true, `${name} rejects ${value}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("tool registration gates support legacy and per-tool config", () => {
|
|
87
|
-
assert.deepEqual(registeredToolNames({ webSearch: { enabled: false } }), ["fetch_content", "get_search_content"]);
|
|
88
|
-
assert.deepEqual(registeredToolNames({
|
|
89
|
-
webSearch: { enabled: false },
|
|
90
|
-
tools: { webSearch: { enabled: true }, sourceCheck: { enabled: true }, fetchContent: { enabled: false } },
|
|
91
|
-
}), ["web_search", "source_check", "get_search_content"]);
|
|
92
|
-
assert.deepEqual(registeredToolNames({
|
|
93
|
-
tools: { sourceCheck: { enabled: false }, getSearchContent: { enabled: false } },
|
|
94
|
-
}), ["web_search", "fetch_content"]);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("command registration gates default to enabled", () => {
|
|
98
|
-
assert.deepEqual(registeredCommandNames({}), ["google-account", "search"]);
|
|
99
|
-
assert.deepEqual(registeredCommandNames({
|
|
100
|
-
commands: { websearch: { enabled: false }, search: { enabled: false } },
|
|
101
|
-
}), ["google-account"]);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("fetch_content schema exposes auth profile opt-in", () => {
|
|
105
|
-
const schema = registeredTool({}, "fetch_content").parameters.properties.auth;
|
|
106
|
-
assert.ok(schema);
|
|
107
|
-
assert.deepEqual(schema.anyOf.map(option => option.type), ["string", "boolean"]);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("registered tools do not advertise disabled get_search_content", () => {
|
|
111
|
-
const fetchTool = registeredTool({ tools: { getSearchContent: { enabled: false } } }, "fetch_content");
|
|
112
|
-
assert.ok(fetchTool);
|
|
113
|
-
assert.doesNotMatch(fetchTool.description, /get_search_content/);
|
|
114
|
-
assert.match(fetchTool.description, /retrieval tool is not registered/);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("web activity shortcut renders through the supported string-array API", async () => {
|
|
118
|
-
const shortcuts = [];
|
|
119
|
-
initializeExtension({
|
|
120
|
-
registerTool() {},
|
|
121
|
-
registerCommand() {},
|
|
122
|
-
registerShortcut(name, shortcut) { shortcuts.push({ name, shortcut }); },
|
|
123
|
-
on() {},
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const activityShortcut = shortcuts.find(({ shortcut }) => shortcut.description === "Toggle web search activity");
|
|
127
|
-
assert.ok(activityShortcut, "activity shortcut was not registered");
|
|
128
|
-
const widgets = [];
|
|
129
|
-
const ctx = {
|
|
130
|
-
ui: {
|
|
131
|
-
theme: { fg: (_color, text) => text },
|
|
132
|
-
setWidget(key, content) { widgets.push({ key, content }); },
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
await activityShortcut.shortcut.handler(ctx);
|
|
137
|
-
assert.equal(widgets[0].key, "web-activity");
|
|
138
|
-
assert.ok(Array.isArray(widgets[0].content), "activity widget content must be a string array");
|
|
139
|
-
assert.ok(widgets[0].content.length > 0);
|
|
140
|
-
|
|
141
|
-
await activityShortcut.shortcut.handler(ctx);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test("tool names can be configured without changing defaults", () => {
|
|
145
|
-
assert.deepEqual(registeredToolNames({}), ["web_search", "source_check", "fetch_content", "get_search_content"]);
|
|
146
|
-
assert.deepEqual(registeredToolNames({
|
|
147
|
-
toolNames: {
|
|
148
|
-
webSearch: "research_web",
|
|
149
|
-
sourceCheck: "verify_sources",
|
|
150
|
-
fetchContent: "grab_content",
|
|
151
|
-
getSearchContent: "open_content",
|
|
152
|
-
},
|
|
153
|
-
}), ["research_web", "verify_sources", "grab_content", "open_content"]);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("tool name config rejects invalid and duplicate registered names", () => {
|
|
157
|
-
assert.match(registrationError({ toolNames: { webSearch: "1bad" } }), /toolNames\.webSearch/);
|
|
158
|
-
assert.match(registrationError({ toolNames: { webSearch: "same_name", fetchContent: "same_name" } }), /duplicates/);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("webSearch.enabled false registers only fetch tools and ignores disabled-name duplicates", () => {
|
|
162
|
-
assert.deepEqual(registeredToolNames({
|
|
163
|
-
webSearch: { enabled: false },
|
|
164
|
-
toolNames: {
|
|
165
|
-
webSearch: "content_only",
|
|
166
|
-
sourceCheck: "content_only",
|
|
167
|
-
fetchContent: "grab_content",
|
|
168
|
-
getSearchContent: "open_content",
|
|
169
|
-
},
|
|
170
|
-
}), ["grab_content", "open_content"]);
|
|
171
|
-
assert.match(registrationError({
|
|
172
|
-
webSearch: { enabled: false },
|
|
173
|
-
toolNames: { fetchContent: "same_name", getSearchContent: "same_name" },
|
|
174
|
-
}), /duplicates/);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("README documents registration gates and toolNames", () => {
|
|
178
|
-
assert.match(readmeSrc, /"tools": \{/);
|
|
179
|
-
assert.match(readmeSrc, /"commands": \{/);
|
|
180
|
-
assert.match(readmeSrc, /Pi restart is required for tool and command registration changes/);
|
|
181
|
-
assert.match(readmeSrc, /`toolNames` can opt into alternate public tool names/);
|
|
182
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { test } from "node:test";
|
|
7
|
+
import { Value } from "typebox/value";
|
|
8
|
+
|
|
9
|
+
import initializeExtension from "../index.ts";
|
|
10
|
+
|
|
11
|
+
const indexUrl = new URL("../index.ts", import.meta.url).href;
|
|
12
|
+
const indexSrc = readFileSync(new URL("../index.ts", import.meta.url), "utf8");
|
|
13
|
+
const readmeSrc = readFileSync(new URL("../README.md", import.meta.url), "utf8");
|
|
14
|
+
|
|
15
|
+
function runRegistration(config) {
|
|
16
|
+
return runRegistrationWithConfig(JSON.stringify(config) + "\n");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function runRegistrationWithConfig(configText) {
|
|
20
|
+
const root = mkdtempSync(join(tmpdir(), "pi-web-access-tool-names-"));
|
|
21
|
+
writeFileSync(join(root, "web-search.json"), configText, "utf8");
|
|
22
|
+
return spawnSync(process.execPath, ["--input-type=module"], {
|
|
23
|
+
input: `
|
|
24
|
+
const { default: initializeExtension } = await import(${JSON.stringify(indexUrl)});
|
|
25
|
+
const tools = [];
|
|
26
|
+
const commands = [];
|
|
27
|
+
initializeExtension({
|
|
28
|
+
registerTool(tool) { tools.push({ name: tool.name, description: tool.description, promptSnippet: tool.promptSnippet, parameters: tool.parameters }); },
|
|
29
|
+
registerCommand(name) { commands.push(name); },
|
|
30
|
+
registerShortcut() {},
|
|
31
|
+
on() {},
|
|
32
|
+
});
|
|
33
|
+
console.log(JSON.stringify({ tools, commands }));
|
|
34
|
+
`,
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
env: { ...process.env, PI_CODING_AGENT_DIR: root, XDG_CONFIG_HOME: "", HOME: join(root, "home"), USERPROFILE: join(root, "home") },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function registered(config) {
|
|
41
|
+
const child = runRegistration(config);
|
|
42
|
+
assert.equal(child.status, 0, child.stderr);
|
|
43
|
+
return JSON.parse(child.stdout);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function registeredToolNames(config) {
|
|
47
|
+
return registered(config).tools.map(tool => tool.name);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function registeredTool(config, name) {
|
|
51
|
+
return registered(config).tools.find(tool => tool.name === name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function registeredCommandNames(config) {
|
|
55
|
+
return registered(config).commands;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function registrationError(config) {
|
|
59
|
+
const child = runRegistration(config);
|
|
60
|
+
assert.notEqual(child.status, 0, child.stdout);
|
|
61
|
+
return child.stderr;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
test("malformed config falls back during extension registration", () => {
|
|
65
|
+
const child = runRegistrationWithConfig("{");
|
|
66
|
+
assert.equal(child.status, 0, child.stderr);
|
|
67
|
+
const registered = JSON.parse(child.stdout);
|
|
68
|
+
assert.deepEqual(registered.tools.map(tool => tool.name), ["web_search", "source_check", "fetch_content", "get_search_content"]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("search tools constrain numResults to integer values from 1 through 20", () => {
|
|
72
|
+
for (const name of ["web_search", "source_check"]) {
|
|
73
|
+
const schema = registeredTool({}, name).parameters.properties.numResults;
|
|
74
|
+
assert.equal(schema.type, "integer");
|
|
75
|
+
assert.equal(schema.minimum, 1);
|
|
76
|
+
assert.equal(schema.maximum, 20);
|
|
77
|
+
for (const value of [0, -1, 1.5, 21, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
78
|
+
assert.equal(Value.Check(schema, value), false, `${name} accepts ${value}`);
|
|
79
|
+
}
|
|
80
|
+
for (const value of [1, 5, 20]) {
|
|
81
|
+
assert.equal(Value.Check(schema, value), true, `${name} rejects ${value}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("tool registration gates support legacy and per-tool config", () => {
|
|
87
|
+
assert.deepEqual(registeredToolNames({ webSearch: { enabled: false } }), ["fetch_content", "get_search_content"]);
|
|
88
|
+
assert.deepEqual(registeredToolNames({
|
|
89
|
+
webSearch: { enabled: false },
|
|
90
|
+
tools: { webSearch: { enabled: true }, sourceCheck: { enabled: true }, fetchContent: { enabled: false } },
|
|
91
|
+
}), ["web_search", "source_check", "get_search_content"]);
|
|
92
|
+
assert.deepEqual(registeredToolNames({
|
|
93
|
+
tools: { sourceCheck: { enabled: false }, getSearchContent: { enabled: false } },
|
|
94
|
+
}), ["web_search", "fetch_content"]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("command registration gates default to enabled", () => {
|
|
98
|
+
assert.deepEqual(registeredCommandNames({}), ["google-account", "search"]);
|
|
99
|
+
assert.deepEqual(registeredCommandNames({
|
|
100
|
+
commands: { websearch: { enabled: false }, search: { enabled: false } },
|
|
101
|
+
}), ["google-account"]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("fetch_content schema exposes auth profile opt-in", () => {
|
|
105
|
+
const schema = registeredTool({}, "fetch_content").parameters.properties.auth;
|
|
106
|
+
assert.ok(schema);
|
|
107
|
+
assert.deepEqual(schema.anyOf.map(option => option.type), ["string", "boolean"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("registered tools do not advertise disabled get_search_content", () => {
|
|
111
|
+
const fetchTool = registeredTool({ tools: { getSearchContent: { enabled: false } } }, "fetch_content");
|
|
112
|
+
assert.ok(fetchTool);
|
|
113
|
+
assert.doesNotMatch(fetchTool.description, /get_search_content/);
|
|
114
|
+
assert.match(fetchTool.description, /retrieval tool is not registered/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("web activity shortcut renders through the supported string-array API", async () => {
|
|
118
|
+
const shortcuts = [];
|
|
119
|
+
initializeExtension({
|
|
120
|
+
registerTool() {},
|
|
121
|
+
registerCommand() {},
|
|
122
|
+
registerShortcut(name, shortcut) { shortcuts.push({ name, shortcut }); },
|
|
123
|
+
on() {},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const activityShortcut = shortcuts.find(({ shortcut }) => shortcut.description === "Toggle web search activity");
|
|
127
|
+
assert.ok(activityShortcut, "activity shortcut was not registered");
|
|
128
|
+
const widgets = [];
|
|
129
|
+
const ctx = {
|
|
130
|
+
ui: {
|
|
131
|
+
theme: { fg: (_color, text) => text },
|
|
132
|
+
setWidget(key, content) { widgets.push({ key, content }); },
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
await activityShortcut.shortcut.handler(ctx);
|
|
137
|
+
assert.equal(widgets[0].key, "web-activity");
|
|
138
|
+
assert.ok(Array.isArray(widgets[0].content), "activity widget content must be a string array");
|
|
139
|
+
assert.ok(widgets[0].content.length > 0);
|
|
140
|
+
|
|
141
|
+
await activityShortcut.shortcut.handler(ctx);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("tool names can be configured without changing defaults", () => {
|
|
145
|
+
assert.deepEqual(registeredToolNames({}), ["web_search", "source_check", "fetch_content", "get_search_content"]);
|
|
146
|
+
assert.deepEqual(registeredToolNames({
|
|
147
|
+
toolNames: {
|
|
148
|
+
webSearch: "research_web",
|
|
149
|
+
sourceCheck: "verify_sources",
|
|
150
|
+
fetchContent: "grab_content",
|
|
151
|
+
getSearchContent: "open_content",
|
|
152
|
+
},
|
|
153
|
+
}), ["research_web", "verify_sources", "grab_content", "open_content"]);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("tool name config rejects invalid and duplicate registered names", () => {
|
|
157
|
+
assert.match(registrationError({ toolNames: { webSearch: "1bad" } }), /toolNames\.webSearch/);
|
|
158
|
+
assert.match(registrationError({ toolNames: { webSearch: "same_name", fetchContent: "same_name" } }), /duplicates/);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("webSearch.enabled false registers only fetch tools and ignores disabled-name duplicates", () => {
|
|
162
|
+
assert.deepEqual(registeredToolNames({
|
|
163
|
+
webSearch: { enabled: false },
|
|
164
|
+
toolNames: {
|
|
165
|
+
webSearch: "content_only",
|
|
166
|
+
sourceCheck: "content_only",
|
|
167
|
+
fetchContent: "grab_content",
|
|
168
|
+
getSearchContent: "open_content",
|
|
169
|
+
},
|
|
170
|
+
}), ["grab_content", "open_content"]);
|
|
171
|
+
assert.match(registrationError({
|
|
172
|
+
webSearch: { enabled: false },
|
|
173
|
+
toolNames: { fetchContent: "same_name", getSearchContent: "same_name" },
|
|
174
|
+
}), /duplicates/);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("README documents registration gates and toolNames", () => {
|
|
178
|
+
assert.match(readmeSrc, /"tools": \{/);
|
|
179
|
+
assert.match(readmeSrc, /"commands": \{/);
|
|
180
|
+
assert.match(readmeSrc, /Pi restart is required for tool and command registration changes/);
|
|
181
|
+
assert.match(readmeSrc, /`toolNames` can opt into alternate public tool names/);
|
|
182
|
+
});
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { mkdtemp } from "node:fs/promises";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { test } from "node:test";
|
|
7
|
-
|
|
8
|
-
const extractorUrl = new URL("../extract.ts", import.meta.url).href;
|
|
9
|
-
|
|
10
|
-
test("YouTube extraction surfaces Gemini API errors", async () => {
|
|
11
|
-
const home = await mkdtemp(join(tmpdir(), "pi-web-access-youtube-errors-"));
|
|
12
|
-
const env = {
|
|
13
|
-
...process.env,
|
|
14
|
-
HOME: home,
|
|
15
|
-
USERPROFILE: home,
|
|
16
|
-
GEMINI_API_KEY: "test-gemini-key",
|
|
17
|
-
PERPLEXITY_API_KEY: "",
|
|
18
|
-
};
|
|
19
|
-
delete env.PI_ALLOW_BROWSER_COOKIES;
|
|
20
|
-
delete env.FEYNMAN_ALLOW_BROWSER_COOKIES;
|
|
21
|
-
|
|
22
|
-
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
23
|
-
input: buildChildScript(extractorUrl),
|
|
24
|
-
encoding: "utf8",
|
|
25
|
-
env,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
assert.equal(child.status, 0, child.stderr || child.stdout);
|
|
29
|
-
const result = JSON.parse(child.stdout);
|
|
30
|
-
assert.match(result.error, /Gemini API error 503/);
|
|
31
|
-
assert.doesNotMatch(result.error, /Sign into Google in a supported Chromium browser/);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
function buildChildScript(moduleUrl) {
|
|
35
|
-
return `
|
|
36
|
-
process.on("uncaughtException", (error) => {
|
|
37
|
-
console.error(error?.stack || error);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
});
|
|
40
|
-
process.on("unhandledRejection", (error) => {
|
|
41
|
-
console.error(error?.stack || error);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
globalThis.fetch = async (input) => {
|
|
46
|
-
const url = String(input);
|
|
47
|
-
if (url.startsWith("https://generativelanguage.googleapis.com/")) {
|
|
48
|
-
return new Response(JSON.stringify({ error: { message: "model overloaded" } }), {
|
|
49
|
-
status: 503,
|
|
50
|
-
statusText: "Service Unavailable",
|
|
51
|
-
headers: { "content-type": "application/json" },
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
throw new Error("Unexpected fetch: " + url);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const { extractContent } = await import(${JSON.stringify(moduleUrl)});
|
|
58
|
-
// Inject DNS so SSRF validation never depends on real resolution (which a
|
|
59
|
-
// local fake-IP/TUN proxy would map into a blocked reserved range).
|
|
60
|
-
const lookup = async () => [{ address: "93.184.216.34", family: 4 }];
|
|
61
|
-
const result = await extractContent("https://www.youtube.com/watch?v=dQw4w9WgXcQ", undefined, { lookup });
|
|
62
|
-
console.log(JSON.stringify(result));
|
|
63
|
-
`;
|
|
64
|
-
}
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { mkdtemp } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { test } from "node:test";
|
|
7
|
+
|
|
8
|
+
const extractorUrl = new URL("../extract.ts", import.meta.url).href;
|
|
9
|
+
|
|
10
|
+
test("YouTube extraction surfaces Gemini API errors", async () => {
|
|
11
|
+
const home = await mkdtemp(join(tmpdir(), "pi-web-access-youtube-errors-"));
|
|
12
|
+
const env = {
|
|
13
|
+
...process.env,
|
|
14
|
+
HOME: home,
|
|
15
|
+
USERPROFILE: home,
|
|
16
|
+
GEMINI_API_KEY: "test-gemini-key",
|
|
17
|
+
PERPLEXITY_API_KEY: "",
|
|
18
|
+
};
|
|
19
|
+
delete env.PI_ALLOW_BROWSER_COOKIES;
|
|
20
|
+
delete env.FEYNMAN_ALLOW_BROWSER_COOKIES;
|
|
21
|
+
|
|
22
|
+
const child = spawnSync(process.execPath, ["--input-type=module"], {
|
|
23
|
+
input: buildChildScript(extractorUrl),
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
env,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
assert.equal(child.status, 0, child.stderr || child.stdout);
|
|
29
|
+
const result = JSON.parse(child.stdout);
|
|
30
|
+
assert.match(result.error, /Gemini API error 503/);
|
|
31
|
+
assert.doesNotMatch(result.error, /Sign into Google in a supported Chromium browser/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function buildChildScript(moduleUrl) {
|
|
35
|
+
return `
|
|
36
|
+
process.on("uncaughtException", (error) => {
|
|
37
|
+
console.error(error?.stack || error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
40
|
+
process.on("unhandledRejection", (error) => {
|
|
41
|
+
console.error(error?.stack || error);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
globalThis.fetch = async (input) => {
|
|
46
|
+
const url = String(input);
|
|
47
|
+
if (url.startsWith("https://generativelanguage.googleapis.com/")) {
|
|
48
|
+
return new Response(JSON.stringify({ error: { message: "model overloaded" } }), {
|
|
49
|
+
status: 503,
|
|
50
|
+
statusText: "Service Unavailable",
|
|
51
|
+
headers: { "content-type": "application/json" },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
throw new Error("Unexpected fetch: " + url);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const { extractContent } = await import(${JSON.stringify(moduleUrl)});
|
|
58
|
+
// Inject DNS so SSRF validation never depends on real resolution (which a
|
|
59
|
+
// local fake-IP/TUN proxy would map into a blocked reserved range).
|
|
60
|
+
const lookup = async () => [{ address: "93.184.216.34", family: 4 }];
|
|
61
|
+
const result = await extractContent("https://www.youtube.com/watch?v=dQw4w9WgXcQ", undefined, { lookup });
|
|
62
|
+
console.log(JSON.stringify(result));
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"allowImportingTsExtensions": true,
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"noEmit": true,
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
"target": "ES2022"
|
|
9
|
-
},
|
|
10
|
-
"include": ["*.ts"]
|
|
11
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowImportingTsExtensions": true,
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"noEmit": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"target": "ES2022"
|
|
9
|
+
},
|
|
10
|
+
"include": ["*.ts"]
|
|
11
|
+
}
|