@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,179 +1,179 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { test } from "node:test";
|
|
3
|
-
|
|
4
|
-
import initializeExtension from "../index.ts";
|
|
5
|
-
import {
|
|
6
|
-
assessClaim,
|
|
7
|
-
buildResearchArtifact,
|
|
8
|
-
buildPassages,
|
|
9
|
-
getResearchArtifact,
|
|
10
|
-
hashContent,
|
|
11
|
-
storeResearchArtifact,
|
|
12
|
-
} from "../source-check.ts";
|
|
13
|
-
import { clearResults } from "../storage.ts";
|
|
14
|
-
|
|
15
|
-
const result = (url, snippet, rank = 1) => ({ url, title: "Example", snippet, rank });
|
|
16
|
-
|
|
17
|
-
test("source-check creates a real SHA-256 hash and exact whitespace offsets", () => {
|
|
18
|
-
const content = "Intro.\n\nThe API\t supports streaming responses.\nTail.";
|
|
19
|
-
const passages = buildPassages(
|
|
20
|
-
[{ rank: 1, url: "https://docs.example.com/api", title: "API", snippet: "API supports streaming responses.", quality: "official_docs" }],
|
|
21
|
-
[{ url: "https://docs.example.com/api", title: "API", content, error: null }],
|
|
22
|
-
);
|
|
23
|
-
const pagePassage = passages.find((passage) => passage.extraction_span);
|
|
24
|
-
assert.ok(pagePassage);
|
|
25
|
-
assert.equal(pagePassage.text, "The API\t supports streaming responses.");
|
|
26
|
-
assert.equal(content.slice(pagePassage.extraction_span.start, pagePassage.extraction_span.end), pagePassage.text);
|
|
27
|
-
assert.equal(hashContent("abc"), "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("fetched content supplies exact passages when the provider snippet is empty", () => {
|
|
31
|
-
const content = "The API supports streaming responses. Other details follow.";
|
|
32
|
-
const artifact = buildResearchArtifact({
|
|
33
|
-
query: "API supports streaming responses",
|
|
34
|
-
results: [result("https://docs.example.com/api", "")],
|
|
35
|
-
fetched: [{ url: "https://docs.example.com/api", title: "API", content, error: null }],
|
|
36
|
-
});
|
|
37
|
-
assert.deepEqual(artifact.passages.map((passage) => passage.text), ["The API supports streaming responses."]);
|
|
38
|
-
assert.deepEqual(artifact.passages[0].extraction_span, { start: 0, end: 37 });
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("artifact assembly handles omitted domain filters and failed fetches", () => {
|
|
42
|
-
const artifact = buildResearchArtifact({
|
|
43
|
-
query: "API claim",
|
|
44
|
-
results: [result("https://example.com/a", "The API is confirmed.")],
|
|
45
|
-
fetched: [{ url: "https://example.com/a", title: "Example", content: "", error: "blocked" }],
|
|
46
|
-
});
|
|
47
|
-
assert.equal(artifact.filters.domain_include.length, 0);
|
|
48
|
-
assert.equal(artifact.sources[0].fetched, false);
|
|
49
|
-
assert.equal(artifact.sources[0].fetch_error, "blocked");
|
|
50
|
-
assert.equal(artifact.sources[0].content_hash, undefined);
|
|
51
|
-
assert.equal(typeof artifact.sources[0].fetch_timestamp, "number");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("claim assessment references passage IDs and stores a non-empty artifact ID", () => {
|
|
55
|
-
clearResults();
|
|
56
|
-
const artifact = buildResearchArtifact({
|
|
57
|
-
query: "API claim",
|
|
58
|
-
results: [result("https://example.com/a", "According to the API documentation, the API is confirmed.")],
|
|
59
|
-
});
|
|
60
|
-
const assessed = { ...artifact, claims: [assessClaim("API documentation is confirmed", artifact.passages)] };
|
|
61
|
-
storeResearchArtifact(assessed);
|
|
62
|
-
assert.ok(assessed.id);
|
|
63
|
-
assert.deepEqual(getResearchArtifact(assessed.id), assessed);
|
|
64
|
-
assert.deepEqual(assessed.claims[0].supporting_passages, ["p-1-0"]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("claim assessment ignores polarity substrings, negated markers, and discourse words", () => {
|
|
68
|
-
const claim = "API supports streaming responses";
|
|
69
|
-
const passage = (passage_id, text) => ({ passage_id, source_url: "https://example.com/api", source_rank: 1, text });
|
|
70
|
-
for (const [passage_id, text] of [
|
|
71
|
-
["p-yesterday", "Yesterday, the API documentation discussed streaming responses."],
|
|
72
|
-
["p-unverified", "The API is unverified; documentation discusses streaming responses."],
|
|
73
|
-
["p-however", "However, the API supports streaming responses."],
|
|
74
|
-
]) {
|
|
75
|
-
const assessment = assessClaim(claim, [passage(passage_id, text)]);
|
|
76
|
-
assert.equal(assessment.status, "unclear", text);
|
|
77
|
-
assert.deepEqual(assessment.supporting_passages, [], text);
|
|
78
|
-
assert.deepEqual(assessment.contradicting_passages, [], text);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
function registerSourceCheck() {
|
|
83
|
-
const tools = [];
|
|
84
|
-
const entries = [];
|
|
85
|
-
initializeExtension({
|
|
86
|
-
registerTool(tool) { tools.push(tool); },
|
|
87
|
-
registerCommand() {},
|
|
88
|
-
registerShortcut() {},
|
|
89
|
-
on() {},
|
|
90
|
-
appendEntry(type, data) { entries.push({ type, data }); },
|
|
91
|
-
});
|
|
92
|
-
return { tool: tools.find((candidate) => candidate.name === "source_check"), entries };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
test("source_check executes a successful Exa provider response with runtime context", async () => {
|
|
96
|
-
const previousFetch = globalThis.fetch;
|
|
97
|
-
const previousKey = process.env.EXA_API_KEY;
|
|
98
|
-
process.env.EXA_API_KEY = "source-check-test-key";
|
|
99
|
-
globalThis.fetch = async (url) => {
|
|
100
|
-
assert.equal(String(url), "https://api.exa.ai/answer");
|
|
101
|
-
return new Response(JSON.stringify({
|
|
102
|
-
answer: "The API supports streaming responses.",
|
|
103
|
-
citations: [{ title: "API docs", url: "https://docs.example.com/api" }],
|
|
104
|
-
}), { status: 200 });
|
|
105
|
-
};
|
|
106
|
-
try {
|
|
107
|
-
const { tool, entries } = registerSourceCheck();
|
|
108
|
-
const response = await tool.execute("call", { claim: "API supports streaming responses" }, undefined, undefined, { modelRegistry: {} });
|
|
109
|
-
assert.equal(response.details.sourceCount, 1);
|
|
110
|
-
assert.equal(response.details.passageCount, 0);
|
|
111
|
-
assert.equal(entries[0].type, "web-search-results");
|
|
112
|
-
} finally {
|
|
113
|
-
globalThis.fetch = previousFetch;
|
|
114
|
-
if (previousKey === undefined) delete process.env.EXA_API_KEY;
|
|
115
|
-
else process.env.EXA_API_KEY = previousKey;
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("source_check stops on cancellation instead of continuing queries", async () => {
|
|
120
|
-
const previousFetch = globalThis.fetch;
|
|
121
|
-
const previousKey = process.env.EXA_API_KEY;
|
|
122
|
-
let calls = 0;
|
|
123
|
-
const controller = new AbortController();
|
|
124
|
-
process.env.EXA_API_KEY = "source-check-test-key";
|
|
125
|
-
globalThis.fetch = async () => {
|
|
126
|
-
calls++;
|
|
127
|
-
controller.abort();
|
|
128
|
-
throw new Error("AbortError: canceled");
|
|
129
|
-
};
|
|
130
|
-
try {
|
|
131
|
-
const { tool } = registerSourceCheck();
|
|
132
|
-
const response = await tool.execute("call", { claim: "cancel this", queries: ["first", "second"] }, controller.signal, undefined, { modelRegistry: {} });
|
|
133
|
-
assert.equal(calls, 1);
|
|
134
|
-
assert.equal(response.details.sourceCount, 0);
|
|
135
|
-
} finally {
|
|
136
|
-
globalThis.fetch = previousFetch;
|
|
137
|
-
if (previousKey === undefined) delete process.env.EXA_API_KEY;
|
|
138
|
-
else process.env.EXA_API_KEY = previousKey;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("source_check retains a rejected page fetch in the artifact", async () => {
|
|
143
|
-
const previousFetch = globalThis.fetch;
|
|
144
|
-
const previousKey = process.env.EXA_API_KEY;
|
|
145
|
-
process.env.EXA_API_KEY = "source-check-test-key";
|
|
146
|
-
globalThis.fetch = async (url) => {
|
|
147
|
-
if (String(url) === "https://api.exa.ai/answer") {
|
|
148
|
-
return new Response(JSON.stringify({
|
|
149
|
-
answer: "The API docs are available.",
|
|
150
|
-
citations: [{ title: "API docs", url: "https://example.com/api" }],
|
|
151
|
-
}), { status: 200 });
|
|
152
|
-
}
|
|
153
|
-
throw new Error("fetch rejected");
|
|
154
|
-
};
|
|
155
|
-
try {
|
|
156
|
-
const { tool } = registerSourceCheck();
|
|
157
|
-
const response = await tool.execute("call", { claim: "API docs", fetchContent: true }, undefined, undefined, { modelRegistry: {} });
|
|
158
|
-
assert.equal(response.details.sourceCount, 1);
|
|
159
|
-
assert.match(response.details.artifact.sources[0].fetch_error, /^fetch rejected/);
|
|
160
|
-
} finally {
|
|
161
|
-
globalThis.fetch = previousFetch;
|
|
162
|
-
if (previousKey === undefined) delete process.env.EXA_API_KEY;
|
|
163
|
-
else process.env.EXA_API_KEY = previousKey;
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("registered source_check validates the claim at runtime", async () => {
|
|
168
|
-
const tools = [];
|
|
169
|
-
initializeExtension({
|
|
170
|
-
registerTool(tool) { tools.push(tool); },
|
|
171
|
-
registerCommand() {},
|
|
172
|
-
registerShortcut() {},
|
|
173
|
-
on() {},
|
|
174
|
-
});
|
|
175
|
-
const tool = tools.find((candidate) => candidate.name === "source_check");
|
|
176
|
-
assert.ok(tool);
|
|
177
|
-
const response = await tool.execute("call", { claim: " " });
|
|
178
|
-
assert.equal(response.details.error, "Missing claim");
|
|
179
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import initializeExtension from "../index.ts";
|
|
5
|
+
import {
|
|
6
|
+
assessClaim,
|
|
7
|
+
buildResearchArtifact,
|
|
8
|
+
buildPassages,
|
|
9
|
+
getResearchArtifact,
|
|
10
|
+
hashContent,
|
|
11
|
+
storeResearchArtifact,
|
|
12
|
+
} from "../source-check.ts";
|
|
13
|
+
import { clearResults } from "../storage.ts";
|
|
14
|
+
|
|
15
|
+
const result = (url, snippet, rank = 1) => ({ url, title: "Example", snippet, rank });
|
|
16
|
+
|
|
17
|
+
test("source-check creates a real SHA-256 hash and exact whitespace offsets", () => {
|
|
18
|
+
const content = "Intro.\n\nThe API\t supports streaming responses.\nTail.";
|
|
19
|
+
const passages = buildPassages(
|
|
20
|
+
[{ rank: 1, url: "https://docs.example.com/api", title: "API", snippet: "API supports streaming responses.", quality: "official_docs" }],
|
|
21
|
+
[{ url: "https://docs.example.com/api", title: "API", content, error: null }],
|
|
22
|
+
);
|
|
23
|
+
const pagePassage = passages.find((passage) => passage.extraction_span);
|
|
24
|
+
assert.ok(pagePassage);
|
|
25
|
+
assert.equal(pagePassage.text, "The API\t supports streaming responses.");
|
|
26
|
+
assert.equal(content.slice(pagePassage.extraction_span.start, pagePassage.extraction_span.end), pagePassage.text);
|
|
27
|
+
assert.equal(hashContent("abc"), "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("fetched content supplies exact passages when the provider snippet is empty", () => {
|
|
31
|
+
const content = "The API supports streaming responses. Other details follow.";
|
|
32
|
+
const artifact = buildResearchArtifact({
|
|
33
|
+
query: "API supports streaming responses",
|
|
34
|
+
results: [result("https://docs.example.com/api", "")],
|
|
35
|
+
fetched: [{ url: "https://docs.example.com/api", title: "API", content, error: null }],
|
|
36
|
+
});
|
|
37
|
+
assert.deepEqual(artifact.passages.map((passage) => passage.text), ["The API supports streaming responses."]);
|
|
38
|
+
assert.deepEqual(artifact.passages[0].extraction_span, { start: 0, end: 37 });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("artifact assembly handles omitted domain filters and failed fetches", () => {
|
|
42
|
+
const artifact = buildResearchArtifact({
|
|
43
|
+
query: "API claim",
|
|
44
|
+
results: [result("https://example.com/a", "The API is confirmed.")],
|
|
45
|
+
fetched: [{ url: "https://example.com/a", title: "Example", content: "", error: "blocked" }],
|
|
46
|
+
});
|
|
47
|
+
assert.equal(artifact.filters.domain_include.length, 0);
|
|
48
|
+
assert.equal(artifact.sources[0].fetched, false);
|
|
49
|
+
assert.equal(artifact.sources[0].fetch_error, "blocked");
|
|
50
|
+
assert.equal(artifact.sources[0].content_hash, undefined);
|
|
51
|
+
assert.equal(typeof artifact.sources[0].fetch_timestamp, "number");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("claim assessment references passage IDs and stores a non-empty artifact ID", () => {
|
|
55
|
+
clearResults();
|
|
56
|
+
const artifact = buildResearchArtifact({
|
|
57
|
+
query: "API claim",
|
|
58
|
+
results: [result("https://example.com/a", "According to the API documentation, the API is confirmed.")],
|
|
59
|
+
});
|
|
60
|
+
const assessed = { ...artifact, claims: [assessClaim("API documentation is confirmed", artifact.passages)] };
|
|
61
|
+
storeResearchArtifact(assessed);
|
|
62
|
+
assert.ok(assessed.id);
|
|
63
|
+
assert.deepEqual(getResearchArtifact(assessed.id), assessed);
|
|
64
|
+
assert.deepEqual(assessed.claims[0].supporting_passages, ["p-1-0"]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("claim assessment ignores polarity substrings, negated markers, and discourse words", () => {
|
|
68
|
+
const claim = "API supports streaming responses";
|
|
69
|
+
const passage = (passage_id, text) => ({ passage_id, source_url: "https://example.com/api", source_rank: 1, text });
|
|
70
|
+
for (const [passage_id, text] of [
|
|
71
|
+
["p-yesterday", "Yesterday, the API documentation discussed streaming responses."],
|
|
72
|
+
["p-unverified", "The API is unverified; documentation discusses streaming responses."],
|
|
73
|
+
["p-however", "However, the API supports streaming responses."],
|
|
74
|
+
]) {
|
|
75
|
+
const assessment = assessClaim(claim, [passage(passage_id, text)]);
|
|
76
|
+
assert.equal(assessment.status, "unclear", text);
|
|
77
|
+
assert.deepEqual(assessment.supporting_passages, [], text);
|
|
78
|
+
assert.deepEqual(assessment.contradicting_passages, [], text);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function registerSourceCheck() {
|
|
83
|
+
const tools = [];
|
|
84
|
+
const entries = [];
|
|
85
|
+
initializeExtension({
|
|
86
|
+
registerTool(tool) { tools.push(tool); },
|
|
87
|
+
registerCommand() {},
|
|
88
|
+
registerShortcut() {},
|
|
89
|
+
on() {},
|
|
90
|
+
appendEntry(type, data) { entries.push({ type, data }); },
|
|
91
|
+
});
|
|
92
|
+
return { tool: tools.find((candidate) => candidate.name === "source_check"), entries };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
test("source_check executes a successful Exa provider response with runtime context", async () => {
|
|
96
|
+
const previousFetch = globalThis.fetch;
|
|
97
|
+
const previousKey = process.env.EXA_API_KEY;
|
|
98
|
+
process.env.EXA_API_KEY = "source-check-test-key";
|
|
99
|
+
globalThis.fetch = async (url) => {
|
|
100
|
+
assert.equal(String(url), "https://api.exa.ai/answer");
|
|
101
|
+
return new Response(JSON.stringify({
|
|
102
|
+
answer: "The API supports streaming responses.",
|
|
103
|
+
citations: [{ title: "API docs", url: "https://docs.example.com/api" }],
|
|
104
|
+
}), { status: 200 });
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
const { tool, entries } = registerSourceCheck();
|
|
108
|
+
const response = await tool.execute("call", { claim: "API supports streaming responses" }, undefined, undefined, { modelRegistry: {} });
|
|
109
|
+
assert.equal(response.details.sourceCount, 1);
|
|
110
|
+
assert.equal(response.details.passageCount, 0);
|
|
111
|
+
assert.equal(entries[0].type, "web-search-results");
|
|
112
|
+
} finally {
|
|
113
|
+
globalThis.fetch = previousFetch;
|
|
114
|
+
if (previousKey === undefined) delete process.env.EXA_API_KEY;
|
|
115
|
+
else process.env.EXA_API_KEY = previousKey;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("source_check stops on cancellation instead of continuing queries", async () => {
|
|
120
|
+
const previousFetch = globalThis.fetch;
|
|
121
|
+
const previousKey = process.env.EXA_API_KEY;
|
|
122
|
+
let calls = 0;
|
|
123
|
+
const controller = new AbortController();
|
|
124
|
+
process.env.EXA_API_KEY = "source-check-test-key";
|
|
125
|
+
globalThis.fetch = async () => {
|
|
126
|
+
calls++;
|
|
127
|
+
controller.abort();
|
|
128
|
+
throw new Error("AbortError: canceled");
|
|
129
|
+
};
|
|
130
|
+
try {
|
|
131
|
+
const { tool } = registerSourceCheck();
|
|
132
|
+
const response = await tool.execute("call", { claim: "cancel this", queries: ["first", "second"] }, controller.signal, undefined, { modelRegistry: {} });
|
|
133
|
+
assert.equal(calls, 1);
|
|
134
|
+
assert.equal(response.details.sourceCount, 0);
|
|
135
|
+
} finally {
|
|
136
|
+
globalThis.fetch = previousFetch;
|
|
137
|
+
if (previousKey === undefined) delete process.env.EXA_API_KEY;
|
|
138
|
+
else process.env.EXA_API_KEY = previousKey;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("source_check retains a rejected page fetch in the artifact", async () => {
|
|
143
|
+
const previousFetch = globalThis.fetch;
|
|
144
|
+
const previousKey = process.env.EXA_API_KEY;
|
|
145
|
+
process.env.EXA_API_KEY = "source-check-test-key";
|
|
146
|
+
globalThis.fetch = async (url) => {
|
|
147
|
+
if (String(url) === "https://api.exa.ai/answer") {
|
|
148
|
+
return new Response(JSON.stringify({
|
|
149
|
+
answer: "The API docs are available.",
|
|
150
|
+
citations: [{ title: "API docs", url: "https://example.com/api" }],
|
|
151
|
+
}), { status: 200 });
|
|
152
|
+
}
|
|
153
|
+
throw new Error("fetch rejected");
|
|
154
|
+
};
|
|
155
|
+
try {
|
|
156
|
+
const { tool } = registerSourceCheck();
|
|
157
|
+
const response = await tool.execute("call", { claim: "API docs", fetchContent: true }, undefined, undefined, { modelRegistry: {} });
|
|
158
|
+
assert.equal(response.details.sourceCount, 1);
|
|
159
|
+
assert.match(response.details.artifact.sources[0].fetch_error, /^fetch rejected/);
|
|
160
|
+
} finally {
|
|
161
|
+
globalThis.fetch = previousFetch;
|
|
162
|
+
if (previousKey === undefined) delete process.env.EXA_API_KEY;
|
|
163
|
+
else process.env.EXA_API_KEY = previousKey;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("registered source_check validates the claim at runtime", async () => {
|
|
168
|
+
const tools = [];
|
|
169
|
+
initializeExtension({
|
|
170
|
+
registerTool(tool) { tools.push(tool); },
|
|
171
|
+
registerCommand() {},
|
|
172
|
+
registerShortcut() {},
|
|
173
|
+
on() {},
|
|
174
|
+
});
|
|
175
|
+
const tool = tools.find((candidate) => candidate.name === "source_check");
|
|
176
|
+
assert.ok(tool);
|
|
177
|
+
const response = await tool.execute("call", { claim: " " });
|
|
178
|
+
assert.equal(response.details.error, "Missing claim");
|
|
179
|
+
});
|