@askalf/deepdive 0.1.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/DISCLAIMER.md +158 -0
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/agent.d.ts +55 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +100 -0
- package/dist/agent.js.map +1 -0
- package/dist/browser.d.ts +26 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +75 -0
- package/dist/browser.js.map +1 -0
- package/dist/citations.d.ts +10 -0
- package/dist/citations.d.ts.map +1 -0
- package/dist/citations.js +25 -0
- package/dist/citations.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +206 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +63 -0
- package/dist/config.js.map +1 -0
- package/dist/extract.d.ts +10 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +52 -0
- package/dist/extract.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.d.ts +19 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +33 -0
- package/dist/llm.js.map +1 -0
- package/dist/plan.d.ts +8 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +72 -0
- package/dist/plan.js.map +1 -0
- package/dist/search/brave.d.ts +8 -0
- package/dist/search/brave.d.ts.map +1 -0
- package/dist/search/brave.js +31 -0
- package/dist/search/brave.js.map +1 -0
- package/dist/search/duckduckgo.d.ts +7 -0
- package/dist/search/duckduckgo.d.ts.map +1 -0
- package/dist/search/duckduckgo.js +94 -0
- package/dist/search/duckduckgo.js.map +1 -0
- package/dist/search/searxng.d.ts +8 -0
- package/dist/search/searxng.d.ts.map +1 -0
- package/dist/search/searxng.js +29 -0
- package/dist/search/searxng.js.map +1 -0
- package/dist/search/tavily.d.ts +8 -0
- package/dist/search/tavily.d.ts.map +1 -0
- package/dist/search/tavily.js +38 -0
- package/dist/search/tavily.js.map +1 -0
- package/dist/search.d.ts +13 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +47 -0
- package/dist/search.js.map +1 -0
- package/dist/synthesize.d.ts +8 -0
- package/dist/synthesize.d.ts.map +1 -0
- package/dist/synthesize.js +40 -0
- package/dist/synthesize.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brave.js","sourceRoot":"","sources":["../../src/search/brave.ts"],"names":[],"mappings":"AAAA,yDAAyD;AAIzD,MAAM,OAAO,WAAW;IAEO;IADpB,IAAI,GAAG,OAAO,CAAC;IACxB,YAA6B,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAE5C,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,KAAa,EAAE,MAAoB;QAC7D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,gDAAgD,CAAC,CAAC;QACtE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,sBAAsB,EAAE,IAAI,CAAC,GAAG;aACjC;YACD,MAAM;SACP,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;QACtE,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAE7B,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,IAAI,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1B,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;YACpB,OAAO,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE;YAC5B,IAAI,EAAE,CAAC,GAAG,CAAC;SACZ,CAAC,CAAC,CAAC;IACN,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SearchAdapter, SearchResult } from "../search.js";
|
|
2
|
+
export declare class DuckDuckGoSearch implements SearchAdapter {
|
|
3
|
+
readonly name = "duckduckgo";
|
|
4
|
+
search(query: string, limit: number, signal?: AbortSignal): Promise<SearchResult[]>;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseDuckDuckGoHTML(html: string, limit: number): SearchResult[];
|
|
7
|
+
//# sourceMappingURL=duckduckgo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duckduckgo.d.ts","sourceRoot":"","sources":["../../src/search/duckduckgo.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIhE,qBAAa,gBAAiB,YAAW,aAAa;IACpD,QAAQ,CAAC,IAAI,gBAAgB;IAEvB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;CAiB1F;AAGD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAuB/E"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// DuckDuckGo HTML search. Uses the html.duckduckgo.com endpoint, which returns
|
|
2
|
+
// a static HTML document that's scrapable without an API key. Parses result
|
|
3
|
+
// links from the document structure, unwraps DDG's //duckduckgo.com/l/?uddg=
|
|
4
|
+
// redirect tracking, and returns { url, title, snippet }.
|
|
5
|
+
//
|
|
6
|
+
// If DDG changes their HTML layout, this breaks. Regenerate parser accordingly.
|
|
7
|
+
const ENDPOINT = "https://html.duckduckgo.com/html/";
|
|
8
|
+
export class DuckDuckGoSearch {
|
|
9
|
+
name = "duckduckgo";
|
|
10
|
+
async search(query, limit, signal) {
|
|
11
|
+
const form = new URLSearchParams({ q: query });
|
|
12
|
+
const res = await fetch(ENDPOINT, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: {
|
|
15
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
16
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
|
|
17
|
+
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
18
|
+
},
|
|
19
|
+
body: form.toString(),
|
|
20
|
+
signal,
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok)
|
|
23
|
+
throw new Error(`duckduckgo ${res.status} ${res.statusText}`);
|
|
24
|
+
const html = await res.text();
|
|
25
|
+
return parseDuckDuckGoHTML(html, limit);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Exported for unit tests.
|
|
29
|
+
export function parseDuckDuckGoHTML(html, limit) {
|
|
30
|
+
const results = [];
|
|
31
|
+
// Each result is wrapped in <div class="result results_links ...">.
|
|
32
|
+
// We match non-greedily on the title/snippet anchors within.
|
|
33
|
+
const blockRe = /<div class="result[^"]*"[\s\S]*?<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
|
|
34
|
+
let rank = 0;
|
|
35
|
+
let m;
|
|
36
|
+
while ((m = blockRe.exec(html)) !== null) {
|
|
37
|
+
const [, rawHref, rawTitle, rawSnippet] = m;
|
|
38
|
+
const url = unwrapDDGRedirect(decodeHtmlEntities(rawHref));
|
|
39
|
+
if (!isValidHttpUrl(url))
|
|
40
|
+
continue;
|
|
41
|
+
rank++;
|
|
42
|
+
results.push({
|
|
43
|
+
url,
|
|
44
|
+
title: stripTags(decodeHtmlEntities(rawTitle)).trim(),
|
|
45
|
+
snippet: stripTags(decodeHtmlEntities(rawSnippet)).trim(),
|
|
46
|
+
rank,
|
|
47
|
+
});
|
|
48
|
+
if (results.length >= limit)
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
function unwrapDDGRedirect(href) {
|
|
54
|
+
// DDG wraps real URLs behind //duckduckgo.com/l/?uddg=<encoded>&rut=<hash>
|
|
55
|
+
// The real target is in the uddg param.
|
|
56
|
+
if (href.startsWith("//"))
|
|
57
|
+
href = "https:" + href;
|
|
58
|
+
try {
|
|
59
|
+
const u = new URL(href);
|
|
60
|
+
if (u.hostname.endsWith("duckduckgo.com") && u.pathname === "/l/") {
|
|
61
|
+
const uddg = u.searchParams.get("uddg");
|
|
62
|
+
if (uddg)
|
|
63
|
+
return decodeURIComponent(uddg);
|
|
64
|
+
}
|
|
65
|
+
return u.toString();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return href;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function isValidHttpUrl(s) {
|
|
72
|
+
try {
|
|
73
|
+
const u = new URL(s);
|
|
74
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function decodeHtmlEntities(s) {
|
|
81
|
+
return s
|
|
82
|
+
.replace(/&/g, "&")
|
|
83
|
+
.replace(/</g, "<")
|
|
84
|
+
.replace(/>/g, ">")
|
|
85
|
+
.replace(/"/g, '"')
|
|
86
|
+
.replace(/'/g, "'")
|
|
87
|
+
.replace(/ /g, " ")
|
|
88
|
+
.replace(///g, "/")
|
|
89
|
+
.replace(/&#(\d+);/g, (_, n) => String.fromCharCode(parseInt(n, 10)));
|
|
90
|
+
}
|
|
91
|
+
function stripTags(s) {
|
|
92
|
+
return s.replace(/<[^>]+>/g, "");
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=duckduckgo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duckduckgo.js","sourceRoot":"","sources":["../../src/search/duckduckgo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SearchAdapter, SearchResult } from "../search.js";
|
|
2
|
+
export declare class SearXNGSearch implements SearchAdapter {
|
|
3
|
+
private readonly baseUrl;
|
|
4
|
+
readonly name = "searxng";
|
|
5
|
+
constructor(baseUrl: string);
|
|
6
|
+
search(query: string, limit: number, signal?: AbortSignal): Promise<SearchResult[]>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=searxng.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"searxng.d.ts","sourceRoot":"","sources":["../../src/search/searxng.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE,qBAAa,aAAc,YAAW,aAAa;IAErC,OAAO,CAAC,QAAQ,CAAC,OAAO;IADpC,QAAQ,CAAC,IAAI,aAAa;gBACG,OAAO,EAAE,MAAM;IAEtC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;CAoB1F"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SearXNG adapter. Points at an existing SearXNG instance (self-hosted or
|
|
2
|
+
// public). Requires DEEPDIVE_SEARXNG_URL. Uses the JSON output format.
|
|
3
|
+
export class SearXNGSearch {
|
|
4
|
+
baseUrl;
|
|
5
|
+
name = "searxng";
|
|
6
|
+
constructor(baseUrl) {
|
|
7
|
+
this.baseUrl = baseUrl;
|
|
8
|
+
}
|
|
9
|
+
async search(query, limit, signal) {
|
|
10
|
+
const url = new URL(this.baseUrl.replace(/\/+$/, "") + "/search");
|
|
11
|
+
url.searchParams.set("q", query);
|
|
12
|
+
url.searchParams.set("format", "json");
|
|
13
|
+
const res = await fetch(url, {
|
|
14
|
+
headers: { accept: "application/json" },
|
|
15
|
+
signal,
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok)
|
|
18
|
+
throw new Error(`searxng ${res.status} ${res.statusText}`);
|
|
19
|
+
const json = (await res.json());
|
|
20
|
+
const items = json.results ?? [];
|
|
21
|
+
return items.slice(0, limit).map((r, i) => ({
|
|
22
|
+
url: r.url,
|
|
23
|
+
title: r.title ?? "",
|
|
24
|
+
snippet: r.content ?? "",
|
|
25
|
+
rank: i + 1,
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=searxng.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"searxng.js","sourceRoot":"","sources":["../../src/search/searxng.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,uEAAuE;AAIvE,MAAM,OAAO,aAAa;IAEK;IADpB,IAAI,GAAG,SAAS,CAAC;IAC1B,YAA6B,OAAe;QAAf,YAAO,GAAP,OAAO,CAAQ;IAAG,CAAC;IAEhD,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,KAAa,EAAE,MAAoB;QAC7D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;QAClE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;YACvC,MAAM;SACP,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,WAAW,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;QACxE,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAE7B,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1C,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;YACpB,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,EAAE;YACxB,IAAI,EAAE,CAAC,GAAG,CAAC;SACZ,CAAC,CAAC,CAAC;IACN,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SearchAdapter, SearchResult } from "../search.js";
|
|
2
|
+
export declare class TavilySearch implements SearchAdapter {
|
|
3
|
+
private readonly key;
|
|
4
|
+
readonly name = "tavily";
|
|
5
|
+
constructor(key: string);
|
|
6
|
+
search(query: string, limit: number, signal?: AbortSignal): Promise<SearchResult[]>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=tavily.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tavily.d.ts","sourceRoot":"","sources":["../../src/search/tavily.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE,qBAAa,YAAa,YAAW,aAAa;IAEpC,OAAO,CAAC,QAAQ,CAAC,GAAG;IADhC,QAAQ,CAAC,IAAI,YAAY;gBACI,GAAG,EAAE,MAAM;IAElC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;CA2B1F"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Tavily adapter. Requires DEEPDIVE_TAVILY_KEY. Tavily returns search results
|
|
2
|
+
// with pre-extracted content, which means the browser fetch step can be
|
|
3
|
+
// skipped when using this adapter — but deepdive re-fetches anyway so the
|
|
4
|
+
// downstream extract/synthesis path is identical across adapters.
|
|
5
|
+
export class TavilySearch {
|
|
6
|
+
key;
|
|
7
|
+
name = "tavily";
|
|
8
|
+
constructor(key) {
|
|
9
|
+
this.key = key;
|
|
10
|
+
}
|
|
11
|
+
async search(query, limit, signal) {
|
|
12
|
+
const res = await fetch("https://api.tavily.com/search", {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: {
|
|
15
|
+
"content-type": "application/json",
|
|
16
|
+
accept: "application/json",
|
|
17
|
+
},
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
api_key: this.key,
|
|
20
|
+
query,
|
|
21
|
+
max_results: limit,
|
|
22
|
+
search_depth: "basic",
|
|
23
|
+
}),
|
|
24
|
+
signal,
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok)
|
|
27
|
+
throw new Error(`tavily ${res.status} ${res.statusText}`);
|
|
28
|
+
const json = (await res.json());
|
|
29
|
+
const items = json.results ?? [];
|
|
30
|
+
return items.slice(0, limit).map((r, i) => ({
|
|
31
|
+
url: r.url,
|
|
32
|
+
title: r.title ?? "",
|
|
33
|
+
snippet: r.content ?? "",
|
|
34
|
+
rank: i + 1,
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=tavily.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tavily.js","sourceRoot":"","sources":["../../src/search/tavily.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,wEAAwE;AACxE,0EAA0E;AAC1E,kEAAkE;AAIlE,MAAM,OAAO,YAAY;IAEM;IADpB,IAAI,GAAG,QAAQ,CAAC;IACzB,YAA6B,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAE5C,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,KAAa,EAAE,MAAoB;QAC7D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,+BAA+B,EAAE;YACvD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,MAAM,EAAE,kBAAkB;aAC3B;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,OAAO,EAAE,IAAI,CAAC,GAAG;gBACjB,KAAK;gBACL,WAAW,EAAE,KAAK;gBAClB,YAAY,EAAE,OAAO;aACtB,CAAC;YACF,MAAM;SACP,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;QACvE,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAE7B,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1C,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;YACpB,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,EAAE;YACxB,IAAI,EAAE,CAAC,GAAG,CAAC;SACZ,CAAC,CAAC,CAAC;IACN,CAAC;CACF"}
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface SearchResult {
|
|
2
|
+
url: string;
|
|
3
|
+
title: string;
|
|
4
|
+
snippet: string;
|
|
5
|
+
rank: number;
|
|
6
|
+
}
|
|
7
|
+
export interface SearchAdapter {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
search(query: string, limit: number, signal?: AbortSignal): Promise<SearchResult[]>;
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveSearchAdapter(name: string, env: Record<string, string | undefined>): Promise<SearchAdapter>;
|
|
12
|
+
export declare function dedupeByUrl(results: SearchResult[]): SearchResult[];
|
|
13
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CACrF;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC,OAAO,CAAC,aAAa,CAAC,CA4BxB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CAUnE"}
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Search adapter interface. A "search" returns candidate URLs with metadata.
|
|
2
|
+
// Adapters live under src/search/*. Default is DuckDuckGo HTML (no API key).
|
|
3
|
+
export async function resolveSearchAdapter(name, env) {
|
|
4
|
+
switch (name) {
|
|
5
|
+
case "duckduckgo":
|
|
6
|
+
case "ddg": {
|
|
7
|
+
const { DuckDuckGoSearch } = await import("./search/duckduckgo.js");
|
|
8
|
+
return new DuckDuckGoSearch();
|
|
9
|
+
}
|
|
10
|
+
case "searxng": {
|
|
11
|
+
const { SearXNGSearch } = await import("./search/searxng.js");
|
|
12
|
+
const url = env.DEEPDIVE_SEARXNG_URL;
|
|
13
|
+
if (!url)
|
|
14
|
+
throw new Error("searxng adapter requires DEEPDIVE_SEARXNG_URL");
|
|
15
|
+
return new SearXNGSearch(url);
|
|
16
|
+
}
|
|
17
|
+
case "brave": {
|
|
18
|
+
const { BraveSearch } = await import("./search/brave.js");
|
|
19
|
+
const key = env.DEEPDIVE_BRAVE_KEY;
|
|
20
|
+
if (!key)
|
|
21
|
+
throw new Error("brave adapter requires DEEPDIVE_BRAVE_KEY");
|
|
22
|
+
return new BraveSearch(key);
|
|
23
|
+
}
|
|
24
|
+
case "tavily": {
|
|
25
|
+
const { TavilySearch } = await import("./search/tavily.js");
|
|
26
|
+
const key = env.DEEPDIVE_TAVILY_KEY;
|
|
27
|
+
if (!key)
|
|
28
|
+
throw new Error("tavily adapter requires DEEPDIVE_TAVILY_KEY");
|
|
29
|
+
return new TavilySearch(key);
|
|
30
|
+
}
|
|
31
|
+
default:
|
|
32
|
+
throw new Error(`unknown search adapter: ${name}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function dedupeByUrl(results) {
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const r of results) {
|
|
39
|
+
const key = r.url.replace(/#.*$/, "").replace(/\/+$/, "");
|
|
40
|
+
if (seen.has(key))
|
|
41
|
+
continue;
|
|
42
|
+
seen.add(key);
|
|
43
|
+
out.push(r);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAc7E,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAY,EACZ,GAAuC;IAEvC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,YAAY,CAAC;QAClB,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;YACpE,OAAO,IAAI,gBAAgB,EAAE,CAAC;QAChC,CAAC;QACD,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;YAC9D,MAAM,GAAG,GAAG,GAAG,CAAC,oBAAoB,CAAC;YACrC,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YAC3E,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QACD,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;YAC1D,MAAM,GAAG,GAAG,GAAG,CAAC,kBAAkB,CAAC;YACnC,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;YACvE,OAAO,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC5D,MAAM,GAAG,GAAG,GAAG,CAAC,mBAAmB,CAAC;YACpC,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;YACzE,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC;QACD;YACE,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAuB;IACjD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC1D,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type LLMConfig } from "./llm.js";
|
|
2
|
+
import type { Source } from "./citations.js";
|
|
3
|
+
export interface SourceWithContent extends Source {
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function synthesize(question: string, sources: SourceWithContent[], config: LLMConfig, signal?: AbortSignal): Promise<string>;
|
|
7
|
+
export declare function buildSourcePacket(sources: SourceWithContent[]): string;
|
|
8
|
+
//# sourceMappingURL=synthesize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synthesize.d.ts","sourceRoot":"","sources":["../src/synthesize.ts"],"names":[],"mappings":"AAIA,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,iBAAkB,SAAQ,MAAM;IAC/C,OAAO,EAAE,MAAM,CAAC;CACjB;AAkBD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,iBAAiB,EAAE,EAC5B,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAGD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAOtE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Final answer synthesis — takes the original question and the collected
|
|
2
|
+
// sources, asks the LLM to produce a cited markdown answer. Sources are
|
|
3
|
+
// passed as a numbered list so the model can cite them inline as [1], [2].
|
|
4
|
+
import { callLLM } from "./llm.js";
|
|
5
|
+
const SYNTH_SYSTEM = `You are a careful research assistant. You will be given:
|
|
6
|
+
1. The user's original question.
|
|
7
|
+
2. A numbered list of source documents with titles, URLs, and extracted text.
|
|
8
|
+
|
|
9
|
+
Your job: write a direct, factual, well-structured markdown answer to the question, citing sources inline as [N] where N matches the numbered source list.
|
|
10
|
+
|
|
11
|
+
Rules:
|
|
12
|
+
- Cite every non-trivial claim inline with [N] (multiple sources: [1][3]).
|
|
13
|
+
- Do not invent a source number that is not in the provided list.
|
|
14
|
+
- Do not cite general knowledge; cite the sources.
|
|
15
|
+
- Prefer concrete facts, dates, numbers, names, mechanisms over hedged prose.
|
|
16
|
+
- If the sources disagree, surface the disagreement.
|
|
17
|
+
- If the sources do not answer the question, say so — do not hallucinate.
|
|
18
|
+
- Do not include a "Sources" section yourself — the caller appends it.
|
|
19
|
+
- Length: match the complexity of the question. A one-line question can get a paragraph; a comparison question may need headers and a table.`;
|
|
20
|
+
export async function synthesize(question, sources, config, signal) {
|
|
21
|
+
if (sources.length === 0) {
|
|
22
|
+
return "_No sources could be fetched or extracted. Unable to answer._";
|
|
23
|
+
}
|
|
24
|
+
const packet = buildSourcePacket(sources);
|
|
25
|
+
const userMessage = `Question: ${question}\n\n` +
|
|
26
|
+
`Sources (${sources.length}):\n\n${packet}\n\n` +
|
|
27
|
+
`Write the cited markdown answer now.`;
|
|
28
|
+
const { text } = await callLLM([{ role: "user", content: userMessage }], SYNTH_SYSTEM, config, signal);
|
|
29
|
+
return text;
|
|
30
|
+
}
|
|
31
|
+
// Exported for unit tests.
|
|
32
|
+
export function buildSourcePacket(sources) {
|
|
33
|
+
return sources
|
|
34
|
+
.map((s) => {
|
|
35
|
+
const header = `[${s.id}] ${s.title || "(untitled)"} — ${s.url}`;
|
|
36
|
+
return `${header}\n\n${s.content}`;
|
|
37
|
+
})
|
|
38
|
+
.join("\n\n---\n\n");
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=synthesize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synthesize.js","sourceRoot":"","sources":["../src/synthesize.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,wEAAwE;AACxE,2EAA2E;AAE3E,OAAO,EAAE,OAAO,EAAkB,MAAM,UAAU,CAAC;AAOnD,MAAM,YAAY,GAAG;;;;;;;;;;;;;;6IAcwH,CAAC;AAE9I,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAgB,EAChB,OAA4B,EAC5B,MAAiB,EACjB,MAAoB;IAEpB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,+DAA+D,CAAC;IACzE,CAAC;IACD,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,WAAW,GACf,aAAa,QAAQ,MAAM;QAC3B,YAAY,OAAO,CAAC,MAAM,SAAS,MAAM,MAAM;QAC/C,sCAAsC,CAAC;IACzC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAC5B,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EACxC,YAAY,EACZ,MAAM,EACN,MAAM,CACP,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2BAA2B;AAC3B,MAAM,UAAU,iBAAiB,CAAC,OAA4B;IAC5D,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,IAAI,YAAY,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC;QACjE,OAAO,GAAG,MAAM,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;IACrC,CAAC,CAAC;SACD,IAAI,CAAC,aAAa,CAAC,CAAC;AACzB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@askalf/deepdive",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A local research agent. One command, cited answer. Routes every LLM call through your own proxy (dario, Anthropic-compat, OpenAI-compat). Headless browser + pluggable search + multi-provider LLM — zero hosted dependencies.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"deepdive": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"DISCLAIMER.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"test": "node --test --test-concurrency=4 test/*.test.mjs",
|
|
26
|
+
"dev": "tsx src/cli.ts",
|
|
27
|
+
"start": "node dist/cli.js",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"research",
|
|
32
|
+
"agent",
|
|
33
|
+
"llm",
|
|
34
|
+
"local",
|
|
35
|
+
"cli",
|
|
36
|
+
"anthropic",
|
|
37
|
+
"openai",
|
|
38
|
+
"claude",
|
|
39
|
+
"search",
|
|
40
|
+
"deep-research",
|
|
41
|
+
"perplexity-alternative",
|
|
42
|
+
"cite",
|
|
43
|
+
"citations",
|
|
44
|
+
"self-hosted"
|
|
45
|
+
],
|
|
46
|
+
"author": "askalf (https://github.com/askalf)",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/askalf/deepdive.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/askalf/deepdive",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/askalf/deepdive/issues"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20.0.0"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"playwright": "^1.47.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^22.0.0",
|
|
64
|
+
"tsx": "^4.19.0",
|
|
65
|
+
"typescript": "^5.7.0"
|
|
66
|
+
}
|
|
67
|
+
}
|