agentic-pi 0.2.1 → 0.2.3
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/README.md +83 -27
- package/dist/args.d.ts +16 -0
- package/dist/args.js +29 -0
- package/dist/args.js.map +1 -1
- package/dist/extensions/web-search/extract.d.ts +18 -0
- package/dist/extensions/web-search/extract.js +110 -0
- package/dist/extensions/web-search/extract.js.map +1 -0
- package/dist/extensions/web-search/index.d.ts +43 -0
- package/dist/extensions/web-search/index.js +86 -0
- package/dist/extensions/web-search/index.js.map +1 -0
- package/dist/extensions/web-search/providers/brave.d.ts +21 -0
- package/dist/extensions/web-search/providers/brave.js +73 -0
- package/dist/extensions/web-search/providers/brave.js.map +1 -0
- package/dist/extensions/web-search/providers/exa.d.ts +16 -0
- package/dist/extensions/web-search/providers/exa.js +85 -0
- package/dist/extensions/web-search/providers/exa.js.map +1 -0
- package/dist/extensions/web-search/providers/tavily.d.ts +18 -0
- package/dist/extensions/web-search/providers/tavily.js +85 -0
- package/dist/extensions/web-search/providers/tavily.js.map +1 -0
- package/dist/extensions/web-search/rate-limit.d.ts +14 -0
- package/dist/extensions/web-search/rate-limit.js +24 -0
- package/dist/extensions/web-search/rate-limit.js.map +1 -0
- package/dist/extensions/web-search/safe-fetch.d.ts +54 -0
- package/dist/extensions/web-search/safe-fetch.js +172 -0
- package/dist/extensions/web-search/safe-fetch.js.map +1 -0
- package/dist/extensions/web-search/selection.d.ts +42 -0
- package/dist/extensions/web-search/selection.js +64 -0
- package/dist/extensions/web-search/selection.js.map +1 -0
- package/dist/extensions/web-search/tools.d.ts +13 -0
- package/dist/extensions/web-search/tools.js +136 -0
- package/dist/extensions/web-search/tools.js.map +1 -0
- package/dist/extensions/web-search/types.d.ts +65 -0
- package/dist/extensions/web-search/types.js +10 -0
- package/dist/extensions/web-search/types.js.map +1 -0
- package/dist/run.d.ts +27 -0
- package/dist/run.js +13 -0
- package/dist/run.js.map +1 -1
- package/dist/runner.js +29 -1
- package/dist/runner.js.map +1 -1
- package/dist/sandbox/gondolin.d.ts +13 -4
- package/dist/sandbox/gondolin.js +10 -3
- package/dist/sandbox/gondolin.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brave.js","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/brave.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA2BH,SAAS,WAAW,CAAC,GAAW,EAAE,OAAe;IAC/C,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAChC,OAAO,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAqB;IACvD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAK,UAAU,CAAC,KAAmB,CAAC;IACvE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,qCAAqC,CAAC;IAEzE,OAAO;QACL,IAAI,EAAE,OAAO;QACb,wBAAwB,EAAE,KAAK;QAE/B,KAAK,CAAC,MAAM,CAAC,MAAoB;YAC/B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,OAAO,aAAa,CAAC,CAAC;YAC7C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACxC,6DAA6D;YAC7D,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAExF,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;gBACxC,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE;oBACP,MAAM,EAAE,kBAAkB;oBAC1B,sBAAsB,EAAE,OAAO,CAAC,MAAM;iBACvC;aACF,CAAC,CAAC;YACH,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACV,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;gBAC5C,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YACjF,CAAC;YACD,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAkB,CAAC;YAC/C,MAAM,KAAK,GAAuB,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;gBACvE,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE;gBACrB,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE;gBACjB,OAAO,EAAE,EAAE,CAAC,WAAW;gBACvB,aAAa,EAAE,EAAE,CAAC,GAAG;aACtB,CAAC,CAAC,CAAC;YAEJ,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,IAAI,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;gBAClC,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,CAAC;gBACnC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAChC,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CACnD,CAAC;YACJ,CAAC;YACD,IAAI,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;gBAClC,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,CAAC;gBACnC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAChC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CACtD,CAAC;YACJ,CAAC;YACD,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YAEhD,OAAO;gBACL,QAAQ,EAAE,OAAO;gBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,OAAO,EAAE,QAAQ;aAClB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exa provider. https://docs.exa.ai/
|
|
3
|
+
*
|
|
4
|
+
* Endpoints used:
|
|
5
|
+
* POST https://api.exa.ai/search — neural/keyword search
|
|
6
|
+
* POST https://api.exa.ai/contents — fetch extracted content for one or more URLs
|
|
7
|
+
*
|
|
8
|
+
* Auth: `x-api-key: <key>`.
|
|
9
|
+
*/
|
|
10
|
+
import type { FetchImpl, Provider } from "../types.js";
|
|
11
|
+
export interface ExaOptions {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
fetchImpl?: FetchImpl;
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function createExaProvider(options: ExaOptions): Provider;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exa provider. https://docs.exa.ai/
|
|
3
|
+
*
|
|
4
|
+
* Endpoints used:
|
|
5
|
+
* POST https://api.exa.ai/search — neural/keyword search
|
|
6
|
+
* POST https://api.exa.ai/contents — fetch extracted content for one or more URLs
|
|
7
|
+
*
|
|
8
|
+
* Auth: `x-api-key: <key>`.
|
|
9
|
+
*/
|
|
10
|
+
export function createExaProvider(options) {
|
|
11
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
12
|
+
const baseUrl = options.baseUrl ?? "https://api.exa.ai";
|
|
13
|
+
return {
|
|
14
|
+
name: "exa",
|
|
15
|
+
supportsExtractedContent: true,
|
|
16
|
+
async search(params) {
|
|
17
|
+
const body = {
|
|
18
|
+
query: params.query,
|
|
19
|
+
numResults: params.maxResults,
|
|
20
|
+
type: params.searchDepth === "advanced" ? "neural" : "auto",
|
|
21
|
+
};
|
|
22
|
+
if (params.includeDomains?.length)
|
|
23
|
+
body.includeDomains = params.includeDomains;
|
|
24
|
+
if (params.excludeDomains?.length)
|
|
25
|
+
body.excludeDomains = params.excludeDomains;
|
|
26
|
+
if (params.includeContent === true) {
|
|
27
|
+
body.contents = { text: { maxCharacters: 4000 } };
|
|
28
|
+
}
|
|
29
|
+
const r = await fetchImpl(`${baseUrl}/search`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"content-type": "application/json",
|
|
33
|
+
"x-api-key": options.apiKey,
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(body),
|
|
36
|
+
});
|
|
37
|
+
if (!r.ok) {
|
|
38
|
+
const text = await r.text().catch(() => "");
|
|
39
|
+
throw new Error(`exa search failed: http ${r.status} ${text.slice(0, 200)}`);
|
|
40
|
+
}
|
|
41
|
+
const data = (await r.json());
|
|
42
|
+
return {
|
|
43
|
+
provider: "exa",
|
|
44
|
+
query: params.query,
|
|
45
|
+
results: (data.results ?? []).map((it) => ({
|
|
46
|
+
title: it.title ?? "",
|
|
47
|
+
url: it.url ?? "",
|
|
48
|
+
content: it.text,
|
|
49
|
+
score: it.score,
|
|
50
|
+
publishedDate: it.publishedDate,
|
|
51
|
+
})),
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
async fetch(params) {
|
|
55
|
+
const r = await fetchImpl(`${baseUrl}/contents`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
"content-type": "application/json",
|
|
59
|
+
"x-api-key": options.apiKey,
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
urls: [params.url],
|
|
63
|
+
text: true,
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
if (!r.ok) {
|
|
67
|
+
const text = await r.text().catch(() => "");
|
|
68
|
+
throw new Error(`exa contents failed: http ${r.status} ${text.slice(0, 200)}`);
|
|
69
|
+
}
|
|
70
|
+
const data = (await r.json());
|
|
71
|
+
const hit = data.results?.[0];
|
|
72
|
+
if (!hit?.text) {
|
|
73
|
+
throw new Error(`exa contents returned no text for ${params.url}`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
provider: "exa",
|
|
77
|
+
url: params.url,
|
|
78
|
+
resolvedUrl: hit.url,
|
|
79
|
+
text: hit.text,
|
|
80
|
+
title: hit.title,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=exa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exa.js","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/exa.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tavily provider. https://docs.tavily.com/
|
|
3
|
+
*
|
|
4
|
+
* Endpoints used:
|
|
5
|
+
* POST https://api.tavily.com/search — ranked links + optional content + optional answer
|
|
6
|
+
* POST https://api.tavily.com/extract — bulk fetch + extracted text for a list of URLs
|
|
7
|
+
*
|
|
8
|
+
* API key passed as `api_key` in the JSON body (Tavily's documented
|
|
9
|
+
* convention; the Bearer header is rejected on /search).
|
|
10
|
+
*/
|
|
11
|
+
import type { FetchImpl, Provider } from "../types.js";
|
|
12
|
+
export interface TavilyOptions {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
fetchImpl?: FetchImpl;
|
|
15
|
+
/** Override for tests. */
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function createTavilyProvider(options: TavilyOptions): Provider;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tavily provider. https://docs.tavily.com/
|
|
3
|
+
*
|
|
4
|
+
* Endpoints used:
|
|
5
|
+
* POST https://api.tavily.com/search — ranked links + optional content + optional answer
|
|
6
|
+
* POST https://api.tavily.com/extract — bulk fetch + extracted text for a list of URLs
|
|
7
|
+
*
|
|
8
|
+
* API key passed as `api_key` in the JSON body (Tavily's documented
|
|
9
|
+
* convention; the Bearer header is rejected on /search).
|
|
10
|
+
*/
|
|
11
|
+
export function createTavilyProvider(options) {
|
|
12
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
13
|
+
const baseUrl = options.baseUrl ?? "https://api.tavily.com";
|
|
14
|
+
return {
|
|
15
|
+
name: "tavily",
|
|
16
|
+
supportsExtractedContent: true,
|
|
17
|
+
async search(params) {
|
|
18
|
+
const body = {
|
|
19
|
+
api_key: options.apiKey,
|
|
20
|
+
query: params.query,
|
|
21
|
+
max_results: params.maxResults,
|
|
22
|
+
search_depth: params.searchDepth === "advanced" ? "advanced" : "basic",
|
|
23
|
+
include_answer: true,
|
|
24
|
+
include_raw_content: params.includeContent === true,
|
|
25
|
+
};
|
|
26
|
+
if (params.includeDomains?.length)
|
|
27
|
+
body.include_domains = params.includeDomains;
|
|
28
|
+
if (params.excludeDomains?.length)
|
|
29
|
+
body.exclude_domains = params.excludeDomains;
|
|
30
|
+
const r = await fetchImpl(`${baseUrl}/search`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "content-type": "application/json" },
|
|
33
|
+
body: JSON.stringify(body),
|
|
34
|
+
});
|
|
35
|
+
if (!r.ok) {
|
|
36
|
+
const text = await r.text().catch(() => "");
|
|
37
|
+
throw new Error(`tavily search failed: http ${r.status} ${text.slice(0, 200)}`);
|
|
38
|
+
}
|
|
39
|
+
const data = (await r.json());
|
|
40
|
+
return {
|
|
41
|
+
provider: "tavily",
|
|
42
|
+
query: data.query ?? params.query,
|
|
43
|
+
answer: data.answer,
|
|
44
|
+
results: (data.results ?? []).map((it) => ({
|
|
45
|
+
title: it.title ?? "",
|
|
46
|
+
url: it.url ?? "",
|
|
47
|
+
snippet: it.content,
|
|
48
|
+
content: it.raw_content ?? undefined,
|
|
49
|
+
score: it.score,
|
|
50
|
+
publishedDate: it.published_date,
|
|
51
|
+
})),
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
async fetch(params) {
|
|
55
|
+
const r = await fetchImpl(`${baseUrl}/extract`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "content-type": "application/json" },
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
api_key: options.apiKey,
|
|
60
|
+
urls: [params.url],
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
if (!r.ok) {
|
|
64
|
+
const text = await r.text().catch(() => "");
|
|
65
|
+
throw new Error(`tavily extract failed: http ${r.status} ${text.slice(0, 200)}`);
|
|
66
|
+
}
|
|
67
|
+
const data = (await r.json());
|
|
68
|
+
const hit = data.results?.[0];
|
|
69
|
+
if (!hit?.raw_content) {
|
|
70
|
+
const fail = data.failed_results?.[0];
|
|
71
|
+
throw new Error(fail?.error
|
|
72
|
+
? `tavily extract returned no content for ${params.url}: ${fail.error}`
|
|
73
|
+
: `tavily extract returned no content for ${params.url}`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
provider: "tavily",
|
|
77
|
+
url: params.url,
|
|
78
|
+
resolvedUrl: hit.url,
|
|
79
|
+
text: hit.raw_content,
|
|
80
|
+
title: hit.title,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=tavily.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tavily.js","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/tavily.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny per-run call counter. Shared between web_search and web_fetch so the
|
|
3
|
+
* combined call count is bounded.
|
|
4
|
+
*
|
|
5
|
+
* When `max` is exceeded `consume()` returns false; the tool layer surfaces
|
|
6
|
+
* this as a structured error result (never throws).
|
|
7
|
+
*/
|
|
8
|
+
export declare class RateLimiter {
|
|
9
|
+
readonly max: number;
|
|
10
|
+
private used;
|
|
11
|
+
constructor(max: number);
|
|
12
|
+
consume(): boolean;
|
|
13
|
+
get remaining(): number;
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny per-run call counter. Shared between web_search and web_fetch so the
|
|
3
|
+
* combined call count is bounded.
|
|
4
|
+
*
|
|
5
|
+
* When `max` is exceeded `consume()` returns false; the tool layer surfaces
|
|
6
|
+
* this as a structured error result (never throws).
|
|
7
|
+
*/
|
|
8
|
+
export class RateLimiter {
|
|
9
|
+
max;
|
|
10
|
+
used = 0;
|
|
11
|
+
constructor(max) {
|
|
12
|
+
this.max = max;
|
|
13
|
+
}
|
|
14
|
+
consume() {
|
|
15
|
+
if (this.used >= this.max)
|
|
16
|
+
return false;
|
|
17
|
+
this.used += 1;
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
get remaining() {
|
|
21
|
+
return Math.max(0, this.max - this.used);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../../src/extensions/web-search/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,OAAO,WAAW;IAED;IADb,IAAI,GAAG,CAAC,CAAC;IACjB,YAAqB,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAEpC,OAAO;QACL,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;QACxC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;CACF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe-by-default HTTP GET wrapper for web_fetch.
|
|
3
|
+
*
|
|
4
|
+
* Rails:
|
|
5
|
+
* - scheme must be http or https
|
|
6
|
+
* - timeout via AbortController (default 15s)
|
|
7
|
+
* - max response bytes hard-capped (default 1 MiB); streaming is aborted
|
|
8
|
+
* once the cap is exceeded
|
|
9
|
+
* - redirects followed manually, at most 3; scheme re-checked at each hop
|
|
10
|
+
* - caller passes the content-type allowlist; non-matching responses
|
|
11
|
+
* raise so the tool layer can return a structured error
|
|
12
|
+
*
|
|
13
|
+
* No SSRF private-range blocking is performed (deliberate per user
|
|
14
|
+
* decision). Operators who care should run this behind their own egress
|
|
15
|
+
* firewall.
|
|
16
|
+
*/
|
|
17
|
+
import type { FetchImpl } from "./types.js";
|
|
18
|
+
export interface SafeFetchOptions {
|
|
19
|
+
/** Defaults to globalThis.fetch. Injected in tests. */
|
|
20
|
+
fetchImpl?: FetchImpl;
|
|
21
|
+
/** Per-request timeout in ms. Default 15_000. */
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
/** Hard cap on response body bytes. Default 1 MiB. */
|
|
24
|
+
maxBytes?: number;
|
|
25
|
+
/** Max redirect hops. Default 3. */
|
|
26
|
+
maxRedirects?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Allowed content-types as case-insensitive prefixes. A response is
|
|
29
|
+
* accepted if its Content-Type starts with any entry. Default: text/*,
|
|
30
|
+
* application/(xhtml+xml|xml|json).
|
|
31
|
+
*/
|
|
32
|
+
allowedContentTypePrefixes?: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface SafeFetchResult {
|
|
35
|
+
status: number;
|
|
36
|
+
contentType?: string;
|
|
37
|
+
body: string;
|
|
38
|
+
finalUrl: string;
|
|
39
|
+
}
|
|
40
|
+
export declare const SAFE_FETCH_DEFAULTS: {
|
|
41
|
+
timeoutMs: number;
|
|
42
|
+
maxBytes: number;
|
|
43
|
+
maxRedirects: number;
|
|
44
|
+
};
|
|
45
|
+
export declare class SafeFetchError extends Error {
|
|
46
|
+
readonly code: string;
|
|
47
|
+
readonly status?: number | undefined;
|
|
48
|
+
constructor(message: string, code: string, status?: number | undefined);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Issue a GET, follow redirects manually, cap byte count, enforce timeout
|
|
52
|
+
* and content-type. Returns the decoded body as a UTF-8 string.
|
|
53
|
+
*/
|
|
54
|
+
export declare function safeFetch(rawUrl: string, options?: SafeFetchOptions): Promise<SafeFetchResult>;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe-by-default HTTP GET wrapper for web_fetch.
|
|
3
|
+
*
|
|
4
|
+
* Rails:
|
|
5
|
+
* - scheme must be http or https
|
|
6
|
+
* - timeout via AbortController (default 15s)
|
|
7
|
+
* - max response bytes hard-capped (default 1 MiB); streaming is aborted
|
|
8
|
+
* once the cap is exceeded
|
|
9
|
+
* - redirects followed manually, at most 3; scheme re-checked at each hop
|
|
10
|
+
* - caller passes the content-type allowlist; non-matching responses
|
|
11
|
+
* raise so the tool layer can return a structured error
|
|
12
|
+
*
|
|
13
|
+
* No SSRF private-range blocking is performed (deliberate per user
|
|
14
|
+
* decision). Operators who care should run this behind their own egress
|
|
15
|
+
* firewall.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_ALLOWED_PREFIXES = [
|
|
18
|
+
"text/",
|
|
19
|
+
"application/xhtml+xml",
|
|
20
|
+
"application/xml",
|
|
21
|
+
"application/json",
|
|
22
|
+
];
|
|
23
|
+
export const SAFE_FETCH_DEFAULTS = {
|
|
24
|
+
timeoutMs: 15_000,
|
|
25
|
+
maxBytes: 1024 * 1024,
|
|
26
|
+
maxRedirects: 3,
|
|
27
|
+
};
|
|
28
|
+
export class SafeFetchError extends Error {
|
|
29
|
+
code;
|
|
30
|
+
status;
|
|
31
|
+
constructor(message, code, status) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.code = code;
|
|
34
|
+
this.status = status;
|
|
35
|
+
this.name = "SafeFetchError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function assertHttpScheme(url) {
|
|
39
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
40
|
+
throw new SafeFetchError(`unsupported url scheme '${url.protocol}' (only http/https allowed)`, "bad-scheme");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function isAllowedContentType(ct, prefixes) {
|
|
44
|
+
if (!ct)
|
|
45
|
+
return false;
|
|
46
|
+
const lower = ct.toLowerCase();
|
|
47
|
+
return prefixes.some((p) => lower.startsWith(p));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Issue a GET, follow redirects manually, cap byte count, enforce timeout
|
|
51
|
+
* and content-type. Returns the decoded body as a UTF-8 string.
|
|
52
|
+
*/
|
|
53
|
+
export async function safeFetch(rawUrl, options = {}) {
|
|
54
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
55
|
+
if (!fetchImpl) {
|
|
56
|
+
throw new SafeFetchError("no fetch implementation available", "no-fetch");
|
|
57
|
+
}
|
|
58
|
+
const timeoutMs = options.timeoutMs ?? SAFE_FETCH_DEFAULTS.timeoutMs;
|
|
59
|
+
const maxBytes = options.maxBytes ?? SAFE_FETCH_DEFAULTS.maxBytes;
|
|
60
|
+
const maxRedirects = options.maxRedirects ?? SAFE_FETCH_DEFAULTS.maxRedirects;
|
|
61
|
+
const allowedPrefixes = options.allowedContentTypePrefixes ?? DEFAULT_ALLOWED_PREFIXES;
|
|
62
|
+
let currentUrl;
|
|
63
|
+
try {
|
|
64
|
+
currentUrl = new URL(rawUrl);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
throw new SafeFetchError(`invalid url '${rawUrl}'`, "bad-url");
|
|
68
|
+
}
|
|
69
|
+
assertHttpScheme(currentUrl);
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
72
|
+
try {
|
|
73
|
+
for (let hop = 0; hop <= maxRedirects; hop++) {
|
|
74
|
+
const response = await fetchImpl(currentUrl.toString(), {
|
|
75
|
+
method: "GET",
|
|
76
|
+
redirect: "manual",
|
|
77
|
+
signal: controller.signal,
|
|
78
|
+
headers: { "user-agent": "agentic-pi/web-search" },
|
|
79
|
+
});
|
|
80
|
+
const status = response.status;
|
|
81
|
+
if (status >= 300 && status < 400) {
|
|
82
|
+
const loc = response.headers.get("location");
|
|
83
|
+
if (!loc) {
|
|
84
|
+
throw new SafeFetchError(`redirect (${status}) without Location header`, "bad-redirect", status);
|
|
85
|
+
}
|
|
86
|
+
if (hop === maxRedirects) {
|
|
87
|
+
throw new SafeFetchError(`too many redirects (>${maxRedirects})`, "too-many-redirects");
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
currentUrl = new URL(loc, currentUrl);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new SafeFetchError(`invalid redirect target '${loc}'`, "bad-url");
|
|
94
|
+
}
|
|
95
|
+
assertHttpScheme(currentUrl);
|
|
96
|
+
// Drain/close any body the redirect carried, just in case.
|
|
97
|
+
try {
|
|
98
|
+
await response.body?.cancel();
|
|
99
|
+
}
|
|
100
|
+
catch { /* ignore */ }
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (status >= 400) {
|
|
104
|
+
try {
|
|
105
|
+
await response.body?.cancel();
|
|
106
|
+
}
|
|
107
|
+
catch { /* ignore */ }
|
|
108
|
+
throw new SafeFetchError(`http ${status}`, "http-error", status);
|
|
109
|
+
}
|
|
110
|
+
const contentType = response.headers.get("content-type") ?? undefined;
|
|
111
|
+
if (!isAllowedContentType(contentType, allowedPrefixes)) {
|
|
112
|
+
try {
|
|
113
|
+
await response.body?.cancel();
|
|
114
|
+
}
|
|
115
|
+
catch { /* ignore */ }
|
|
116
|
+
throw new SafeFetchError(`disallowed content-type '${contentType ?? "(none)"}'`, "bad-content-type", status);
|
|
117
|
+
}
|
|
118
|
+
const body = await readCapped(response, maxBytes);
|
|
119
|
+
return {
|
|
120
|
+
status,
|
|
121
|
+
contentType,
|
|
122
|
+
body,
|
|
123
|
+
finalUrl: currentUrl.toString(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Unreachable — the for-loop returns or throws every path.
|
|
127
|
+
throw new SafeFetchError("redirect loop fell through", "internal");
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function readCapped(response, maxBytes) {
|
|
134
|
+
// Prefer streaming so we can abort when the cap is hit without buffering
|
|
135
|
+
// the entire body first.
|
|
136
|
+
if (response.body && typeof response.body.getReader === "function") {
|
|
137
|
+
const reader = response.body.getReader();
|
|
138
|
+
const chunks = [];
|
|
139
|
+
let total = 0;
|
|
140
|
+
while (true) {
|
|
141
|
+
const { done, value } = await reader.read();
|
|
142
|
+
if (done)
|
|
143
|
+
break;
|
|
144
|
+
if (!value)
|
|
145
|
+
continue;
|
|
146
|
+
total += value.byteLength;
|
|
147
|
+
if (total > maxBytes) {
|
|
148
|
+
try {
|
|
149
|
+
await reader.cancel();
|
|
150
|
+
}
|
|
151
|
+
catch { /* ignore */ }
|
|
152
|
+
throw new SafeFetchError(`response exceeded ${maxBytes} bytes`, "too-large");
|
|
153
|
+
}
|
|
154
|
+
chunks.push(value);
|
|
155
|
+
}
|
|
156
|
+
const buf = new Uint8Array(total);
|
|
157
|
+
let offset = 0;
|
|
158
|
+
for (const c of chunks) {
|
|
159
|
+
buf.set(c, offset);
|
|
160
|
+
offset += c.byteLength;
|
|
161
|
+
}
|
|
162
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(buf);
|
|
163
|
+
}
|
|
164
|
+
// Fallback: text() without streaming cap — only triggers when the
|
|
165
|
+
// injected fetch returns a stub Response.
|
|
166
|
+
const text = await response.text();
|
|
167
|
+
if (text.length > maxBytes) {
|
|
168
|
+
throw new SafeFetchError(`response exceeded ${maxBytes} bytes`, "too-large");
|
|
169
|
+
}
|
|
170
|
+
return text;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=safe-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-fetch.js","sourceRoot":"","sources":["../../../src/extensions/web-search/safe-fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider selection logic — pure, no IO, easy to unit-test.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order:
|
|
5
|
+
* 1. config.webSearch === false → skipped (disabled-by-flag).
|
|
6
|
+
* 2. Explicit provider (config.webSearchProvider OR WEB_SEARCH_PROVIDER env):
|
|
7
|
+
* - key present → selected.
|
|
8
|
+
* - key missing → skipped (no-credentials) naming the env var.
|
|
9
|
+
* 3. Auto: scan env keys in fixed priority Tavily → Exa → Brave.
|
|
10
|
+
* - first hit wins; if more than one is set, attach an advisory
|
|
11
|
+
* message so the user can override via WEB_SEARCH_PROVIDER.
|
|
12
|
+
* 4. None present → skipped (no-credentials), silent.
|
|
13
|
+
* 5. Unknown explicit provider name → throw (config error).
|
|
14
|
+
*/
|
|
15
|
+
import { type ProviderName, type WebSearchSkipReason } from "./types.js";
|
|
16
|
+
export interface SelectionInput {
|
|
17
|
+
/** When false, force-skip with reason `disabled-by-flag`. */
|
|
18
|
+
webSearch: boolean;
|
|
19
|
+
/** Explicit provider from --web-search-provider (overrides env). */
|
|
20
|
+
webSearchProvider?: string;
|
|
21
|
+
/** Process env or test override. */
|
|
22
|
+
env: Record<string, string | undefined>;
|
|
23
|
+
}
|
|
24
|
+
export interface SelectedProvider {
|
|
25
|
+
status: "configured";
|
|
26
|
+
provider: ProviderName;
|
|
27
|
+
apiKey: string;
|
|
28
|
+
/** Optional advisory note (e.g. multi-key collision). */
|
|
29
|
+
message?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface SkippedProvider {
|
|
32
|
+
status: "skipped";
|
|
33
|
+
reason: WebSearchSkipReason;
|
|
34
|
+
message?: string;
|
|
35
|
+
/** Echoed back when the user explicitly asked for one. */
|
|
36
|
+
provider?: ProviderName;
|
|
37
|
+
}
|
|
38
|
+
export type SelectionResult = SelectedProvider | SkippedProvider;
|
|
39
|
+
/** env var name that holds each provider's key. */
|
|
40
|
+
export declare const PROVIDER_ENV_VAR: Record<ProviderName, string>;
|
|
41
|
+
export declare function isProviderName(s: string): s is ProviderName;
|
|
42
|
+
export declare function selectProvider(input: SelectionInput): SelectionResult;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider selection logic — pure, no IO, easy to unit-test.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order:
|
|
5
|
+
* 1. config.webSearch === false → skipped (disabled-by-flag).
|
|
6
|
+
* 2. Explicit provider (config.webSearchProvider OR WEB_SEARCH_PROVIDER env):
|
|
7
|
+
* - key present → selected.
|
|
8
|
+
* - key missing → skipped (no-credentials) naming the env var.
|
|
9
|
+
* 3. Auto: scan env keys in fixed priority Tavily → Exa → Brave.
|
|
10
|
+
* - first hit wins; if more than one is set, attach an advisory
|
|
11
|
+
* message so the user can override via WEB_SEARCH_PROVIDER.
|
|
12
|
+
* 4. None present → skipped (no-credentials), silent.
|
|
13
|
+
* 5. Unknown explicit provider name → throw (config error).
|
|
14
|
+
*/
|
|
15
|
+
import { PROVIDER_NAMES, } from "./types.js";
|
|
16
|
+
/** env var name that holds each provider's key. */
|
|
17
|
+
export const PROVIDER_ENV_VAR = {
|
|
18
|
+
tavily: "TAVILY_API_KEY",
|
|
19
|
+
exa: "EXA_API_KEY",
|
|
20
|
+
brave: "BRAVE_SEARCH_API_KEY",
|
|
21
|
+
};
|
|
22
|
+
/** Priority order for auto-detection. */
|
|
23
|
+
const AUTO_PRIORITY = ["tavily", "exa", "brave"];
|
|
24
|
+
export function isProviderName(s) {
|
|
25
|
+
return PROVIDER_NAMES.includes(s);
|
|
26
|
+
}
|
|
27
|
+
export function selectProvider(input) {
|
|
28
|
+
if (input.webSearch === false) {
|
|
29
|
+
return { status: "skipped", reason: "disabled-by-flag" };
|
|
30
|
+
}
|
|
31
|
+
const envExplicit = input.env.WEB_SEARCH_PROVIDER?.trim();
|
|
32
|
+
const explicit = input.webSearchProvider ?? envExplicit;
|
|
33
|
+
if (explicit) {
|
|
34
|
+
if (!isProviderName(explicit)) {
|
|
35
|
+
throw new Error(`Unknown web-search provider '${explicit}'. Expected one of: ${PROVIDER_NAMES.join(", ")}`);
|
|
36
|
+
}
|
|
37
|
+
const envVar = PROVIDER_ENV_VAR[explicit];
|
|
38
|
+
const apiKey = input.env[envVar]?.trim();
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
return {
|
|
41
|
+
status: "skipped",
|
|
42
|
+
reason: "no-credentials",
|
|
43
|
+
provider: explicit,
|
|
44
|
+
message: `--web-search-provider=${explicit} set but ${envVar} is empty`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return { status: "configured", provider: explicit, apiKey };
|
|
48
|
+
}
|
|
49
|
+
const present = AUTO_PRIORITY.filter((p) => (input.env[PROVIDER_ENV_VAR[p]] ?? "").trim().length > 0);
|
|
50
|
+
if (present.length === 0) {
|
|
51
|
+
return { status: "skipped", reason: "no-credentials" };
|
|
52
|
+
}
|
|
53
|
+
const chosen = present[0];
|
|
54
|
+
const message = present.length > 1
|
|
55
|
+
? `multiple provider keys present (${present.join(", ")}); using ${chosen} — set WEB_SEARCH_PROVIDER to override`
|
|
56
|
+
: undefined;
|
|
57
|
+
return {
|
|
58
|
+
status: "configured",
|
|
59
|
+
provider: chosen,
|
|
60
|
+
apiKey: input.env[PROVIDER_ENV_VAR[chosen]].trim(),
|
|
61
|
+
message,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.js","sourceRoot":"","sources":["../../../src/extensions/web-search/selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,cAAc,GAGf,MAAM,YAAY,CAAC;AA6BpB,mDAAmD;AACnD,MAAM,CAAC,MAAM,gBAAgB,GAAiC;IAC5D,MAAM,EAAE,gBAAgB;IACxB,GAAG,EAAE,aAAa;IAClB,KAAK,EAAE,sBAAsB;CAC9B,CAAC;AAEF,yCAAyC;AACzC,MAAM,aAAa,GAA4B,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAE1E,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,OAAQ,cAAoC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAqB;IAClD,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;QAC9B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC3D,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,CAAC;IAC1D,MAAM,QAAQ,GAAG,KAAK,CAAC,iBAAiB,IAAI,WAAW,CAAC;IAExD,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,gCAAgC,QAAQ,uBAAuB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3F,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;QACzC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO;gBACL,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,gBAAgB;gBACxB,QAAQ,EAAE,QAAQ;gBAClB,OAAO,EAAE,yBAAyB,QAAQ,YAAY,MAAM,WAAW;aACxE,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC9D,CAAC;IAED,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAClC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAChE,CAAC;IAEF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IACzD,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,OAAO,GACX,OAAO,CAAC,MAAM,GAAG,CAAC;QAChB,CAAC,CAAC,mCAAmC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,MAAM,wCAAwC;QACjH,CAAC,CAAC,SAAS,CAAC;IAEhB,OAAO;QACL,MAAM,EAAE,YAAY;QACpB,QAAQ,EAAE,MAAM;QAChB,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAE,CAAC,IAAI,EAAE;QACnD,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Pi tools `web_search` and `web_fetch`.
|
|
3
|
+
*
|
|
4
|
+
* Both tools follow the GitHub-extension convention: errors return a
|
|
5
|
+
* structured JSON payload (instead of throwing) and the JSON payload is
|
|
6
|
+
* stuffed into a single text content block via `jsonContent`. The agent
|
|
7
|
+
* sees the active provider name in every result so it can attribute
|
|
8
|
+
* findings.
|
|
9
|
+
*/
|
|
10
|
+
import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import type { RateLimiter } from "./rate-limit.js";
|
|
12
|
+
import type { Provider } from "./types.js";
|
|
13
|
+
export declare function buildWebSearchTools(provider: Provider, limiter: RateLimiter): ToolDefinition<any>[];
|