pi-web-providers 3.2.0 → 3.4.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/README.md +24 -3
- package/dist/index.js +1697 -626
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
3
|
-
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
4
3
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { basename,
|
|
4
|
+
import { basename, join as join3 } from "node:path";
|
|
6
5
|
import {
|
|
6
|
+
copyToClipboard,
|
|
7
7
|
DEFAULT_MAX_BYTES,
|
|
8
8
|
DEFAULT_MAX_LINES,
|
|
9
9
|
formatSize as formatSize2,
|
|
10
|
-
getMarkdownTheme,
|
|
10
|
+
getMarkdownTheme as getMarkdownTheme2,
|
|
11
11
|
truncateHead
|
|
12
12
|
} from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import {
|
|
14
14
|
Box,
|
|
15
15
|
Editor,
|
|
16
|
-
getKeybindings,
|
|
17
|
-
Key,
|
|
18
|
-
Markdown,
|
|
19
|
-
matchesKey,
|
|
16
|
+
getKeybindings as getKeybindings2,
|
|
17
|
+
Key as Key2,
|
|
18
|
+
Markdown as Markdown2,
|
|
19
|
+
matchesKey as matchesKey2,
|
|
20
20
|
Text,
|
|
21
|
-
truncateToWidth,
|
|
22
|
-
visibleWidth,
|
|
21
|
+
truncateToWidth as truncateToWidth2,
|
|
22
|
+
visibleWidth as visibleWidth2,
|
|
23
23
|
wrapTextWithAnsi
|
|
24
24
|
} from "@earendil-works/pi-tui";
|
|
25
25
|
import { Type as Type16 } from "typebox";
|
|
@@ -2454,39 +2454,39 @@ function createDefaultExecutionSettings(overrides = {}) {
|
|
|
2454
2454
|
function normalizeDiagnosticDetail(detail) {
|
|
2455
2455
|
return detail.trim().replace(/[.\s]+$/u, "");
|
|
2456
2456
|
}
|
|
2457
|
-
function startsWithProviderLabel(
|
|
2458
|
-
return detail.toLowerCase().startsWith(
|
|
2457
|
+
function startsWithProviderLabel(providerLabel2, detail) {
|
|
2458
|
+
return detail.toLowerCase().startsWith(providerLabel2.toLowerCase());
|
|
2459
2459
|
}
|
|
2460
2460
|
function readsLikeProviderClause(detail) {
|
|
2461
2461
|
return /^(is|has|was|returned|did|does|could|cannot|must|should|search\b|contents\b|answer\b|research\b|output\b|response\b|result\b|query\b|no\b|missing\b|deep research\b)/iu.test(
|
|
2462
2462
|
detail
|
|
2463
2463
|
);
|
|
2464
2464
|
}
|
|
2465
|
-
function formatProviderDiagnostic(
|
|
2465
|
+
function formatProviderDiagnostic(providerLabel2, detail) {
|
|
2466
2466
|
const normalized = normalizeDiagnosticDetail(detail);
|
|
2467
2467
|
if (!normalized) {
|
|
2468
|
-
return `${
|
|
2468
|
+
return `${providerLabel2} failed.`;
|
|
2469
2469
|
}
|
|
2470
|
-
if (startsWithProviderLabel(
|
|
2470
|
+
if (startsWithProviderLabel(providerLabel2, normalized)) {
|
|
2471
2471
|
return `${normalized}.`;
|
|
2472
2472
|
}
|
|
2473
2473
|
if (readsLikeProviderClause(normalized)) {
|
|
2474
|
-
return `${
|
|
2474
|
+
return `${providerLabel2} ${normalized}.`;
|
|
2475
2475
|
}
|
|
2476
|
-
return `${
|
|
2476
|
+
return `${providerLabel2}: ${normalized}.`;
|
|
2477
2477
|
}
|
|
2478
|
-
function formatResearchTerminalDiagnostic(
|
|
2478
|
+
function formatResearchTerminalDiagnostic(providerLabel2, status, detail) {
|
|
2479
2479
|
const normalized = detail ? normalizeDiagnosticDetail(detail) : "";
|
|
2480
2480
|
if (!normalized) {
|
|
2481
|
-
return status === "cancelled" ? `${
|
|
2481
|
+
return status === "cancelled" ? `${providerLabel2} research was canceled.` : `${providerLabel2} research failed.`;
|
|
2482
2482
|
}
|
|
2483
|
-
if (startsWithProviderLabel(
|
|
2483
|
+
if (startsWithProviderLabel(providerLabel2, normalized)) {
|
|
2484
2484
|
return `${normalized}.`;
|
|
2485
2485
|
}
|
|
2486
2486
|
if (/^research\b/iu.test(normalized)) {
|
|
2487
|
-
return `${
|
|
2487
|
+
return `${providerLabel2} ${normalized}.`;
|
|
2488
2488
|
}
|
|
2489
|
-
return status === "cancelled" ? `${
|
|
2489
|
+
return status === "cancelled" ? `${providerLabel2} research was canceled: ${normalized}.` : `${providerLabel2} research failed: ${normalized}.`;
|
|
2490
2490
|
}
|
|
2491
2491
|
|
|
2492
2492
|
// src/execution-policy.ts
|
|
@@ -2532,7 +2532,7 @@ async function runWithExecutionPolicy(label, operation, settings, context) {
|
|
|
2532
2532
|
throw new Error(`${label} failed.`);
|
|
2533
2533
|
}
|
|
2534
2534
|
async function executeAsyncResearch({
|
|
2535
|
-
providerLabel,
|
|
2535
|
+
providerLabel: providerLabel2,
|
|
2536
2536
|
providerId,
|
|
2537
2537
|
context,
|
|
2538
2538
|
pollIntervalMs = DEFAULT_RESEARCH_POLL_INTERVAL_MS,
|
|
@@ -2541,7 +2541,7 @@ async function executeAsyncResearch({
|
|
|
2541
2541
|
start,
|
|
2542
2542
|
poll
|
|
2543
2543
|
}) {
|
|
2544
|
-
const timeoutMessage = `${
|
|
2544
|
+
const timeoutMessage = `${providerLabel2} research exceeded ${formatDuration(timeoutMs)}.`;
|
|
2545
2545
|
const deadline = createDeadlineSignal(
|
|
2546
2546
|
context.signal,
|
|
2547
2547
|
timeoutMs,
|
|
@@ -2555,7 +2555,7 @@ async function executeAsyncResearch({
|
|
|
2555
2555
|
let lastProgressStatus;
|
|
2556
2556
|
const startedAt = Date.now();
|
|
2557
2557
|
try {
|
|
2558
|
-
researchContext.onProgress?.(`Starting research via ${
|
|
2558
|
+
researchContext.onProgress?.(`Starting research via ${providerLabel2}`);
|
|
2559
2559
|
const job = await withAbortAndOptionalTimeout(
|
|
2560
2560
|
start(researchContext),
|
|
2561
2561
|
void 0,
|
|
@@ -2564,14 +2564,14 @@ async function executeAsyncResearch({
|
|
|
2564
2564
|
);
|
|
2565
2565
|
const jobId = job.id;
|
|
2566
2566
|
if (!jobId) {
|
|
2567
|
-
throw new Error(`${
|
|
2567
|
+
throw new Error(`${providerLabel2} research did not return a job id.`);
|
|
2568
2568
|
}
|
|
2569
|
-
researchContext.onProgress?.(`${
|
|
2569
|
+
researchContext.onProgress?.(`${providerLabel2} research started: ${jobId}`);
|
|
2570
2570
|
let consecutivePollErrors = 0;
|
|
2571
2571
|
while (true) {
|
|
2572
2572
|
throwIfAborted(
|
|
2573
2573
|
researchContext.signal,
|
|
2574
|
-
`${
|
|
2574
|
+
`${providerLabel2} research aborted.`
|
|
2575
2575
|
);
|
|
2576
2576
|
try {
|
|
2577
2577
|
const result = await withAbortAndOptionalTimeout(
|
|
@@ -2584,7 +2584,7 @@ async function executeAsyncResearch({
|
|
|
2584
2584
|
const progressStatus = result.statusText ?? result.status;
|
|
2585
2585
|
if (result.status !== lastStatus || progressStatus !== lastProgressStatus) {
|
|
2586
2586
|
researchContext.onProgress?.(
|
|
2587
|
-
`Research via ${
|
|
2587
|
+
`Research via ${providerLabel2}: ${progressStatus} (${formatElapsed(Date.now() - startedAt)} elapsed)`
|
|
2588
2588
|
);
|
|
2589
2589
|
lastStatus = result.status;
|
|
2590
2590
|
lastProgressStatus = progressStatus;
|
|
@@ -2592,13 +2592,13 @@ async function executeAsyncResearch({
|
|
|
2592
2592
|
if (result.status === "completed") {
|
|
2593
2593
|
return result.output ?? {
|
|
2594
2594
|
provider: providerId,
|
|
2595
|
-
text: `${
|
|
2595
|
+
text: `${providerLabel2} research completed without textual output.`
|
|
2596
2596
|
};
|
|
2597
2597
|
}
|
|
2598
2598
|
if (result.status === "failed" || result.status === "cancelled") {
|
|
2599
2599
|
throw new Error(
|
|
2600
2600
|
formatResearchTerminalDiagnostic(
|
|
2601
|
-
|
|
2601
|
+
providerLabel2,
|
|
2602
2602
|
result.status,
|
|
2603
2603
|
result.error
|
|
2604
2604
|
)
|
|
@@ -2614,11 +2614,11 @@ async function executeAsyncResearch({
|
|
|
2614
2614
|
consecutivePollErrors += 1;
|
|
2615
2615
|
if (consecutivePollErrors >= maxConsecutivePollErrors) {
|
|
2616
2616
|
throw new Error(
|
|
2617
|
-
`${
|
|
2617
|
+
`${providerLabel2} research polling failed too many times in a row: ${formatErrorMessage(error)}`
|
|
2618
2618
|
);
|
|
2619
2619
|
}
|
|
2620
2620
|
researchContext.onProgress?.(
|
|
2621
|
-
`${
|
|
2621
|
+
`${providerLabel2} research poll is still retrying after transient errors (${consecutivePollErrors}/${maxConsecutivePollErrors} consecutive poll failures). Background job id: ${jobId}`
|
|
2622
2622
|
);
|
|
2623
2623
|
}
|
|
2624
2624
|
await sleep(pollIntervalMs, researchContext.signal);
|
|
@@ -2626,11 +2626,11 @@ async function executeAsyncResearch({
|
|
|
2626
2626
|
} catch (error) {
|
|
2627
2627
|
if (isAbortErrorFromSignal(researchContext.signal, error)) {
|
|
2628
2628
|
throw new Error(
|
|
2629
|
-
formatProviderDiagnostic(
|
|
2629
|
+
formatProviderDiagnostic(providerLabel2, formatErrorMessage(error))
|
|
2630
2630
|
);
|
|
2631
2631
|
}
|
|
2632
2632
|
throw new Error(
|
|
2633
|
-
formatProviderDiagnostic(
|
|
2633
|
+
formatProviderDiagnostic(providerLabel2, formatErrorMessage(error))
|
|
2634
2634
|
);
|
|
2635
2635
|
} finally {
|
|
2636
2636
|
deadline.cleanup();
|
|
@@ -3097,6 +3097,8 @@ var exaProvider = defineProvider({
|
|
|
3097
3097
|
import FirecrawlClient from "@mendable/firecrawl-js";
|
|
3098
3098
|
import { Type as Type7 } from "typebox";
|
|
3099
3099
|
var FIRECRAWL_CLOUD_HOST = "api.firecrawl.dev";
|
|
3100
|
+
var FIRECRAWL_DEFAULT_API_URL = "https://api.firecrawl.dev";
|
|
3101
|
+
var FIRECRAWL_QUESTION_LIMIT = 1e4;
|
|
3100
3102
|
var firecrawlSearchOptionsSchema = Type7.Object(
|
|
3101
3103
|
{
|
|
3102
3104
|
lang: Type7.Optional(
|
|
@@ -3203,6 +3205,55 @@ var firecrawlScrapeOptionsSchema = Type7.Object(
|
|
|
3203
3205
|
},
|
|
3204
3206
|
{ description: "Firecrawl scrape options." }
|
|
3205
3207
|
);
|
|
3208
|
+
var firecrawlAnswerOptionsSchema = Type7.Object(
|
|
3209
|
+
{
|
|
3210
|
+
url: Type7.String({
|
|
3211
|
+
minLength: 1,
|
|
3212
|
+
description: "URL of the page to ask about."
|
|
3213
|
+
}),
|
|
3214
|
+
onlyMainContent: Type7.Optional(
|
|
3215
|
+
Type7.Boolean({ description: "Extract only the main content." })
|
|
3216
|
+
),
|
|
3217
|
+
includeTags: Type7.Optional(
|
|
3218
|
+
Type7.Array(Type7.String(), { description: "CSS selectors to include." })
|
|
3219
|
+
),
|
|
3220
|
+
excludeTags: Type7.Optional(
|
|
3221
|
+
Type7.Array(Type7.String(), { description: "CSS selectors to exclude." })
|
|
3222
|
+
),
|
|
3223
|
+
waitFor: Type7.Optional(
|
|
3224
|
+
Type7.Integer({
|
|
3225
|
+
minimum: 0,
|
|
3226
|
+
description: "Milliseconds to wait before scraping."
|
|
3227
|
+
})
|
|
3228
|
+
),
|
|
3229
|
+
headers: Type7.Optional(
|
|
3230
|
+
Type7.Record(Type7.String(), Type7.String(), {
|
|
3231
|
+
description: "Headers to send when scraping."
|
|
3232
|
+
})
|
|
3233
|
+
),
|
|
3234
|
+
location: Type7.Optional(
|
|
3235
|
+
Type7.Object(
|
|
3236
|
+
{
|
|
3237
|
+
country: Type7.Optional(Type7.String({ description: "Country hint." })),
|
|
3238
|
+
region: Type7.Optional(Type7.String({ description: "Region hint." })),
|
|
3239
|
+
city: Type7.Optional(Type7.String({ description: "City hint." }))
|
|
3240
|
+
},
|
|
3241
|
+
{ description: "Location hint for scraping." }
|
|
3242
|
+
)
|
|
3243
|
+
),
|
|
3244
|
+
mobile: Type7.Optional(
|
|
3245
|
+
Type7.Boolean({ description: "Use a mobile browser profile." })
|
|
3246
|
+
),
|
|
3247
|
+
proxy: Type7.Optional(
|
|
3248
|
+
Type7.String({
|
|
3249
|
+
description: "Proxy mode passed through to Firecrawl."
|
|
3250
|
+
})
|
|
3251
|
+
)
|
|
3252
|
+
},
|
|
3253
|
+
{
|
|
3254
|
+
description: "Firecrawl page-question options. The URL is required; the question comes from the web_answer query."
|
|
3255
|
+
}
|
|
3256
|
+
);
|
|
3206
3257
|
var firecrawlImplementation = {
|
|
3207
3258
|
id: "firecrawl",
|
|
3208
3259
|
label: "Firecrawl",
|
|
@@ -3213,6 +3264,8 @@ var firecrawlImplementation = {
|
|
|
3213
3264
|
return firecrawlSearchOptionsSchema;
|
|
3214
3265
|
case "contents":
|
|
3215
3266
|
return firecrawlScrapeOptionsSchema;
|
|
3267
|
+
case "answer":
|
|
3268
|
+
return firecrawlAnswerOptionsSchema;
|
|
3216
3269
|
default:
|
|
3217
3270
|
return void 0;
|
|
3218
3271
|
}
|
|
@@ -3279,6 +3332,34 @@ var firecrawlImplementation = {
|
|
|
3279
3332
|
})
|
|
3280
3333
|
)
|
|
3281
3334
|
};
|
|
3335
|
+
},
|
|
3336
|
+
async answer(query2, config, _context, options) {
|
|
3337
|
+
const question = validateQuestion(query2);
|
|
3338
|
+
const defaults = asJsonObject(config.options?.scrape);
|
|
3339
|
+
const answerDefaults = asJsonObject(config.options?.answer);
|
|
3340
|
+
const mergedOptions = {
|
|
3341
|
+
onlyMainContent: true,
|
|
3342
|
+
...defaults,
|
|
3343
|
+
...answerDefaults,
|
|
3344
|
+
...options ?? {}
|
|
3345
|
+
};
|
|
3346
|
+
const url2 = validateUrl(mergedOptions.url);
|
|
3347
|
+
const scrapeOptions = stripAnswerOnlyOptions(mergedOptions);
|
|
3348
|
+
const response = await scrapeQuestion(config, url2, question, scrapeOptions);
|
|
3349
|
+
const document = getFirecrawlDocument(response);
|
|
3350
|
+
const answer = readString2(document.answer);
|
|
3351
|
+
if (!answer?.trim()) {
|
|
3352
|
+
throw new Error("No answer returned for this URL.");
|
|
3353
|
+
}
|
|
3354
|
+
return {
|
|
3355
|
+
provider: firecrawlImplementation.id,
|
|
3356
|
+
text: answer.trim(),
|
|
3357
|
+
itemCount: 1,
|
|
3358
|
+
metadata: {
|
|
3359
|
+
url: url2,
|
|
3360
|
+
...asRecord(document.metadata) ? { metadata: document.metadata } : {}
|
|
3361
|
+
}
|
|
3362
|
+
};
|
|
3282
3363
|
}
|
|
3283
3364
|
};
|
|
3284
3365
|
function createClient3(config) {
|
|
@@ -3302,6 +3383,88 @@ function getFirecrawlCapabilityStatus(config, options) {
|
|
|
3302
3383
|
function isFirecrawlCloudApiUrl(apiUrl) {
|
|
3303
3384
|
return !apiUrl || apiUrl.includes(FIRECRAWL_CLOUD_HOST);
|
|
3304
3385
|
}
|
|
3386
|
+
function validateQuestion(query2) {
|
|
3387
|
+
const question = query2.trim();
|
|
3388
|
+
if (!question) {
|
|
3389
|
+
throw new Error("question must be a non-empty string.");
|
|
3390
|
+
}
|
|
3391
|
+
if (question.length > FIRECRAWL_QUESTION_LIMIT) {
|
|
3392
|
+
throw new Error(
|
|
3393
|
+
`Firecrawl question must be at most ${FIRECRAWL_QUESTION_LIMIT} characters.`
|
|
3394
|
+
);
|
|
3395
|
+
}
|
|
3396
|
+
return question;
|
|
3397
|
+
}
|
|
3398
|
+
function validateUrl(value) {
|
|
3399
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
3400
|
+
throw new Error("Firecrawl answer requires options.url.");
|
|
3401
|
+
}
|
|
3402
|
+
return value.trim();
|
|
3403
|
+
}
|
|
3404
|
+
function stripAnswerOnlyOptions(options) {
|
|
3405
|
+
const { url: _url, formats: _formats, ...scrapeOptions } = options;
|
|
3406
|
+
return scrapeOptions;
|
|
3407
|
+
}
|
|
3408
|
+
async function scrapeQuestion(config, url2, question, options) {
|
|
3409
|
+
const apiUrl = resolveConfigValue(config.baseUrl) ?? FIRECRAWL_DEFAULT_API_URL;
|
|
3410
|
+
const apiKey = resolveConfigValue(config.credentials?.api);
|
|
3411
|
+
if (isFirecrawlCloudApiUrl(apiUrl) && !apiKey) {
|
|
3412
|
+
throw new Error("is missing an API key");
|
|
3413
|
+
}
|
|
3414
|
+
const response = await fetch(joinUrl(apiUrl, "/v2/scrape"), {
|
|
3415
|
+
method: "POST",
|
|
3416
|
+
headers: {
|
|
3417
|
+
"Content-Type": "application/json",
|
|
3418
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
3419
|
+
},
|
|
3420
|
+
body: JSON.stringify({
|
|
3421
|
+
...options,
|
|
3422
|
+
url: url2,
|
|
3423
|
+
formats: [{ type: "question", question }]
|
|
3424
|
+
})
|
|
3425
|
+
});
|
|
3426
|
+
const payload = await readJsonResponse(response);
|
|
3427
|
+
if (!response.ok) {
|
|
3428
|
+
throw new Error(readFirecrawlError(payload, response.statusText));
|
|
3429
|
+
}
|
|
3430
|
+
if (isFirecrawlFailure(payload)) {
|
|
3431
|
+
throw new Error(readFirecrawlError(payload, "Firecrawl scrape failed."));
|
|
3432
|
+
}
|
|
3433
|
+
return payload;
|
|
3434
|
+
}
|
|
3435
|
+
function joinUrl(baseUrl, path) {
|
|
3436
|
+
return `${baseUrl.replace(/\/+$/g, "")}/${path.replace(/^\/+/g, "")}`;
|
|
3437
|
+
}
|
|
3438
|
+
async function readJsonResponse(response) {
|
|
3439
|
+
const text = await response.text();
|
|
3440
|
+
if (!text) {
|
|
3441
|
+
return {};
|
|
3442
|
+
}
|
|
3443
|
+
try {
|
|
3444
|
+
return JSON.parse(text);
|
|
3445
|
+
} catch {
|
|
3446
|
+
return text;
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
function isFirecrawlFailure(value) {
|
|
3450
|
+
const record = asRecord(value);
|
|
3451
|
+
return record?.success === false || record?.error !== void 0;
|
|
3452
|
+
}
|
|
3453
|
+
function readFirecrawlError(value, fallback) {
|
|
3454
|
+
const record = asRecord(value);
|
|
3455
|
+
return readString2(record?.error) ?? readString2(record?.message) ?? (typeof value === "string" ? value : void 0) ?? fallback;
|
|
3456
|
+
}
|
|
3457
|
+
function getFirecrawlDocument(value) {
|
|
3458
|
+
const record = asRecord(value);
|
|
3459
|
+
const data = asRecord(record?.data);
|
|
3460
|
+
if (data) {
|
|
3461
|
+
return data;
|
|
3462
|
+
}
|
|
3463
|
+
if (record) {
|
|
3464
|
+
return record;
|
|
3465
|
+
}
|
|
3466
|
+
throw new Error(`Unexpected Firecrawl response: ${formatJson(value)}`);
|
|
3467
|
+
}
|
|
3305
3468
|
function flattenSearchResults(response) {
|
|
3306
3469
|
return ["web", "news", "images"].flatMap(
|
|
3307
3470
|
(source) => (response[source] ?? []).map((entry) => toSearchResult(source, entry)).filter((entry) => entry !== null)
|
|
@@ -3389,6 +3552,21 @@ var firecrawlProvider = defineProvider({
|
|
|
3389
3552
|
input.options
|
|
3390
3553
|
);
|
|
3391
3554
|
}
|
|
3555
|
+
}),
|
|
3556
|
+
answer: defineCapability({
|
|
3557
|
+
options: firecrawlImplementation.getToolOptionsSchema?.("answer"),
|
|
3558
|
+
promptGuidelines: [
|
|
3559
|
+
"Firecrawl web_answer is page-scoped: set options.url to the specific page URL to ask about.",
|
|
3560
|
+
"Do not use Firecrawl web_answer for general multi-source answers; use web_search plus web_contents or web_research instead."
|
|
3561
|
+
],
|
|
3562
|
+
async execute(input, ctx) {
|
|
3563
|
+
return await firecrawlImplementation.answer(
|
|
3564
|
+
input.query,
|
|
3565
|
+
ctx.config,
|
|
3566
|
+
ctx,
|
|
3567
|
+
input.options
|
|
3568
|
+
);
|
|
3569
|
+
}
|
|
3392
3570
|
})
|
|
3393
3571
|
}
|
|
3394
3572
|
});
|
|
@@ -3535,7 +3713,7 @@ var geminiImplementation = {
|
|
|
3535
3713
|
context.signal
|
|
3536
3714
|
);
|
|
3537
3715
|
const results = await Promise.all(
|
|
3538
|
-
extractGoogleSearchResults(interaction
|
|
3716
|
+
extractGoogleSearchResults(readInteractionSteps(interaction)).slice(0, maxResults).map(async (result) => {
|
|
3539
3717
|
const resolvedUrl = await resolveGoogleSearchUrl(
|
|
3540
3718
|
result.url,
|
|
3541
3719
|
context.signal
|
|
@@ -3622,7 +3800,7 @@ var geminiImplementation = {
|
|
|
3622
3800
|
);
|
|
3623
3801
|
const status = readNonEmptyString3(interaction.status) ?? "unknown";
|
|
3624
3802
|
if (status === "completed") {
|
|
3625
|
-
const text =
|
|
3803
|
+
const text = formatInteractionSteps(readInteractionSteps(interaction));
|
|
3626
3804
|
return {
|
|
3627
3805
|
status: "completed",
|
|
3628
3806
|
output: {
|
|
@@ -3652,7 +3830,7 @@ var geminiImplementation = {
|
|
|
3652
3830
|
if (status === "requires_action") {
|
|
3653
3831
|
return {
|
|
3654
3832
|
status: "failed",
|
|
3655
|
-
error: describeGeminiRequiredAction(interaction
|
|
3833
|
+
error: describeGeminiRequiredAction(readInteractionSteps(interaction))
|
|
3656
3834
|
};
|
|
3657
3835
|
}
|
|
3658
3836
|
return status === "in_progress" ? { status: "in_progress" } : { status: "in_progress", statusText: status };
|
|
@@ -3686,17 +3864,20 @@ function addAbortSignalToGeminiConfig(config, signal) {
|
|
|
3686
3864
|
abortSignal: signal
|
|
3687
3865
|
};
|
|
3688
3866
|
}
|
|
3689
|
-
function
|
|
3867
|
+
function readInteractionSteps(interaction) {
|
|
3868
|
+
return typeof interaction === "object" && interaction !== null ? interaction.steps : void 0;
|
|
3869
|
+
}
|
|
3870
|
+
function extractGoogleSearchResults(steps) {
|
|
3690
3871
|
const seen = /* @__PURE__ */ new Set();
|
|
3691
3872
|
const results = [];
|
|
3692
|
-
if (!Array.isArray(
|
|
3873
|
+
if (!Array.isArray(steps)) {
|
|
3693
3874
|
return results;
|
|
3694
3875
|
}
|
|
3695
|
-
for (const
|
|
3696
|
-
if (typeof
|
|
3876
|
+
for (const step of steps) {
|
|
3877
|
+
if (typeof step !== "object" || step === null) {
|
|
3697
3878
|
continue;
|
|
3698
3879
|
}
|
|
3699
|
-
const content =
|
|
3880
|
+
const content = step;
|
|
3700
3881
|
if (content.type !== "google_search_result") {
|
|
3701
3882
|
continue;
|
|
3702
3883
|
}
|
|
@@ -3862,16 +4043,21 @@ function extractGroundingSources(chunks) {
|
|
|
3862
4043
|
}
|
|
3863
4044
|
return sources;
|
|
3864
4045
|
}
|
|
3865
|
-
function
|
|
4046
|
+
function formatInteractionSteps(steps) {
|
|
3866
4047
|
const lines = [];
|
|
3867
|
-
if (!Array.isArray(
|
|
4048
|
+
if (!Array.isArray(steps)) {
|
|
3868
4049
|
return "";
|
|
3869
4050
|
}
|
|
3870
|
-
for (const
|
|
3871
|
-
if (typeof
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
4051
|
+
for (const step of steps) {
|
|
4052
|
+
if (typeof step !== "object" || step === null || !("type" in step) || step.type !== "model_output" || !("content" in step) || !Array.isArray(step.content)) {
|
|
4053
|
+
continue;
|
|
4054
|
+
}
|
|
4055
|
+
for (const part of step.content) {
|
|
4056
|
+
if (typeof part === "object" && part !== null && "type" in part && part.type === "text" && "text" in part && typeof part.text === "string") {
|
|
4057
|
+
const text = part.text.trim();
|
|
4058
|
+
if (text) {
|
|
4059
|
+
lines.push(text);
|
|
4060
|
+
}
|
|
3875
4061
|
}
|
|
3876
4062
|
}
|
|
3877
4063
|
}
|
|
@@ -4066,14 +4252,12 @@ function buildGeminiGenerateContentRequest({
|
|
|
4066
4252
|
}
|
|
4067
4253
|
};
|
|
4068
4254
|
}
|
|
4069
|
-
function describeGeminiRequiredAction(
|
|
4070
|
-
if (!Array.isArray(
|
|
4255
|
+
function describeGeminiRequiredAction(steps) {
|
|
4256
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
4071
4257
|
return "research requires additional action";
|
|
4072
4258
|
}
|
|
4073
|
-
const
|
|
4074
|
-
|
|
4075
|
-
);
|
|
4076
|
-
const type = readNonEmptyString3(firstOutput?.type);
|
|
4259
|
+
const lastStep = [...steps].reverse().find((value) => typeof value === "object" && value !== null);
|
|
4260
|
+
const type = readNonEmptyString3(lastStep?.type);
|
|
4077
4261
|
if (!type) {
|
|
4078
4262
|
return "research requires additional action";
|
|
4079
4263
|
}
|
|
@@ -6040,7 +6224,7 @@ var serperImplementation = {
|
|
|
6040
6224
|
requestOptions
|
|
6041
6225
|
);
|
|
6042
6226
|
const response = await fetch(
|
|
6043
|
-
|
|
6227
|
+
joinUrl2(resolveConfigValue(config.baseUrl), requestOptions.mode),
|
|
6044
6228
|
{
|
|
6045
6229
|
method: "POST",
|
|
6046
6230
|
headers: {
|
|
@@ -6075,7 +6259,7 @@ var serperImplementation = {
|
|
|
6075
6259
|
};
|
|
6076
6260
|
}
|
|
6077
6261
|
};
|
|
6078
|
-
function
|
|
6262
|
+
function joinUrl2(baseUrl, mode = "search") {
|
|
6079
6263
|
const base2 = (baseUrl ?? DEFAULT_BASE_URL3).replace(/\/+$/, "");
|
|
6080
6264
|
if (mode === "webpage" && base2 === DEFAULT_BASE_URL3) {
|
|
6081
6265
|
return DEFAULT_SCRAPE_URL;
|
|
@@ -7671,45 +7855,6 @@ ${JSON.stringify(value, null, 2).trim()}
|
|
|
7671
7855
|
\`\`\``;
|
|
7672
7856
|
}
|
|
7673
7857
|
|
|
7674
|
-
// src/options.ts
|
|
7675
|
-
function buildToolOptionsSchema(_capability, providerSchema) {
|
|
7676
|
-
if (!providerSchema || Object.keys(providerSchema.properties).length === 0) {
|
|
7677
|
-
return void 0;
|
|
7678
|
-
}
|
|
7679
|
-
return closeObjectSchemas(providerSchema);
|
|
7680
|
-
}
|
|
7681
|
-
function closeObjectSchemas(schema) {
|
|
7682
|
-
if (!isSchemaRecord(schema)) {
|
|
7683
|
-
return schema;
|
|
7684
|
-
}
|
|
7685
|
-
const properties = isSchemaRecord(schema.properties) ? Object.fromEntries(
|
|
7686
|
-
Object.entries(schema.properties).map(([key, value]) => [
|
|
7687
|
-
key,
|
|
7688
|
-
closeObjectSchemas(value)
|
|
7689
|
-
])
|
|
7690
|
-
) : schema.properties;
|
|
7691
|
-
const items = isSchemaRecord(schema.items) ? closeObjectSchemas(schema.items) : Array.isArray(schema.items) ? schema.items.map((item) => closeObjectSchemas(item)) : schema.items;
|
|
7692
|
-
return {
|
|
7693
|
-
...schema,
|
|
7694
|
-
...properties ? { properties } : {},
|
|
7695
|
-
...items ? { items } : {},
|
|
7696
|
-
...mapSchemaArray(schema, "anyOf"),
|
|
7697
|
-
...mapSchemaArray(schema, "oneOf"),
|
|
7698
|
-
...mapSchemaArray(schema, "allOf"),
|
|
7699
|
-
...schema.type === "object" && isSchemaRecord(schema.properties) ? { additionalProperties: false } : {}
|
|
7700
|
-
};
|
|
7701
|
-
}
|
|
7702
|
-
function mapSchemaArray(schema, key) {
|
|
7703
|
-
const value = schema[key];
|
|
7704
|
-
return Array.isArray(value) ? { [key]: value.map((entry) => closeObjectSchemas(entry)) } : {};
|
|
7705
|
-
}
|
|
7706
|
-
function isSchemaRecord(value) {
|
|
7707
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7708
|
-
}
|
|
7709
|
-
|
|
7710
|
-
// src/prefetch-manager.ts
|
|
7711
|
-
import { createHash } from "node:crypto";
|
|
7712
|
-
|
|
7713
7858
|
// src/provider-resolution.ts
|
|
7714
7859
|
function supportsTool2(provider, tool) {
|
|
7715
7860
|
return provider.capabilities[tool] !== void 0;
|
|
@@ -7851,58 +7996,192 @@ function resolveProviderForTool(config, cwd, tool, explicit) {
|
|
|
7851
7996
|
return provider;
|
|
7852
7997
|
}
|
|
7853
7998
|
|
|
7854
|
-
// src/
|
|
7855
|
-
|
|
7856
|
-
|
|
7999
|
+
// src/managed-tools.ts
|
|
8000
|
+
var CAPABILITY_TOOL_NAMES = {
|
|
8001
|
+
search: "web_search",
|
|
8002
|
+
contents: "web_contents",
|
|
8003
|
+
answer: "web_answer",
|
|
8004
|
+
research: "web_research"
|
|
8005
|
+
};
|
|
8006
|
+
var MANAGED_TOOL_NAMES = Object.values(CAPABILITY_TOOL_NAMES);
|
|
8007
|
+
function getAvailableProviderIdsForCapability(config, cwd, capability) {
|
|
8008
|
+
const providerId = getMappedProviderIdForTool(config, capability);
|
|
8009
|
+
if (!providerId) {
|
|
8010
|
+
return [];
|
|
8011
|
+
}
|
|
8012
|
+
const provider = PROVIDERS_BY_ID[providerId];
|
|
8013
|
+
if (!supportsTool2(provider, capability)) {
|
|
8014
|
+
return [];
|
|
8015
|
+
}
|
|
8016
|
+
const status = getProviderCapabilityStatus(
|
|
8017
|
+
config,
|
|
8018
|
+
cwd,
|
|
8019
|
+
providerId,
|
|
8020
|
+
capability,
|
|
7857
8021
|
{
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
settings: config.settings,
|
|
7861
|
-
execute: (executionContext) => executeProviderCapability(
|
|
7862
|
-
provider,
|
|
7863
|
-
request.capability,
|
|
7864
|
-
providerInputFromRequest(request),
|
|
7865
|
-
{
|
|
7866
|
-
...executionContext,
|
|
7867
|
-
config
|
|
7868
|
-
}
|
|
7869
|
-
)
|
|
7870
|
-
},
|
|
7871
|
-
context
|
|
8022
|
+
resolveSecrets: false
|
|
8023
|
+
}
|
|
7872
8024
|
);
|
|
8025
|
+
return isProviderCapabilityExposable(status) ? [providerId] : [];
|
|
7873
8026
|
}
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
formatErrorMessage(error)
|
|
7892
|
-
)
|
|
7893
|
-
);
|
|
7894
|
-
} finally {
|
|
7895
|
-
deadline?.cleanup();
|
|
8027
|
+
function getProviderStatusForTool(config, cwd, providerId, capability) {
|
|
8028
|
+
return getProviderCapabilityStatus(config, cwd, providerId, capability);
|
|
8029
|
+
}
|
|
8030
|
+
function getAvailableManagedToolNames(config, cwd) {
|
|
8031
|
+
return Object.keys(CAPABILITY_TOOL_NAMES).filter(
|
|
8032
|
+
(capability) => getAvailableProviderIdsForCapability(config, cwd, capability).length > 0
|
|
8033
|
+
).map((capability) => CAPABILITY_TOOL_NAMES[capability]);
|
|
8034
|
+
}
|
|
8035
|
+
function getSyncedActiveTools(config, cwd, activeToolNames, options) {
|
|
8036
|
+
const availableToolNames = new Set(getAvailableManagedToolNames(config, cwd));
|
|
8037
|
+
const nextActiveTools = new Set(activeToolNames);
|
|
8038
|
+
for (const toolName of MANAGED_TOOL_NAMES) {
|
|
8039
|
+
if (availableToolNames.has(toolName)) {
|
|
8040
|
+
if (options.addAvailable) {
|
|
8041
|
+
nextActiveTools.add(toolName);
|
|
8042
|
+
}
|
|
8043
|
+
continue;
|
|
7896
8044
|
}
|
|
8045
|
+
nextActiveTools.delete(toolName);
|
|
7897
8046
|
}
|
|
7898
|
-
|
|
8047
|
+
return nextActiveTools;
|
|
8048
|
+
}
|
|
8049
|
+
async function refreshManagedTools(pi, registerManagedTools2, cwd, options) {
|
|
8050
|
+
const config = await loadConfig();
|
|
8051
|
+
const nextActiveTools = getSyncedActiveTools(
|
|
8052
|
+
config,
|
|
8053
|
+
cwd,
|
|
8054
|
+
pi.getActiveTools(),
|
|
8055
|
+
options
|
|
8056
|
+
);
|
|
8057
|
+
registerManagedTools2({
|
|
8058
|
+
search: getAvailableProviderIdsForCapability(config, cwd, "search"),
|
|
8059
|
+
contents: getAvailableProviderIdsForCapability(config, cwd, "contents"),
|
|
8060
|
+
answer: getAvailableProviderIdsForCapability(config, cwd, "answer"),
|
|
8061
|
+
research: getAvailableProviderIdsForCapability(config, cwd, "research")
|
|
8062
|
+
});
|
|
8063
|
+
await syncManagedToolAvailability(pi, nextActiveTools);
|
|
8064
|
+
}
|
|
8065
|
+
async function refreshManagedToolsOnStartup(pi, registerManagedTools2, cwd, options) {
|
|
7899
8066
|
try {
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
8067
|
+
await refreshManagedTools(pi, registerManagedTools2, cwd, options);
|
|
8068
|
+
} catch (error) {
|
|
8069
|
+
pi.sendMessage({
|
|
8070
|
+
customType: "web-providers-config-error",
|
|
8071
|
+
content: formatStartupConfigError(error),
|
|
8072
|
+
display: true
|
|
8073
|
+
});
|
|
8074
|
+
await syncManagedToolAvailability(
|
|
8075
|
+
pi,
|
|
8076
|
+
new Set(
|
|
8077
|
+
pi.getActiveTools().filter((toolName) => !MANAGED_TOOL_NAMES.includes(toolName))
|
|
8078
|
+
)
|
|
8079
|
+
);
|
|
8080
|
+
}
|
|
8081
|
+
}
|
|
8082
|
+
function formatStartupConfigError(error) {
|
|
8083
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
8084
|
+
return `web-providers config error: ${detail.replace(getConfigPath(), "~/.pi/agent/web-providers.json")}`;
|
|
8085
|
+
}
|
|
8086
|
+
async function syncManagedToolAvailability(pi, nextActiveTools) {
|
|
8087
|
+
const activeTools = pi.getActiveTools();
|
|
8088
|
+
const changed = activeTools.length !== nextActiveTools.size || activeTools.some((toolName) => !nextActiveTools.has(toolName));
|
|
8089
|
+
if (changed) {
|
|
8090
|
+
pi.setActiveTools(Array.from(nextActiveTools));
|
|
8091
|
+
}
|
|
8092
|
+
}
|
|
8093
|
+
|
|
8094
|
+
// src/options.ts
|
|
8095
|
+
function buildToolOptionsSchema(_capability, providerSchema) {
|
|
8096
|
+
if (!providerSchema || Object.keys(providerSchema.properties).length === 0) {
|
|
8097
|
+
return void 0;
|
|
8098
|
+
}
|
|
8099
|
+
return closeObjectSchemas(providerSchema);
|
|
8100
|
+
}
|
|
8101
|
+
function closeObjectSchemas(schema) {
|
|
8102
|
+
if (!isSchemaRecord(schema)) {
|
|
8103
|
+
return schema;
|
|
8104
|
+
}
|
|
8105
|
+
const properties = isSchemaRecord(schema.properties) ? Object.fromEntries(
|
|
8106
|
+
Object.entries(schema.properties).map(([key, value]) => [
|
|
8107
|
+
key,
|
|
8108
|
+
closeObjectSchemas(value)
|
|
8109
|
+
])
|
|
8110
|
+
) : schema.properties;
|
|
8111
|
+
const items = isSchemaRecord(schema.items) ? closeObjectSchemas(schema.items) : Array.isArray(schema.items) ? schema.items.map((item) => closeObjectSchemas(item)) : schema.items;
|
|
8112
|
+
return {
|
|
8113
|
+
...schema,
|
|
8114
|
+
...properties ? { properties } : {},
|
|
8115
|
+
...items ? { items } : {},
|
|
8116
|
+
...mapSchemaArray(schema, "anyOf"),
|
|
8117
|
+
...mapSchemaArray(schema, "oneOf"),
|
|
8118
|
+
...mapSchemaArray(schema, "allOf"),
|
|
8119
|
+
...schema.type === "object" && isSchemaRecord(schema.properties) ? { additionalProperties: false } : {}
|
|
8120
|
+
};
|
|
8121
|
+
}
|
|
8122
|
+
function mapSchemaArray(schema, key) {
|
|
8123
|
+
const value = schema[key];
|
|
8124
|
+
return Array.isArray(value) ? { [key]: value.map((entry) => closeObjectSchemas(entry)) } : {};
|
|
8125
|
+
}
|
|
8126
|
+
function isSchemaRecord(value) {
|
|
8127
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8128
|
+
}
|
|
8129
|
+
|
|
8130
|
+
// src/prefetch-manager.ts
|
|
8131
|
+
import { createHash } from "node:crypto";
|
|
8132
|
+
|
|
8133
|
+
// src/provider-runtime.ts
|
|
8134
|
+
async function executeProviderRequest(provider, config, request, context) {
|
|
8135
|
+
return await executeProviderExecution(
|
|
8136
|
+
{
|
|
8137
|
+
capability: request.capability,
|
|
8138
|
+
providerLabel: provider.label,
|
|
8139
|
+
settings: config.settings,
|
|
8140
|
+
execute: (executionContext) => executeProviderCapability(
|
|
8141
|
+
provider,
|
|
8142
|
+
request.capability,
|
|
8143
|
+
providerInputFromRequest(request),
|
|
8144
|
+
{
|
|
8145
|
+
...executionContext,
|
|
8146
|
+
config
|
|
8147
|
+
}
|
|
8148
|
+
)
|
|
8149
|
+
},
|
|
8150
|
+
context
|
|
8151
|
+
);
|
|
8152
|
+
}
|
|
8153
|
+
async function executeProviderExecution(execution, context) {
|
|
8154
|
+
if (execution.capability === "research") {
|
|
8155
|
+
const deadline = createResearchDeadlineSignal(
|
|
8156
|
+
context.signal,
|
|
8157
|
+
execution.providerLabel,
|
|
8158
|
+
execution.settings?.researchTimeoutMs
|
|
8159
|
+
);
|
|
8160
|
+
try {
|
|
8161
|
+
const researchContext = deadline ? { ...context, signal: deadline.signal } : context;
|
|
8162
|
+
return await withAbortSignal(
|
|
8163
|
+
execution.execute(researchContext),
|
|
8164
|
+
researchContext.signal
|
|
8165
|
+
);
|
|
8166
|
+
} catch (error) {
|
|
8167
|
+
throw new Error(
|
|
8168
|
+
formatProviderDiagnostic(
|
|
8169
|
+
execution.providerLabel,
|
|
8170
|
+
formatErrorMessage(error)
|
|
8171
|
+
)
|
|
8172
|
+
);
|
|
8173
|
+
} finally {
|
|
8174
|
+
deadline?.cleanup();
|
|
8175
|
+
}
|
|
8176
|
+
}
|
|
8177
|
+
const requestPolicy = resolveExecutionPolicy(execution.settings);
|
|
8178
|
+
try {
|
|
8179
|
+
return await runWithExecutionPolicy(
|
|
8180
|
+
`${execution.providerLabel} ${execution.capability} request`,
|
|
8181
|
+
execution.execute,
|
|
8182
|
+
requestPolicy,
|
|
8183
|
+
context
|
|
8184
|
+
);
|
|
7906
8185
|
} catch (error) {
|
|
7907
8186
|
throw new Error(
|
|
7908
8187
|
formatProviderDiagnostic(
|
|
@@ -7944,7 +8223,7 @@ function resolveExecutionPolicy(defaults) {
|
|
|
7944
8223
|
retryDelayMs: defaults?.retryDelayMs ?? 2e3
|
|
7945
8224
|
};
|
|
7946
8225
|
}
|
|
7947
|
-
function createResearchDeadlineSignal(signal,
|
|
8226
|
+
function createResearchDeadlineSignal(signal, providerLabel2, timeoutMs) {
|
|
7948
8227
|
if (timeoutMs === void 0) {
|
|
7949
8228
|
return void 0;
|
|
7950
8229
|
}
|
|
@@ -7959,7 +8238,7 @@ function createResearchDeadlineSignal(signal, providerLabel, timeoutMs) {
|
|
|
7959
8238
|
const timer = setTimeout(() => {
|
|
7960
8239
|
controller.abort(
|
|
7961
8240
|
new Error(
|
|
7962
|
-
`${
|
|
8241
|
+
`${providerLabel2} research exceeded ${formatDuration(timeoutMs)}.`
|
|
7963
8242
|
)
|
|
7964
8243
|
);
|
|
7965
8244
|
}, timeoutMs);
|
|
@@ -9614,93 +9893,1107 @@ function getFirstLine(text) {
|
|
|
9614
9893
|
return text?.split("\n").map((line) => line.trim()).find((line) => line.length > 0);
|
|
9615
9894
|
}
|
|
9616
9895
|
|
|
9617
|
-
// src/
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
var RESEARCH_HEARTBEAT_MS = 15e3;
|
|
9622
|
-
var WEB_RESEARCH_RESULT_MESSAGE_TYPE = "web-research-result";
|
|
9623
|
-
var WEB_RESEARCH_WIDGET_KEY = "web-research-jobs";
|
|
9896
|
+
// src/web-research-lifecycle.ts
|
|
9897
|
+
import { randomUUID } from "node:crypto";
|
|
9898
|
+
import { mkdir as mkdir2, readdir, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
9899
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
9624
9900
|
var RESEARCH_ARTIFACTS_DIR = join2(".pi", "artifacts", "research");
|
|
9901
|
+
var MAX_RESEARCH_HISTORY_ITEMS = 20;
|
|
9902
|
+
var RESEARCH_PREVIEW_MAX_BYTES = 5e4;
|
|
9903
|
+
var RESEARCH_REPORT_MAX_BYTES = 2e5;
|
|
9625
9904
|
var pendingResearchTasks = /* @__PURE__ */ new Set();
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
const
|
|
9648
|
-
|
|
9649
|
-
|
|
9905
|
+
async function dispatchWebResearch({
|
|
9906
|
+
activeWebResearchRequests,
|
|
9907
|
+
config,
|
|
9908
|
+
explicitProvider,
|
|
9909
|
+
ctx,
|
|
9910
|
+
options,
|
|
9911
|
+
input,
|
|
9912
|
+
executionOverride,
|
|
9913
|
+
executeResearch,
|
|
9914
|
+
deliverResult,
|
|
9915
|
+
onJobsChanged,
|
|
9916
|
+
resultMessageType
|
|
9917
|
+
}) {
|
|
9918
|
+
await cleanupContentStore();
|
|
9919
|
+
const provider = resolveProviderForTool(
|
|
9920
|
+
config,
|
|
9921
|
+
ctx.cwd,
|
|
9922
|
+
"research",
|
|
9923
|
+
explicitProvider
|
|
9924
|
+
);
|
|
9925
|
+
const request = createWebResearchRequest(ctx.cwd, provider.id, input);
|
|
9926
|
+
const abortController = new AbortController();
|
|
9927
|
+
const task = { request, abortController };
|
|
9928
|
+
const providerConfig = getEffectiveProviderConfig(config, provider.id);
|
|
9929
|
+
activeWebResearchRequests.set(request.id, task);
|
|
9930
|
+
onJobsChanged();
|
|
9931
|
+
trackPendingResearchTask(
|
|
9932
|
+
runDispatchedWebResearch({
|
|
9933
|
+
activeWebResearchRequests,
|
|
9934
|
+
task,
|
|
9935
|
+
config,
|
|
9936
|
+
provider,
|
|
9937
|
+
providerConfig,
|
|
9938
|
+
ctx,
|
|
9939
|
+
options,
|
|
9940
|
+
executionOverride,
|
|
9941
|
+
executeResearch,
|
|
9942
|
+
deliverResult,
|
|
9943
|
+
onJobsChanged,
|
|
9944
|
+
resultMessageType
|
|
9945
|
+
})
|
|
9946
|
+
);
|
|
9947
|
+
return {
|
|
9948
|
+
content: [
|
|
9949
|
+
{
|
|
9950
|
+
type: "text",
|
|
9951
|
+
text: `Started web research via ${provider.label}.`
|
|
9952
|
+
}
|
|
9953
|
+
],
|
|
9954
|
+
details: request,
|
|
9955
|
+
display: {
|
|
9956
|
+
provider: { id: provider.id, label: provider.label },
|
|
9957
|
+
outcome: { success: "started" }
|
|
9650
9958
|
}
|
|
9651
|
-
webResearchWidgetTimer = setInterval(() => {
|
|
9652
|
-
updateWebResearchWidget();
|
|
9653
|
-
}, 1e3);
|
|
9654
9959
|
};
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9960
|
+
}
|
|
9961
|
+
async function runDispatchedWebResearch({
|
|
9962
|
+
activeWebResearchRequests,
|
|
9963
|
+
task,
|
|
9964
|
+
config,
|
|
9965
|
+
provider,
|
|
9966
|
+
providerConfig,
|
|
9967
|
+
ctx,
|
|
9968
|
+
options,
|
|
9969
|
+
executionOverride,
|
|
9970
|
+
executeResearch,
|
|
9971
|
+
deliverResult,
|
|
9972
|
+
onJobsChanged,
|
|
9973
|
+
resultMessageType
|
|
9974
|
+
}) {
|
|
9975
|
+
const { request, abortController } = task;
|
|
9976
|
+
let result;
|
|
9977
|
+
let reportText = "";
|
|
9978
|
+
try {
|
|
9979
|
+
const response = await executeResearch({
|
|
9980
|
+
config,
|
|
9981
|
+
provider,
|
|
9982
|
+
providerConfig,
|
|
9983
|
+
ctx,
|
|
9984
|
+
signal: abortController.signal,
|
|
9985
|
+
options,
|
|
9986
|
+
input: request.input,
|
|
9987
|
+
onProgress: (message) => {
|
|
9988
|
+
request.progress = summarizeWebResearchProgress(
|
|
9989
|
+
message,
|
|
9990
|
+
provider.label
|
|
9991
|
+
);
|
|
9992
|
+
onJobsChanged();
|
|
9993
|
+
},
|
|
9994
|
+
executionOverride
|
|
9995
|
+
});
|
|
9996
|
+
result = buildWebResearchResult(request, abortController, task, response);
|
|
9997
|
+
if (result.status === "completed") {
|
|
9998
|
+
reportText = response.text;
|
|
9669
9999
|
}
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
)
|
|
9677
|
-
);
|
|
9678
|
-
};
|
|
9679
|
-
if ("registerMessageRenderer" in pi) {
|
|
9680
|
-
pi.registerMessageRenderer(
|
|
9681
|
-
WEB_RESEARCH_RESULT_MESSAGE_TYPE,
|
|
9682
|
-
(message, state, theme) => renderWebResearchResultMessage(message, state, theme)
|
|
10000
|
+
} catch (error) {
|
|
10001
|
+
result = buildFailedWebResearchResult(
|
|
10002
|
+
request,
|
|
10003
|
+
abortController,
|
|
10004
|
+
task,
|
|
10005
|
+
error
|
|
9683
10006
|
);
|
|
9684
10007
|
}
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
10008
|
+
try {
|
|
10009
|
+
await writeWebResearchArtifact(result, reportText);
|
|
10010
|
+
deliverResult({
|
|
10011
|
+
customType: resultMessageType,
|
|
10012
|
+
content: formatWebResearchResultMessage(result, reportText),
|
|
10013
|
+
display: true,
|
|
10014
|
+
details: result
|
|
10015
|
+
});
|
|
10016
|
+
} finally {
|
|
10017
|
+
activeWebResearchRequests.delete(request.id);
|
|
10018
|
+
onJobsChanged();
|
|
10019
|
+
}
|
|
10020
|
+
}
|
|
10021
|
+
function buildWebResearchResult(request, abortController, task, response) {
|
|
10022
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10023
|
+
if (abortController.signal.aborted && task.cancelRequestedAt !== void 0) {
|
|
10024
|
+
return {
|
|
10025
|
+
...request,
|
|
10026
|
+
status: "cancelled",
|
|
10027
|
+
completedAt,
|
|
10028
|
+
elapsedMs: elapsedMs(request.startedAt, completedAt),
|
|
10029
|
+
error: "web research was cancelled by the user."
|
|
10030
|
+
};
|
|
10031
|
+
}
|
|
10032
|
+
return {
|
|
10033
|
+
...request,
|
|
10034
|
+
status: "completed",
|
|
10035
|
+
completedAt,
|
|
10036
|
+
elapsedMs: elapsedMs(request.startedAt, completedAt),
|
|
10037
|
+
itemCount: response.itemCount
|
|
10038
|
+
};
|
|
10039
|
+
}
|
|
10040
|
+
function buildFailedWebResearchResult(request, abortController, task, error) {
|
|
10041
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10042
|
+
const cancelled = abortController.signal.aborted && task.cancelRequestedAt !== void 0;
|
|
10043
|
+
return {
|
|
10044
|
+
...request,
|
|
10045
|
+
status: cancelled ? "cancelled" : "failed",
|
|
10046
|
+
completedAt,
|
|
10047
|
+
elapsedMs: elapsedMs(request.startedAt, completedAt),
|
|
10048
|
+
error: cancelled ? "web research was cancelled by the user." : formatErrorMessage(error)
|
|
10049
|
+
};
|
|
10050
|
+
}
|
|
10051
|
+
function elapsedMs(startedAt, completedAt) {
|
|
10052
|
+
return Math.max(0, Date.parse(completedAt) - Date.parse(startedAt));
|
|
10053
|
+
}
|
|
10054
|
+
function getActiveWebResearchRequests(tasks) {
|
|
10055
|
+
return [...tasks.values()].map((task) => task.request);
|
|
10056
|
+
}
|
|
10057
|
+
function getWebResearchTaskSnapshots(tasks) {
|
|
10058
|
+
return [...tasks.values()].map((task) => ({
|
|
10059
|
+
request: task.request,
|
|
10060
|
+
cancelRequestedAt: task.cancelRequestedAt
|
|
10061
|
+
}));
|
|
10062
|
+
}
|
|
10063
|
+
function cancelWebResearchTask(tasks, id) {
|
|
10064
|
+
const task = tasks.get(id);
|
|
10065
|
+
if (!task || task.abortController.signal.aborted) return false;
|
|
10066
|
+
task.cancelRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10067
|
+
task.request.progress = "cancelling";
|
|
10068
|
+
task.abortController.abort(
|
|
10069
|
+
new Error("web research was cancelled by the user.")
|
|
10070
|
+
);
|
|
10071
|
+
return true;
|
|
10072
|
+
}
|
|
10073
|
+
function createWebResearchRequest(cwd, provider, input) {
|
|
10074
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10075
|
+
return {
|
|
10076
|
+
tool: "web_research",
|
|
10077
|
+
id: randomUUID(),
|
|
10078
|
+
provider,
|
|
10079
|
+
input,
|
|
10080
|
+
outputPath: buildWebResearchArtifactPath(cwd, input, startedAt),
|
|
10081
|
+
startedAt
|
|
10082
|
+
};
|
|
10083
|
+
}
|
|
10084
|
+
function buildWebResearchArtifactPath(cwd, input, startedAt) {
|
|
10085
|
+
const timestamp = startedAt.replaceAll(":", "-").replace(".", "-");
|
|
10086
|
+
const slug = slugifyWebResearchInput(input);
|
|
10087
|
+
return join2(cwd, RESEARCH_ARTIFACTS_DIR, `${timestamp}-${slug}.md`);
|
|
10088
|
+
}
|
|
10089
|
+
function slugifyWebResearchInput(input) {
|
|
10090
|
+
const slug = input.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
|
|
10091
|
+
return slug.length > 0 ? slug : "research";
|
|
10092
|
+
}
|
|
10093
|
+
function getWebResearchProgressIcon(request) {
|
|
10094
|
+
if (request.progress === "poll retrying after transient errors") {
|
|
10095
|
+
return "\u27F3";
|
|
10096
|
+
}
|
|
10097
|
+
if (request.progress === "queued" || request.progress === "cancelling") {
|
|
10098
|
+
return "\u25CC";
|
|
10099
|
+
}
|
|
10100
|
+
if (request.progress === "starting") {
|
|
10101
|
+
return "\u25D4";
|
|
10102
|
+
}
|
|
10103
|
+
if (request.progress?.startsWith("started:")) {
|
|
10104
|
+
return "\u25D1";
|
|
10105
|
+
}
|
|
10106
|
+
return "\u25CF";
|
|
10107
|
+
}
|
|
10108
|
+
function summarizeWebResearchProgress(message, providerLabel2) {
|
|
10109
|
+
const startingMessage = `Starting research via ${providerLabel2}`;
|
|
10110
|
+
if (message === startingMessage) {
|
|
10111
|
+
return "starting";
|
|
10112
|
+
}
|
|
10113
|
+
const startedPrefix = `${providerLabel2} research started: `;
|
|
10114
|
+
if (message.startsWith(startedPrefix)) {
|
|
10115
|
+
return `started: ${message.slice(startedPrefix.length)}`;
|
|
10116
|
+
}
|
|
10117
|
+
const statusPrefix = `Research via ${providerLabel2}: `;
|
|
10118
|
+
if (message.startsWith(statusPrefix)) {
|
|
10119
|
+
return message.slice(statusPrefix.length).replace(/\s+\([^)]* elapsed\)$/u, "").trim();
|
|
10120
|
+
}
|
|
10121
|
+
const retryPrefix = `${providerLabel2} research poll is still retrying after transient errors`;
|
|
10122
|
+
if (message.startsWith(retryPrefix)) {
|
|
10123
|
+
return "poll retrying after transient errors";
|
|
10124
|
+
}
|
|
10125
|
+
return message.trim();
|
|
10126
|
+
}
|
|
10127
|
+
function formatWebResearchResultMessage(result, reportText) {
|
|
10128
|
+
const text = reportText.trim();
|
|
10129
|
+
if (text.length > 0) {
|
|
10130
|
+
return `${text}
|
|
10131
|
+
`;
|
|
10132
|
+
}
|
|
10133
|
+
if (result.error) {
|
|
10134
|
+
return `${result.error}
|
|
10135
|
+
`;
|
|
10136
|
+
}
|
|
10137
|
+
return "";
|
|
10138
|
+
}
|
|
10139
|
+
async function writeWebResearchArtifact(result, reportText) {
|
|
10140
|
+
await mkdir2(dirname2(result.outputPath), { recursive: true });
|
|
10141
|
+
await writeFile2(
|
|
10142
|
+
result.outputPath,
|
|
10143
|
+
formatWebResearchArtifact(result, reportText),
|
|
10144
|
+
"utf-8"
|
|
10145
|
+
);
|
|
10146
|
+
}
|
|
10147
|
+
function formatWebResearchArtifact(result, reportText) {
|
|
10148
|
+
const providerLabel2 = PROVIDERS_BY_ID[result.provider]?.label ?? result.provider;
|
|
10149
|
+
const metadata = {
|
|
10150
|
+
query: result.input,
|
|
10151
|
+
provider: providerLabel2,
|
|
10152
|
+
providerId: result.provider,
|
|
10153
|
+
status: result.status,
|
|
10154
|
+
startedAt: result.startedAt,
|
|
10155
|
+
completedAt: result.completedAt,
|
|
10156
|
+
elapsedMs: result.elapsedMs,
|
|
10157
|
+
itemCount: result.itemCount,
|
|
10158
|
+
error: result.error
|
|
10159
|
+
};
|
|
10160
|
+
const lines = [
|
|
10161
|
+
"---",
|
|
10162
|
+
...Object.entries(metadata).flatMap(
|
|
10163
|
+
([key, value]) => value === void 0 ? [] : [`${key}: ${formatYamlScalar(value)}`]
|
|
10164
|
+
),
|
|
10165
|
+
"---",
|
|
10166
|
+
"",
|
|
10167
|
+
"# Web research report"
|
|
10168
|
+
];
|
|
10169
|
+
if (reportText) {
|
|
10170
|
+
lines.push("", reportText);
|
|
10171
|
+
}
|
|
10172
|
+
return `${lines.join("\n")}
|
|
10173
|
+
`;
|
|
10174
|
+
}
|
|
10175
|
+
function formatYamlScalar(value) {
|
|
10176
|
+
if (typeof value === "number") {
|
|
10177
|
+
return String(value);
|
|
10178
|
+
}
|
|
10179
|
+
return JSON.stringify(value);
|
|
10180
|
+
}
|
|
10181
|
+
async function loadWebResearchHistory(cwd, maxItems = MAX_RESEARCH_HISTORY_ITEMS) {
|
|
10182
|
+
const dir = join2(cwd, RESEARCH_ARTIFACTS_DIR);
|
|
10183
|
+
let entries;
|
|
10184
|
+
try {
|
|
10185
|
+
entries = await readdir(dir);
|
|
10186
|
+
} catch {
|
|
10187
|
+
return [];
|
|
10188
|
+
}
|
|
10189
|
+
const markdown = entries.filter((name) => name.endsWith(".md"));
|
|
10190
|
+
const withStats = await Promise.all(
|
|
10191
|
+
markdown.map(async (fileName) => {
|
|
10192
|
+
const outputPath = join2(dir, fileName);
|
|
10193
|
+
try {
|
|
10194
|
+
return { fileName, outputPath, stat: await stat(outputPath) };
|
|
10195
|
+
} catch {
|
|
10196
|
+
return void 0;
|
|
10197
|
+
}
|
|
10198
|
+
})
|
|
10199
|
+
);
|
|
10200
|
+
const newest = withStats.filter((item) => item !== void 0).sort((left, right) => right.stat.mtimeMs - left.stat.mtimeMs).slice(0, maxItems);
|
|
10201
|
+
return Promise.all(
|
|
10202
|
+
newest.map(async ({ fileName, outputPath, stat: stat2 }) => {
|
|
10203
|
+
let content = "";
|
|
10204
|
+
try {
|
|
10205
|
+
content = await readFile2(outputPath, "utf-8");
|
|
10206
|
+
} catch {
|
|
10207
|
+
}
|
|
10208
|
+
const metadata = parseWebResearchArtifactMetadata(content);
|
|
10209
|
+
return {
|
|
10210
|
+
outputPath,
|
|
10211
|
+
fileName,
|
|
10212
|
+
query: metadata.query ?? "",
|
|
10213
|
+
title: deriveWebResearchTitle(content, metadata.query ?? ""),
|
|
10214
|
+
provider: metadata.provider ?? "",
|
|
10215
|
+
status: metadata.status ?? "unknown",
|
|
10216
|
+
startedAt: metadata.startedAt ?? "",
|
|
10217
|
+
completedAt: metadata.completedAt ?? "",
|
|
10218
|
+
elapsedMs: computeHistoryElapsedMs(metadata),
|
|
10219
|
+
mtimeMs: stat2.mtimeMs
|
|
10220
|
+
};
|
|
10221
|
+
})
|
|
10222
|
+
);
|
|
10223
|
+
}
|
|
10224
|
+
function parseWebResearchArtifactMetadata(content) {
|
|
10225
|
+
return parseWebResearchFrontmatter(content) ?? parseLegacyWebResearchArtifactMetadata(content);
|
|
10226
|
+
}
|
|
10227
|
+
function parseWebResearchFrontmatter(content) {
|
|
10228
|
+
if (!content.startsWith("---\n")) {
|
|
10229
|
+
return void 0;
|
|
10230
|
+
}
|
|
10231
|
+
const end = content.indexOf("\n---", 4);
|
|
10232
|
+
if (end === -1) {
|
|
10233
|
+
return void 0;
|
|
10234
|
+
}
|
|
10235
|
+
const result = {};
|
|
10236
|
+
const frontmatter = content.slice(4, end);
|
|
10237
|
+
for (const line of frontmatter.split(/\r?\n/u)) {
|
|
10238
|
+
const match = /^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/u.exec(line);
|
|
10239
|
+
if (!match) {
|
|
10240
|
+
continue;
|
|
10241
|
+
}
|
|
10242
|
+
result[match[1] ?? ""] = parseYamlScalar(match[2] ?? "");
|
|
10243
|
+
}
|
|
10244
|
+
return result;
|
|
10245
|
+
}
|
|
10246
|
+
function parseYamlScalar(value) {
|
|
10247
|
+
const trimmed = value.trim();
|
|
10248
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
10249
|
+
try {
|
|
10250
|
+
const parsed = JSON.parse(trimmed);
|
|
10251
|
+
return typeof parsed === "string" ? parsed : String(parsed);
|
|
10252
|
+
} catch {
|
|
10253
|
+
}
|
|
10254
|
+
}
|
|
10255
|
+
return trimmed;
|
|
10256
|
+
}
|
|
10257
|
+
function parseLegacyWebResearchArtifactMetadata(content) {
|
|
10258
|
+
const result = {};
|
|
10259
|
+
const metadataHeadings = /* @__PURE__ */ new Map([
|
|
10260
|
+
["Query", "query"],
|
|
10261
|
+
["Provider", "provider"],
|
|
10262
|
+
["Status", "status"],
|
|
10263
|
+
["Started", "startedAt"],
|
|
10264
|
+
["Completed", "completedAt"]
|
|
10265
|
+
]);
|
|
10266
|
+
const artifactBodyHeadings = /* @__PURE__ */ new Set(["Elapsed", "Items", "Error", "Report"]);
|
|
10267
|
+
const lines = content.split(/\r?\n/u);
|
|
10268
|
+
for (let index = 0; index < lines.length; index++) {
|
|
10269
|
+
const match = /^##\s+(.+)$/u.exec(lines[index] ?? "");
|
|
10270
|
+
if (!match) continue;
|
|
10271
|
+
const heading = match[1] ?? "";
|
|
10272
|
+
if (artifactBodyHeadings.has(heading)) break;
|
|
10273
|
+
const key = metadataHeadings.get(heading);
|
|
10274
|
+
if (key === void 0 || result[key] !== void 0) continue;
|
|
10275
|
+
const values = [];
|
|
10276
|
+
for (let next = index + 1; next < lines.length; next++) {
|
|
10277
|
+
if (/^##\s+/u.test(lines[next] ?? "")) break;
|
|
10278
|
+
if ((lines[next] ?? "").trim() || values.length > 0)
|
|
10279
|
+
values.push(lines[next] ?? "");
|
|
10280
|
+
}
|
|
10281
|
+
result[key] = values.join("\n").trim();
|
|
10282
|
+
}
|
|
10283
|
+
return result;
|
|
10284
|
+
}
|
|
10285
|
+
var RESEARCH_TITLE_MAX_LENGTH = 80;
|
|
10286
|
+
var NON_TITLE_HEADINGS = /* @__PURE__ */ new Set([
|
|
10287
|
+
"Web research report",
|
|
10288
|
+
"Query",
|
|
10289
|
+
"Provider",
|
|
10290
|
+
"Status",
|
|
10291
|
+
"Started",
|
|
10292
|
+
"Completed",
|
|
10293
|
+
"Elapsed",
|
|
10294
|
+
"Items",
|
|
10295
|
+
"Error",
|
|
10296
|
+
"Report"
|
|
10297
|
+
]);
|
|
10298
|
+
function deriveWebResearchTitle(content, query2) {
|
|
10299
|
+
const body = getWebResearchBody(content);
|
|
10300
|
+
for (const line of body.split(/\r?\n/u)) {
|
|
10301
|
+
const match = /^#{1,2}\s+(.+?)\s*$/u.exec(line);
|
|
10302
|
+
if (!match) continue;
|
|
10303
|
+
const heading = (match[1] ?? "").trim();
|
|
10304
|
+
if (heading.length > 0 && !NON_TITLE_HEADINGS.has(heading)) {
|
|
10305
|
+
return heading;
|
|
10306
|
+
}
|
|
10307
|
+
}
|
|
10308
|
+
const fallback = query2.replace(/\s+/g, " ").trim();
|
|
10309
|
+
if (fallback.length <= RESEARCH_TITLE_MAX_LENGTH) {
|
|
10310
|
+
return fallback;
|
|
10311
|
+
}
|
|
10312
|
+
return `${fallback.slice(0, RESEARCH_TITLE_MAX_LENGTH - 1)}\u2026`;
|
|
10313
|
+
}
|
|
10314
|
+
function computeHistoryElapsedMs(metadata) {
|
|
10315
|
+
if (metadata.elapsedMs !== void 0) {
|
|
10316
|
+
const parsed = Number(metadata.elapsedMs);
|
|
10317
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
10318
|
+
return parsed;
|
|
10319
|
+
}
|
|
10320
|
+
}
|
|
10321
|
+
const started = Date.parse(metadata.startedAt ?? "");
|
|
10322
|
+
const completed = Date.parse(metadata.completedAt ?? "");
|
|
10323
|
+
if (Number.isFinite(started) && Number.isFinite(completed)) {
|
|
10324
|
+
return Math.max(0, completed - started);
|
|
10325
|
+
}
|
|
10326
|
+
return void 0;
|
|
10327
|
+
}
|
|
10328
|
+
function getWebResearchBody(content) {
|
|
10329
|
+
if (!content.startsWith("---\n")) {
|
|
10330
|
+
return content;
|
|
10331
|
+
}
|
|
10332
|
+
const end = content.indexOf("\n---", 4);
|
|
10333
|
+
if (end === -1) {
|
|
10334
|
+
return content;
|
|
10335
|
+
}
|
|
10336
|
+
const afterClose = content.indexOf("\n", end + 4);
|
|
10337
|
+
return afterClose === -1 ? "" : content.slice(afterClose + 1);
|
|
10338
|
+
}
|
|
10339
|
+
async function loadWebResearchReport(outputPath) {
|
|
10340
|
+
const buffer = await readFile2(outputPath);
|
|
10341
|
+
const truncated = buffer.byteLength > RESEARCH_REPORT_MAX_BYTES;
|
|
10342
|
+
const content = buffer.subarray(0, RESEARCH_REPORT_MAX_BYTES).toString("utf-8");
|
|
10343
|
+
return {
|
|
10344
|
+
body: getWebResearchBody(content).trim(),
|
|
10345
|
+
metadata: parseWebResearchArtifactMetadata(content),
|
|
10346
|
+
truncated
|
|
10347
|
+
};
|
|
10348
|
+
}
|
|
10349
|
+
async function loadWebResearchPreview(outputPath) {
|
|
10350
|
+
const buffer = await readFile2(outputPath);
|
|
10351
|
+
const truncated = buffer.byteLength > RESEARCH_PREVIEW_MAX_BYTES;
|
|
10352
|
+
const text = buffer.subarray(0, RESEARCH_PREVIEW_MAX_BYTES).toString("utf-8");
|
|
10353
|
+
return truncated ? `${text}
|
|
10354
|
+
|
|
10355
|
+
---
|
|
10356
|
+
Preview truncated. Open the full report at \`${outputPath}\`.` : `${text}
|
|
10357
|
+
|
|
10358
|
+
---
|
|
10359
|
+
Full report: \`${outputPath}\``;
|
|
10360
|
+
}
|
|
10361
|
+
function trackPendingResearchTask(task) {
|
|
10362
|
+
const tracked = task.catch(() => {
|
|
10363
|
+
}).finally(() => {
|
|
10364
|
+
pendingResearchTasks.delete(tracked);
|
|
10365
|
+
});
|
|
10366
|
+
pendingResearchTasks.add(tracked);
|
|
10367
|
+
}
|
|
10368
|
+
async function waitForPendingResearchTasks() {
|
|
10369
|
+
await Promise.all([...pendingResearchTasks]);
|
|
10370
|
+
}
|
|
10371
|
+
|
|
10372
|
+
// src/web-research-view.ts
|
|
10373
|
+
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
10374
|
+
import {
|
|
10375
|
+
getKeybindings,
|
|
10376
|
+
Key,
|
|
10377
|
+
Markdown,
|
|
10378
|
+
matchesKey,
|
|
10379
|
+
truncateToWidth,
|
|
10380
|
+
visibleWidth
|
|
10381
|
+
} from "@earendil-works/pi-tui";
|
|
10382
|
+
var STATUS_MESSAGE_TTL_MS = 1500;
|
|
10383
|
+
var PAGE_JUMP = 10;
|
|
10384
|
+
var WebResearchManagerView = class {
|
|
10385
|
+
constructor(tui, theme, done, cwd, tasks, onChange, actions, loadHistory = loadWebResearchHistory) {
|
|
10386
|
+
this.tui = tui;
|
|
10387
|
+
this.theme = theme;
|
|
10388
|
+
this.done = done;
|
|
10389
|
+
this.cwd = cwd;
|
|
10390
|
+
this.tasks = tasks;
|
|
10391
|
+
this.onChange = onChange;
|
|
10392
|
+
this.actions = actions;
|
|
10393
|
+
this.loadHistory = loadHistory;
|
|
10394
|
+
void this.reloadHistory();
|
|
10395
|
+
}
|
|
10396
|
+
tui;
|
|
10397
|
+
theme;
|
|
10398
|
+
done;
|
|
10399
|
+
cwd;
|
|
10400
|
+
tasks;
|
|
10401
|
+
onChange;
|
|
10402
|
+
actions;
|
|
10403
|
+
loadHistory;
|
|
10404
|
+
selectedIndex = 0;
|
|
10405
|
+
history = [];
|
|
10406
|
+
confirmCancelId;
|
|
10407
|
+
open;
|
|
10408
|
+
scrollOffset = 0;
|
|
10409
|
+
statusMessage;
|
|
10410
|
+
statusMessageTimer;
|
|
10411
|
+
isReportOpen() {
|
|
10412
|
+
return this.open !== void 0;
|
|
10413
|
+
}
|
|
10414
|
+
render(width) {
|
|
10415
|
+
if (this.open?.kind === "report") {
|
|
10416
|
+
return this.renderReport(this.open, width);
|
|
10417
|
+
}
|
|
10418
|
+
if (this.open?.kind === "detail") {
|
|
10419
|
+
const snapshot = this.findSnapshot(this.open.taskId);
|
|
10420
|
+
if (snapshot) return this.renderDetail(snapshot, width);
|
|
10421
|
+
this.open = void 0;
|
|
10422
|
+
}
|
|
10423
|
+
return this.renderTable(width);
|
|
10424
|
+
}
|
|
10425
|
+
invalidate() {
|
|
10426
|
+
}
|
|
10427
|
+
dispose() {
|
|
10428
|
+
if (this.statusMessageTimer) clearTimeout(this.statusMessageTimer);
|
|
10429
|
+
}
|
|
10430
|
+
handleInput(data) {
|
|
10431
|
+
if (this.open) {
|
|
10432
|
+
this.handleOpenInput(data);
|
|
10433
|
+
} else {
|
|
10434
|
+
this.handleTableInput(data);
|
|
10435
|
+
}
|
|
10436
|
+
this.tui.requestRender();
|
|
10437
|
+
}
|
|
10438
|
+
refresh() {
|
|
10439
|
+
if (this.open?.kind === "detail" && !this.findSnapshot(this.open.taskId)) {
|
|
10440
|
+
this.open = void 0;
|
|
10441
|
+
this.confirmCancelId = void 0;
|
|
10442
|
+
}
|
|
10443
|
+
void this.reloadHistory();
|
|
10444
|
+
this.tui.requestRender();
|
|
10445
|
+
}
|
|
10446
|
+
// --- table mode ---
|
|
10447
|
+
getRows() {
|
|
10448
|
+
const snapshots = getWebResearchTaskSnapshots(this.tasks).sort(
|
|
10449
|
+
(a, b) => a.request.startedAt.localeCompare(b.request.startedAt)
|
|
10450
|
+
);
|
|
10451
|
+
const runningPaths = new Set(
|
|
10452
|
+
snapshots.map((snapshot) => snapshot.request.outputPath)
|
|
10453
|
+
);
|
|
10454
|
+
const rows = snapshots.map((snapshot) => ({
|
|
10455
|
+
kind: "running",
|
|
10456
|
+
snapshot
|
|
10457
|
+
}));
|
|
10458
|
+
for (const item of this.history) {
|
|
10459
|
+
if (runningPaths.has(item.outputPath)) continue;
|
|
10460
|
+
rows.push({ kind: "history", item });
|
|
10461
|
+
}
|
|
10462
|
+
return rows;
|
|
10463
|
+
}
|
|
10464
|
+
border(width) {
|
|
10465
|
+
return this.theme.fg("border", "\u2500".repeat(Math.max(1, width)));
|
|
10466
|
+
}
|
|
10467
|
+
renderTable(width) {
|
|
10468
|
+
const rows = this.getRows();
|
|
10469
|
+
this.selectedIndex = clamp2(this.selectedIndex, 0, rows.length - 1);
|
|
10470
|
+
const lines = [
|
|
10471
|
+
this.border(width),
|
|
10472
|
+
this.theme.fg("accent", " Web research"),
|
|
10473
|
+
""
|
|
10474
|
+
];
|
|
10475
|
+
if (rows.length === 0) {
|
|
10476
|
+
lines.push(this.theme.fg("muted", " No research jobs or reports"));
|
|
10477
|
+
} else {
|
|
10478
|
+
const layout = computeTableLayout(rows, width);
|
|
10479
|
+
const now = Date.now();
|
|
10480
|
+
rows.forEach((row, index) => {
|
|
10481
|
+
lines.push(
|
|
10482
|
+
formatResearchTableRow(
|
|
10483
|
+
row,
|
|
10484
|
+
layout,
|
|
10485
|
+
this.theme,
|
|
10486
|
+
index === this.selectedIndex,
|
|
10487
|
+
now
|
|
10488
|
+
)
|
|
10489
|
+
);
|
|
10490
|
+
if (row.kind === "running" && this.confirmCancelId === row.snapshot.request.id) {
|
|
10491
|
+
lines.push(
|
|
10492
|
+
this.theme.fg(
|
|
10493
|
+
"warning",
|
|
10494
|
+
truncateToWidth(
|
|
10495
|
+
` Press c again to cancel this ${providerLabel(row.snapshot.request.provider)} research`,
|
|
10496
|
+
width
|
|
10497
|
+
)
|
|
10498
|
+
)
|
|
10499
|
+
);
|
|
10500
|
+
}
|
|
10501
|
+
});
|
|
10502
|
+
}
|
|
10503
|
+
lines.push("");
|
|
10504
|
+
if (this.statusMessage) {
|
|
10505
|
+
lines.push(this.theme.fg("accent", ` ${this.statusMessage}`));
|
|
10506
|
+
}
|
|
10507
|
+
lines.push(
|
|
10508
|
+
this.theme.fg(
|
|
10509
|
+
"dim",
|
|
10510
|
+
" \u2191\u2193 move \xB7 Enter open \xB7 c cancel running \xB7 Esc close"
|
|
10511
|
+
),
|
|
10512
|
+
this.border(width)
|
|
10513
|
+
);
|
|
10514
|
+
return lines;
|
|
10515
|
+
}
|
|
10516
|
+
handleTableInput(data) {
|
|
10517
|
+
const kb = getKeybindings();
|
|
10518
|
+
const rows = this.getRows();
|
|
10519
|
+
if (kb.matches(data, "tui.select.up")) this.move(rows.length, -1);
|
|
10520
|
+
else if (kb.matches(data, "tui.select.down")) this.move(rows.length, 1);
|
|
10521
|
+
else if (kb.matches(data, "tui.select.pageUp"))
|
|
10522
|
+
this.move(rows.length, -PAGE_JUMP, false);
|
|
10523
|
+
else if (kb.matches(data, "tui.select.pageDown"))
|
|
10524
|
+
this.move(rows.length, PAGE_JUMP, false);
|
|
10525
|
+
else if (kb.matches(data, "tui.select.confirm"))
|
|
10526
|
+
void this.openRow(rows[this.selectedIndex]);
|
|
10527
|
+
else if (data === "c") this.cancelRow(rows[this.selectedIndex]);
|
|
10528
|
+
else if (kb.matches(data, "tui.select.cancel")) {
|
|
10529
|
+
if (this.confirmCancelId) this.confirmCancelId = void 0;
|
|
10530
|
+
else this.done(void 0);
|
|
10531
|
+
}
|
|
10532
|
+
}
|
|
10533
|
+
move(count, delta, wrap = true) {
|
|
10534
|
+
this.confirmCancelId = void 0;
|
|
10535
|
+
if (count === 0) return;
|
|
10536
|
+
const next = this.selectedIndex + delta;
|
|
10537
|
+
this.selectedIndex = wrap ? (next + count) % count : clamp2(next, 0, count - 1);
|
|
10538
|
+
}
|
|
10539
|
+
async openRow(row) {
|
|
10540
|
+
if (!row) return;
|
|
10541
|
+
this.confirmCancelId = void 0;
|
|
10542
|
+
if (row.kind === "running") {
|
|
10543
|
+
this.open = { kind: "detail", taskId: row.snapshot.request.id };
|
|
10544
|
+
this.scrollOffset = 0;
|
|
10545
|
+
return;
|
|
10546
|
+
}
|
|
10547
|
+
try {
|
|
10548
|
+
const report = await loadWebResearchReport(row.item.outputPath);
|
|
10549
|
+
this.open = {
|
|
10550
|
+
kind: "report",
|
|
10551
|
+
title: row.item.title || row.item.query,
|
|
10552
|
+
body: report.body,
|
|
10553
|
+
truncated: report.truncated,
|
|
10554
|
+
item: row.item,
|
|
10555
|
+
markdown: new Markdown(report.body, 1, 0, getMarkdownTheme())
|
|
10556
|
+
};
|
|
10557
|
+
this.scrollOffset = 0;
|
|
10558
|
+
} catch (error) {
|
|
10559
|
+
this.showStatusMessage(
|
|
10560
|
+
`Failed to read ${row.item.outputPath}: ${formatErrorMessage(error)}`
|
|
10561
|
+
);
|
|
10562
|
+
}
|
|
10563
|
+
this.tui.requestRender();
|
|
10564
|
+
}
|
|
10565
|
+
cancelRow(row) {
|
|
10566
|
+
if (!row || row.kind !== "running") return;
|
|
10567
|
+
const id = row.snapshot.request.id;
|
|
10568
|
+
if (this.confirmCancelId !== id) {
|
|
10569
|
+
this.confirmCancelId = id;
|
|
10570
|
+
return;
|
|
10571
|
+
}
|
|
10572
|
+
cancelWebResearchTask(this.tasks, id);
|
|
10573
|
+
this.confirmCancelId = void 0;
|
|
10574
|
+
this.onChange();
|
|
10575
|
+
}
|
|
10576
|
+
// --- report / detail mode ---
|
|
10577
|
+
renderReport(open, width) {
|
|
10578
|
+
const lines = [
|
|
10579
|
+
this.border(width),
|
|
10580
|
+
this.theme.fg("accent", truncateToWidth(` ${open.title}`, width)),
|
|
10581
|
+
this.theme.fg(
|
|
10582
|
+
"dim",
|
|
10583
|
+
" c copy markdown \xB7 i inject into context \xB7 \u2191\u2193/PgUp/PgDn scroll \xB7 Esc back"
|
|
10584
|
+
),
|
|
10585
|
+
this.border(width),
|
|
10586
|
+
""
|
|
10587
|
+
];
|
|
10588
|
+
let body;
|
|
10589
|
+
try {
|
|
10590
|
+
body = open.markdown.render(Math.max(20, width - 2));
|
|
10591
|
+
} catch {
|
|
10592
|
+
body = open.body.split("\n").map((line) => truncateToWidth(line, width));
|
|
10593
|
+
}
|
|
10594
|
+
const viewport = this.viewportHeight();
|
|
10595
|
+
const maxOffset = Math.max(0, body.length - viewport);
|
|
10596
|
+
this.scrollOffset = clamp2(this.scrollOffset, 0, maxOffset);
|
|
10597
|
+
lines.push(...body.slice(this.scrollOffset, this.scrollOffset + viewport));
|
|
10598
|
+
lines.push("");
|
|
10599
|
+
const footer = [];
|
|
10600
|
+
if (body.length > viewport) {
|
|
10601
|
+
const end = Math.min(body.length, this.scrollOffset + viewport);
|
|
10602
|
+
footer.push(`lines ${this.scrollOffset + 1}\u2013${end} of ${body.length}`);
|
|
10603
|
+
}
|
|
10604
|
+
if (open.truncated) {
|
|
10605
|
+
footer.push(`report truncated \xB7 full text: ${open.item.outputPath}`);
|
|
10606
|
+
}
|
|
10607
|
+
if (this.statusMessage) {
|
|
10608
|
+
lines.push(this.theme.fg("accent", ` ${this.statusMessage}`));
|
|
10609
|
+
}
|
|
10610
|
+
if (footer.length > 0) {
|
|
10611
|
+
lines.push(
|
|
10612
|
+
this.theme.fg("dim", truncateToWidth(` ${footer.join(" \xB7 ")}`, width))
|
|
10613
|
+
);
|
|
10614
|
+
}
|
|
10615
|
+
lines.push(this.border(width));
|
|
10616
|
+
return lines;
|
|
10617
|
+
}
|
|
10618
|
+
renderDetail(snapshot, width) {
|
|
10619
|
+
const request = snapshot.request;
|
|
10620
|
+
const elapsed = formatCompactElapsed(
|
|
10621
|
+
Date.now() - Date.parse(request.startedAt)
|
|
10622
|
+
);
|
|
10623
|
+
const progress = snapshot.cancelRequestedAt ? "cancelling" : request.progress ?? "running";
|
|
10624
|
+
const lines = [
|
|
10625
|
+
this.border(width),
|
|
10626
|
+
this.theme.fg(
|
|
10627
|
+
"accent",
|
|
10628
|
+
truncateToWidth(
|
|
10629
|
+
` Running research via ${providerLabel(request.provider)}`,
|
|
10630
|
+
width
|
|
10631
|
+
)
|
|
10632
|
+
),
|
|
10633
|
+
this.theme.fg("dim", " c cancel \xB7 Esc back"),
|
|
10634
|
+
this.border(width),
|
|
10635
|
+
"",
|
|
10636
|
+
truncateToWidth(` Status: ${progress} (${elapsed} elapsed)`, width),
|
|
10637
|
+
truncateToWidth(` Report path: ${request.outputPath}`, width),
|
|
10638
|
+
"",
|
|
10639
|
+
this.theme.fg("accent", " Research brief"),
|
|
10640
|
+
""
|
|
10641
|
+
];
|
|
10642
|
+
for (const line of request.input.split("\n").slice(0, 100)) {
|
|
10643
|
+
lines.push(truncateToWidth(` ${line}`, width));
|
|
10644
|
+
}
|
|
10645
|
+
if (this.confirmCancelId === request.id) {
|
|
10646
|
+
lines.push(
|
|
10647
|
+
"",
|
|
10648
|
+
this.theme.fg("warning", " Press c again to cancel this research")
|
|
10649
|
+
);
|
|
10650
|
+
}
|
|
10651
|
+
if (this.statusMessage) {
|
|
10652
|
+
lines.push("", this.theme.fg("accent", ` ${this.statusMessage}`));
|
|
10653
|
+
}
|
|
10654
|
+
lines.push(this.border(width));
|
|
10655
|
+
return lines;
|
|
10656
|
+
}
|
|
10657
|
+
handleOpenInput(data) {
|
|
10658
|
+
const kb = getKeybindings();
|
|
10659
|
+
const open = this.open;
|
|
10660
|
+
if (!open) return;
|
|
10661
|
+
if (kb.matches(data, "tui.select.cancel")) {
|
|
10662
|
+
if (this.confirmCancelId) {
|
|
10663
|
+
this.confirmCancelId = void 0;
|
|
10664
|
+
return;
|
|
10665
|
+
}
|
|
10666
|
+
this.open = void 0;
|
|
10667
|
+
this.scrollOffset = 0;
|
|
10668
|
+
return;
|
|
10669
|
+
}
|
|
10670
|
+
if (open.kind === "detail") {
|
|
10671
|
+
if (data === "c") {
|
|
10672
|
+
const snapshot = this.findSnapshot(open.taskId);
|
|
10673
|
+
if (!snapshot) return;
|
|
10674
|
+
if (this.confirmCancelId !== open.taskId) {
|
|
10675
|
+
this.confirmCancelId = open.taskId;
|
|
10676
|
+
return;
|
|
10677
|
+
}
|
|
10678
|
+
cancelWebResearchTask(this.tasks, open.taskId);
|
|
10679
|
+
this.confirmCancelId = void 0;
|
|
10680
|
+
this.onChange();
|
|
10681
|
+
this.showStatusMessage("Cancellation requested");
|
|
10682
|
+
}
|
|
10683
|
+
return;
|
|
10684
|
+
}
|
|
10685
|
+
if (kb.matches(data, "tui.select.up")) this.scrollOffset -= 1;
|
|
10686
|
+
else if (kb.matches(data, "tui.select.down")) this.scrollOffset += 1;
|
|
10687
|
+
else if (kb.matches(data, "tui.select.pageUp"))
|
|
10688
|
+
this.scrollOffset -= this.viewportHeight();
|
|
10689
|
+
else if (kb.matches(data, "tui.select.pageDown"))
|
|
10690
|
+
this.scrollOffset += this.viewportHeight();
|
|
10691
|
+
else if (matchesKey(data, Key.home)) this.scrollOffset = 0;
|
|
10692
|
+
else if (matchesKey(data, Key.end))
|
|
10693
|
+
this.scrollOffset = Number.MAX_SAFE_INTEGER;
|
|
10694
|
+
else if (data === "c") {
|
|
10695
|
+
void this.actions.copyToClipboard(open.body).then(() => this.showStatusMessage("Copied report to clipboard")).catch((error) => {
|
|
10696
|
+
const message = `Copy failed: ${formatErrorMessage(error)}`;
|
|
10697
|
+
this.showStatusMessage(message);
|
|
10698
|
+
this.actions.notify(message, "error");
|
|
10699
|
+
});
|
|
10700
|
+
} else if (data === "i") {
|
|
10701
|
+
this.actions.injectReport({
|
|
10702
|
+
title: open.title,
|
|
10703
|
+
body: open.body,
|
|
10704
|
+
item: open.item
|
|
10705
|
+
});
|
|
10706
|
+
this.showStatusMessage("Report added to conversation context");
|
|
10707
|
+
}
|
|
10708
|
+
}
|
|
10709
|
+
viewportHeight() {
|
|
10710
|
+
return Math.max(5, Math.floor(this.tui.terminal.rows * 0.85) - 6);
|
|
10711
|
+
}
|
|
10712
|
+
showStatusMessage(message) {
|
|
10713
|
+
this.statusMessage = message;
|
|
10714
|
+
if (this.statusMessageTimer) clearTimeout(this.statusMessageTimer);
|
|
10715
|
+
this.statusMessageTimer = setTimeout(() => {
|
|
10716
|
+
this.statusMessage = void 0;
|
|
10717
|
+
this.statusMessageTimer = void 0;
|
|
10718
|
+
this.tui.requestRender();
|
|
10719
|
+
}, STATUS_MESSAGE_TTL_MS);
|
|
10720
|
+
}
|
|
10721
|
+
findSnapshot(taskId) {
|
|
10722
|
+
return getWebResearchTaskSnapshots(this.tasks).find(
|
|
10723
|
+
(snapshot) => snapshot.request.id === taskId
|
|
10724
|
+
);
|
|
10725
|
+
}
|
|
10726
|
+
async reloadHistory() {
|
|
10727
|
+
this.history = await this.loadHistory(this.cwd);
|
|
10728
|
+
this.tui.requestRender();
|
|
10729
|
+
}
|
|
10730
|
+
};
|
|
10731
|
+
function computeTableLayout(rows, width) {
|
|
10732
|
+
const providerWidth = clamp2(
|
|
10733
|
+
Math.max(0, ...rows.map((row) => visibleWidth(rowProvider(row)))),
|
|
10734
|
+
4,
|
|
10735
|
+
12
|
|
10736
|
+
);
|
|
10737
|
+
const date = 10;
|
|
10738
|
+
const duration = 7;
|
|
10739
|
+
const fixed = 2 + 2 + date + 1 + providerWidth + 1 + duration + 1;
|
|
10740
|
+
return {
|
|
10741
|
+
date,
|
|
10742
|
+
provider: providerWidth,
|
|
10743
|
+
duration,
|
|
10744
|
+
title: Math.max(10, width - fixed)
|
|
10745
|
+
};
|
|
10746
|
+
}
|
|
10747
|
+
function formatResearchTableRow(row, layout, theme, selected, now = Date.now()) {
|
|
10748
|
+
const cursor = selected ? "\u203A " : " ";
|
|
10749
|
+
const glyph = statusGlyph(row, theme);
|
|
10750
|
+
const date = padCell(
|
|
10751
|
+
formatRelativeDate(rowTimestampMs(row), now),
|
|
10752
|
+
layout.date
|
|
10753
|
+
);
|
|
10754
|
+
const provider = padCell(
|
|
10755
|
+
truncateToWidth(rowProvider(row), layout.provider),
|
|
10756
|
+
layout.provider
|
|
10757
|
+
);
|
|
10758
|
+
const duration = padCell(rowDuration(row, now), layout.duration, "right");
|
|
10759
|
+
const title = truncateToWidth(rowTitle(row), layout.title);
|
|
10760
|
+
const dim = (text) => row.kind === "history" ? text : theme.fg("dim", text);
|
|
10761
|
+
return `${cursor}${glyph} ${dim(date)} ${provider} ${theme.fg("muted", duration)} ${title}`;
|
|
10762
|
+
}
|
|
10763
|
+
function statusGlyph(row, theme) {
|
|
10764
|
+
if (row.kind === "running") {
|
|
10765
|
+
const icon = row.snapshot.cancelRequestedAt ? "\u25CC" : getWebResearchProgressIcon(row.snapshot.request);
|
|
10766
|
+
return theme.fg("accent", icon);
|
|
10767
|
+
}
|
|
10768
|
+
switch (row.item.status) {
|
|
10769
|
+
case "completed":
|
|
10770
|
+
return theme.fg("success", "\u2714");
|
|
10771
|
+
case "failed":
|
|
10772
|
+
return theme.fg("error", "\u2718");
|
|
10773
|
+
case "cancelled":
|
|
10774
|
+
return theme.fg("warning", "\u2718");
|
|
10775
|
+
default:
|
|
10776
|
+
return theme.fg("dim", "?");
|
|
10777
|
+
}
|
|
10778
|
+
}
|
|
10779
|
+
function rowProvider(row) {
|
|
10780
|
+
return row.kind === "running" ? providerLabel(row.snapshot.request.provider) : row.item.provider || "?";
|
|
10781
|
+
}
|
|
10782
|
+
function rowTimestampMs(row) {
|
|
10783
|
+
if (row.kind === "running") {
|
|
10784
|
+
return Date.parse(row.snapshot.request.startedAt);
|
|
10785
|
+
}
|
|
10786
|
+
const completed = Date.parse(row.item.completedAt);
|
|
10787
|
+
if (Number.isFinite(completed)) return completed;
|
|
10788
|
+
const started = Date.parse(row.item.startedAt);
|
|
10789
|
+
if (Number.isFinite(started)) return started;
|
|
10790
|
+
return row.item.mtimeMs;
|
|
10791
|
+
}
|
|
10792
|
+
function rowDuration(row, now) {
|
|
10793
|
+
if (row.kind === "running") {
|
|
10794
|
+
return formatCompactElapsed(
|
|
10795
|
+
now - Date.parse(row.snapshot.request.startedAt)
|
|
10796
|
+
);
|
|
10797
|
+
}
|
|
10798
|
+
return row.item.elapsedMs === void 0 ? "\u2014" : formatCompactElapsed(row.item.elapsedMs);
|
|
10799
|
+
}
|
|
10800
|
+
var REDUNDANT_PROGRESS = /* @__PURE__ */ new Set([
|
|
10801
|
+
"in_progress",
|
|
10802
|
+
"running",
|
|
10803
|
+
"starting",
|
|
10804
|
+
"queued",
|
|
10805
|
+
"cancelling"
|
|
10806
|
+
]);
|
|
10807
|
+
function rowTitle(row) {
|
|
10808
|
+
if (row.kind === "running") {
|
|
10809
|
+
const request = row.snapshot.request;
|
|
10810
|
+
const progress = row.snapshot.cancelRequestedAt ? void 0 : request.progress;
|
|
10811
|
+
const prefix = progress && !REDUNDANT_PROGRESS.has(progress) ? `${progress} \u2014 ` : "";
|
|
10812
|
+
return `${prefix}${cleanSingleLine(request.input)}`;
|
|
10813
|
+
}
|
|
10814
|
+
return row.item.title || cleanSingleLine(row.item.query);
|
|
10815
|
+
}
|
|
10816
|
+
function formatRelativeDate(timestampMs, now) {
|
|
10817
|
+
if (!Number.isFinite(timestampMs)) return "?";
|
|
10818
|
+
const diff = Math.max(0, now - timestampMs);
|
|
10819
|
+
if (diff < 6e4) return "now";
|
|
10820
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
10821
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
10822
|
+
if (diff < 7 * 864e5) return `${Math.floor(diff / 864e5)}d ago`;
|
|
10823
|
+
const date = new Date(timestampMs);
|
|
10824
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
10825
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
10826
|
+
if (date.getFullYear() === new Date(now).getFullYear()) {
|
|
10827
|
+
return `${month}-${day}`;
|
|
10828
|
+
}
|
|
10829
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
10830
|
+
}
|
|
10831
|
+
function providerLabel(providerId) {
|
|
10832
|
+
return PROVIDERS_BY_ID[providerId]?.label ?? providerId;
|
|
10833
|
+
}
|
|
10834
|
+
function padCell(text, width, align = "left") {
|
|
10835
|
+
const pad = " ".repeat(Math.max(0, width - visibleWidth(text)));
|
|
10836
|
+
return align === "left" ? `${text}${pad}` : `${pad}${text}`;
|
|
10837
|
+
}
|
|
10838
|
+
function clamp2(value, min, max) {
|
|
10839
|
+
return Math.min(Math.max(value, min), Math.max(min, max));
|
|
10840
|
+
}
|
|
10841
|
+
function formatCompactElapsed(ms) {
|
|
10842
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
10843
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
10844
|
+
const seconds = totalSeconds % 60;
|
|
10845
|
+
if (minutes > 0) {
|
|
10846
|
+
return `${minutes}m${seconds}s`;
|
|
10847
|
+
}
|
|
10848
|
+
return `${totalSeconds}s`;
|
|
10849
|
+
}
|
|
10850
|
+
function cleanSingleLine(text) {
|
|
10851
|
+
return text.replace(/\s+/g, " ").trim();
|
|
10852
|
+
}
|
|
10853
|
+
|
|
10854
|
+
// src/index.ts
|
|
10855
|
+
var DEFAULT_MAX_RESULTS = 5;
|
|
10856
|
+
var MAX_ALLOWED_RESULTS = 20;
|
|
10857
|
+
var MAX_SEARCH_QUERIES = 10;
|
|
10858
|
+
var RESEARCH_HEARTBEAT_MS = 15e3;
|
|
10859
|
+
var WEB_RESEARCH_RESULT_MESSAGE_TYPE = "web-research-result";
|
|
10860
|
+
var WEB_RESEARCH_REPORT_MESSAGE_TYPE = "web-research-report";
|
|
10861
|
+
var WEB_RESEARCH_WIDGET_KEY = "web-research-jobs";
|
|
10862
|
+
var DEFAULT_SUMMARY_SYMBOLS = {
|
|
10863
|
+
success: "\u2714",
|
|
10864
|
+
failure: "\u2718"
|
|
10865
|
+
};
|
|
10866
|
+
function webProvidersExtension(pi) {
|
|
10867
|
+
const activeWebResearchRequests = /* @__PURE__ */ new Map();
|
|
10868
|
+
let latestWidgetContext;
|
|
10869
|
+
let webResearchWidgetTimer;
|
|
10870
|
+
const stopWebResearchWidgetTimer = () => {
|
|
10871
|
+
if (webResearchWidgetTimer) {
|
|
10872
|
+
clearInterval(webResearchWidgetTimer);
|
|
10873
|
+
webResearchWidgetTimer = void 0;
|
|
10874
|
+
}
|
|
10875
|
+
};
|
|
10876
|
+
const ensureWebResearchWidgetTimer = () => {
|
|
10877
|
+
if (webResearchWidgetTimer || activeWebResearchRequests.size === 0) {
|
|
10878
|
+
return;
|
|
10879
|
+
}
|
|
10880
|
+
webResearchWidgetTimer = setInterval(() => {
|
|
10881
|
+
updateWebResearchWidget();
|
|
10882
|
+
}, 1e3);
|
|
10883
|
+
};
|
|
10884
|
+
const updateWebResearchWidget = (ctx) => {
|
|
10885
|
+
const widgetContext = ctx ?? latestWidgetContext;
|
|
10886
|
+
if (!widgetContext) {
|
|
10887
|
+
return;
|
|
10888
|
+
}
|
|
10889
|
+
latestWidgetContext = widgetContext;
|
|
10890
|
+
if (!widgetContext.hasUI) {
|
|
10891
|
+
stopWebResearchWidgetTimer();
|
|
10892
|
+
return;
|
|
10893
|
+
}
|
|
10894
|
+
if (activeWebResearchRequests.size === 0) {
|
|
10895
|
+
stopWebResearchWidgetTimer();
|
|
10896
|
+
widgetContext.ui.setWidget(WEB_RESEARCH_WIDGET_KEY, void 0);
|
|
10897
|
+
return;
|
|
10898
|
+
}
|
|
10899
|
+
ensureWebResearchWidgetTimer();
|
|
10900
|
+
widgetContext.ui.setWidget(
|
|
10901
|
+
WEB_RESEARCH_WIDGET_KEY,
|
|
10902
|
+
buildWebResearchWidgetLines(
|
|
10903
|
+
getActiveWebResearchRequests(activeWebResearchRequests),
|
|
10904
|
+
widgetContext.ui.theme
|
|
10905
|
+
)
|
|
10906
|
+
);
|
|
10907
|
+
};
|
|
10908
|
+
if ("registerMessageRenderer" in pi) {
|
|
10909
|
+
pi.registerMessageRenderer(
|
|
10910
|
+
WEB_RESEARCH_RESULT_MESSAGE_TYPE,
|
|
10911
|
+
(message, state, theme) => renderWebResearchResultMessage(message, state, theme)
|
|
10912
|
+
);
|
|
10913
|
+
pi.registerMessageRenderer(
|
|
10914
|
+
WEB_RESEARCH_REPORT_MESSAGE_TYPE,
|
|
10915
|
+
(message, state, theme) => renderWebResearchReportMessage(message, state, theme)
|
|
10916
|
+
);
|
|
10917
|
+
}
|
|
10918
|
+
pi.registerCommand("web-providers", {
|
|
10919
|
+
description: "Configure web search providers",
|
|
10920
|
+
handler: async (_args, ctx) => {
|
|
10921
|
+
if (!ctx.hasUI) {
|
|
10922
|
+
ctx.ui.notify("web-providers requires interactive mode", "error");
|
|
10923
|
+
return;
|
|
10924
|
+
}
|
|
10925
|
+
await runWebProvidersConfig(
|
|
10926
|
+
pi,
|
|
10927
|
+
{ activeWebResearchRequests, updateWebResearchWidget },
|
|
10928
|
+
ctx
|
|
10929
|
+
);
|
|
10930
|
+
}
|
|
10931
|
+
});
|
|
10932
|
+
pi.registerCommand("web-research", {
|
|
10933
|
+
description: "Browse, inspect, and manage web researches",
|
|
10934
|
+
handler: async (_args, ctx) => {
|
|
10935
|
+
if (!ctx.hasUI) {
|
|
10936
|
+
ctx.ui.notify("web-research requires interactive mode", "error");
|
|
10937
|
+
return;
|
|
10938
|
+
}
|
|
10939
|
+
const actions = {
|
|
10940
|
+
copyToClipboard,
|
|
10941
|
+
injectReport: ({ title, body, item }) => {
|
|
10942
|
+
pi.sendMessage(
|
|
10943
|
+
{
|
|
10944
|
+
customType: WEB_RESEARCH_REPORT_MESSAGE_TYPE,
|
|
10945
|
+
content: formatWebResearchReportMessage(title, body, item),
|
|
10946
|
+
display: true,
|
|
10947
|
+
details: {
|
|
10948
|
+
title,
|
|
10949
|
+
outputPath: item.outputPath,
|
|
10950
|
+
provider: item.provider,
|
|
10951
|
+
query: item.query,
|
|
10952
|
+
status: item.status
|
|
10953
|
+
}
|
|
10954
|
+
},
|
|
10955
|
+
{ triggerTurn: false }
|
|
10956
|
+
);
|
|
10957
|
+
},
|
|
10958
|
+
notify: (message, type) => ctx.ui.notify(message, type ?? "info")
|
|
10959
|
+
};
|
|
10960
|
+
let timer;
|
|
10961
|
+
let view;
|
|
10962
|
+
try {
|
|
10963
|
+
await ctx.ui.custom(
|
|
10964
|
+
(tui, theme, _keybindings, done) => {
|
|
10965
|
+
view = new WebResearchManagerView(
|
|
10966
|
+
tui,
|
|
10967
|
+
theme,
|
|
10968
|
+
done,
|
|
10969
|
+
ctx.cwd,
|
|
10970
|
+
activeWebResearchRequests,
|
|
10971
|
+
() => updateWebResearchWidget(ctx),
|
|
10972
|
+
actions
|
|
10973
|
+
);
|
|
10974
|
+
timer = setInterval(() => view?.refresh(), 1e3);
|
|
10975
|
+
return view;
|
|
10976
|
+
},
|
|
10977
|
+
{
|
|
10978
|
+
overlay: true,
|
|
10979
|
+
overlayOptions: () => view?.isReportOpen() ? { anchor: "center", width: "85%", maxHeight: "85%" } : {
|
|
10980
|
+
anchor: "center",
|
|
10981
|
+
width: "75%",
|
|
10982
|
+
maxHeight: "60%",
|
|
10983
|
+
minWidth: 60
|
|
10984
|
+
}
|
|
10985
|
+
}
|
|
10986
|
+
);
|
|
10987
|
+
} finally {
|
|
10988
|
+
if (timer) clearInterval(timer);
|
|
10989
|
+
}
|
|
10990
|
+
}
|
|
9698
10991
|
});
|
|
9699
10992
|
pi.on("session_start", async (_event, ctx) => {
|
|
9700
10993
|
latestWidgetContext = ctx;
|
|
9701
10994
|
resetContentStore();
|
|
9702
10995
|
updateWebResearchWidget(ctx);
|
|
9703
|
-
await
|
|
10996
|
+
await refreshManagedToolsOnStartup2(
|
|
9704
10997
|
pi,
|
|
9705
10998
|
{ activeWebResearchRequests, updateWebResearchWidget },
|
|
9706
10999
|
ctx.cwd,
|
|
@@ -9711,7 +11004,7 @@ function webProvidersExtension(pi) {
|
|
|
9711
11004
|
latestWidgetContext = ctx;
|
|
9712
11005
|
await cleanupContentStore();
|
|
9713
11006
|
updateWebResearchWidget(ctx);
|
|
9714
|
-
await
|
|
11007
|
+
await refreshManagedToolsOnStartup2(
|
|
9715
11008
|
pi,
|
|
9716
11009
|
{ activeWebResearchRequests, updateWebResearchWidget },
|
|
9717
11010
|
ctx.cwd,
|
|
@@ -9934,7 +11227,7 @@ function registerWebResearchTool(pi, webResearchLifecycle, providerIds) {
|
|
|
9934
11227
|
"Do not expect the final report in the same turn; tell the user that web research has started and wait for the completion message with the saved report path."
|
|
9935
11228
|
]),
|
|
9936
11229
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
9937
|
-
return
|
|
11230
|
+
return dispatchWebResearch2({
|
|
9938
11231
|
pi,
|
|
9939
11232
|
activeWebResearchRequests: webResearchLifecycle.activeWebResearchRequests,
|
|
9940
11233
|
updateWebResearchWidget: webResearchLifecycle.updateWebResearchWidget,
|
|
@@ -9959,109 +11252,38 @@ function registerWebResearchTool(pi, webResearchLifecycle, providerIds) {
|
|
|
9959
11252
|
}
|
|
9960
11253
|
});
|
|
9961
11254
|
}
|
|
9962
|
-
async function runWebProvidersConfig(pi, webResearchLifecycle, ctx) {
|
|
9963
|
-
const config = await loadConfig();
|
|
9964
|
-
const activeProvider = getInitialProviderSelection(config);
|
|
9965
|
-
await ctx.ui.custom(
|
|
9966
|
-
(tui, theme, _keybindings, done) => new WebProvidersSettingsView(
|
|
9967
|
-
tui,
|
|
9968
|
-
theme,
|
|
9969
|
-
done,
|
|
9970
|
-
ctx,
|
|
9971
|
-
config,
|
|
9972
|
-
activeProvider
|
|
9973
|
-
)
|
|
9974
|
-
);
|
|
9975
|
-
await refreshManagedTools(pi, webResearchLifecycle, ctx.cwd, {
|
|
9976
|
-
addAvailable: true
|
|
9977
|
-
});
|
|
9978
|
-
}
|
|
9979
|
-
function formatStartupConfigError(error) {
|
|
9980
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
9981
|
-
return `web-providers config error: ${detail.replace(getConfigPath(), "~/.pi/agent/web-providers.json")}`;
|
|
9982
|
-
}
|
|
9983
|
-
function getAvailableProviderIdsForCapability(config, cwd, capability) {
|
|
9984
|
-
const providerId = getMappedProviderIdForTool(config, capability);
|
|
9985
|
-
if (!providerId) {
|
|
9986
|
-
return [];
|
|
9987
|
-
}
|
|
9988
|
-
const provider = PROVIDERS_BY_ID[providerId];
|
|
9989
|
-
if (!supportsTool2(provider, capability)) {
|
|
9990
|
-
return [];
|
|
9991
|
-
}
|
|
9992
|
-
const status = getProviderCapabilityStatus(
|
|
9993
|
-
config,
|
|
9994
|
-
cwd,
|
|
9995
|
-
providerId,
|
|
9996
|
-
capability,
|
|
9997
|
-
{
|
|
9998
|
-
resolveSecrets: false
|
|
9999
|
-
}
|
|
10000
|
-
);
|
|
10001
|
-
return isProviderCapabilityExposable(status) ? [providerId] : [];
|
|
10002
|
-
}
|
|
10003
|
-
function getProviderStatusForTool(config, cwd, providerId, capability) {
|
|
10004
|
-
return getProviderCapabilityStatus(config, cwd, providerId, capability);
|
|
10005
|
-
}
|
|
10006
|
-
function getAvailableManagedToolNames(config, cwd) {
|
|
10007
|
-
return Object.keys(CAPABILITY_TOOL_NAMES).filter(
|
|
10008
|
-
(capability) => getAvailableProviderIdsForCapability(config, cwd, capability).length > 0
|
|
10009
|
-
).map((capability) => CAPABILITY_TOOL_NAMES[capability]);
|
|
10010
|
-
}
|
|
10011
|
-
function getSyncedActiveTools(config, cwd, activeToolNames, options) {
|
|
10012
|
-
const availableToolNames = new Set(getAvailableManagedToolNames(config, cwd));
|
|
10013
|
-
const nextActiveTools = new Set(activeToolNames);
|
|
10014
|
-
for (const toolName of MANAGED_TOOL_NAMES) {
|
|
10015
|
-
if (availableToolNames.has(toolName)) {
|
|
10016
|
-
if (options.addAvailable) {
|
|
10017
|
-
nextActiveTools.add(toolName);
|
|
10018
|
-
}
|
|
10019
|
-
continue;
|
|
10020
|
-
}
|
|
10021
|
-
nextActiveTools.delete(toolName);
|
|
10022
|
-
}
|
|
10023
|
-
return nextActiveTools;
|
|
10024
|
-
}
|
|
10025
|
-
async function refreshManagedTools(pi, webResearchLifecycle, cwd, options) {
|
|
11255
|
+
async function runWebProvidersConfig(pi, webResearchLifecycle, ctx) {
|
|
10026
11256
|
const config = await loadConfig();
|
|
10027
|
-
const
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
11257
|
+
const activeProvider = getInitialProviderSelection(config);
|
|
11258
|
+
await ctx.ui.custom(
|
|
11259
|
+
(tui, theme, _keybindings, done) => new WebProvidersSettingsView(
|
|
11260
|
+
tui,
|
|
11261
|
+
theme,
|
|
11262
|
+
done,
|
|
11263
|
+
ctx,
|
|
11264
|
+
config,
|
|
11265
|
+
activeProvider
|
|
11266
|
+
)
|
|
10032
11267
|
);
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
contents: getAvailableProviderIdsForCapability(config, cwd, "contents"),
|
|
10036
|
-
answer: getAvailableProviderIdsForCapability(config, cwd, "answer"),
|
|
10037
|
-
research: getAvailableProviderIdsForCapability(config, cwd, "research")
|
|
11268
|
+
await refreshManagedTools2(pi, webResearchLifecycle, ctx.cwd, {
|
|
11269
|
+
addAvailable: true
|
|
10038
11270
|
});
|
|
10039
|
-
await syncManagedToolAvailability(pi, nextActiveTools);
|
|
10040
11271
|
}
|
|
10041
|
-
async function
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
content: message,
|
|
10049
|
-
display: true
|
|
10050
|
-
});
|
|
10051
|
-
await syncManagedToolAvailability(
|
|
10052
|
-
pi,
|
|
10053
|
-
new Set(
|
|
10054
|
-
pi.getActiveTools().filter((toolName) => !MANAGED_TOOL_NAMES.includes(toolName))
|
|
10055
|
-
)
|
|
10056
|
-
);
|
|
10057
|
-
}
|
|
11272
|
+
async function refreshManagedTools2(pi, webResearchLifecycle, cwd, options) {
|
|
11273
|
+
await refreshManagedTools(
|
|
11274
|
+
pi,
|
|
11275
|
+
(providerIdsByCapability) => registerManagedTools(pi, webResearchLifecycle, providerIdsByCapability),
|
|
11276
|
+
cwd,
|
|
11277
|
+
options
|
|
11278
|
+
);
|
|
10058
11279
|
}
|
|
10059
|
-
async function
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
11280
|
+
async function refreshManagedToolsOnStartup2(pi, webResearchLifecycle, cwd, options) {
|
|
11281
|
+
await refreshManagedToolsOnStartup(
|
|
11282
|
+
pi,
|
|
11283
|
+
(providerIdsByCapability) => registerManagedTools(pi, webResearchLifecycle, providerIdsByCapability),
|
|
11284
|
+
cwd,
|
|
11285
|
+
options
|
|
11286
|
+
);
|
|
10065
11287
|
}
|
|
10066
11288
|
function getSearchMaxResultsLimit(providerId) {
|
|
10067
11289
|
const capabilities = PROVIDERS_BY_ID[providerId].capabilities;
|
|
@@ -10278,12 +11500,12 @@ async function executeRawProviderRequest({
|
|
|
10278
11500
|
input
|
|
10279
11501
|
});
|
|
10280
11502
|
}
|
|
10281
|
-
function buildSearchBatchError(outcomes,
|
|
11503
|
+
function buildSearchBatchError(outcomes, providerLabel2) {
|
|
10282
11504
|
const failed = outcomes.filter((outcome) => outcome.error !== void 0);
|
|
10283
11505
|
if (failed.length === 1) {
|
|
10284
11506
|
return new Error(
|
|
10285
11507
|
formatProviderCapabilityFailure(
|
|
10286
|
-
|
|
11508
|
+
providerLabel2,
|
|
10287
11509
|
"search",
|
|
10288
11510
|
failed[0]?.error ?? ""
|
|
10289
11511
|
)
|
|
@@ -10293,7 +11515,7 @@ function buildSearchBatchError(outcomes, providerLabel) {
|
|
|
10293
11515
|
(outcome, index) => `${index + 1}. ${formatQuotedPreview(outcome.query, 40)} \u2014 ${outcome.error}`
|
|
10294
11516
|
).join("; ");
|
|
10295
11517
|
return new Error(
|
|
10296
|
-
`${
|
|
11518
|
+
`${providerLabel2} search failed for ${failed.length} queries: ${summary}`
|
|
10297
11519
|
);
|
|
10298
11520
|
}
|
|
10299
11521
|
async function executeSingleSearchQuery({
|
|
@@ -10430,12 +11652,12 @@ async function executeAnswerToolInternal({
|
|
|
10430
11652
|
})
|
|
10431
11653
|
};
|
|
10432
11654
|
}
|
|
10433
|
-
function buildAnswerBatchError(outcomes,
|
|
11655
|
+
function buildAnswerBatchError(outcomes, providerLabel2) {
|
|
10434
11656
|
const failed = outcomes.filter((outcome) => outcome.error !== void 0);
|
|
10435
11657
|
if (failed.length === 1) {
|
|
10436
11658
|
return new Error(
|
|
10437
11659
|
formatProviderCapabilityFailure(
|
|
10438
|
-
|
|
11660
|
+
providerLabel2,
|
|
10439
11661
|
"answer",
|
|
10440
11662
|
failed[0]?.error ?? ""
|
|
10441
11663
|
)
|
|
@@ -10445,7 +11667,7 @@ function buildAnswerBatchError(outcomes, providerLabel) {
|
|
|
10445
11667
|
(outcome, index) => `${index + 1}. ${formatQuotedPreview(outcome.query, 40)} \u2014 ${outcome.error}`
|
|
10446
11668
|
).join("; ");
|
|
10447
11669
|
return new Error(
|
|
10448
|
-
`${
|
|
11670
|
+
`${providerLabel2} answer failed for ${failed.length} questions: ${summary}`
|
|
10449
11671
|
);
|
|
10450
11672
|
}
|
|
10451
11673
|
function formatAnswerResponses(outcomes) {
|
|
@@ -10661,7 +11883,7 @@ async function executeProviderToolInternal({
|
|
|
10661
11883
|
})
|
|
10662
11884
|
};
|
|
10663
11885
|
}
|
|
10664
|
-
async function
|
|
11886
|
+
async function dispatchWebResearch2({
|
|
10665
11887
|
pi,
|
|
10666
11888
|
activeWebResearchRequests,
|
|
10667
11889
|
updateWebResearchWidget,
|
|
@@ -10690,188 +11912,59 @@ async function dispatchWebResearchInternal({
|
|
|
10690
11912
|
input,
|
|
10691
11913
|
executionOverride
|
|
10692
11914
|
}) {
|
|
10693
|
-
|
|
10694
|
-
|
|
11915
|
+
return dispatchWebResearch({
|
|
11916
|
+
activeWebResearchRequests,
|
|
10695
11917
|
config,
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
updateWebResearchWidget(ctx);
|
|
10704
|
-
trackPendingResearchTask(
|
|
10705
|
-
runDispatchedWebResearch({
|
|
10706
|
-
pi,
|
|
10707
|
-
activeWebResearchRequests,
|
|
10708
|
-
updateWebResearchWidget,
|
|
10709
|
-
request,
|
|
10710
|
-
config,
|
|
11918
|
+
explicitProvider,
|
|
11919
|
+
ctx: { cwd: ctx.cwd },
|
|
11920
|
+
options: providerOptions,
|
|
11921
|
+
input,
|
|
11922
|
+
executionOverride,
|
|
11923
|
+
executeResearch: async ({
|
|
11924
|
+
config: config2,
|
|
10711
11925
|
provider,
|
|
10712
11926
|
providerConfig,
|
|
10713
|
-
ctx,
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
{
|
|
10721
|
-
type: "text",
|
|
10722
|
-
text: `Started web research via ${provider.label}.`
|
|
10723
|
-
}
|
|
10724
|
-
],
|
|
10725
|
-
details: request,
|
|
10726
|
-
display: buildProviderToolDisplay2({
|
|
10727
|
-
capability: "research",
|
|
10728
|
-
providerId: provider.id,
|
|
10729
|
-
details: { tool: "web_research", provider: provider.id },
|
|
10730
|
-
text: "started"
|
|
10731
|
-
})
|
|
10732
|
-
};
|
|
10733
|
-
}
|
|
10734
|
-
async function runDispatchedWebResearch({
|
|
10735
|
-
pi,
|
|
10736
|
-
activeWebResearchRequests,
|
|
10737
|
-
updateWebResearchWidget,
|
|
10738
|
-
request,
|
|
10739
|
-
config,
|
|
10740
|
-
provider,
|
|
10741
|
-
providerConfig,
|
|
10742
|
-
ctx,
|
|
10743
|
-
options,
|
|
10744
|
-
executionOverride
|
|
10745
|
-
}) {
|
|
10746
|
-
let result;
|
|
10747
|
-
let reportText = "";
|
|
10748
|
-
try {
|
|
10749
|
-
const response = await executeProviderOperation({
|
|
11927
|
+
ctx: ctx2,
|
|
11928
|
+
signal,
|
|
11929
|
+
options,
|
|
11930
|
+
input: input2,
|
|
11931
|
+
onProgress,
|
|
11932
|
+
executionOverride: executionOverride2
|
|
11933
|
+
}) => executeProviderOperation({
|
|
10750
11934
|
capability: "research",
|
|
10751
|
-
config,
|
|
11935
|
+
config: config2,
|
|
10752
11936
|
provider,
|
|
10753
11937
|
providerConfig,
|
|
10754
|
-
ctx,
|
|
10755
|
-
signal
|
|
11938
|
+
ctx: ctx2,
|
|
11939
|
+
signal,
|
|
10756
11940
|
options,
|
|
10757
|
-
input:
|
|
10758
|
-
onProgress
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
executionOverride
|
|
10766
|
-
});
|
|
10767
|
-
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10768
|
-
result = {
|
|
10769
|
-
...request,
|
|
10770
|
-
status: "completed",
|
|
10771
|
-
completedAt,
|
|
10772
|
-
elapsedMs: Math.max(
|
|
10773
|
-
0,
|
|
10774
|
-
Date.parse(completedAt) - Date.parse(request.startedAt)
|
|
10775
|
-
),
|
|
10776
|
-
itemCount: response.itemCount
|
|
10777
|
-
};
|
|
10778
|
-
reportText = response.text;
|
|
10779
|
-
} catch (error) {
|
|
10780
|
-
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10781
|
-
result = {
|
|
10782
|
-
...request,
|
|
10783
|
-
status: "failed",
|
|
10784
|
-
completedAt,
|
|
10785
|
-
elapsedMs: Math.max(
|
|
10786
|
-
0,
|
|
10787
|
-
Date.parse(completedAt) - Date.parse(request.startedAt)
|
|
10788
|
-
),
|
|
10789
|
-
error: formatErrorMessage(error)
|
|
10790
|
-
};
|
|
10791
|
-
}
|
|
10792
|
-
try {
|
|
10793
|
-
await writeWebResearchArtifact(result, reportText);
|
|
10794
|
-
pi.sendMessage({
|
|
10795
|
-
customType: WEB_RESEARCH_RESULT_MESSAGE_TYPE,
|
|
10796
|
-
content: formatWebResearchResultMessage(result, reportText),
|
|
10797
|
-
display: true,
|
|
10798
|
-
details: result
|
|
10799
|
-
});
|
|
10800
|
-
} finally {
|
|
10801
|
-
activeWebResearchRequests.delete(request.id);
|
|
10802
|
-
updateWebResearchWidget();
|
|
10803
|
-
}
|
|
10804
|
-
}
|
|
10805
|
-
function createWebResearchRequest(cwd, provider, input) {
|
|
10806
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10807
|
-
return {
|
|
10808
|
-
tool: "web_research",
|
|
10809
|
-
id: randomUUID(),
|
|
10810
|
-
provider,
|
|
10811
|
-
input,
|
|
10812
|
-
outputPath: buildWebResearchArtifactPath(cwd, input, startedAt),
|
|
10813
|
-
startedAt
|
|
10814
|
-
};
|
|
10815
|
-
}
|
|
10816
|
-
function buildWebResearchArtifactPath(cwd, input, startedAt) {
|
|
10817
|
-
const timestamp = startedAt.replaceAll(":", "-").replace(".", "-");
|
|
10818
|
-
const slug = slugifyWebResearchInput(input);
|
|
10819
|
-
return join2(cwd, RESEARCH_ARTIFACTS_DIR, `${timestamp}-${slug}.md`);
|
|
10820
|
-
}
|
|
10821
|
-
function slugifyWebResearchInput(input) {
|
|
10822
|
-
const slug = input.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
|
|
10823
|
-
return slug.length > 0 ? slug : "research";
|
|
11941
|
+
input: input2,
|
|
11942
|
+
onProgress,
|
|
11943
|
+
executionOverride: executionOverride2
|
|
11944
|
+
}),
|
|
11945
|
+
deliverResult: (message) => pi.sendMessage(message),
|
|
11946
|
+
onJobsChanged: () => updateWebResearchWidget(ctx),
|
|
11947
|
+
resultMessageType: WEB_RESEARCH_RESULT_MESSAGE_TYPE
|
|
11948
|
+
});
|
|
10824
11949
|
}
|
|
10825
11950
|
function buildWebResearchWidgetLines(requests, theme, now = Date.now()) {
|
|
10826
|
-
const
|
|
10827
|
-
|
|
10828
|
-
const
|
|
10829
|
-
const elapsed =
|
|
10830
|
-
const icon =
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
lines.push(theme.fg("muted", `+${requests.length - 3} more`));
|
|
10837
|
-
}
|
|
10838
|
-
return lines;
|
|
10839
|
-
}
|
|
10840
|
-
function getWebResearchWidgetIcon(request, _now) {
|
|
10841
|
-
if (request.progress === "poll retrying after transient errors") {
|
|
10842
|
-
return "\u27F3 ";
|
|
10843
|
-
}
|
|
10844
|
-
if (request.progress === "queued") {
|
|
10845
|
-
return "\u25CC ";
|
|
10846
|
-
}
|
|
10847
|
-
if (request.progress === "starting") {
|
|
10848
|
-
return "\u25D4 ";
|
|
10849
|
-
}
|
|
10850
|
-
if (request.progress?.startsWith("started:")) {
|
|
10851
|
-
return "\u25D1 ";
|
|
10852
|
-
}
|
|
10853
|
-
return "\u25CF ";
|
|
10854
|
-
}
|
|
10855
|
-
function summarizeWebResearchProgress(message, providerLabel) {
|
|
10856
|
-
const startingMessage = `Starting research via ${providerLabel}`;
|
|
10857
|
-
if (message === startingMessage) {
|
|
10858
|
-
return "starting";
|
|
10859
|
-
}
|
|
10860
|
-
const startedPrefix = `${providerLabel} research started: `;
|
|
10861
|
-
if (message.startsWith(startedPrefix)) {
|
|
10862
|
-
return `started: ${message.slice(startedPrefix.length)}`;
|
|
10863
|
-
}
|
|
10864
|
-
const statusPrefix = `Research via ${providerLabel}: `;
|
|
10865
|
-
if (message.startsWith(statusPrefix)) {
|
|
10866
|
-
return message.slice(statusPrefix.length).replace(/\s+\([^)]* elapsed\)$/u, "").trim();
|
|
10867
|
-
}
|
|
10868
|
-
const retryPrefix = `${providerLabel} research poll is still retrying after transient errors`;
|
|
10869
|
-
if (message.startsWith(retryPrefix)) {
|
|
10870
|
-
return "poll retrying after transient errors";
|
|
11951
|
+
const sorted = requests.slice().sort((left, right) => left.startedAt.localeCompare(right.startedAt));
|
|
11952
|
+
const jobs = sorted.slice(0, 3).map((request) => {
|
|
11953
|
+
const providerLabel2 = PROVIDERS_BY_ID[request.provider]?.label ?? request.provider;
|
|
11954
|
+
const elapsed = formatCompactElapsed2(now - Date.parse(request.startedAt));
|
|
11955
|
+
const icon = getWebResearchProgressIcon(request);
|
|
11956
|
+
const status = request.progress === "cancelling" ? " cancelling" : "";
|
|
11957
|
+
return `${icon} ${providerLabel2} ${elapsed}${status}`;
|
|
11958
|
+
});
|
|
11959
|
+
if (sorted.length > 3) {
|
|
11960
|
+
jobs.push(`+${sorted.length - 3} more`);
|
|
10871
11961
|
}
|
|
10872
|
-
|
|
11962
|
+
const count = sorted.length === 1 ? "1 research" : `${sorted.length} researches`;
|
|
11963
|
+
return [
|
|
11964
|
+
`${theme.fg("accent", `${count} running`)}${theme.fg("muted", ` \xB7 ${jobs.join(" \xB7 ")} \xB7 /web-research`)}`
|
|
11965
|
+
];
|
|
10873
11966
|
}
|
|
10874
|
-
function
|
|
11967
|
+
function formatCompactElapsed2(ms) {
|
|
10875
11968
|
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
10876
11969
|
const minutes = Math.floor(totalSeconds / 60);
|
|
10877
11970
|
const seconds = totalSeconds % 60;
|
|
@@ -10880,68 +11973,6 @@ function formatCompactElapsed(ms) {
|
|
|
10880
11973
|
}
|
|
10881
11974
|
return `${totalSeconds}s`;
|
|
10882
11975
|
}
|
|
10883
|
-
function formatWebResearchResultMessage(result, reportText) {
|
|
10884
|
-
const text = reportText.trim();
|
|
10885
|
-
if (text.length > 0) {
|
|
10886
|
-
return `${text}
|
|
10887
|
-
`;
|
|
10888
|
-
}
|
|
10889
|
-
if (result.error) {
|
|
10890
|
-
return `${result.error}
|
|
10891
|
-
`;
|
|
10892
|
-
}
|
|
10893
|
-
return "";
|
|
10894
|
-
}
|
|
10895
|
-
async function writeWebResearchArtifact(result, reportText) {
|
|
10896
|
-
await mkdir2(dirname2(result.outputPath), { recursive: true });
|
|
10897
|
-
await writeFile2(
|
|
10898
|
-
result.outputPath,
|
|
10899
|
-
formatWebResearchArtifact(result, reportText),
|
|
10900
|
-
"utf-8"
|
|
10901
|
-
);
|
|
10902
|
-
}
|
|
10903
|
-
function formatWebResearchArtifact(result, reportText) {
|
|
10904
|
-
const providerLabel = PROVIDERS_BY_ID[result.provider]?.label ?? result.provider;
|
|
10905
|
-
const lines = [
|
|
10906
|
-
"# Web research report",
|
|
10907
|
-
"",
|
|
10908
|
-
"## Query",
|
|
10909
|
-
result.input,
|
|
10910
|
-
"",
|
|
10911
|
-
"## Provider",
|
|
10912
|
-
providerLabel,
|
|
10913
|
-
"",
|
|
10914
|
-
"## Status",
|
|
10915
|
-
result.status,
|
|
10916
|
-
"",
|
|
10917
|
-
"## Started",
|
|
10918
|
-
result.startedAt,
|
|
10919
|
-
"",
|
|
10920
|
-
"## Completed",
|
|
10921
|
-
result.completedAt,
|
|
10922
|
-
"",
|
|
10923
|
-
"## Elapsed",
|
|
10924
|
-
formatElapsed(result.elapsedMs)
|
|
10925
|
-
];
|
|
10926
|
-
if (typeof result.itemCount === "number") {
|
|
10927
|
-
lines.push("", "## Items", String(result.itemCount));
|
|
10928
|
-
}
|
|
10929
|
-
if (result.error) {
|
|
10930
|
-
lines.push("", "## Error", result.error);
|
|
10931
|
-
}
|
|
10932
|
-
if (reportText) {
|
|
10933
|
-
lines.push("", "## Report", reportText);
|
|
10934
|
-
}
|
|
10935
|
-
return `${lines.join("\n")}
|
|
10936
|
-
`;
|
|
10937
|
-
}
|
|
10938
|
-
function trackPendingResearchTask(task) {
|
|
10939
|
-
const tracked = task.catch(() => {
|
|
10940
|
-
}).finally(() => {
|
|
10941
|
-
pendingResearchTasks.delete(tracked);
|
|
10942
|
-
});
|
|
10943
|
-
pendingResearchTasks.add(tracked);
|
|
10944
|
-
}
|
|
10945
11976
|
async function executeBatchedContentsTool({
|
|
10946
11977
|
config,
|
|
10947
11978
|
provider,
|
|
@@ -11103,10 +12134,10 @@ function createToolProgressReporter(capability, providerId, progress) {
|
|
|
11103
12134
|
if (Date.now() - lastUpdateAt < RESEARCH_HEARTBEAT_MS) {
|
|
11104
12135
|
return;
|
|
11105
12136
|
}
|
|
11106
|
-
const
|
|
12137
|
+
const providerLabel2 = PROVIDERS_BY_ID[providerId]?.label ?? providerId;
|
|
11107
12138
|
const elapsed = formatElapsed(Date.now() - startedAt);
|
|
11108
12139
|
emit(
|
|
11109
|
-
`Researching via ${
|
|
12140
|
+
`Researching via ${providerLabel2} (${elapsed} elapsed)`,
|
|
11110
12141
|
buildProgressDisplay2(providerId, `Researching ${elapsed}`)
|
|
11111
12142
|
);
|
|
11112
12143
|
lastUpdateAt = Date.now();
|
|
@@ -11129,38 +12160,38 @@ function renderListCallHeader(toolName, items, theme, suffix, options = {}) {
|
|
|
11129
12160
|
invalidate() {
|
|
11130
12161
|
},
|
|
11131
12162
|
render(width) {
|
|
11132
|
-
const normalizedItems = items.map((item) =>
|
|
12163
|
+
const normalizedItems = items.map((item) => cleanSingleLine2(item)).filter((item) => item.length > 0);
|
|
11133
12164
|
const toolTitle = theme.fg("toolTitle", theme.bold(toolName));
|
|
11134
12165
|
const mutedSuffix = suffix ? theme.fg("muted", suffix) : "";
|
|
11135
12166
|
if (!options.forceMultiline && normalizedItems.length === 1) {
|
|
11136
12167
|
const singleItem = options.quoteSingleItem ? formatQuotedPreview(normalizedItems[0], 80) : truncateInline(normalizedItems[0], 120);
|
|
11137
12168
|
const inline = `${toolTitle} ${theme.fg("accent", singleItem)}${mutedSuffix}`;
|
|
11138
|
-
const line =
|
|
11139
|
-
return [line + " ".repeat(Math.max(0, width -
|
|
12169
|
+
const line = truncateToWidth2(inline.trimEnd(), width);
|
|
12170
|
+
return [line + " ".repeat(Math.max(0, width - visibleWidth2(line)))];
|
|
11140
12171
|
}
|
|
11141
12172
|
let header = toolTitle;
|
|
11142
12173
|
if (mutedSuffix) {
|
|
11143
12174
|
header += mutedSuffix;
|
|
11144
12175
|
}
|
|
11145
12176
|
const lines = [];
|
|
11146
|
-
const headerLine =
|
|
12177
|
+
const headerLine = truncateToWidth2(header.trimEnd(), width);
|
|
11147
12178
|
lines.push(
|
|
11148
|
-
headerLine + " ".repeat(Math.max(0, width -
|
|
12179
|
+
headerLine + " ".repeat(Math.max(0, width - visibleWidth2(headerLine)))
|
|
11149
12180
|
);
|
|
11150
12181
|
for (const item of normalizedItems) {
|
|
11151
12182
|
const itemLines = options.forceMultiline ? wrapTextWithAnsi(
|
|
11152
12183
|
theme.fg("accent", item),
|
|
11153
12184
|
Math.max(1, width - 2)
|
|
11154
12185
|
).map((line) => ` ${line}`) : [
|
|
11155
|
-
|
|
12186
|
+
truncateToWidth2(
|
|
11156
12187
|
` ${theme.fg("accent", truncateInline(item, 120))}`,
|
|
11157
12188
|
width
|
|
11158
12189
|
)
|
|
11159
12190
|
];
|
|
11160
12191
|
for (const itemLine of itemLines) {
|
|
11161
|
-
const line =
|
|
12192
|
+
const line = truncateToWidth2(itemLine, width);
|
|
11162
12193
|
lines.push(
|
|
11163
|
-
line + " ".repeat(Math.max(0, width -
|
|
12194
|
+
line + " ".repeat(Math.max(0, width - visibleWidth2(line)))
|
|
11164
12195
|
);
|
|
11165
12196
|
}
|
|
11166
12197
|
}
|
|
@@ -11255,7 +12286,8 @@ function renderWebResearchResultMessage(message, { expanded }, theme, symbols =
|
|
|
11255
12286
|
const text = typeof message.content === "string" ? message.content : extractTextContent(message.content);
|
|
11256
12287
|
const details = isWebResearchResult(message.details) ? message.details : void 0;
|
|
11257
12288
|
const isSuccess = details?.status === "completed";
|
|
11258
|
-
const
|
|
12289
|
+
const isCancelled = details?.status === "cancelled";
|
|
12290
|
+
const accent = isSuccess ? "success" : isCancelled ? "warning" : "error";
|
|
11259
12291
|
const box = new Box(1, 1, (value) => theme.bg("customMessageBg", value));
|
|
11260
12292
|
if (!expanded) {
|
|
11261
12293
|
const summary = details ? buildWebResearchResultSummaryLine(details, theme, symbols) : theme.fg(accent, "Web research update");
|
|
@@ -11265,10 +12297,42 @@ function renderWebResearchResultMessage(message, { expanded }, theme, symbols =
|
|
|
11265
12297
|
return box;
|
|
11266
12298
|
}
|
|
11267
12299
|
box.addChild(
|
|
11268
|
-
details ? renderMarkdownBlock(renderWebResearchResultMarkdown(details)) : isSuccess ? renderMarkdownBlock(text ?? "") : renderBlockText(
|
|
12300
|
+
details ? renderMarkdownBlock(renderWebResearchResultMarkdown(details)) : isSuccess ? renderMarkdownBlock(text ?? "") : renderBlockText(
|
|
12301
|
+
text ?? "",
|
|
12302
|
+
theme,
|
|
12303
|
+
isCancelled ? "toolOutput" : "error"
|
|
12304
|
+
)
|
|
11269
12305
|
);
|
|
11270
12306
|
return box;
|
|
11271
12307
|
}
|
|
12308
|
+
function formatWebResearchReportMessage(title, body, item) {
|
|
12309
|
+
const provenance = `Saved web research report "${title}" (provider: ${item.provider || "unknown"}, status: ${item.status}, artifact: \`${item.outputPath}\`):`;
|
|
12310
|
+
return `${provenance}
|
|
12311
|
+
|
|
12312
|
+
${body}
|
|
12313
|
+
`;
|
|
12314
|
+
}
|
|
12315
|
+
function renderWebResearchReportMessage(message, { expanded }, theme) {
|
|
12316
|
+
const text = typeof message.content === "string" ? message.content : extractTextContent(message.content);
|
|
12317
|
+
const details = isWebResearchReportDetails(message.details) ? message.details : void 0;
|
|
12318
|
+
const box = new Box(1, 1, (value) => theme.bg("customMessageBg", value));
|
|
12319
|
+
if (!expanded) {
|
|
12320
|
+
const title = details?.title ?? "saved report";
|
|
12321
|
+
box.addChild(
|
|
12322
|
+
new Text(
|
|
12323
|
+
`${theme.fg("success", `Injected research report: ${title}`)}${theme.fg("muted", ` (${getExpandHint()})`)}`,
|
|
12324
|
+
0,
|
|
12325
|
+
0
|
|
12326
|
+
)
|
|
12327
|
+
);
|
|
12328
|
+
return box;
|
|
12329
|
+
}
|
|
12330
|
+
box.addChild(renderMarkdownBlock(text ?? ""));
|
|
12331
|
+
return box;
|
|
12332
|
+
}
|
|
12333
|
+
function isWebResearchReportDetails(details) {
|
|
12334
|
+
return typeof details === "object" && details !== null && "title" in details && "outputPath" in details && !("tool" in details);
|
|
12335
|
+
}
|
|
11272
12336
|
function renderWebResearchRequestMarkdown(request) {
|
|
11273
12337
|
return [
|
|
11274
12338
|
"### Web research",
|
|
@@ -11294,7 +12358,7 @@ function renderWebResearchResultMarkdown(result) {
|
|
|
11294
12358
|
].join("\n");
|
|
11295
12359
|
}
|
|
11296
12360
|
function buildWebResearchResultSummaryLine(result, theme, symbols) {
|
|
11297
|
-
const
|
|
12361
|
+
const providerLabel2 = PROVIDERS_BY_ID[result.provider]?.label ?? result.provider;
|
|
11298
12362
|
if (result.status === "completed") {
|
|
11299
12363
|
return renderSuccessSummary(
|
|
11300
12364
|
`${formatSummaryElapsed(result.elapsedMs)} \xB7 ${basename(result.outputPath)}`,
|
|
@@ -11302,8 +12366,8 @@ function buildWebResearchResultSummaryLine(result, theme, symbols) {
|
|
|
11302
12366
|
symbols
|
|
11303
12367
|
);
|
|
11304
12368
|
}
|
|
11305
|
-
const statusText = result.status === "cancelled" ? `${
|
|
11306
|
-
const errorSuffix = result.error ? `: ${normalizeProviderFailureDetail(
|
|
12369
|
+
const statusText = result.status === "cancelled" ? `${providerLabel2} research canceled after ${formatSummaryElapsed(result.elapsedMs)}` : `${providerLabel2} research failed after ${formatSummaryElapsed(result.elapsedMs)}`;
|
|
12370
|
+
const errorSuffix = result.error ? `: ${normalizeProviderFailureDetail(providerLabel2, result.error)}` : "";
|
|
11307
12371
|
return renderFailureSummary(`${statusText}${errorSuffix}`, theme, symbols);
|
|
11308
12372
|
}
|
|
11309
12373
|
function isWebResearchRequest(details) {
|
|
@@ -11402,7 +12466,7 @@ function renderEntryList(width, theme, entries, selection) {
|
|
|
11402
12466
|
const paddedLabel = entry.label.padEnd(labelWidth, " ");
|
|
11403
12467
|
const label = selected ? theme.fg("accent", paddedLabel) : paddedLabel;
|
|
11404
12468
|
const value = selected ? theme.fg("accent", entry.currentValue) : theme.fg("muted", entry.currentValue);
|
|
11405
|
-
return
|
|
12469
|
+
return truncateToWidth2(`${prefix}${label} ${value}`, width);
|
|
11406
12470
|
});
|
|
11407
12471
|
}
|
|
11408
12472
|
function renderSelectedEntryDescription(width, theme, entry) {
|
|
@@ -11410,7 +12474,7 @@ function renderSelectedEntryDescription(width, theme, entry) {
|
|
|
11410
12474
|
return [];
|
|
11411
12475
|
}
|
|
11412
12476
|
return wrapTextWithAnsi(entry.description, Math.max(10, width - 2)).map(
|
|
11413
|
-
(line) =>
|
|
12477
|
+
(line) => truncateToWidth2(theme.fg("dim", line), width)
|
|
11414
12478
|
);
|
|
11415
12479
|
}
|
|
11416
12480
|
function formatProviderCapabilityChecks(providerId, theme) {
|
|
@@ -11591,7 +12655,7 @@ var WebProvidersSettingsView = class {
|
|
|
11591
12655
|
}
|
|
11592
12656
|
lines.push("");
|
|
11593
12657
|
lines.push(
|
|
11594
|
-
|
|
12658
|
+
truncateToWidth2(
|
|
11595
12659
|
this.theme.fg(
|
|
11596
12660
|
"dim",
|
|
11597
12661
|
"\u2191\u2193 move \xB7 Tab/Shift+Tab switch section \xB7 Enter edit/open \xB7 Esc close"
|
|
@@ -11610,7 +12674,7 @@ var WebProvidersSettingsView = class {
|
|
|
11610
12674
|
this.tui.requestRender();
|
|
11611
12675
|
return;
|
|
11612
12676
|
}
|
|
11613
|
-
const kb =
|
|
12677
|
+
const kb = getKeybindings2();
|
|
11614
12678
|
const entries = this.getActiveSectionEntries();
|
|
11615
12679
|
if (kb.matches(data, "tui.select.up")) {
|
|
11616
12680
|
if (entries.length > 0) {
|
|
@@ -11620,9 +12684,9 @@ var WebProvidersSettingsView = class {
|
|
|
11620
12684
|
if (entries.length > 0) {
|
|
11621
12685
|
this.moveSelection(1);
|
|
11622
12686
|
}
|
|
11623
|
-
} else if (
|
|
12687
|
+
} else if (matchesKey2(data, Key2.tab)) {
|
|
11624
12688
|
this.moveSection(1);
|
|
11625
|
-
} else if (
|
|
12689
|
+
} else if (matchesKey2(data, Key2.shift("tab"))) {
|
|
11626
12690
|
this.moveSection(-1);
|
|
11627
12691
|
} else if (kb.matches(data, "tui.select.confirm") || data === " ") {
|
|
11628
12692
|
void this.activateCurrentEntry();
|
|
@@ -11755,14 +12819,14 @@ var WebProvidersSettingsView = class {
|
|
|
11755
12819
|
Math.max(20, Math.floor(width * 0.45))
|
|
11756
12820
|
);
|
|
11757
12821
|
const lines = [
|
|
11758
|
-
|
|
12822
|
+
truncateToWidth2(
|
|
11759
12823
|
this.activeSection === section ? this.theme.fg("accent", this.theme.bold(title)) : this.theme.bold(title),
|
|
11760
12824
|
width
|
|
11761
12825
|
)
|
|
11762
12826
|
];
|
|
11763
12827
|
if (section === "provider") {
|
|
11764
12828
|
lines.push(
|
|
11765
|
-
|
|
12829
|
+
truncateToWidth2(
|
|
11766
12830
|
this.theme.fg(
|
|
11767
12831
|
"dim",
|
|
11768
12832
|
` ${"Provider".padEnd(labelWidth, " ")} S C A R Status`
|
|
@@ -11777,15 +12841,15 @@ var WebProvidersSettingsView = class {
|
|
|
11777
12841
|
const paddedLabel = entry.label.padEnd(labelWidth, " ");
|
|
11778
12842
|
const label = selected ? this.theme.fg("accent", paddedLabel) : paddedLabel;
|
|
11779
12843
|
if (entry.currentValue.trim().length === 0) {
|
|
11780
|
-
lines.push(
|
|
12844
|
+
lines.push(truncateToWidth2(`${prefix}${label}`, width));
|
|
11781
12845
|
continue;
|
|
11782
12846
|
}
|
|
11783
12847
|
const value = entry.preserveValueStyle ? entry.currentValue : selected ? this.theme.fg("accent", entry.currentValue) : this.theme.fg("muted", entry.currentValue);
|
|
11784
|
-
lines.push(
|
|
12848
|
+
lines.push(truncateToWidth2(`${prefix}${label} ${value}`, width));
|
|
11785
12849
|
}
|
|
11786
12850
|
if (section === "provider") {
|
|
11787
12851
|
lines.push(
|
|
11788
|
-
|
|
12852
|
+
truncateToWidth2(
|
|
11789
12853
|
this.theme.fg("dim", " S=Search C=Contents A=Answer R=Research"),
|
|
11790
12854
|
width
|
|
11791
12855
|
)
|
|
@@ -11920,7 +12984,7 @@ var ToolSettingsSubmenu = class {
|
|
|
11920
12984
|
}
|
|
11921
12985
|
const entries = this.getEntries();
|
|
11922
12986
|
const lines = [
|
|
11923
|
-
|
|
12987
|
+
truncateToWidth2(
|
|
11924
12988
|
this.theme.fg("accent", TOOL_INFO[this.toolId].label),
|
|
11925
12989
|
width
|
|
11926
12990
|
),
|
|
@@ -11936,7 +13000,7 @@ var ToolSettingsSubmenu = class {
|
|
|
11936
13000
|
}
|
|
11937
13001
|
lines.push("");
|
|
11938
13002
|
lines.push(
|
|
11939
|
-
|
|
13003
|
+
truncateToWidth2(
|
|
11940
13004
|
this.theme.fg("dim", "\u2191\u2193 move \xB7 Enter edit/toggle \xB7 Esc back"),
|
|
11941
13005
|
width
|
|
11942
13006
|
)
|
|
@@ -11952,7 +13016,7 @@ var ToolSettingsSubmenu = class {
|
|
|
11952
13016
|
this.tui.requestRender();
|
|
11953
13017
|
return;
|
|
11954
13018
|
}
|
|
11955
|
-
const kb =
|
|
13019
|
+
const kb = getKeybindings2();
|
|
11956
13020
|
const entries = this.getEntries();
|
|
11957
13021
|
if (kb.matches(data, "tui.select.up")) {
|
|
11958
13022
|
if (this.selection > 0) {
|
|
@@ -12155,7 +13219,7 @@ var ProviderSettingsSubmenu = class {
|
|
|
12155
13219
|
const providerConfig = this.getProviderConfig();
|
|
12156
13220
|
const entries = this.getEntries();
|
|
12157
13221
|
const lines = [
|
|
12158
|
-
|
|
13222
|
+
truncateToWidth2(this.theme.fg("accent", provider.label), width),
|
|
12159
13223
|
"",
|
|
12160
13224
|
...renderEntryList(width, this.theme, entries, this.selection)
|
|
12161
13225
|
];
|
|
@@ -12172,10 +13236,10 @@ var ProviderSettingsSubmenu = class {
|
|
|
12172
13236
|
);
|
|
12173
13237
|
lines.push("");
|
|
12174
13238
|
lines.push(
|
|
12175
|
-
|
|
13239
|
+
truncateToWidth2(this.theme.fg("dim", `Status: ${status}`), width)
|
|
12176
13240
|
);
|
|
12177
13241
|
lines.push(
|
|
12178
|
-
|
|
13242
|
+
truncateToWidth2(
|
|
12179
13243
|
this.theme.fg("dim", "\u2191\u2193 move \xB7 Enter edit/toggle \xB7 Esc back"),
|
|
12180
13244
|
width
|
|
12181
13245
|
)
|
|
@@ -12191,7 +13255,7 @@ var ProviderSettingsSubmenu = class {
|
|
|
12191
13255
|
this.tui.requestRender();
|
|
12192
13256
|
return;
|
|
12193
13257
|
}
|
|
12194
|
-
const kb =
|
|
13258
|
+
const kb = getKeybindings2();
|
|
12195
13259
|
const entries = this.getEntries();
|
|
12196
13260
|
if (kb.matches(data, "tui.select.up")) {
|
|
12197
13261
|
if (this.selection > 0) {
|
|
@@ -12326,12 +13390,12 @@ var TextValueSubmenu = class {
|
|
|
12326
13390
|
editor;
|
|
12327
13391
|
render(width) {
|
|
12328
13392
|
return [
|
|
12329
|
-
|
|
13393
|
+
truncateToWidth2(this.theme.fg("accent", this.title), width),
|
|
12330
13394
|
"",
|
|
12331
13395
|
...this.editor.render(width),
|
|
12332
13396
|
"",
|
|
12333
|
-
|
|
12334
|
-
|
|
13397
|
+
truncateToWidth2(this.theme.fg("dim", this.help), width),
|
|
13398
|
+
truncateToWidth2(
|
|
12335
13399
|
this.theme.fg(
|
|
12336
13400
|
"dim",
|
|
12337
13401
|
"Enter to save \xB7 Shift+Enter for newline \xB7 Esc to cancel"
|
|
@@ -12344,7 +13408,7 @@ var TextValueSubmenu = class {
|
|
|
12344
13408
|
this.editor.invalidate();
|
|
12345
13409
|
}
|
|
12346
13410
|
handleInput(data) {
|
|
12347
|
-
if (
|
|
13411
|
+
if (matchesKey2(data, Key2.escape)) {
|
|
12348
13412
|
this.done(void 0);
|
|
12349
13413
|
return;
|
|
12350
13414
|
}
|
|
@@ -12468,7 +13532,7 @@ function getSearchQueriesForDisplay(queries) {
|
|
|
12468
13532
|
function getAnswerQueriesForDisplay(queries) {
|
|
12469
13533
|
return getSearchQueriesForDisplay(queries);
|
|
12470
13534
|
}
|
|
12471
|
-
function createBatchCompletionReporter(verb, providerId,
|
|
13535
|
+
function createBatchCompletionReporter(verb, providerId, providerLabel2, total, report) {
|
|
12472
13536
|
if (!report) {
|
|
12473
13537
|
return {
|
|
12474
13538
|
start: () => {
|
|
@@ -12482,7 +13546,7 @@ function createBatchCompletionReporter(verb, providerId, providerLabel, total, r
|
|
|
12482
13546
|
let completedCount = 0;
|
|
12483
13547
|
let failedCount = 0;
|
|
12484
13548
|
const emit = () => {
|
|
12485
|
-
let message = `${verb} via ${
|
|
13549
|
+
let message = `${verb} via ${providerLabel2}: ${completedCount}/${total} completed`;
|
|
12486
13550
|
if (failedCount > 0) {
|
|
12487
13551
|
message += `, ${failedCount} failed`;
|
|
12488
13552
|
}
|
|
@@ -12534,8 +13598,8 @@ function renderMarkdownBlock(text) {
|
|
|
12534
13598
|
if (!text) {
|
|
12535
13599
|
return new Text("", 0, 0);
|
|
12536
13600
|
}
|
|
12537
|
-
return new
|
|
12538
|
-
${text}`, 0, 0,
|
|
13601
|
+
return new Markdown2(`
|
|
13602
|
+
${text}`, 0, 0, getMarkdownTheme2());
|
|
12539
13603
|
}
|
|
12540
13604
|
function renderBlockText(text, theme, color) {
|
|
12541
13605
|
if (!text) {
|
|
@@ -12569,12 +13633,12 @@ function prefixWithSymbol(text, symbol) {
|
|
|
12569
13633
|
}
|
|
12570
13634
|
function renderToolProgress(display, fallbackText, theme) {
|
|
12571
13635
|
const progress = display?.progress;
|
|
12572
|
-
const
|
|
12573
|
-
if (!progress || !
|
|
13636
|
+
const providerLabel2 = display?.provider?.label;
|
|
13637
|
+
if (!progress || !providerLabel2) {
|
|
12574
13638
|
return renderSimpleText(fallbackText ?? "Working\u2026", theme, "warning");
|
|
12575
13639
|
}
|
|
12576
13640
|
return new Text(
|
|
12577
|
-
`${theme.fg("warning", progress.action)} ${theme.fg("muted", `via ${
|
|
13641
|
+
`${theme.fg("warning", progress.action)} ${theme.fg("muted", `via ${providerLabel2}`)}`,
|
|
12578
13642
|
0,
|
|
12579
13643
|
0
|
|
12580
13644
|
);
|
|
@@ -12626,15 +13690,15 @@ function buildFailureSummary({
|
|
|
12626
13690
|
fallback
|
|
12627
13691
|
}) {
|
|
12628
13692
|
const detail = stripTrailingSentencePunctuation(getFirstLine2(text) ?? "");
|
|
12629
|
-
const
|
|
12630
|
-
if (!
|
|
13693
|
+
const providerLabel2 = details?.provider !== void 0 ? PROVIDERS_BY_ID[details.provider]?.label ?? details.provider : void 0;
|
|
13694
|
+
if (!providerLabel2) {
|
|
12631
13695
|
return detail || fallback;
|
|
12632
13696
|
}
|
|
12633
|
-
return formatProviderCapabilityFailure(
|
|
13697
|
+
return formatProviderCapabilityFailure(providerLabel2, capability, detail);
|
|
12634
13698
|
}
|
|
12635
|
-
function formatProviderCapabilityFailure(
|
|
13699
|
+
function formatProviderCapabilityFailure(providerLabel2, capability, detail) {
|
|
12636
13700
|
const action = getFailureAction(capability);
|
|
12637
|
-
const base2 = `${
|
|
13701
|
+
const base2 = `${providerLabel2} ${action} failed`;
|
|
12638
13702
|
if (!detail || detail === base2) {
|
|
12639
13703
|
return base2;
|
|
12640
13704
|
}
|
|
@@ -12642,14 +13706,14 @@ function formatProviderCapabilityFailure(providerLabel, capability, detail) {
|
|
|
12642
13706
|
return detail;
|
|
12643
13707
|
}
|
|
12644
13708
|
const normalizedDetail = normalizeProviderFailureDetail(
|
|
12645
|
-
|
|
13709
|
+
providerLabel2,
|
|
12646
13710
|
detail
|
|
12647
13711
|
);
|
|
12648
13712
|
return `${base2}: ${normalizedDetail}`;
|
|
12649
13713
|
}
|
|
12650
|
-
function normalizeProviderFailureDetail(
|
|
13714
|
+
function normalizeProviderFailureDetail(providerLabel2, detail) {
|
|
12651
13715
|
const normalized = stripTrailingSentencePunctuation(detail);
|
|
12652
|
-
const providerPrefix = `${
|
|
13716
|
+
const providerPrefix = `${providerLabel2}:`;
|
|
12653
13717
|
return normalized.toLowerCase().startsWith(providerPrefix.toLowerCase()) ? normalized.slice(providerPrefix.length).trim() : normalized;
|
|
12654
13718
|
}
|
|
12655
13719
|
function getFailureAction(capability) {
|
|
@@ -12698,7 +13762,7 @@ function getFirstLine2(text) {
|
|
|
12698
13762
|
}
|
|
12699
13763
|
function getExpandHint() {
|
|
12700
13764
|
try {
|
|
12701
|
-
const keys =
|
|
13765
|
+
const keys = getKeybindings2().getKeys("app.tools.expand");
|
|
12702
13766
|
if (keys.length > 0) {
|
|
12703
13767
|
return `${keys.join("/")} to expand`;
|
|
12704
13768
|
}
|
|
@@ -12706,11 +13770,11 @@ function getExpandHint() {
|
|
|
12706
13770
|
}
|
|
12707
13771
|
return "ctrl+o to expand";
|
|
12708
13772
|
}
|
|
12709
|
-
function
|
|
13773
|
+
function cleanSingleLine2(text) {
|
|
12710
13774
|
return text.replace(/\s+/g, " ").trim();
|
|
12711
13775
|
}
|
|
12712
13776
|
function formatQuotedPreview(text, maxLength = 80) {
|
|
12713
|
-
return `"${truncateInline(
|
|
13777
|
+
return `"${truncateInline(cleanSingleLine2(text), maxLength)}"`;
|
|
12714
13778
|
}
|
|
12715
13779
|
function formatSearchResponses(outcomes, prefetch) {
|
|
12716
13780
|
const body = outcomes.map(
|
|
@@ -12736,10 +13800,10 @@ function formatSearchOutcomeSection(outcome, index, total) {
|
|
|
12736
13800
|
${body}`;
|
|
12737
13801
|
}
|
|
12738
13802
|
function formatSearchHeading(query2) {
|
|
12739
|
-
return `"${escapeMarkdownText(
|
|
13803
|
+
return `"${escapeMarkdownText(cleanSingleLine2(query2))}"`;
|
|
12740
13804
|
}
|
|
12741
13805
|
function formatAnswerHeading(query2) {
|
|
12742
|
-
return `"${escapeMarkdownText(
|
|
13806
|
+
return `"${escapeMarkdownText(cleanSingleLine2(query2))}"`;
|
|
12743
13807
|
}
|
|
12744
13808
|
function collectSearchResultUrls(outcomes) {
|
|
12745
13809
|
return outcomes.flatMap(
|
|
@@ -12755,7 +13819,7 @@ function formatSearchResponseMarkdown(response) {
|
|
|
12755
13819
|
`${index + 1}. ${formatMarkdownLink(result.title, result.url)}`
|
|
12756
13820
|
];
|
|
12757
13821
|
if (result.snippet) {
|
|
12758
|
-
lines.push(` ${escapeMarkdownText(
|
|
13822
|
+
lines.push(` ${escapeMarkdownText(cleanSingleLine2(result.snippet))}`);
|
|
12759
13823
|
}
|
|
12760
13824
|
return lines.join("\n");
|
|
12761
13825
|
}).join("\n\n");
|
|
@@ -12764,7 +13828,7 @@ function formatMarkdownLink(label, url2) {
|
|
|
12764
13828
|
return `[${escapeMarkdownLinkLabel(label)}](<${url2}>)`;
|
|
12765
13829
|
}
|
|
12766
13830
|
function escapeMarkdownLinkLabel(text) {
|
|
12767
|
-
return
|
|
13831
|
+
return cleanSingleLine2(text).replaceAll("\\", "\\\\").replaceAll("]", "\\]");
|
|
12768
13832
|
}
|
|
12769
13833
|
function escapeMarkdownText(text) {
|
|
12770
13834
|
return text.replaceAll("\\", "\\\\").replaceAll("*", "\\*").replaceAll("_", "\\_").replaceAll("`", "\\`").replaceAll("#", "\\#").replaceAll("[", "\\[").replaceAll("]", "\\]");
|
|
@@ -12785,10 +13849,10 @@ async function truncateAndSaveWithMetadata(text, prefix) {
|
|
|
12785
13849
|
truncated: false
|
|
12786
13850
|
};
|
|
12787
13851
|
}
|
|
12788
|
-
const dir =
|
|
12789
|
-
await
|
|
12790
|
-
const fullPath =
|
|
12791
|
-
await
|
|
13852
|
+
const dir = join3(tmpdir(), `pi-web-providers-${prefix}-${Date.now()}`);
|
|
13853
|
+
await mkdir3(dir, { recursive: true });
|
|
13854
|
+
const fullPath = join3(dir, "output.txt");
|
|
13855
|
+
await writeFile3(fullPath, text, "utf-8");
|
|
12792
13856
|
return {
|
|
12793
13857
|
text: truncation.content + `
|
|
12794
13858
|
|
|
@@ -12895,6 +13959,12 @@ var __test__ = {
|
|
|
12895
13959
|
}),
|
|
12896
13960
|
extractTextContent,
|
|
12897
13961
|
formatWebResearchResultMessage,
|
|
13962
|
+
getActiveWebResearchRequests,
|
|
13963
|
+
getWebResearchTaskSnapshots,
|
|
13964
|
+
cancelWebResearchTask,
|
|
13965
|
+
loadWebResearchHistory,
|
|
13966
|
+
loadWebResearchPreview,
|
|
13967
|
+
loadWebResearchReport,
|
|
12898
13968
|
getAvailableManagedToolNames,
|
|
12899
13969
|
getReadyCompatibleProvidersForTool,
|
|
12900
13970
|
getEnabledCompatibleProvidersForTool: getReadyCompatibleProvidersForTool,
|
|
@@ -12912,9 +13982,10 @@ var __test__ = {
|
|
|
12912
13982
|
renderProviderToolResult,
|
|
12913
13983
|
renderWebResearchDispatchResult,
|
|
12914
13984
|
renderWebResearchResultMessage,
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
|
|
13985
|
+
renderWebResearchReportMessage,
|
|
13986
|
+
formatWebResearchReportMessage,
|
|
13987
|
+
buildWebResearchWidgetLines,
|
|
13988
|
+
waitForPendingResearchTasks,
|
|
12918
13989
|
formatSearchResponses,
|
|
12919
13990
|
formatAnswerResponses
|
|
12920
13991
|
};
|