pi-web-providers 3.3.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 +16 -1
- package/dist/index.js +1439 -546
- 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();
|
|
@@ -3713,7 +3713,7 @@ var geminiImplementation = {
|
|
|
3713
3713
|
context.signal
|
|
3714
3714
|
);
|
|
3715
3715
|
const results = await Promise.all(
|
|
3716
|
-
extractGoogleSearchResults(interaction
|
|
3716
|
+
extractGoogleSearchResults(readInteractionSteps(interaction)).slice(0, maxResults).map(async (result) => {
|
|
3717
3717
|
const resolvedUrl = await resolveGoogleSearchUrl(
|
|
3718
3718
|
result.url,
|
|
3719
3719
|
context.signal
|
|
@@ -3800,7 +3800,7 @@ var geminiImplementation = {
|
|
|
3800
3800
|
);
|
|
3801
3801
|
const status = readNonEmptyString3(interaction.status) ?? "unknown";
|
|
3802
3802
|
if (status === "completed") {
|
|
3803
|
-
const text =
|
|
3803
|
+
const text = formatInteractionSteps(readInteractionSteps(interaction));
|
|
3804
3804
|
return {
|
|
3805
3805
|
status: "completed",
|
|
3806
3806
|
output: {
|
|
@@ -3830,7 +3830,7 @@ var geminiImplementation = {
|
|
|
3830
3830
|
if (status === "requires_action") {
|
|
3831
3831
|
return {
|
|
3832
3832
|
status: "failed",
|
|
3833
|
-
error: describeGeminiRequiredAction(interaction
|
|
3833
|
+
error: describeGeminiRequiredAction(readInteractionSteps(interaction))
|
|
3834
3834
|
};
|
|
3835
3835
|
}
|
|
3836
3836
|
return status === "in_progress" ? { status: "in_progress" } : { status: "in_progress", statusText: status };
|
|
@@ -3864,17 +3864,20 @@ function addAbortSignalToGeminiConfig(config, signal) {
|
|
|
3864
3864
|
abortSignal: signal
|
|
3865
3865
|
};
|
|
3866
3866
|
}
|
|
3867
|
-
function
|
|
3867
|
+
function readInteractionSteps(interaction) {
|
|
3868
|
+
return typeof interaction === "object" && interaction !== null ? interaction.steps : void 0;
|
|
3869
|
+
}
|
|
3870
|
+
function extractGoogleSearchResults(steps) {
|
|
3868
3871
|
const seen = /* @__PURE__ */ new Set();
|
|
3869
3872
|
const results = [];
|
|
3870
|
-
if (!Array.isArray(
|
|
3873
|
+
if (!Array.isArray(steps)) {
|
|
3871
3874
|
return results;
|
|
3872
3875
|
}
|
|
3873
|
-
for (const
|
|
3874
|
-
if (typeof
|
|
3876
|
+
for (const step of steps) {
|
|
3877
|
+
if (typeof step !== "object" || step === null) {
|
|
3875
3878
|
continue;
|
|
3876
3879
|
}
|
|
3877
|
-
const content =
|
|
3880
|
+
const content = step;
|
|
3878
3881
|
if (content.type !== "google_search_result") {
|
|
3879
3882
|
continue;
|
|
3880
3883
|
}
|
|
@@ -4040,16 +4043,21 @@ function extractGroundingSources(chunks) {
|
|
|
4040
4043
|
}
|
|
4041
4044
|
return sources;
|
|
4042
4045
|
}
|
|
4043
|
-
function
|
|
4046
|
+
function formatInteractionSteps(steps) {
|
|
4044
4047
|
const lines = [];
|
|
4045
|
-
if (!Array.isArray(
|
|
4048
|
+
if (!Array.isArray(steps)) {
|
|
4046
4049
|
return "";
|
|
4047
4050
|
}
|
|
4048
|
-
for (const
|
|
4049
|
-
if (typeof
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
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
|
+
}
|
|
4053
4061
|
}
|
|
4054
4062
|
}
|
|
4055
4063
|
}
|
|
@@ -4244,14 +4252,12 @@ function buildGeminiGenerateContentRequest({
|
|
|
4244
4252
|
}
|
|
4245
4253
|
};
|
|
4246
4254
|
}
|
|
4247
|
-
function describeGeminiRequiredAction(
|
|
4248
|
-
if (!Array.isArray(
|
|
4255
|
+
function describeGeminiRequiredAction(steps) {
|
|
4256
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
4249
4257
|
return "research requires additional action";
|
|
4250
4258
|
}
|
|
4251
|
-
const
|
|
4252
|
-
|
|
4253
|
-
);
|
|
4254
|
-
const type = readNonEmptyString3(firstOutput?.type);
|
|
4259
|
+
const lastStep = [...steps].reverse().find((value) => typeof value === "object" && value !== null);
|
|
4260
|
+
const type = readNonEmptyString3(lastStep?.type);
|
|
4255
4261
|
if (!type) {
|
|
4256
4262
|
return "research requires additional action";
|
|
4257
4263
|
}
|
|
@@ -7849,45 +7855,6 @@ ${JSON.stringify(value, null, 2).trim()}
|
|
|
7849
7855
|
\`\`\``;
|
|
7850
7856
|
}
|
|
7851
7857
|
|
|
7852
|
-
// src/options.ts
|
|
7853
|
-
function buildToolOptionsSchema(_capability, providerSchema) {
|
|
7854
|
-
if (!providerSchema || Object.keys(providerSchema.properties).length === 0) {
|
|
7855
|
-
return void 0;
|
|
7856
|
-
}
|
|
7857
|
-
return closeObjectSchemas(providerSchema);
|
|
7858
|
-
}
|
|
7859
|
-
function closeObjectSchemas(schema) {
|
|
7860
|
-
if (!isSchemaRecord(schema)) {
|
|
7861
|
-
return schema;
|
|
7862
|
-
}
|
|
7863
|
-
const properties = isSchemaRecord(schema.properties) ? Object.fromEntries(
|
|
7864
|
-
Object.entries(schema.properties).map(([key, value]) => [
|
|
7865
|
-
key,
|
|
7866
|
-
closeObjectSchemas(value)
|
|
7867
|
-
])
|
|
7868
|
-
) : schema.properties;
|
|
7869
|
-
const items = isSchemaRecord(schema.items) ? closeObjectSchemas(schema.items) : Array.isArray(schema.items) ? schema.items.map((item) => closeObjectSchemas(item)) : schema.items;
|
|
7870
|
-
return {
|
|
7871
|
-
...schema,
|
|
7872
|
-
...properties ? { properties } : {},
|
|
7873
|
-
...items ? { items } : {},
|
|
7874
|
-
...mapSchemaArray(schema, "anyOf"),
|
|
7875
|
-
...mapSchemaArray(schema, "oneOf"),
|
|
7876
|
-
...mapSchemaArray(schema, "allOf"),
|
|
7877
|
-
...schema.type === "object" && isSchemaRecord(schema.properties) ? { additionalProperties: false } : {}
|
|
7878
|
-
};
|
|
7879
|
-
}
|
|
7880
|
-
function mapSchemaArray(schema, key) {
|
|
7881
|
-
const value = schema[key];
|
|
7882
|
-
return Array.isArray(value) ? { [key]: value.map((entry) => closeObjectSchemas(entry)) } : {};
|
|
7883
|
-
}
|
|
7884
|
-
function isSchemaRecord(value) {
|
|
7885
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7886
|
-
}
|
|
7887
|
-
|
|
7888
|
-
// src/prefetch-manager.ts
|
|
7889
|
-
import { createHash } from "node:crypto";
|
|
7890
|
-
|
|
7891
7858
|
// src/provider-resolution.ts
|
|
7892
7859
|
function supportsTool2(provider, tool) {
|
|
7893
7860
|
return provider.capabilities[tool] !== void 0;
|
|
@@ -8029,6 +7996,140 @@ function resolveProviderForTool(config, cwd, tool, explicit) {
|
|
|
8029
7996
|
return provider;
|
|
8030
7997
|
}
|
|
8031
7998
|
|
|
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,
|
|
8021
|
+
{
|
|
8022
|
+
resolveSecrets: false
|
|
8023
|
+
}
|
|
8024
|
+
);
|
|
8025
|
+
return isProviderCapabilityExposable(status) ? [providerId] : [];
|
|
8026
|
+
}
|
|
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;
|
|
8044
|
+
}
|
|
8045
|
+
nextActiveTools.delete(toolName);
|
|
8046
|
+
}
|
|
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) {
|
|
8066
|
+
try {
|
|
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
|
+
|
|
8032
8133
|
// src/provider-runtime.ts
|
|
8033
8134
|
async function executeProviderRequest(provider, config, request, context) {
|
|
8034
8135
|
return await executeProviderExecution(
|
|
@@ -8122,7 +8223,7 @@ function resolveExecutionPolicy(defaults) {
|
|
|
8122
8223
|
retryDelayMs: defaults?.retryDelayMs ?? 2e3
|
|
8123
8224
|
};
|
|
8124
8225
|
}
|
|
8125
|
-
function createResearchDeadlineSignal(signal,
|
|
8226
|
+
function createResearchDeadlineSignal(signal, providerLabel2, timeoutMs) {
|
|
8126
8227
|
if (timeoutMs === void 0) {
|
|
8127
8228
|
return void 0;
|
|
8128
8229
|
}
|
|
@@ -8137,7 +8238,7 @@ function createResearchDeadlineSignal(signal, providerLabel, timeoutMs) {
|
|
|
8137
8238
|
const timer = setTimeout(() => {
|
|
8138
8239
|
controller.abort(
|
|
8139
8240
|
new Error(
|
|
8140
|
-
`${
|
|
8241
|
+
`${providerLabel2} research exceeded ${formatDuration(timeoutMs)}.`
|
|
8141
8242
|
)
|
|
8142
8243
|
);
|
|
8143
8244
|
}, timeoutMs);
|
|
@@ -9792,51 +9893,1001 @@ function getFirstLine(text) {
|
|
|
9792
9893
|
return text?.split("\n").map((line) => line.trim()).find((line) => line.length > 0);
|
|
9793
9894
|
}
|
|
9794
9895
|
|
|
9795
|
-
// src/
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
var RESEARCH_HEARTBEAT_MS = 15e3;
|
|
9800
|
-
var WEB_RESEARCH_RESULT_MESSAGE_TYPE = "web-research-result";
|
|
9801
|
-
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";
|
|
9802
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;
|
|
9803
9904
|
var pendingResearchTasks = /* @__PURE__ */ new Set();
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
const
|
|
9826
|
-
|
|
9827
|
-
|
|
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" }
|
|
9828
9958
|
}
|
|
9829
|
-
webResearchWidgetTimer = setInterval(() => {
|
|
9830
|
-
updateWebResearchWidget();
|
|
9831
|
-
}, 1e3);
|
|
9832
9959
|
};
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
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;
|
|
9999
|
+
}
|
|
10000
|
+
} catch (error) {
|
|
10001
|
+
result = buildFailedWebResearchResult(
|
|
10002
|
+
request,
|
|
10003
|
+
abortController,
|
|
10004
|
+
task,
|
|
10005
|
+
error
|
|
10006
|
+
);
|
|
10007
|
+
}
|
|
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) {
|
|
9840
10891
|
stopWebResearchWidgetTimer();
|
|
9841
10892
|
return;
|
|
9842
10893
|
}
|
|
@@ -9849,7 +10900,7 @@ function webProvidersExtension(pi) {
|
|
|
9849
10900
|
widgetContext.ui.setWidget(
|
|
9850
10901
|
WEB_RESEARCH_WIDGET_KEY,
|
|
9851
10902
|
buildWebResearchWidgetLines(
|
|
9852
|
-
|
|
10903
|
+
getActiveWebResearchRequests(activeWebResearchRequests),
|
|
9853
10904
|
widgetContext.ui.theme
|
|
9854
10905
|
)
|
|
9855
10906
|
);
|
|
@@ -9859,6 +10910,10 @@ function webProvidersExtension(pi) {
|
|
|
9859
10910
|
WEB_RESEARCH_RESULT_MESSAGE_TYPE,
|
|
9860
10911
|
(message, state, theme) => renderWebResearchResultMessage(message, state, theme)
|
|
9861
10912
|
);
|
|
10913
|
+
pi.registerMessageRenderer(
|
|
10914
|
+
WEB_RESEARCH_REPORT_MESSAGE_TYPE,
|
|
10915
|
+
(message, state, theme) => renderWebResearchReportMessage(message, state, theme)
|
|
10916
|
+
);
|
|
9862
10917
|
}
|
|
9863
10918
|
pi.registerCommand("web-providers", {
|
|
9864
10919
|
description: "Configure web search providers",
|
|
@@ -9874,11 +10929,71 @@ function webProvidersExtension(pi) {
|
|
|
9874
10929
|
);
|
|
9875
10930
|
}
|
|
9876
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
|
+
}
|
|
10991
|
+
});
|
|
9877
10992
|
pi.on("session_start", async (_event, ctx) => {
|
|
9878
10993
|
latestWidgetContext = ctx;
|
|
9879
10994
|
resetContentStore();
|
|
9880
10995
|
updateWebResearchWidget(ctx);
|
|
9881
|
-
await
|
|
10996
|
+
await refreshManagedToolsOnStartup2(
|
|
9882
10997
|
pi,
|
|
9883
10998
|
{ activeWebResearchRequests, updateWebResearchWidget },
|
|
9884
10999
|
ctx.cwd,
|
|
@@ -9889,7 +11004,7 @@ function webProvidersExtension(pi) {
|
|
|
9889
11004
|
latestWidgetContext = ctx;
|
|
9890
11005
|
await cleanupContentStore();
|
|
9891
11006
|
updateWebResearchWidget(ctx);
|
|
9892
|
-
await
|
|
11007
|
+
await refreshManagedToolsOnStartup2(
|
|
9893
11008
|
pi,
|
|
9894
11009
|
{ activeWebResearchRequests, updateWebResearchWidget },
|
|
9895
11010
|
ctx.cwd,
|
|
@@ -10112,7 +11227,7 @@ function registerWebResearchTool(pi, webResearchLifecycle, providerIds) {
|
|
|
10112
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."
|
|
10113
11228
|
]),
|
|
10114
11229
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
10115
|
-
return
|
|
11230
|
+
return dispatchWebResearch2({
|
|
10116
11231
|
pi,
|
|
10117
11232
|
activeWebResearchRequests: webResearchLifecycle.activeWebResearchRequests,
|
|
10118
11233
|
updateWebResearchWidget: webResearchLifecycle.updateWebResearchWidget,
|
|
@@ -10137,109 +11252,38 @@ function registerWebResearchTool(pi, webResearchLifecycle, providerIds) {
|
|
|
10137
11252
|
}
|
|
10138
11253
|
});
|
|
10139
11254
|
}
|
|
10140
|
-
async function runWebProvidersConfig(pi, webResearchLifecycle, ctx) {
|
|
10141
|
-
const config = await loadConfig();
|
|
10142
|
-
const activeProvider = getInitialProviderSelection(config);
|
|
10143
|
-
await ctx.ui.custom(
|
|
10144
|
-
(tui, theme, _keybindings, done) => new WebProvidersSettingsView(
|
|
10145
|
-
tui,
|
|
10146
|
-
theme,
|
|
10147
|
-
done,
|
|
10148
|
-
ctx,
|
|
10149
|
-
config,
|
|
10150
|
-
activeProvider
|
|
10151
|
-
)
|
|
10152
|
-
);
|
|
10153
|
-
await refreshManagedTools(pi, webResearchLifecycle, ctx.cwd, {
|
|
10154
|
-
addAvailable: true
|
|
10155
|
-
});
|
|
10156
|
-
}
|
|
10157
|
-
function formatStartupConfigError(error) {
|
|
10158
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
10159
|
-
return `web-providers config error: ${detail.replace(getConfigPath(), "~/.pi/agent/web-providers.json")}`;
|
|
10160
|
-
}
|
|
10161
|
-
function getAvailableProviderIdsForCapability(config, cwd, capability) {
|
|
10162
|
-
const providerId = getMappedProviderIdForTool(config, capability);
|
|
10163
|
-
if (!providerId) {
|
|
10164
|
-
return [];
|
|
10165
|
-
}
|
|
10166
|
-
const provider = PROVIDERS_BY_ID[providerId];
|
|
10167
|
-
if (!supportsTool2(provider, capability)) {
|
|
10168
|
-
return [];
|
|
10169
|
-
}
|
|
10170
|
-
const status = getProviderCapabilityStatus(
|
|
10171
|
-
config,
|
|
10172
|
-
cwd,
|
|
10173
|
-
providerId,
|
|
10174
|
-
capability,
|
|
10175
|
-
{
|
|
10176
|
-
resolveSecrets: false
|
|
10177
|
-
}
|
|
10178
|
-
);
|
|
10179
|
-
return isProviderCapabilityExposable(status) ? [providerId] : [];
|
|
10180
|
-
}
|
|
10181
|
-
function getProviderStatusForTool(config, cwd, providerId, capability) {
|
|
10182
|
-
return getProviderCapabilityStatus(config, cwd, providerId, capability);
|
|
10183
|
-
}
|
|
10184
|
-
function getAvailableManagedToolNames(config, cwd) {
|
|
10185
|
-
return Object.keys(CAPABILITY_TOOL_NAMES).filter(
|
|
10186
|
-
(capability) => getAvailableProviderIdsForCapability(config, cwd, capability).length > 0
|
|
10187
|
-
).map((capability) => CAPABILITY_TOOL_NAMES[capability]);
|
|
10188
|
-
}
|
|
10189
|
-
function getSyncedActiveTools(config, cwd, activeToolNames, options) {
|
|
10190
|
-
const availableToolNames = new Set(getAvailableManagedToolNames(config, cwd));
|
|
10191
|
-
const nextActiveTools = new Set(activeToolNames);
|
|
10192
|
-
for (const toolName of MANAGED_TOOL_NAMES) {
|
|
10193
|
-
if (availableToolNames.has(toolName)) {
|
|
10194
|
-
if (options.addAvailable) {
|
|
10195
|
-
nextActiveTools.add(toolName);
|
|
10196
|
-
}
|
|
10197
|
-
continue;
|
|
10198
|
-
}
|
|
10199
|
-
nextActiveTools.delete(toolName);
|
|
10200
|
-
}
|
|
10201
|
-
return nextActiveTools;
|
|
10202
|
-
}
|
|
10203
|
-
async function refreshManagedTools(pi, webResearchLifecycle, cwd, options) {
|
|
11255
|
+
async function runWebProvidersConfig(pi, webResearchLifecycle, ctx) {
|
|
10204
11256
|
const config = await loadConfig();
|
|
10205
|
-
const
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
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
|
+
)
|
|
10210
11267
|
);
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
contents: getAvailableProviderIdsForCapability(config, cwd, "contents"),
|
|
10214
|
-
answer: getAvailableProviderIdsForCapability(config, cwd, "answer"),
|
|
10215
|
-
research: getAvailableProviderIdsForCapability(config, cwd, "research")
|
|
11268
|
+
await refreshManagedTools2(pi, webResearchLifecycle, ctx.cwd, {
|
|
11269
|
+
addAvailable: true
|
|
10216
11270
|
});
|
|
10217
|
-
await syncManagedToolAvailability(pi, nextActiveTools);
|
|
10218
11271
|
}
|
|
10219
|
-
async function
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
content: message,
|
|
10227
|
-
display: true
|
|
10228
|
-
});
|
|
10229
|
-
await syncManagedToolAvailability(
|
|
10230
|
-
pi,
|
|
10231
|
-
new Set(
|
|
10232
|
-
pi.getActiveTools().filter((toolName) => !MANAGED_TOOL_NAMES.includes(toolName))
|
|
10233
|
-
)
|
|
10234
|
-
);
|
|
10235
|
-
}
|
|
11272
|
+
async function refreshManagedTools2(pi, webResearchLifecycle, cwd, options) {
|
|
11273
|
+
await refreshManagedTools(
|
|
11274
|
+
pi,
|
|
11275
|
+
(providerIdsByCapability) => registerManagedTools(pi, webResearchLifecycle, providerIdsByCapability),
|
|
11276
|
+
cwd,
|
|
11277
|
+
options
|
|
11278
|
+
);
|
|
10236
11279
|
}
|
|
10237
|
-
async function
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
11280
|
+
async function refreshManagedToolsOnStartup2(pi, webResearchLifecycle, cwd, options) {
|
|
11281
|
+
await refreshManagedToolsOnStartup(
|
|
11282
|
+
pi,
|
|
11283
|
+
(providerIdsByCapability) => registerManagedTools(pi, webResearchLifecycle, providerIdsByCapability),
|
|
11284
|
+
cwd,
|
|
11285
|
+
options
|
|
11286
|
+
);
|
|
10243
11287
|
}
|
|
10244
11288
|
function getSearchMaxResultsLimit(providerId) {
|
|
10245
11289
|
const capabilities = PROVIDERS_BY_ID[providerId].capabilities;
|
|
@@ -10456,12 +11500,12 @@ async function executeRawProviderRequest({
|
|
|
10456
11500
|
input
|
|
10457
11501
|
});
|
|
10458
11502
|
}
|
|
10459
|
-
function buildSearchBatchError(outcomes,
|
|
11503
|
+
function buildSearchBatchError(outcomes, providerLabel2) {
|
|
10460
11504
|
const failed = outcomes.filter((outcome) => outcome.error !== void 0);
|
|
10461
11505
|
if (failed.length === 1) {
|
|
10462
11506
|
return new Error(
|
|
10463
11507
|
formatProviderCapabilityFailure(
|
|
10464
|
-
|
|
11508
|
+
providerLabel2,
|
|
10465
11509
|
"search",
|
|
10466
11510
|
failed[0]?.error ?? ""
|
|
10467
11511
|
)
|
|
@@ -10471,7 +11515,7 @@ function buildSearchBatchError(outcomes, providerLabel) {
|
|
|
10471
11515
|
(outcome, index) => `${index + 1}. ${formatQuotedPreview(outcome.query, 40)} \u2014 ${outcome.error}`
|
|
10472
11516
|
).join("; ");
|
|
10473
11517
|
return new Error(
|
|
10474
|
-
`${
|
|
11518
|
+
`${providerLabel2} search failed for ${failed.length} queries: ${summary}`
|
|
10475
11519
|
);
|
|
10476
11520
|
}
|
|
10477
11521
|
async function executeSingleSearchQuery({
|
|
@@ -10608,12 +11652,12 @@ async function executeAnswerToolInternal({
|
|
|
10608
11652
|
})
|
|
10609
11653
|
};
|
|
10610
11654
|
}
|
|
10611
|
-
function buildAnswerBatchError(outcomes,
|
|
11655
|
+
function buildAnswerBatchError(outcomes, providerLabel2) {
|
|
10612
11656
|
const failed = outcomes.filter((outcome) => outcome.error !== void 0);
|
|
10613
11657
|
if (failed.length === 1) {
|
|
10614
11658
|
return new Error(
|
|
10615
11659
|
formatProviderCapabilityFailure(
|
|
10616
|
-
|
|
11660
|
+
providerLabel2,
|
|
10617
11661
|
"answer",
|
|
10618
11662
|
failed[0]?.error ?? ""
|
|
10619
11663
|
)
|
|
@@ -10623,7 +11667,7 @@ function buildAnswerBatchError(outcomes, providerLabel) {
|
|
|
10623
11667
|
(outcome, index) => `${index + 1}. ${formatQuotedPreview(outcome.query, 40)} \u2014 ${outcome.error}`
|
|
10624
11668
|
).join("; ");
|
|
10625
11669
|
return new Error(
|
|
10626
|
-
`${
|
|
11670
|
+
`${providerLabel2} answer failed for ${failed.length} questions: ${summary}`
|
|
10627
11671
|
);
|
|
10628
11672
|
}
|
|
10629
11673
|
function formatAnswerResponses(outcomes) {
|
|
@@ -10839,7 +11883,7 @@ async function executeProviderToolInternal({
|
|
|
10839
11883
|
})
|
|
10840
11884
|
};
|
|
10841
11885
|
}
|
|
10842
|
-
async function
|
|
11886
|
+
async function dispatchWebResearch2({
|
|
10843
11887
|
pi,
|
|
10844
11888
|
activeWebResearchRequests,
|
|
10845
11889
|
updateWebResearchWidget,
|
|
@@ -10868,188 +11912,59 @@ async function dispatchWebResearchInternal({
|
|
|
10868
11912
|
input,
|
|
10869
11913
|
executionOverride
|
|
10870
11914
|
}) {
|
|
10871
|
-
|
|
10872
|
-
|
|
11915
|
+
return dispatchWebResearch({
|
|
11916
|
+
activeWebResearchRequests,
|
|
10873
11917
|
config,
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
updateWebResearchWidget(ctx);
|
|
10882
|
-
trackPendingResearchTask(
|
|
10883
|
-
runDispatchedWebResearch({
|
|
10884
|
-
pi,
|
|
10885
|
-
activeWebResearchRequests,
|
|
10886
|
-
updateWebResearchWidget,
|
|
10887
|
-
request,
|
|
10888
|
-
config,
|
|
11918
|
+
explicitProvider,
|
|
11919
|
+
ctx: { cwd: ctx.cwd },
|
|
11920
|
+
options: providerOptions,
|
|
11921
|
+
input,
|
|
11922
|
+
executionOverride,
|
|
11923
|
+
executeResearch: async ({
|
|
11924
|
+
config: config2,
|
|
10889
11925
|
provider,
|
|
10890
11926
|
providerConfig,
|
|
10891
|
-
ctx,
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
{
|
|
10899
|
-
type: "text",
|
|
10900
|
-
text: `Started web research via ${provider.label}.`
|
|
10901
|
-
}
|
|
10902
|
-
],
|
|
10903
|
-
details: request,
|
|
10904
|
-
display: buildProviderToolDisplay2({
|
|
10905
|
-
capability: "research",
|
|
10906
|
-
providerId: provider.id,
|
|
10907
|
-
details: { tool: "web_research", provider: provider.id },
|
|
10908
|
-
text: "started"
|
|
10909
|
-
})
|
|
10910
|
-
};
|
|
10911
|
-
}
|
|
10912
|
-
async function runDispatchedWebResearch({
|
|
10913
|
-
pi,
|
|
10914
|
-
activeWebResearchRequests,
|
|
10915
|
-
updateWebResearchWidget,
|
|
10916
|
-
request,
|
|
10917
|
-
config,
|
|
10918
|
-
provider,
|
|
10919
|
-
providerConfig,
|
|
10920
|
-
ctx,
|
|
10921
|
-
options,
|
|
10922
|
-
executionOverride
|
|
10923
|
-
}) {
|
|
10924
|
-
let result;
|
|
10925
|
-
let reportText = "";
|
|
10926
|
-
try {
|
|
10927
|
-
const response = await executeProviderOperation({
|
|
11927
|
+
ctx: ctx2,
|
|
11928
|
+
signal,
|
|
11929
|
+
options,
|
|
11930
|
+
input: input2,
|
|
11931
|
+
onProgress,
|
|
11932
|
+
executionOverride: executionOverride2
|
|
11933
|
+
}) => executeProviderOperation({
|
|
10928
11934
|
capability: "research",
|
|
10929
|
-
config,
|
|
11935
|
+
config: config2,
|
|
10930
11936
|
provider,
|
|
10931
11937
|
providerConfig,
|
|
10932
|
-
ctx,
|
|
10933
|
-
signal
|
|
11938
|
+
ctx: ctx2,
|
|
11939
|
+
signal,
|
|
10934
11940
|
options,
|
|
10935
|
-
input:
|
|
10936
|
-
onProgress
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
executionOverride
|
|
10944
|
-
});
|
|
10945
|
-
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10946
|
-
result = {
|
|
10947
|
-
...request,
|
|
10948
|
-
status: "completed",
|
|
10949
|
-
completedAt,
|
|
10950
|
-
elapsedMs: Math.max(
|
|
10951
|
-
0,
|
|
10952
|
-
Date.parse(completedAt) - Date.parse(request.startedAt)
|
|
10953
|
-
),
|
|
10954
|
-
itemCount: response.itemCount
|
|
10955
|
-
};
|
|
10956
|
-
reportText = response.text;
|
|
10957
|
-
} catch (error) {
|
|
10958
|
-
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10959
|
-
result = {
|
|
10960
|
-
...request,
|
|
10961
|
-
status: "failed",
|
|
10962
|
-
completedAt,
|
|
10963
|
-
elapsedMs: Math.max(
|
|
10964
|
-
0,
|
|
10965
|
-
Date.parse(completedAt) - Date.parse(request.startedAt)
|
|
10966
|
-
),
|
|
10967
|
-
error: formatErrorMessage(error)
|
|
10968
|
-
};
|
|
10969
|
-
}
|
|
10970
|
-
try {
|
|
10971
|
-
await writeWebResearchArtifact(result, reportText);
|
|
10972
|
-
pi.sendMessage({
|
|
10973
|
-
customType: WEB_RESEARCH_RESULT_MESSAGE_TYPE,
|
|
10974
|
-
content: formatWebResearchResultMessage(result, reportText),
|
|
10975
|
-
display: true,
|
|
10976
|
-
details: result
|
|
10977
|
-
});
|
|
10978
|
-
} finally {
|
|
10979
|
-
activeWebResearchRequests.delete(request.id);
|
|
10980
|
-
updateWebResearchWidget();
|
|
10981
|
-
}
|
|
10982
|
-
}
|
|
10983
|
-
function createWebResearchRequest(cwd, provider, input) {
|
|
10984
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10985
|
-
return {
|
|
10986
|
-
tool: "web_research",
|
|
10987
|
-
id: randomUUID(),
|
|
10988
|
-
provider,
|
|
10989
|
-
input,
|
|
10990
|
-
outputPath: buildWebResearchArtifactPath(cwd, input, startedAt),
|
|
10991
|
-
startedAt
|
|
10992
|
-
};
|
|
10993
|
-
}
|
|
10994
|
-
function buildWebResearchArtifactPath(cwd, input, startedAt) {
|
|
10995
|
-
const timestamp = startedAt.replaceAll(":", "-").replace(".", "-");
|
|
10996
|
-
const slug = slugifyWebResearchInput(input);
|
|
10997
|
-
return join2(cwd, RESEARCH_ARTIFACTS_DIR, `${timestamp}-${slug}.md`);
|
|
10998
|
-
}
|
|
10999
|
-
function slugifyWebResearchInput(input) {
|
|
11000
|
-
const slug = input.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
|
|
11001
|
-
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
|
+
});
|
|
11002
11949
|
}
|
|
11003
11950
|
function buildWebResearchWidgetLines(requests, theme, now = Date.now()) {
|
|
11004
|
-
const
|
|
11005
|
-
|
|
11006
|
-
const
|
|
11007
|
-
const elapsed =
|
|
11008
|
-
const icon =
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
lines.push(theme.fg("muted", `+${requests.length - 3} more`));
|
|
11015
|
-
}
|
|
11016
|
-
return lines;
|
|
11017
|
-
}
|
|
11018
|
-
function getWebResearchWidgetIcon(request, _now) {
|
|
11019
|
-
if (request.progress === "poll retrying after transient errors") {
|
|
11020
|
-
return "\u27F3 ";
|
|
11021
|
-
}
|
|
11022
|
-
if (request.progress === "queued") {
|
|
11023
|
-
return "\u25CC ";
|
|
11024
|
-
}
|
|
11025
|
-
if (request.progress === "starting") {
|
|
11026
|
-
return "\u25D4 ";
|
|
11027
|
-
}
|
|
11028
|
-
if (request.progress?.startsWith("started:")) {
|
|
11029
|
-
return "\u25D1 ";
|
|
11030
|
-
}
|
|
11031
|
-
return "\u25CF ";
|
|
11032
|
-
}
|
|
11033
|
-
function summarizeWebResearchProgress(message, providerLabel) {
|
|
11034
|
-
const startingMessage = `Starting research via ${providerLabel}`;
|
|
11035
|
-
if (message === startingMessage) {
|
|
11036
|
-
return "starting";
|
|
11037
|
-
}
|
|
11038
|
-
const startedPrefix = `${providerLabel} research started: `;
|
|
11039
|
-
if (message.startsWith(startedPrefix)) {
|
|
11040
|
-
return `started: ${message.slice(startedPrefix.length)}`;
|
|
11041
|
-
}
|
|
11042
|
-
const statusPrefix = `Research via ${providerLabel}: `;
|
|
11043
|
-
if (message.startsWith(statusPrefix)) {
|
|
11044
|
-
return message.slice(statusPrefix.length).replace(/\s+\([^)]* elapsed\)$/u, "").trim();
|
|
11045
|
-
}
|
|
11046
|
-
const retryPrefix = `${providerLabel} research poll is still retrying after transient errors`;
|
|
11047
|
-
if (message.startsWith(retryPrefix)) {
|
|
11048
|
-
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`);
|
|
11049
11961
|
}
|
|
11050
|
-
|
|
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
|
+
];
|
|
11051
11966
|
}
|
|
11052
|
-
function
|
|
11967
|
+
function formatCompactElapsed2(ms) {
|
|
11053
11968
|
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
11054
11969
|
const minutes = Math.floor(totalSeconds / 60);
|
|
11055
11970
|
const seconds = totalSeconds % 60;
|
|
@@ -11058,68 +11973,6 @@ function formatCompactElapsed(ms) {
|
|
|
11058
11973
|
}
|
|
11059
11974
|
return `${totalSeconds}s`;
|
|
11060
11975
|
}
|
|
11061
|
-
function formatWebResearchResultMessage(result, reportText) {
|
|
11062
|
-
const text = reportText.trim();
|
|
11063
|
-
if (text.length > 0) {
|
|
11064
|
-
return `${text}
|
|
11065
|
-
`;
|
|
11066
|
-
}
|
|
11067
|
-
if (result.error) {
|
|
11068
|
-
return `${result.error}
|
|
11069
|
-
`;
|
|
11070
|
-
}
|
|
11071
|
-
return "";
|
|
11072
|
-
}
|
|
11073
|
-
async function writeWebResearchArtifact(result, reportText) {
|
|
11074
|
-
await mkdir2(dirname2(result.outputPath), { recursive: true });
|
|
11075
|
-
await writeFile2(
|
|
11076
|
-
result.outputPath,
|
|
11077
|
-
formatWebResearchArtifact(result, reportText),
|
|
11078
|
-
"utf-8"
|
|
11079
|
-
);
|
|
11080
|
-
}
|
|
11081
|
-
function formatWebResearchArtifact(result, reportText) {
|
|
11082
|
-
const providerLabel = PROVIDERS_BY_ID[result.provider]?.label ?? result.provider;
|
|
11083
|
-
const lines = [
|
|
11084
|
-
"# Web research report",
|
|
11085
|
-
"",
|
|
11086
|
-
"## Query",
|
|
11087
|
-
result.input,
|
|
11088
|
-
"",
|
|
11089
|
-
"## Provider",
|
|
11090
|
-
providerLabel,
|
|
11091
|
-
"",
|
|
11092
|
-
"## Status",
|
|
11093
|
-
result.status,
|
|
11094
|
-
"",
|
|
11095
|
-
"## Started",
|
|
11096
|
-
result.startedAt,
|
|
11097
|
-
"",
|
|
11098
|
-
"## Completed",
|
|
11099
|
-
result.completedAt,
|
|
11100
|
-
"",
|
|
11101
|
-
"## Elapsed",
|
|
11102
|
-
formatElapsed(result.elapsedMs)
|
|
11103
|
-
];
|
|
11104
|
-
if (typeof result.itemCount === "number") {
|
|
11105
|
-
lines.push("", "## Items", String(result.itemCount));
|
|
11106
|
-
}
|
|
11107
|
-
if (result.error) {
|
|
11108
|
-
lines.push("", "## Error", result.error);
|
|
11109
|
-
}
|
|
11110
|
-
if (reportText) {
|
|
11111
|
-
lines.push("", "## Report", reportText);
|
|
11112
|
-
}
|
|
11113
|
-
return `${lines.join("\n")}
|
|
11114
|
-
`;
|
|
11115
|
-
}
|
|
11116
|
-
function trackPendingResearchTask(task) {
|
|
11117
|
-
const tracked = task.catch(() => {
|
|
11118
|
-
}).finally(() => {
|
|
11119
|
-
pendingResearchTasks.delete(tracked);
|
|
11120
|
-
});
|
|
11121
|
-
pendingResearchTasks.add(tracked);
|
|
11122
|
-
}
|
|
11123
11976
|
async function executeBatchedContentsTool({
|
|
11124
11977
|
config,
|
|
11125
11978
|
provider,
|
|
@@ -11281,10 +12134,10 @@ function createToolProgressReporter(capability, providerId, progress) {
|
|
|
11281
12134
|
if (Date.now() - lastUpdateAt < RESEARCH_HEARTBEAT_MS) {
|
|
11282
12135
|
return;
|
|
11283
12136
|
}
|
|
11284
|
-
const
|
|
12137
|
+
const providerLabel2 = PROVIDERS_BY_ID[providerId]?.label ?? providerId;
|
|
11285
12138
|
const elapsed = formatElapsed(Date.now() - startedAt);
|
|
11286
12139
|
emit(
|
|
11287
|
-
`Researching via ${
|
|
12140
|
+
`Researching via ${providerLabel2} (${elapsed} elapsed)`,
|
|
11288
12141
|
buildProgressDisplay2(providerId, `Researching ${elapsed}`)
|
|
11289
12142
|
);
|
|
11290
12143
|
lastUpdateAt = Date.now();
|
|
@@ -11307,38 +12160,38 @@ function renderListCallHeader(toolName, items, theme, suffix, options = {}) {
|
|
|
11307
12160
|
invalidate() {
|
|
11308
12161
|
},
|
|
11309
12162
|
render(width) {
|
|
11310
|
-
const normalizedItems = items.map((item) =>
|
|
12163
|
+
const normalizedItems = items.map((item) => cleanSingleLine2(item)).filter((item) => item.length > 0);
|
|
11311
12164
|
const toolTitle = theme.fg("toolTitle", theme.bold(toolName));
|
|
11312
12165
|
const mutedSuffix = suffix ? theme.fg("muted", suffix) : "";
|
|
11313
12166
|
if (!options.forceMultiline && normalizedItems.length === 1) {
|
|
11314
12167
|
const singleItem = options.quoteSingleItem ? formatQuotedPreview(normalizedItems[0], 80) : truncateInline(normalizedItems[0], 120);
|
|
11315
12168
|
const inline = `${toolTitle} ${theme.fg("accent", singleItem)}${mutedSuffix}`;
|
|
11316
|
-
const line =
|
|
11317
|
-
return [line + " ".repeat(Math.max(0, width -
|
|
12169
|
+
const line = truncateToWidth2(inline.trimEnd(), width);
|
|
12170
|
+
return [line + " ".repeat(Math.max(0, width - visibleWidth2(line)))];
|
|
11318
12171
|
}
|
|
11319
12172
|
let header = toolTitle;
|
|
11320
12173
|
if (mutedSuffix) {
|
|
11321
12174
|
header += mutedSuffix;
|
|
11322
12175
|
}
|
|
11323
12176
|
const lines = [];
|
|
11324
|
-
const headerLine =
|
|
12177
|
+
const headerLine = truncateToWidth2(header.trimEnd(), width);
|
|
11325
12178
|
lines.push(
|
|
11326
|
-
headerLine + " ".repeat(Math.max(0, width -
|
|
12179
|
+
headerLine + " ".repeat(Math.max(0, width - visibleWidth2(headerLine)))
|
|
11327
12180
|
);
|
|
11328
12181
|
for (const item of normalizedItems) {
|
|
11329
12182
|
const itemLines = options.forceMultiline ? wrapTextWithAnsi(
|
|
11330
12183
|
theme.fg("accent", item),
|
|
11331
12184
|
Math.max(1, width - 2)
|
|
11332
12185
|
).map((line) => ` ${line}`) : [
|
|
11333
|
-
|
|
12186
|
+
truncateToWidth2(
|
|
11334
12187
|
` ${theme.fg("accent", truncateInline(item, 120))}`,
|
|
11335
12188
|
width
|
|
11336
12189
|
)
|
|
11337
12190
|
];
|
|
11338
12191
|
for (const itemLine of itemLines) {
|
|
11339
|
-
const line =
|
|
12192
|
+
const line = truncateToWidth2(itemLine, width);
|
|
11340
12193
|
lines.push(
|
|
11341
|
-
line + " ".repeat(Math.max(0, width -
|
|
12194
|
+
line + " ".repeat(Math.max(0, width - visibleWidth2(line)))
|
|
11342
12195
|
);
|
|
11343
12196
|
}
|
|
11344
12197
|
}
|
|
@@ -11433,7 +12286,8 @@ function renderWebResearchResultMessage(message, { expanded }, theme, symbols =
|
|
|
11433
12286
|
const text = typeof message.content === "string" ? message.content : extractTextContent(message.content);
|
|
11434
12287
|
const details = isWebResearchResult(message.details) ? message.details : void 0;
|
|
11435
12288
|
const isSuccess = details?.status === "completed";
|
|
11436
|
-
const
|
|
12289
|
+
const isCancelled = details?.status === "cancelled";
|
|
12290
|
+
const accent = isSuccess ? "success" : isCancelled ? "warning" : "error";
|
|
11437
12291
|
const box = new Box(1, 1, (value) => theme.bg("customMessageBg", value));
|
|
11438
12292
|
if (!expanded) {
|
|
11439
12293
|
const summary = details ? buildWebResearchResultSummaryLine(details, theme, symbols) : theme.fg(accent, "Web research update");
|
|
@@ -11443,10 +12297,42 @@ function renderWebResearchResultMessage(message, { expanded }, theme, symbols =
|
|
|
11443
12297
|
return box;
|
|
11444
12298
|
}
|
|
11445
12299
|
box.addChild(
|
|
11446
|
-
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
|
+
)
|
|
11447
12305
|
);
|
|
11448
12306
|
return box;
|
|
11449
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
|
+
}
|
|
11450
12336
|
function renderWebResearchRequestMarkdown(request) {
|
|
11451
12337
|
return [
|
|
11452
12338
|
"### Web research",
|
|
@@ -11472,7 +12358,7 @@ function renderWebResearchResultMarkdown(result) {
|
|
|
11472
12358
|
].join("\n");
|
|
11473
12359
|
}
|
|
11474
12360
|
function buildWebResearchResultSummaryLine(result, theme, symbols) {
|
|
11475
|
-
const
|
|
12361
|
+
const providerLabel2 = PROVIDERS_BY_ID[result.provider]?.label ?? result.provider;
|
|
11476
12362
|
if (result.status === "completed") {
|
|
11477
12363
|
return renderSuccessSummary(
|
|
11478
12364
|
`${formatSummaryElapsed(result.elapsedMs)} \xB7 ${basename(result.outputPath)}`,
|
|
@@ -11480,8 +12366,8 @@ function buildWebResearchResultSummaryLine(result, theme, symbols) {
|
|
|
11480
12366
|
symbols
|
|
11481
12367
|
);
|
|
11482
12368
|
}
|
|
11483
|
-
const statusText = result.status === "cancelled" ? `${
|
|
11484
|
-
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)}` : "";
|
|
11485
12371
|
return renderFailureSummary(`${statusText}${errorSuffix}`, theme, symbols);
|
|
11486
12372
|
}
|
|
11487
12373
|
function isWebResearchRequest(details) {
|
|
@@ -11580,7 +12466,7 @@ function renderEntryList(width, theme, entries, selection) {
|
|
|
11580
12466
|
const paddedLabel = entry.label.padEnd(labelWidth, " ");
|
|
11581
12467
|
const label = selected ? theme.fg("accent", paddedLabel) : paddedLabel;
|
|
11582
12468
|
const value = selected ? theme.fg("accent", entry.currentValue) : theme.fg("muted", entry.currentValue);
|
|
11583
|
-
return
|
|
12469
|
+
return truncateToWidth2(`${prefix}${label} ${value}`, width);
|
|
11584
12470
|
});
|
|
11585
12471
|
}
|
|
11586
12472
|
function renderSelectedEntryDescription(width, theme, entry) {
|
|
@@ -11588,7 +12474,7 @@ function renderSelectedEntryDescription(width, theme, entry) {
|
|
|
11588
12474
|
return [];
|
|
11589
12475
|
}
|
|
11590
12476
|
return wrapTextWithAnsi(entry.description, Math.max(10, width - 2)).map(
|
|
11591
|
-
(line) =>
|
|
12477
|
+
(line) => truncateToWidth2(theme.fg("dim", line), width)
|
|
11592
12478
|
);
|
|
11593
12479
|
}
|
|
11594
12480
|
function formatProviderCapabilityChecks(providerId, theme) {
|
|
@@ -11769,7 +12655,7 @@ var WebProvidersSettingsView = class {
|
|
|
11769
12655
|
}
|
|
11770
12656
|
lines.push("");
|
|
11771
12657
|
lines.push(
|
|
11772
|
-
|
|
12658
|
+
truncateToWidth2(
|
|
11773
12659
|
this.theme.fg(
|
|
11774
12660
|
"dim",
|
|
11775
12661
|
"\u2191\u2193 move \xB7 Tab/Shift+Tab switch section \xB7 Enter edit/open \xB7 Esc close"
|
|
@@ -11788,7 +12674,7 @@ var WebProvidersSettingsView = class {
|
|
|
11788
12674
|
this.tui.requestRender();
|
|
11789
12675
|
return;
|
|
11790
12676
|
}
|
|
11791
|
-
const kb =
|
|
12677
|
+
const kb = getKeybindings2();
|
|
11792
12678
|
const entries = this.getActiveSectionEntries();
|
|
11793
12679
|
if (kb.matches(data, "tui.select.up")) {
|
|
11794
12680
|
if (entries.length > 0) {
|
|
@@ -11798,9 +12684,9 @@ var WebProvidersSettingsView = class {
|
|
|
11798
12684
|
if (entries.length > 0) {
|
|
11799
12685
|
this.moveSelection(1);
|
|
11800
12686
|
}
|
|
11801
|
-
} else if (
|
|
12687
|
+
} else if (matchesKey2(data, Key2.tab)) {
|
|
11802
12688
|
this.moveSection(1);
|
|
11803
|
-
} else if (
|
|
12689
|
+
} else if (matchesKey2(data, Key2.shift("tab"))) {
|
|
11804
12690
|
this.moveSection(-1);
|
|
11805
12691
|
} else if (kb.matches(data, "tui.select.confirm") || data === " ") {
|
|
11806
12692
|
void this.activateCurrentEntry();
|
|
@@ -11933,14 +12819,14 @@ var WebProvidersSettingsView = class {
|
|
|
11933
12819
|
Math.max(20, Math.floor(width * 0.45))
|
|
11934
12820
|
);
|
|
11935
12821
|
const lines = [
|
|
11936
|
-
|
|
12822
|
+
truncateToWidth2(
|
|
11937
12823
|
this.activeSection === section ? this.theme.fg("accent", this.theme.bold(title)) : this.theme.bold(title),
|
|
11938
12824
|
width
|
|
11939
12825
|
)
|
|
11940
12826
|
];
|
|
11941
12827
|
if (section === "provider") {
|
|
11942
12828
|
lines.push(
|
|
11943
|
-
|
|
12829
|
+
truncateToWidth2(
|
|
11944
12830
|
this.theme.fg(
|
|
11945
12831
|
"dim",
|
|
11946
12832
|
` ${"Provider".padEnd(labelWidth, " ")} S C A R Status`
|
|
@@ -11955,15 +12841,15 @@ var WebProvidersSettingsView = class {
|
|
|
11955
12841
|
const paddedLabel = entry.label.padEnd(labelWidth, " ");
|
|
11956
12842
|
const label = selected ? this.theme.fg("accent", paddedLabel) : paddedLabel;
|
|
11957
12843
|
if (entry.currentValue.trim().length === 0) {
|
|
11958
|
-
lines.push(
|
|
12844
|
+
lines.push(truncateToWidth2(`${prefix}${label}`, width));
|
|
11959
12845
|
continue;
|
|
11960
12846
|
}
|
|
11961
12847
|
const value = entry.preserveValueStyle ? entry.currentValue : selected ? this.theme.fg("accent", entry.currentValue) : this.theme.fg("muted", entry.currentValue);
|
|
11962
|
-
lines.push(
|
|
12848
|
+
lines.push(truncateToWidth2(`${prefix}${label} ${value}`, width));
|
|
11963
12849
|
}
|
|
11964
12850
|
if (section === "provider") {
|
|
11965
12851
|
lines.push(
|
|
11966
|
-
|
|
12852
|
+
truncateToWidth2(
|
|
11967
12853
|
this.theme.fg("dim", " S=Search C=Contents A=Answer R=Research"),
|
|
11968
12854
|
width
|
|
11969
12855
|
)
|
|
@@ -12098,7 +12984,7 @@ var ToolSettingsSubmenu = class {
|
|
|
12098
12984
|
}
|
|
12099
12985
|
const entries = this.getEntries();
|
|
12100
12986
|
const lines = [
|
|
12101
|
-
|
|
12987
|
+
truncateToWidth2(
|
|
12102
12988
|
this.theme.fg("accent", TOOL_INFO[this.toolId].label),
|
|
12103
12989
|
width
|
|
12104
12990
|
),
|
|
@@ -12114,7 +13000,7 @@ var ToolSettingsSubmenu = class {
|
|
|
12114
13000
|
}
|
|
12115
13001
|
lines.push("");
|
|
12116
13002
|
lines.push(
|
|
12117
|
-
|
|
13003
|
+
truncateToWidth2(
|
|
12118
13004
|
this.theme.fg("dim", "\u2191\u2193 move \xB7 Enter edit/toggle \xB7 Esc back"),
|
|
12119
13005
|
width
|
|
12120
13006
|
)
|
|
@@ -12130,7 +13016,7 @@ var ToolSettingsSubmenu = class {
|
|
|
12130
13016
|
this.tui.requestRender();
|
|
12131
13017
|
return;
|
|
12132
13018
|
}
|
|
12133
|
-
const kb =
|
|
13019
|
+
const kb = getKeybindings2();
|
|
12134
13020
|
const entries = this.getEntries();
|
|
12135
13021
|
if (kb.matches(data, "tui.select.up")) {
|
|
12136
13022
|
if (this.selection > 0) {
|
|
@@ -12333,7 +13219,7 @@ var ProviderSettingsSubmenu = class {
|
|
|
12333
13219
|
const providerConfig = this.getProviderConfig();
|
|
12334
13220
|
const entries = this.getEntries();
|
|
12335
13221
|
const lines = [
|
|
12336
|
-
|
|
13222
|
+
truncateToWidth2(this.theme.fg("accent", provider.label), width),
|
|
12337
13223
|
"",
|
|
12338
13224
|
...renderEntryList(width, this.theme, entries, this.selection)
|
|
12339
13225
|
];
|
|
@@ -12350,10 +13236,10 @@ var ProviderSettingsSubmenu = class {
|
|
|
12350
13236
|
);
|
|
12351
13237
|
lines.push("");
|
|
12352
13238
|
lines.push(
|
|
12353
|
-
|
|
13239
|
+
truncateToWidth2(this.theme.fg("dim", `Status: ${status}`), width)
|
|
12354
13240
|
);
|
|
12355
13241
|
lines.push(
|
|
12356
|
-
|
|
13242
|
+
truncateToWidth2(
|
|
12357
13243
|
this.theme.fg("dim", "\u2191\u2193 move \xB7 Enter edit/toggle \xB7 Esc back"),
|
|
12358
13244
|
width
|
|
12359
13245
|
)
|
|
@@ -12369,7 +13255,7 @@ var ProviderSettingsSubmenu = class {
|
|
|
12369
13255
|
this.tui.requestRender();
|
|
12370
13256
|
return;
|
|
12371
13257
|
}
|
|
12372
|
-
const kb =
|
|
13258
|
+
const kb = getKeybindings2();
|
|
12373
13259
|
const entries = this.getEntries();
|
|
12374
13260
|
if (kb.matches(data, "tui.select.up")) {
|
|
12375
13261
|
if (this.selection > 0) {
|
|
@@ -12504,12 +13390,12 @@ var TextValueSubmenu = class {
|
|
|
12504
13390
|
editor;
|
|
12505
13391
|
render(width) {
|
|
12506
13392
|
return [
|
|
12507
|
-
|
|
13393
|
+
truncateToWidth2(this.theme.fg("accent", this.title), width),
|
|
12508
13394
|
"",
|
|
12509
13395
|
...this.editor.render(width),
|
|
12510
13396
|
"",
|
|
12511
|
-
|
|
12512
|
-
|
|
13397
|
+
truncateToWidth2(this.theme.fg("dim", this.help), width),
|
|
13398
|
+
truncateToWidth2(
|
|
12513
13399
|
this.theme.fg(
|
|
12514
13400
|
"dim",
|
|
12515
13401
|
"Enter to save \xB7 Shift+Enter for newline \xB7 Esc to cancel"
|
|
@@ -12522,7 +13408,7 @@ var TextValueSubmenu = class {
|
|
|
12522
13408
|
this.editor.invalidate();
|
|
12523
13409
|
}
|
|
12524
13410
|
handleInput(data) {
|
|
12525
|
-
if (
|
|
13411
|
+
if (matchesKey2(data, Key2.escape)) {
|
|
12526
13412
|
this.done(void 0);
|
|
12527
13413
|
return;
|
|
12528
13414
|
}
|
|
@@ -12646,7 +13532,7 @@ function getSearchQueriesForDisplay(queries) {
|
|
|
12646
13532
|
function getAnswerQueriesForDisplay(queries) {
|
|
12647
13533
|
return getSearchQueriesForDisplay(queries);
|
|
12648
13534
|
}
|
|
12649
|
-
function createBatchCompletionReporter(verb, providerId,
|
|
13535
|
+
function createBatchCompletionReporter(verb, providerId, providerLabel2, total, report) {
|
|
12650
13536
|
if (!report) {
|
|
12651
13537
|
return {
|
|
12652
13538
|
start: () => {
|
|
@@ -12660,7 +13546,7 @@ function createBatchCompletionReporter(verb, providerId, providerLabel, total, r
|
|
|
12660
13546
|
let completedCount = 0;
|
|
12661
13547
|
let failedCount = 0;
|
|
12662
13548
|
const emit = () => {
|
|
12663
|
-
let message = `${verb} via ${
|
|
13549
|
+
let message = `${verb} via ${providerLabel2}: ${completedCount}/${total} completed`;
|
|
12664
13550
|
if (failedCount > 0) {
|
|
12665
13551
|
message += `, ${failedCount} failed`;
|
|
12666
13552
|
}
|
|
@@ -12712,8 +13598,8 @@ function renderMarkdownBlock(text) {
|
|
|
12712
13598
|
if (!text) {
|
|
12713
13599
|
return new Text("", 0, 0);
|
|
12714
13600
|
}
|
|
12715
|
-
return new
|
|
12716
|
-
${text}`, 0, 0,
|
|
13601
|
+
return new Markdown2(`
|
|
13602
|
+
${text}`, 0, 0, getMarkdownTheme2());
|
|
12717
13603
|
}
|
|
12718
13604
|
function renderBlockText(text, theme, color) {
|
|
12719
13605
|
if (!text) {
|
|
@@ -12747,12 +13633,12 @@ function prefixWithSymbol(text, symbol) {
|
|
|
12747
13633
|
}
|
|
12748
13634
|
function renderToolProgress(display, fallbackText, theme) {
|
|
12749
13635
|
const progress = display?.progress;
|
|
12750
|
-
const
|
|
12751
|
-
if (!progress || !
|
|
13636
|
+
const providerLabel2 = display?.provider?.label;
|
|
13637
|
+
if (!progress || !providerLabel2) {
|
|
12752
13638
|
return renderSimpleText(fallbackText ?? "Working\u2026", theme, "warning");
|
|
12753
13639
|
}
|
|
12754
13640
|
return new Text(
|
|
12755
|
-
`${theme.fg("warning", progress.action)} ${theme.fg("muted", `via ${
|
|
13641
|
+
`${theme.fg("warning", progress.action)} ${theme.fg("muted", `via ${providerLabel2}`)}`,
|
|
12756
13642
|
0,
|
|
12757
13643
|
0
|
|
12758
13644
|
);
|
|
@@ -12804,15 +13690,15 @@ function buildFailureSummary({
|
|
|
12804
13690
|
fallback
|
|
12805
13691
|
}) {
|
|
12806
13692
|
const detail = stripTrailingSentencePunctuation(getFirstLine2(text) ?? "");
|
|
12807
|
-
const
|
|
12808
|
-
if (!
|
|
13693
|
+
const providerLabel2 = details?.provider !== void 0 ? PROVIDERS_BY_ID[details.provider]?.label ?? details.provider : void 0;
|
|
13694
|
+
if (!providerLabel2) {
|
|
12809
13695
|
return detail || fallback;
|
|
12810
13696
|
}
|
|
12811
|
-
return formatProviderCapabilityFailure(
|
|
13697
|
+
return formatProviderCapabilityFailure(providerLabel2, capability, detail);
|
|
12812
13698
|
}
|
|
12813
|
-
function formatProviderCapabilityFailure(
|
|
13699
|
+
function formatProviderCapabilityFailure(providerLabel2, capability, detail) {
|
|
12814
13700
|
const action = getFailureAction(capability);
|
|
12815
|
-
const base2 = `${
|
|
13701
|
+
const base2 = `${providerLabel2} ${action} failed`;
|
|
12816
13702
|
if (!detail || detail === base2) {
|
|
12817
13703
|
return base2;
|
|
12818
13704
|
}
|
|
@@ -12820,14 +13706,14 @@ function formatProviderCapabilityFailure(providerLabel, capability, detail) {
|
|
|
12820
13706
|
return detail;
|
|
12821
13707
|
}
|
|
12822
13708
|
const normalizedDetail = normalizeProviderFailureDetail(
|
|
12823
|
-
|
|
13709
|
+
providerLabel2,
|
|
12824
13710
|
detail
|
|
12825
13711
|
);
|
|
12826
13712
|
return `${base2}: ${normalizedDetail}`;
|
|
12827
13713
|
}
|
|
12828
|
-
function normalizeProviderFailureDetail(
|
|
13714
|
+
function normalizeProviderFailureDetail(providerLabel2, detail) {
|
|
12829
13715
|
const normalized = stripTrailingSentencePunctuation(detail);
|
|
12830
|
-
const providerPrefix = `${
|
|
13716
|
+
const providerPrefix = `${providerLabel2}:`;
|
|
12831
13717
|
return normalized.toLowerCase().startsWith(providerPrefix.toLowerCase()) ? normalized.slice(providerPrefix.length).trim() : normalized;
|
|
12832
13718
|
}
|
|
12833
13719
|
function getFailureAction(capability) {
|
|
@@ -12876,7 +13762,7 @@ function getFirstLine2(text) {
|
|
|
12876
13762
|
}
|
|
12877
13763
|
function getExpandHint() {
|
|
12878
13764
|
try {
|
|
12879
|
-
const keys =
|
|
13765
|
+
const keys = getKeybindings2().getKeys("app.tools.expand");
|
|
12880
13766
|
if (keys.length > 0) {
|
|
12881
13767
|
return `${keys.join("/")} to expand`;
|
|
12882
13768
|
}
|
|
@@ -12884,11 +13770,11 @@ function getExpandHint() {
|
|
|
12884
13770
|
}
|
|
12885
13771
|
return "ctrl+o to expand";
|
|
12886
13772
|
}
|
|
12887
|
-
function
|
|
13773
|
+
function cleanSingleLine2(text) {
|
|
12888
13774
|
return text.replace(/\s+/g, " ").trim();
|
|
12889
13775
|
}
|
|
12890
13776
|
function formatQuotedPreview(text, maxLength = 80) {
|
|
12891
|
-
return `"${truncateInline(
|
|
13777
|
+
return `"${truncateInline(cleanSingleLine2(text), maxLength)}"`;
|
|
12892
13778
|
}
|
|
12893
13779
|
function formatSearchResponses(outcomes, prefetch) {
|
|
12894
13780
|
const body = outcomes.map(
|
|
@@ -12914,10 +13800,10 @@ function formatSearchOutcomeSection(outcome, index, total) {
|
|
|
12914
13800
|
${body}`;
|
|
12915
13801
|
}
|
|
12916
13802
|
function formatSearchHeading(query2) {
|
|
12917
|
-
return `"${escapeMarkdownText(
|
|
13803
|
+
return `"${escapeMarkdownText(cleanSingleLine2(query2))}"`;
|
|
12918
13804
|
}
|
|
12919
13805
|
function formatAnswerHeading(query2) {
|
|
12920
|
-
return `"${escapeMarkdownText(
|
|
13806
|
+
return `"${escapeMarkdownText(cleanSingleLine2(query2))}"`;
|
|
12921
13807
|
}
|
|
12922
13808
|
function collectSearchResultUrls(outcomes) {
|
|
12923
13809
|
return outcomes.flatMap(
|
|
@@ -12933,7 +13819,7 @@ function formatSearchResponseMarkdown(response) {
|
|
|
12933
13819
|
`${index + 1}. ${formatMarkdownLink(result.title, result.url)}`
|
|
12934
13820
|
];
|
|
12935
13821
|
if (result.snippet) {
|
|
12936
|
-
lines.push(` ${escapeMarkdownText(
|
|
13822
|
+
lines.push(` ${escapeMarkdownText(cleanSingleLine2(result.snippet))}`);
|
|
12937
13823
|
}
|
|
12938
13824
|
return lines.join("\n");
|
|
12939
13825
|
}).join("\n\n");
|
|
@@ -12942,7 +13828,7 @@ function formatMarkdownLink(label, url2) {
|
|
|
12942
13828
|
return `[${escapeMarkdownLinkLabel(label)}](<${url2}>)`;
|
|
12943
13829
|
}
|
|
12944
13830
|
function escapeMarkdownLinkLabel(text) {
|
|
12945
|
-
return
|
|
13831
|
+
return cleanSingleLine2(text).replaceAll("\\", "\\\\").replaceAll("]", "\\]");
|
|
12946
13832
|
}
|
|
12947
13833
|
function escapeMarkdownText(text) {
|
|
12948
13834
|
return text.replaceAll("\\", "\\\\").replaceAll("*", "\\*").replaceAll("_", "\\_").replaceAll("`", "\\`").replaceAll("#", "\\#").replaceAll("[", "\\[").replaceAll("]", "\\]");
|
|
@@ -12963,10 +13849,10 @@ async function truncateAndSaveWithMetadata(text, prefix) {
|
|
|
12963
13849
|
truncated: false
|
|
12964
13850
|
};
|
|
12965
13851
|
}
|
|
12966
|
-
const dir =
|
|
12967
|
-
await
|
|
12968
|
-
const fullPath =
|
|
12969
|
-
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");
|
|
12970
13856
|
return {
|
|
12971
13857
|
text: truncation.content + `
|
|
12972
13858
|
|
|
@@ -13073,6 +13959,12 @@ var __test__ = {
|
|
|
13073
13959
|
}),
|
|
13074
13960
|
extractTextContent,
|
|
13075
13961
|
formatWebResearchResultMessage,
|
|
13962
|
+
getActiveWebResearchRequests,
|
|
13963
|
+
getWebResearchTaskSnapshots,
|
|
13964
|
+
cancelWebResearchTask,
|
|
13965
|
+
loadWebResearchHistory,
|
|
13966
|
+
loadWebResearchPreview,
|
|
13967
|
+
loadWebResearchReport,
|
|
13076
13968
|
getAvailableManagedToolNames,
|
|
13077
13969
|
getReadyCompatibleProvidersForTool,
|
|
13078
13970
|
getEnabledCompatibleProvidersForTool: getReadyCompatibleProvidersForTool,
|
|
@@ -13090,9 +13982,10 @@ var __test__ = {
|
|
|
13090
13982
|
renderProviderToolResult,
|
|
13091
13983
|
renderWebResearchDispatchResult,
|
|
13092
13984
|
renderWebResearchResultMessage,
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13985
|
+
renderWebResearchReportMessage,
|
|
13986
|
+
formatWebResearchReportMessage,
|
|
13987
|
+
buildWebResearchWidgetLines,
|
|
13988
|
+
waitForPendingResearchTasks,
|
|
13096
13989
|
formatSearchResponses,
|
|
13097
13990
|
formatAnswerResponses
|
|
13098
13991
|
};
|