@lincoln504/pi-research 1.3.8 → 1.3.10
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 +7 -7
- package/dist/cli.mjs +105 -58
- package/dist/thread-worker.mjs +69 -31
- package/docs/ARCHITECTURE.md +9 -7
- package/docs/CHANGELOG.md +69 -0
- package/docs/CONFIGURATION.md +3 -1
- package/package.json +1 -1
- package/skills/pi-research/SKILL.md +1 -1
- package/src/cli.ts +5 -1
- package/src/config.ts +12 -0
- package/src/constants.ts +8 -17
- package/src/core/pi-version.ts +2 -2
- package/src/index.ts +5 -3
- package/src/infrastructure/browser/thread-worker.mjs +69 -31
- package/src/knowledge/embedder-utils.ts +19 -9
- package/src/orchestration/quick-research-orchestrator.ts +6 -6
- package/src/orchestration/researcher-executor.ts +8 -6
- package/src/orchestration/researcher.ts +13 -2
- package/src/utils/log-utils.ts +45 -13
- package/src/utils/user-agent.ts +26 -15
- package/src/web-research/scraper-utils.ts +70 -12
- package/src/web-research/web-scraper.ts +19 -9
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/Lincoln504/pi-research/main/docs/media/banner.jpg" alt="pi-research: free unlimited web search and knowledge store for
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Lincoln504/pi-research/main/docs/media/banner.jpg" alt="pi-research: free unlimited web search and knowledge store for agents" width="100%" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<strong>Free unlimited web search & knowledge store for
|
|
6
|
+
<strong>Free unlimited web search & knowledge store for agents</strong>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
@@ -31,7 +31,7 @@ Search and scraping run locally through a stealth browser with no search provide
|
|
|
31
31
|
|
|
32
32
|
### Install
|
|
33
33
|
|
|
34
|
-
As a [
|
|
34
|
+
As a [Pi](https://github.com/earendil-works/pi) Coding Agent extension:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
pi install npm:@lincoln504/pi-research
|
|
@@ -44,7 +44,7 @@ npm install -g @lincoln504/pi-research
|
|
|
44
44
|
pi-research skill install
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
In pi it works out of the box on your session's model and pi's configuration. Standalone use (agent skill
|
|
47
|
+
In pi it works out of the box on your session's model and pi's configuration. Standalone use ([agent skill](docs/AGENT-SKILL.md) or [SDK](docs/SDK.md)) needs a model configured. See [Configuration](docs/CONFIGURATION.md). The first install pulls the stealth browser engine, which takes a few minutes.
|
|
48
48
|
|
|
49
49
|
### How it works
|
|
50
50
|
|
|
@@ -56,14 +56,14 @@ Three depth levels (normal, deep, ultra) set the team size and number of rounds.
|
|
|
56
56
|
|
|
57
57
|
### Use cases
|
|
58
58
|
|
|
59
|
-
- Researching for your
|
|
59
|
+
- Researching for your pi. Plug and play with no API key needed.
|
|
60
60
|
- Researching from Claude Code, Codex, OpenClaw, or another coding agent while a cheaper or local model drives the run so it doesn't spend your main agent's budget.
|
|
61
61
|
- Keeping a persistent and searchable knowledge store of findings, scoped globally or per project.
|
|
62
62
|
- Building agent systems that find and read web content or populating a dataset of web sources.
|
|
63
63
|
|
|
64
64
|
### Why pi-research
|
|
65
65
|
|
|
66
|
-
- **No quota and no monthly fee.** Most AI search providers cap free searches and then charge, with results served from
|
|
66
|
+
- **No quota and no monthly fee.** Most AI search providers cap free searches and then charge, with results served from their index on their servers. pi-research searches and scrapes locally through DuckDuckGo in a stealth browser.
|
|
67
67
|
- **You own your index.** Every finding is written to a local [LanceDB](https://lancedb.com) knowledge store that seeds future runs, so repeat questions get faster and cheaper answers.
|
|
68
68
|
- **Cited reports rather than raw dumps.** You get one sourced Markdown report instead of raw web content pasted into the conversation. Context efficient and easy to verify.
|
|
69
69
|
- **Read-only by design.** The research agent cannot run shell commands or write, edit, or delete anything. It can read and search files, so treat it as you would any read-only agent with access to your machine.
|
|
@@ -74,7 +74,7 @@ Three depth levels (normal, deep, ultra) set the team size and number of rounds.
|
|
|
74
74
|
- Node.js >= 22.19.0
|
|
75
75
|
- An LLM with a 100k+ context window (bring your own key or use a local model)
|
|
76
76
|
- Internet access on a residential IP, since search, scraping, and YouTube transcripts get bot-blocked from datacenter/VPS/cloud IPs
|
|
77
|
-
- The
|
|
77
|
+
- The pi runtime. The pi extension uses the host's copy while the standalone CLI and agent skill install it as a dependency.
|
|
78
78
|
- Cloudflare and similar systems block scraping on some sites. A run flags pages it can't reach and compensates with a wide pool of search results to cite.
|
|
79
79
|
|
|
80
80
|
### Channels
|
package/dist/cli.mjs
CHANGED
|
@@ -247,22 +247,36 @@ function redactSecrets(message) {
|
|
|
247
247
|
function stripTerminalEscapes(message) {
|
|
248
248
|
return message.replace(TERMINAL_ESCAPE_PATTERN, "").replace(CONTROL_CHARS_PATTERN, " ");
|
|
249
249
|
}
|
|
250
|
-
var logContextStorage, MAX_LOG_MESSAGE_LENGTH, ANSI_PATTERN, URL_CREDENTIALS_PATTERN, COOKIE_HEADER_PATTERN, SENSITIVE_KV_PATTERN, KNOWN_TOKEN_PATTERN, JWT_PATTERN, BASIC_AUTH_PATTERN, BEARER_AUTH_PATTERN, PROVIDER_TOKEN_PATTERN, LONG_HEX_SECRET_PATTERN, CONTROL_CHARS_PATTERN, REDACT_SCAN_LENGTH, TOKEN_CHAR_PATTERN, REDACT_SCAN_EXTENSION_MAX, TERMINAL_ESCAPE_PATTERN;
|
|
250
|
+
var logContextStorage, MAX_LOG_MESSAGE_LENGTH, ANSI_PATTERN, URL_CREDENTIALS_PATTERN, B_LEFT, B_RIGHT, COOKIE_HEADER_PATTERN, SENSITIVE_KV_PATTERN, KNOWN_TOKEN_PATTERN, JWT_PATTERN, BASIC_AUTH_PATTERN, BEARER_AUTH_PATTERN, PROVIDER_TOKEN_PATTERN, LONG_HEX_SECRET_PATTERN, CONTROL_CHARS_PATTERN, REDACT_SCAN_LENGTH, TOKEN_CHAR_PATTERN, REDACT_SCAN_EXTENSION_MAX, TERMINAL_ESCAPE_PATTERN;
|
|
251
251
|
var init_log_utils = __esm({
|
|
252
252
|
"src/utils/log-utils.ts"() {
|
|
253
253
|
"use strict";
|
|
254
254
|
logContextStorage = new AsyncLocalStorage2();
|
|
255
255
|
MAX_LOG_MESSAGE_LENGTH = 1e4;
|
|
256
256
|
ANSI_PATTERN = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
257
|
-
URL_CREDENTIALS_PATTERN = /([a-z][a-z0-9+.-]
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
257
|
+
URL_CREDENTIALS_PATTERN = /([a-z][a-z0-9+.-]{0,31}:\/\/)[^/\s:@]+:[^/\s:@]+@/gi;
|
|
258
|
+
B_LEFT = "(?<![A-Za-z0-9])";
|
|
259
|
+
B_RIGHT = "(?![A-Za-z0-9])";
|
|
260
|
+
COOKIE_HEADER_PATTERN = new RegExp(
|
|
261
|
+
`${B_LEFT}(set[_-]?cookie|cookie)${B_RIGHT}(["']?\\s*[:=]\\s*["']?)([^\\r\\n]+)`,
|
|
262
|
+
"gi"
|
|
263
|
+
);
|
|
264
|
+
SENSITIVE_KV_PATTERN = new RegExp(
|
|
265
|
+
`${B_LEFT}(api[_-]?key|apikey|access[_-]?token|auth[_-]?token|refresh[_-]?token|secret|client[_-]?secret|password|passwd|pwd|authorization|bearer|set[_-]?cookie|cookie|session[_-]?id|session|csrf[_-]?token|xsrf[_-]?token|private[_-]?key|key)${B_RIGHT}(["']?\\s*[:=]\\s*["']?)([^\\s"',&)]+)`,
|
|
266
|
+
"gi"
|
|
267
|
+
);
|
|
268
|
+
KNOWN_TOKEN_PATTERN = new RegExp(
|
|
269
|
+
`${B_LEFT}(sk-[A-Za-z0-9_-]{16,}|gh[posru]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})${B_RIGHT}`,
|
|
270
|
+
"g"
|
|
271
|
+
);
|
|
272
|
+
JWT_PATTERN = new RegExp(`${B_LEFT}eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+`, "g");
|
|
273
|
+
BASIC_AUTH_PATTERN = new RegExp(`${B_LEFT}Basic\\s+[A-Za-z0-9+/]{16,}={0,2}`, "gi");
|
|
274
|
+
BEARER_AUTH_PATTERN = new RegExp(`${B_LEFT}Bearer\\s+[A-Za-z0-9._~+/-]+=*`, "gi");
|
|
275
|
+
PROVIDER_TOKEN_PATTERN = new RegExp(
|
|
276
|
+
`${B_LEFT}(AIza[0-9A-Za-z_-]{35}|ya29\\.[0-9A-Za-z_-]{20,}|[sp]k_(?:live|test)_[0-9A-Za-z]{16,})${B_RIGHT}`,
|
|
277
|
+
"g"
|
|
278
|
+
);
|
|
279
|
+
LONG_HEX_SECRET_PATTERN = new RegExp(`${B_LEFT}[0-9a-fA-F]{32,}${B_RIGHT}`, "g");
|
|
266
280
|
CONTROL_CHARS_PATTERN = /[\x00-\x1f\x7f]/g;
|
|
267
281
|
REDACT_SCAN_LENGTH = MAX_LOG_MESSAGE_LENGTH * 4;
|
|
268
282
|
TOKEN_CHAR_PATTERN = /[A-Za-z0-9_\-./+=]/;
|
|
@@ -2648,6 +2662,7 @@ function saveConfig(config, scope = "local", cwd = process.cwd(), changedKeys) {
|
|
|
2648
2662
|
PI_RESEARCH_LLM_THINKING_LEVEL: config.LLM_THINKING_LEVEL,
|
|
2649
2663
|
PI_RESEARCH_PLANNING_MAX_TOKENS: String(config.PLANNING_MAX_TOKENS),
|
|
2650
2664
|
PI_RESEARCH_SYNTHESIS_MAX_TOKENS: String(config.SYNTHESIS_MAX_TOKENS),
|
|
2665
|
+
PI_RESEARCH_RESEARCHER_MAX_TOKENS: String(config.RESEARCHER_MAX_TOKENS),
|
|
2651
2666
|
PI_RESEARCH_MIGRATION_STRATEGY: config.MIGRATION_STRATEGY,
|
|
2652
2667
|
PI_RESEARCH_CONSOLE_LOG: String(config.CONSOLE_LOG),
|
|
2653
2668
|
...config.RESEARCH_MODEL ? { PI_RESEARCH_MODEL: config.RESEARCH_MODEL } : {},
|
|
@@ -2812,6 +2827,7 @@ function createConfig(env, processEnv) {
|
|
|
2812
2827
|
LLM_THINKING_LEVEL: parseEnvEnum(e, "PI_RESEARCH_LLM_THINKING_LEVEL", ["off", "minimal", "low", "medium", "high"], DEFAULTS.LLM_THINKING_LEVEL),
|
|
2813
2828
|
PLANNING_MAX_TOKENS: parseEnvNumber(e, "PI_RESEARCH_PLANNING_MAX_TOKENS", DEFAULTS.PLANNING_MAX_TOKENS, 1024, 131072),
|
|
2814
2829
|
SYNTHESIS_MAX_TOKENS: parseEnvNumber(e, "PI_RESEARCH_SYNTHESIS_MAX_TOKENS", DEFAULTS.SYNTHESIS_MAX_TOKENS, 1024, 131072),
|
|
2830
|
+
RESEARCHER_MAX_TOKENS: parseEnvNumber(e, "PI_RESEARCH_RESEARCHER_MAX_TOKENS", DEFAULTS.RESEARCHER_MAX_TOKENS, 1024, 131072),
|
|
2815
2831
|
MIGRATION_STRATEGY: parseEnvEnum(e, "PI_RESEARCH_MIGRATION_STRATEGY", ["drop", "re-embed", "backup"], DEFAULTS.MIGRATION_STRATEGY),
|
|
2816
2832
|
CONSOLE_LOG: parseEnvBool(e, "PI_RESEARCH_CONSOLE_LOG", DEFAULTS.CONSOLE_LOG),
|
|
2817
2833
|
RESEARCH_MODEL: parseEnvString(e, "PI_RESEARCH_MODEL", DEFAULTS.RESEARCH_MODEL),
|
|
@@ -2999,6 +3015,15 @@ var init_config = __esm({
|
|
|
2999
3015
|
* the model's real maxTokens at call time.
|
|
3000
3016
|
* Not exposed in TUI — controlled via PI_RESEARCH_SYNTHESIS_MAX_TOKENS env var. */
|
|
3001
3017
|
SYNTHESIS_MAX_TOKENS: Type.Number({ minimum: 1024, maximum: 131072, default: 32768 }),
|
|
3018
|
+
/** Max OUTPUT tokens per researcher LLM call (deep and quick sub-agent sessions).
|
|
3019
|
+
* Without a cap the session inherits the model's full catalog ceiling as the
|
|
3020
|
+
* per-request max_tokens — ~230k for large-context models — which providers that
|
|
3021
|
+
* budget-check max_tokens up front (OpenRouter 402s against remaining credits)
|
|
3022
|
+
* reject outright on low balances, even though the actual response would cost
|
|
3023
|
+
* cents. Default 16384: researcher turns are tool calls plus a cited report, far
|
|
3024
|
+
* below this. Clamped to the model's real ceiling at session build time.
|
|
3025
|
+
* Not exposed in TUI — controlled via PI_RESEARCH_RESEARCHER_MAX_TOKENS env var. */
|
|
3026
|
+
RESEARCHER_MAX_TOKENS: Type.Number({ minimum: 1024, maximum: 131072, default: 16384 }),
|
|
3002
3027
|
/** LLM Model override for researcher sub-agents and knowledge synthesis.
|
|
3003
3028
|
* Format: provider/model-id (e.g. google/gemini-2.0-flash-001) or just model-id.
|
|
3004
3029
|
* When set, this overrides ctx.model for researcher sub-agents (both deep and quick)
|
|
@@ -3076,6 +3101,7 @@ var init_config = __esm({
|
|
|
3076
3101
|
"PI_RESEARCH_LLM_THINKING_LEVEL",
|
|
3077
3102
|
"PI_RESEARCH_PLANNING_MAX_TOKENS",
|
|
3078
3103
|
"PI_RESEARCH_SYNTHESIS_MAX_TOKENS",
|
|
3104
|
+
"PI_RESEARCH_RESEARCHER_MAX_TOKENS",
|
|
3079
3105
|
"PI_RESEARCH_MIGRATION_STRATEGY",
|
|
3080
3106
|
"PI_RESEARCH_CONSOLE_LOG",
|
|
3081
3107
|
"PI_RESEARCH_MODEL",
|
|
@@ -3118,8 +3144,7 @@ function getMaxScrapeBatches(config) {
|
|
|
3118
3144
|
}
|
|
3119
3145
|
}
|
|
3120
3146
|
function resolveExcludedTools(callerExclusions, configDisabled = []) {
|
|
3121
|
-
|
|
3122
|
-
return [.../* @__PURE__ */ new Set([...base, ...configDisabled])];
|
|
3147
|
+
return [.../* @__PURE__ */ new Set([...DEFAULT_EXCLUDED_TOOLS, ...callerExclusions ?? [], ...configDisabled])];
|
|
3123
3148
|
}
|
|
3124
3149
|
var MAX_GATHERING_CALLS, MAX_GREP_CALLS, MAX_SCRAPE_URLS, MAX_TEAM_SIZE_LEVEL_1, MAX_TEAM_SIZE_LEVEL_2, MAX_TEAM_SIZE_LEVEL_3, MAX_ROUNDS_LEVEL_1, MAX_ROUNDS_LEVEL_2, MAX_ROUNDS_LEVEL_3, MAX_EXTRA_ROUNDS_WITH_STEERING, OSV_TIMEOUT_MS, WEBGPU_PROBE_TIMEOUT_MS, MAX_QUERY_LENGTH, MAX_FILENAME_QUERY_LENGTH, MIN_QUERY_LENGTH, MAX_EXPORT_RETRIES, DEFAULT_MAX_RETRIES, DEFAULT_INITIAL_DELAY_MS, DEFAULT_MAX_DELAY_MS, DEFAULT_MODEL_CONTEXT_WINDOW, BATCH_2_DEFAULT_CONCURRENCY, MAX_SCRAPE_CONTENT_CHARS_PER_DOC, RESEARCHER_LAUNCH_DELAY_MS, MAX_QUERIES_PER_RESEARCHER_LEVEL_1, MAX_QUERIES_PER_RESEARCHER_LEVEL_2, MAX_QUERIES_PER_RESEARCHER_LEVEL_3, DEFAULT_EXCLUDED_TOOLS;
|
|
3125
3150
|
var init_constants = __esm({
|
|
@@ -5131,14 +5156,16 @@ function unregisterGlobalEmbedder() {
|
|
|
5131
5156
|
async function initializeDawnWebGPU() {
|
|
5132
5157
|
if (dawnInitialized) return true;
|
|
5133
5158
|
try {
|
|
5134
|
-
const
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5159
|
+
const adapter = await logger.runCapturingStderr(async () => {
|
|
5160
|
+
const { create, globals } = await import("webgpu");
|
|
5161
|
+
Object.assign(globalThis, globals);
|
|
5162
|
+
Object.defineProperty(globalThis, "navigator", {
|
|
5163
|
+
value: { gpu: create([]) },
|
|
5164
|
+
writable: true,
|
|
5165
|
+
configurable: true
|
|
5166
|
+
});
|
|
5167
|
+
return await globalThis.navigator.gpu.requestAdapter({ powerPreference: "high-performance" });
|
|
5140
5168
|
});
|
|
5141
|
-
const adapter = await globalThis.navigator.gpu.requestAdapter({ powerPreference: "high-performance" });
|
|
5142
5169
|
if (!adapter) {
|
|
5143
5170
|
logger.warn("[embedder] No WebGPU adapter available \u2014 using CPU.");
|
|
5144
5171
|
return false;
|
|
@@ -16808,21 +16835,21 @@ init_error_tracker();
|
|
|
16808
16835
|
|
|
16809
16836
|
// src/utils/user-agent.ts
|
|
16810
16837
|
var USER_AGENTS = [
|
|
16811
|
-
// Chrome on Windows
|
|
16812
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
16813
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
16838
|
+
// Chrome on Windows
|
|
16839
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
|
|
16840
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
|
|
16814
16841
|
// Chrome on macOS
|
|
16815
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
16816
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
16842
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
|
|
16843
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
|
|
16817
16844
|
// Chrome on Linux
|
|
16818
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
16819
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
16820
|
-
// Firefox on Windows
|
|
16821
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:
|
|
16822
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:
|
|
16845
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
|
|
16846
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
|
|
16847
|
+
// Firefox on Windows
|
|
16848
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:153.0) Gecko/20100101 Firefox/153.0",
|
|
16849
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0",
|
|
16823
16850
|
// Firefox on macOS
|
|
16824
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:
|
|
16825
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:
|
|
16851
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:153.0) Gecko/20100101 Firefox/153.0",
|
|
16852
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0"
|
|
16826
16853
|
];
|
|
16827
16854
|
|
|
16828
16855
|
// src/web-research/scraper-types.ts
|
|
@@ -17008,17 +17035,30 @@ function ssrfSafeLookup(hostname, options, callback, resolveImpl = dnsCb.lookup)
|
|
|
17008
17035
|
return callback(null, safe[0].address, safe[0].family);
|
|
17009
17036
|
});
|
|
17010
17037
|
}
|
|
17011
|
-
|
|
17012
|
-
|
|
17013
|
-
|
|
17038
|
+
function isSsrfBlockError(error) {
|
|
17039
|
+
let e = error;
|
|
17040
|
+
for (let depth = 0; depth < 5 && e instanceof Error; depth++) {
|
|
17041
|
+
if (e.code === "ESSRFBLOCKED" || e.message.includes("not allowed")) {
|
|
17042
|
+
return true;
|
|
17043
|
+
}
|
|
17044
|
+
e = e.cause;
|
|
17045
|
+
}
|
|
17046
|
+
return false;
|
|
17047
|
+
}
|
|
17048
|
+
var cachedSsrfFetcher = void 0;
|
|
17049
|
+
async function getSsrfSafeFetcher() {
|
|
17050
|
+
if (cachedSsrfFetcher !== void 0) return cachedSsrfFetcher;
|
|
17014
17051
|
try {
|
|
17015
|
-
const { Agent: Agent2 } = await import("undici");
|
|
17016
|
-
|
|
17052
|
+
const { Agent: Agent2, fetch: undiciFetch } = await import("undici");
|
|
17053
|
+
cachedSsrfFetcher = {
|
|
17054
|
+
fetch: undiciFetch,
|
|
17055
|
+
dispatcher: new Agent2({ connect: { lookup: ssrfSafeLookup } })
|
|
17056
|
+
};
|
|
17017
17057
|
} catch (e) {
|
|
17018
17058
|
logger.warn(`[Scrapers] undici unavailable; connect-time SSRF pinning disabled (request-time validation still active): ${e instanceof Error ? e.message : String(e)}`);
|
|
17019
|
-
|
|
17059
|
+
cachedSsrfFetcher = null;
|
|
17020
17060
|
}
|
|
17021
|
-
return
|
|
17061
|
+
return cachedSsrfFetcher;
|
|
17022
17062
|
}
|
|
17023
17063
|
function formatErrorWithCause(err) {
|
|
17024
17064
|
try {
|
|
@@ -17068,6 +17108,7 @@ function isPrivateIpv6(ip) {
|
|
|
17068
17108
|
if (normalized.startsWith("2002:")) return true;
|
|
17069
17109
|
if (/^2001:0{0,4}:/.test(normalized) || normalized.startsWith("2001::")) return true;
|
|
17070
17110
|
if (normalized.startsWith("64:ff9b:")) return true;
|
|
17111
|
+
if (/^::(?:\d{1,3}(?:\.\d{1,3}){3}|[0-9a-f]{1,4}(?::[0-9a-f]{1,4})?)$/.test(normalized)) return true;
|
|
17071
17112
|
if (normalized === "::" || normalized === "0:0:0:0:0:0:0:0") return true;
|
|
17072
17113
|
return false;
|
|
17073
17114
|
}
|
|
@@ -17267,7 +17308,7 @@ function decodeHtmlBody(bytes, contentTypeHeader) {
|
|
|
17267
17308
|
}
|
|
17268
17309
|
}
|
|
17269
17310
|
function trackFetchLayerFailure(url, error) {
|
|
17270
|
-
const isSsrf = error
|
|
17311
|
+
const isSsrf = isSsrfBlockError(error);
|
|
17271
17312
|
errorTracker.trackError(error instanceof Error ? error : String(error), {
|
|
17272
17313
|
component: "scrapers",
|
|
17273
17314
|
operation: "fetch",
|
|
@@ -17279,7 +17320,8 @@ function trackFetchLayerFailure(url, error) {
|
|
|
17279
17320
|
}
|
|
17280
17321
|
async function scrapeWithFetch(url, signal) {
|
|
17281
17322
|
await validateUrlForSSRF(url);
|
|
17282
|
-
const
|
|
17323
|
+
const fetcher = await getSsrfSafeFetcher();
|
|
17324
|
+
const doFetch = fetcher?.fetch ?? ((u, init) => fetch(u, init));
|
|
17283
17325
|
const controller = new AbortController();
|
|
17284
17326
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_LAYER_TIMEOUT);
|
|
17285
17327
|
safeUnref(timeoutId);
|
|
@@ -17296,16 +17338,17 @@ async function scrapeWithFetch(url, signal) {
|
|
|
17296
17338
|
const MAX_REDIRECTS = 10;
|
|
17297
17339
|
let currentUrl = url;
|
|
17298
17340
|
let response;
|
|
17341
|
+
const userAgent = getRandomUserAgent();
|
|
17299
17342
|
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
17300
|
-
response = await
|
|
17343
|
+
response = await doFetch(currentUrl, {
|
|
17301
17344
|
signal: controller.signal,
|
|
17302
17345
|
redirect: "manual",
|
|
17303
17346
|
headers: {
|
|
17304
|
-
"User-Agent":
|
|
17347
|
+
"User-Agent": userAgent,
|
|
17305
17348
|
"Accept": "text/html,application/xhtml+xml,application/pdf,*/*;q=0.8"
|
|
17306
17349
|
},
|
|
17307
|
-
//
|
|
17308
|
-
...
|
|
17350
|
+
// Only ever attached alongside the fetch that honours it (see above).
|
|
17351
|
+
...fetcher ? { dispatcher: fetcher.dispatcher } : {}
|
|
17309
17352
|
});
|
|
17310
17353
|
if (response.status >= 300 && response.status < 400) {
|
|
17311
17354
|
const location = response.headers.get("location");
|
|
@@ -17406,7 +17449,7 @@ async function scrapeWithFetch(url, signal) {
|
|
|
17406
17449
|
metrics.observe("scrape_latency_ms", fetchDuration, { layer: "fetch", content_type: "html", status: "success" });
|
|
17407
17450
|
return { source: "fetch", layer: "fetch", markdown };
|
|
17408
17451
|
} catch (error) {
|
|
17409
|
-
if (error
|
|
17452
|
+
if (isSsrfBlockError(error)) {
|
|
17410
17453
|
metrics.increment("scrape_operations_total", 1, { layer: "fetch", status: "ssrf_blocked" });
|
|
17411
17454
|
metrics.observe("scrape_latency_ms", Date.now() - fetchStart, { layer: "fetch", status: "ssrf_blocked" });
|
|
17412
17455
|
throw error;
|
|
@@ -21506,6 +21549,8 @@ async function createResearcherSession(options) {
|
|
|
21506
21549
|
cwd
|
|
21507
21550
|
});
|
|
21508
21551
|
logger.info(`[Researcher] Using model for researcher ${researcherId}: ${modelToUse.provider}/${modelToUse.id}`);
|
|
21552
|
+
const outputCap = config?.RESEARCHER_MAX_TOKENS ?? 16384;
|
|
21553
|
+
const sessionModel = modelToUse.maxTokens > outputCap ? { ...modelToUse, maxTokens: outputCap } : modelToUse;
|
|
21509
21554
|
try {
|
|
21510
21555
|
const tracker = new ToolUsageTracker(createDefaultToolLimits(config));
|
|
21511
21556
|
const globalLinks = updateGlobalLinks || (() => {
|
|
@@ -21546,7 +21591,7 @@ async function createResearcherSession(options) {
|
|
|
21546
21591
|
sessionManager: SessionManager.inMemory(),
|
|
21547
21592
|
// Each researcher gets its own isolated session
|
|
21548
21593
|
settingsManager: researcherSettings,
|
|
21549
|
-
model:
|
|
21594
|
+
model: sessionModel,
|
|
21550
21595
|
// pi 0.80.8 replaced the SDK's `modelRegistry`/`authStorage` session options
|
|
21551
21596
|
// with the async `modelRuntime`. Pass the runtime backing OUR registry when
|
|
21552
21597
|
// this run built one (standalone CLI/SDK): it carries any explicit API key
|
|
@@ -21577,7 +21622,7 @@ async function createResearcherSession(options) {
|
|
|
21577
21622
|
throw new Error("Session creation returned invalid result");
|
|
21578
21623
|
}
|
|
21579
21624
|
sessionRef.session = result.session;
|
|
21580
|
-
return { session: result.session, resolvedModel:
|
|
21625
|
+
return { session: result.session, resolvedModel: sessionModel };
|
|
21581
21626
|
} catch (tailError) {
|
|
21582
21627
|
const session = result?.session;
|
|
21583
21628
|
if (session && typeof session.abort === "function") {
|
|
@@ -21600,7 +21645,7 @@ async function createResearcherSession(options) {
|
|
|
21600
21645
|
|
|
21601
21646
|
// src/orchestration/researcher-executor.ts
|
|
21602
21647
|
init_text_utils();
|
|
21603
|
-
|
|
21648
|
+
init_llm_usage();
|
|
21604
21649
|
init_logger();
|
|
21605
21650
|
init_metrics();
|
|
21606
21651
|
init_service_interfaces();
|
|
@@ -22036,12 +22081,13 @@ ${prompt}`);
|
|
|
22036
22081
|
if (msg2?.["role"] !== "assistant") return;
|
|
22037
22082
|
const rawUsage = msg2["usage"];
|
|
22038
22083
|
if (rawUsage) {
|
|
22039
|
-
const { tokens, cost } =
|
|
22084
|
+
const { tokens, cost } = recordLlmUsage(resolvedModel, rawUsage, {
|
|
22085
|
+
component: "researcher",
|
|
22086
|
+
complexity,
|
|
22087
|
+
observer: { onTokensConsumed: (t, c) => safeObserve(() => observer?.onTokensConsumed?.(t, c)) }
|
|
22088
|
+
});
|
|
22040
22089
|
if (tokens > 0 || cost > 0) {
|
|
22041
|
-
metrics.increment("llm_tokens_total", tokens, { component: "researcher", complexity: String(complexity) });
|
|
22042
|
-
metrics.increment("llm_cost_total", cost, { component: "researcher", complexity: String(complexity) });
|
|
22043
22090
|
safeObserve(() => observer?.onResearcherProgress?.(id, void 0, tokens, cost));
|
|
22044
|
-
safeObserve(() => observer?.onTokensConsumed?.(tokens, cost));
|
|
22045
22091
|
}
|
|
22046
22092
|
}
|
|
22047
22093
|
} else if (event.type === "tool_execution_start") {
|
|
@@ -22168,7 +22214,7 @@ ${responseText}`);
|
|
|
22168
22214
|
|
|
22169
22215
|
// src/orchestration/quick-research-orchestrator.ts
|
|
22170
22216
|
init_prompts();
|
|
22171
|
-
|
|
22217
|
+
init_llm_usage();
|
|
22172
22218
|
init_logger();
|
|
22173
22219
|
init_config();
|
|
22174
22220
|
init_text_utils();
|
|
@@ -22449,12 +22495,13 @@ ${prompt}`);
|
|
|
22449
22495
|
if (msg2?.["role"] !== "assistant") return;
|
|
22450
22496
|
const rawUsage = msg2["usage"];
|
|
22451
22497
|
if (rawUsage) {
|
|
22452
|
-
const { tokens, cost } =
|
|
22498
|
+
const { tokens, cost } = recordLlmUsage(resolvedModel, rawUsage, {
|
|
22499
|
+
component: "quick_researcher",
|
|
22500
|
+
complexity: 0,
|
|
22501
|
+
observer
|
|
22502
|
+
});
|
|
22453
22503
|
if (tokens > 0 || cost > 0) {
|
|
22454
|
-
metrics.increment("llm_tokens_total", tokens, { component: "quick_researcher", complexity: "0" });
|
|
22455
|
-
metrics.increment("llm_cost_total", cost, { component: "quick_researcher", complexity: "0" });
|
|
22456
22504
|
observer?.onResearcherProgress?.("quick", void 0, tokens, cost);
|
|
22457
|
-
observer?.onTokensConsumed?.(tokens, cost);
|
|
22458
22505
|
}
|
|
22459
22506
|
}
|
|
22460
22507
|
} else if (event.type === "tool_execution_start") {
|
|
@@ -25674,7 +25721,7 @@ function parseArgs(argv) {
|
|
|
25674
25721
|
}
|
|
25675
25722
|
if (a === "--depth") {
|
|
25676
25723
|
const v = rest[++i];
|
|
25677
|
-
const n = v
|
|
25724
|
+
const n = v !== void 0 && /^\d+$/.test(v.trim()) ? Number(v.trim()) : NaN;
|
|
25678
25725
|
if (isNaN(n) || n < 0 || n > 3) throw new UsageError("--depth must be an integer 0\u20133.");
|
|
25679
25726
|
depth = n;
|
|
25680
25727
|
} else if (a === "--model") {
|
package/dist/thread-worker.mjs
CHANGED
|
@@ -77,22 +77,36 @@ function redactSecrets(message) {
|
|
|
77
77
|
function stripTerminalEscapes(message) {
|
|
78
78
|
return message.replace(TERMINAL_ESCAPE_PATTERN, "").replace(CONTROL_CHARS_PATTERN, " ");
|
|
79
79
|
}
|
|
80
|
-
var logContextStorage, MAX_LOG_MESSAGE_LENGTH, ANSI_PATTERN, URL_CREDENTIALS_PATTERN, COOKIE_HEADER_PATTERN, SENSITIVE_KV_PATTERN, KNOWN_TOKEN_PATTERN, JWT_PATTERN, BASIC_AUTH_PATTERN, BEARER_AUTH_PATTERN, PROVIDER_TOKEN_PATTERN, LONG_HEX_SECRET_PATTERN, CONTROL_CHARS_PATTERN, REDACT_SCAN_LENGTH, TOKEN_CHAR_PATTERN, REDACT_SCAN_EXTENSION_MAX, TERMINAL_ESCAPE_PATTERN;
|
|
80
|
+
var logContextStorage, MAX_LOG_MESSAGE_LENGTH, ANSI_PATTERN, URL_CREDENTIALS_PATTERN, B_LEFT, B_RIGHT, COOKIE_HEADER_PATTERN, SENSITIVE_KV_PATTERN, KNOWN_TOKEN_PATTERN, JWT_PATTERN, BASIC_AUTH_PATTERN, BEARER_AUTH_PATTERN, PROVIDER_TOKEN_PATTERN, LONG_HEX_SECRET_PATTERN, CONTROL_CHARS_PATTERN, REDACT_SCAN_LENGTH, TOKEN_CHAR_PATTERN, REDACT_SCAN_EXTENSION_MAX, TERMINAL_ESCAPE_PATTERN;
|
|
81
81
|
var init_log_utils = __esm({
|
|
82
82
|
"src/utils/log-utils.ts"() {
|
|
83
83
|
"use strict";
|
|
84
84
|
logContextStorage = new AsyncLocalStorage();
|
|
85
85
|
MAX_LOG_MESSAGE_LENGTH = 1e4;
|
|
86
86
|
ANSI_PATTERN = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
87
|
-
URL_CREDENTIALS_PATTERN = /([a-z][a-z0-9+.-]
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
URL_CREDENTIALS_PATTERN = /([a-z][a-z0-9+.-]{0,31}:\/\/)[^/\s:@]+:[^/\s:@]+@/gi;
|
|
88
|
+
B_LEFT = "(?<![A-Za-z0-9])";
|
|
89
|
+
B_RIGHT = "(?![A-Za-z0-9])";
|
|
90
|
+
COOKIE_HEADER_PATTERN = new RegExp(
|
|
91
|
+
`${B_LEFT}(set[_-]?cookie|cookie)${B_RIGHT}(["']?\\s*[:=]\\s*["']?)([^\\r\\n]+)`,
|
|
92
|
+
"gi"
|
|
93
|
+
);
|
|
94
|
+
SENSITIVE_KV_PATTERN = new RegExp(
|
|
95
|
+
`${B_LEFT}(api[_-]?key|apikey|access[_-]?token|auth[_-]?token|refresh[_-]?token|secret|client[_-]?secret|password|passwd|pwd|authorization|bearer|set[_-]?cookie|cookie|session[_-]?id|session|csrf[_-]?token|xsrf[_-]?token|private[_-]?key|key)${B_RIGHT}(["']?\\s*[:=]\\s*["']?)([^\\s"',&)]+)`,
|
|
96
|
+
"gi"
|
|
97
|
+
);
|
|
98
|
+
KNOWN_TOKEN_PATTERN = new RegExp(
|
|
99
|
+
`${B_LEFT}(sk-[A-Za-z0-9_-]{16,}|gh[posru]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})${B_RIGHT}`,
|
|
100
|
+
"g"
|
|
101
|
+
);
|
|
102
|
+
JWT_PATTERN = new RegExp(`${B_LEFT}eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+`, "g");
|
|
103
|
+
BASIC_AUTH_PATTERN = new RegExp(`${B_LEFT}Basic\\s+[A-Za-z0-9+/]{16,}={0,2}`, "gi");
|
|
104
|
+
BEARER_AUTH_PATTERN = new RegExp(`${B_LEFT}Bearer\\s+[A-Za-z0-9._~+/-]+=*`, "gi");
|
|
105
|
+
PROVIDER_TOKEN_PATTERN = new RegExp(
|
|
106
|
+
`${B_LEFT}(AIza[0-9A-Za-z_-]{35}|ya29\\.[0-9A-Za-z_-]{20,}|[sp]k_(?:live|test)_[0-9A-Za-z]{16,})${B_RIGHT}`,
|
|
107
|
+
"g"
|
|
108
|
+
);
|
|
109
|
+
LONG_HEX_SECRET_PATTERN = new RegExp(`${B_LEFT}[0-9a-fA-F]{32,}${B_RIGHT}`, "g");
|
|
96
110
|
CONTROL_CHARS_PATTERN = /[\x00-\x1f\x7f]/g;
|
|
97
111
|
REDACT_SCAN_LENGTH = MAX_LOG_MESSAGE_LENGTH * 4;
|
|
98
112
|
TOKEN_CHAR_PATTERN = /[A-Za-z0-9_\-./+=]/;
|
|
@@ -110,21 +124,21 @@ var init_user_agent = __esm({
|
|
|
110
124
|
"src/utils/user-agent.ts"() {
|
|
111
125
|
"use strict";
|
|
112
126
|
USER_AGENTS = [
|
|
113
|
-
// Chrome on Windows
|
|
114
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
115
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
127
|
+
// Chrome on Windows
|
|
128
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
|
|
129
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
|
|
116
130
|
// Chrome on macOS
|
|
117
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
118
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
131
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
|
|
132
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
|
|
119
133
|
// Chrome on Linux
|
|
120
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
121
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
122
|
-
// Firefox on Windows
|
|
123
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:
|
|
124
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:
|
|
134
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
|
|
135
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
|
|
136
|
+
// Firefox on Windows
|
|
137
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:153.0) Gecko/20100101 Firefox/153.0",
|
|
138
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0",
|
|
125
139
|
// Firefox on macOS
|
|
126
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:
|
|
127
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:
|
|
140
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:153.0) Gecko/20100101 Firefox/153.0",
|
|
141
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0"
|
|
128
142
|
];
|
|
129
143
|
}
|
|
130
144
|
});
|
|
@@ -1498,8 +1512,9 @@ __export(scraper_utils_exports, {
|
|
|
1498
1512
|
extractDomain: () => extractDomain,
|
|
1499
1513
|
formatErrorWithCause: () => formatErrorWithCause,
|
|
1500
1514
|
getRandomUserAgent: () => getRandomUserAgent,
|
|
1501
|
-
|
|
1515
|
+
getSsrfSafeFetcher: () => getSsrfSafeFetcher,
|
|
1502
1516
|
isBenignScrapeFailure: () => isBenignScrapeFailure,
|
|
1517
|
+
isSsrfBlockError: () => isSsrfBlockError,
|
|
1503
1518
|
ssrfSafeLookup: () => ssrfSafeLookup,
|
|
1504
1519
|
stripImageLinks: () => stripImageLinks,
|
|
1505
1520
|
validateContent: () => validateContent,
|
|
@@ -1619,16 +1634,29 @@ function ssrfSafeLookup(hostname, options, callback, resolveImpl = dnsCb.lookup)
|
|
|
1619
1634
|
return callback(null, safe[0].address, safe[0].family);
|
|
1620
1635
|
});
|
|
1621
1636
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1637
|
+
function isSsrfBlockError(error) {
|
|
1638
|
+
let e = error;
|
|
1639
|
+
for (let depth = 0; depth < 5 && e instanceof Error; depth++) {
|
|
1640
|
+
if (e.code === "ESSRFBLOCKED" || e.message.includes("not allowed")) {
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1643
|
+
e = e.cause;
|
|
1644
|
+
}
|
|
1645
|
+
return false;
|
|
1646
|
+
}
|
|
1647
|
+
async function getSsrfSafeFetcher() {
|
|
1648
|
+
if (cachedSsrfFetcher !== void 0) return cachedSsrfFetcher;
|
|
1624
1649
|
try {
|
|
1625
|
-
const { Agent } = await import("undici");
|
|
1626
|
-
|
|
1650
|
+
const { Agent, fetch: undiciFetch } = await import("undici");
|
|
1651
|
+
cachedSsrfFetcher = {
|
|
1652
|
+
fetch: undiciFetch,
|
|
1653
|
+
dispatcher: new Agent({ connect: { lookup: ssrfSafeLookup } })
|
|
1654
|
+
};
|
|
1627
1655
|
} catch (e) {
|
|
1628
1656
|
logger.warn(`[Scrapers] undici unavailable; connect-time SSRF pinning disabled (request-time validation still active): ${e instanceof Error ? e.message : String(e)}`);
|
|
1629
|
-
|
|
1657
|
+
cachedSsrfFetcher = null;
|
|
1630
1658
|
}
|
|
1631
|
-
return
|
|
1659
|
+
return cachedSsrfFetcher;
|
|
1632
1660
|
}
|
|
1633
1661
|
function formatErrorWithCause(err) {
|
|
1634
1662
|
try {
|
|
@@ -1678,6 +1706,7 @@ function isPrivateIpv6(ip) {
|
|
|
1678
1706
|
if (normalized.startsWith("2002:")) return true;
|
|
1679
1707
|
if (/^2001:0{0,4}:/.test(normalized) || normalized.startsWith("2001::")) return true;
|
|
1680
1708
|
if (normalized.startsWith("64:ff9b:")) return true;
|
|
1709
|
+
if (/^::(?:\d{1,3}(?:\.\d{1,3}){3}|[0-9a-f]{1,4}(?::[0-9a-f]{1,4})?)$/.test(normalized)) return true;
|
|
1681
1710
|
if (normalized === "::" || normalized === "0:0:0:0:0:0:0:0") return true;
|
|
1682
1711
|
return false;
|
|
1683
1712
|
}
|
|
@@ -1761,7 +1790,7 @@ function validateContent(html, markdown, url) {
|
|
|
1761
1790
|
throw error;
|
|
1762
1791
|
}
|
|
1763
1792
|
}
|
|
1764
|
-
var
|
|
1793
|
+
var cachedSsrfFetcher;
|
|
1765
1794
|
var init_scraper_utils = __esm({
|
|
1766
1795
|
"src/web-research/scraper-utils.ts"() {
|
|
1767
1796
|
"use strict";
|
|
@@ -1770,7 +1799,7 @@ var init_scraper_utils = __esm({
|
|
|
1770
1799
|
init_logger();
|
|
1771
1800
|
init_scraper_types();
|
|
1772
1801
|
init_browser_error_utils();
|
|
1773
|
-
|
|
1802
|
+
cachedSsrfFetcher = void 0;
|
|
1774
1803
|
}
|
|
1775
1804
|
});
|
|
1776
1805
|
|
|
@@ -2515,6 +2544,15 @@ var ConfigSchema = Type.Object({
|
|
|
2515
2544
|
* the model's real maxTokens at call time.
|
|
2516
2545
|
* Not exposed in TUI — controlled via PI_RESEARCH_SYNTHESIS_MAX_TOKENS env var. */
|
|
2517
2546
|
SYNTHESIS_MAX_TOKENS: Type.Number({ minimum: 1024, maximum: 131072, default: 32768 }),
|
|
2547
|
+
/** Max OUTPUT tokens per researcher LLM call (deep and quick sub-agent sessions).
|
|
2548
|
+
* Without a cap the session inherits the model's full catalog ceiling as the
|
|
2549
|
+
* per-request max_tokens — ~230k for large-context models — which providers that
|
|
2550
|
+
* budget-check max_tokens up front (OpenRouter 402s against remaining credits)
|
|
2551
|
+
* reject outright on low balances, even though the actual response would cost
|
|
2552
|
+
* cents. Default 16384: researcher turns are tool calls plus a cited report, far
|
|
2553
|
+
* below this. Clamped to the model's real ceiling at session build time.
|
|
2554
|
+
* Not exposed in TUI — controlled via PI_RESEARCH_RESEARCHER_MAX_TOKENS env var. */
|
|
2555
|
+
RESEARCHER_MAX_TOKENS: Type.Number({ minimum: 1024, maximum: 131072, default: 16384 }),
|
|
2518
2556
|
/** LLM Model override for researcher sub-agents and knowledge synthesis.
|
|
2519
2557
|
* Format: provider/model-id (e.g. google/gemini-2.0-flash-001) or just model-id.
|
|
2520
2558
|
* When set, this overrides ctx.model for researcher sub-agents (both deep and quick)
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -115,12 +115,14 @@ agent to invoke:
|
|
|
115
115
|
In deep research `search` is excluded — the coordinator runs the search burst and hands
|
|
116
116
|
out URLs directly.
|
|
117
117
|
|
|
118
|
-
`grep` is excluded
|
|
119
|
-
|
|
120
|
-
searching the local filesystem.
|
|
121
|
-
on the CLI, the `excludeTools` tool parameter in the extension
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
`grep` is excluded at **every** depth and on every front-end (CLI, SDK, agent skill,
|
|
119
|
+
pi extension): this is web research, and a capable model otherwise spends turns
|
|
120
|
+
searching the local filesystem. Both exclusion surfaces — the `excludeTools` list
|
|
121
|
+
(`--exclude-tools` on the CLI, the `excludeTools` tool parameter in the extension) and
|
|
122
|
+
`PI_RESEARCH_DISABLED_TOOLS` — are strictly additive on top of that default: they can
|
|
123
|
+
only ever remove capabilities (see [CONFIGURATION.md](CONFIGURATION.md)). Before 1.3.10
|
|
124
|
+
a non-empty `excludeTools` list replaced the default, so naming any other tool silently
|
|
125
|
+
re-enabled `grep`.
|
|
124
126
|
|
|
125
127
|
Researchers cannot write files, run shell commands, or reach the network outside these
|
|
126
128
|
tools.
|
|
@@ -318,7 +320,7 @@ other). This sits above `@lancedb/lancedb` 0.29's declared Arrow peer ceiling
|
|
|
318
320
|
whenever `@lancedb/lancedb` is upgraded.
|
|
319
321
|
|
|
320
322
|
Pinned validation library — `typebox` is pinned to the exact version the pi host packages
|
|
321
|
-
depend on (`@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent` 0.84.
|
|
323
|
+
depend on (`@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent` 0.84.2 pin `1.3.7`). Every
|
|
322
324
|
tool's parameter schema is built with TypeBox here and handed across the boundary to pi's
|
|
323
325
|
tool system, so the two must agree on `Value.Check`/`Convert` semantics. A floating `^1.1.38`
|
|
324
326
|
range let a fresh consumer install resolve pi-research to a newer TypeBox than pi's, shipping
|