pi-web-search 1.3.1 → 1.5.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 +9 -4
- package/package.json +6 -2
- package/src/api.ts +82 -23
- package/src/index.ts +24 -2
- package/src/utils.ts +3 -5
- package/src/web_search.ts +10 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-web-search
|
|
2
2
|
|
|
3
|
-
Provider-native web search for [pi](https://
|
|
3
|
+
Provider-native web search for [pi](https://pi.dev) with Gemini + URL Context, xAI Grok, OpenAI Responses variants, and Anthropic.
|
|
4
4
|
|
|
5
5
|
## Tools
|
|
6
6
|
|
|
@@ -11,11 +11,16 @@ Search the web using your currently selected model. Automatically picks the righ
|
|
|
11
11
|
| Provider | API |
|
|
12
12
|
|---|---|
|
|
13
13
|
| Google Gemini | Grounding with Google Search |
|
|
14
|
+
| xAI Grok | Responses API `web_search` |
|
|
14
15
|
| OpenAI | Responses API web search |
|
|
15
|
-
| OpenAI
|
|
16
|
+
| Azure OpenAI | Responses API web search (`azure-openai-responses`) |
|
|
17
|
+
| OpenAI Codex | Codex Responses API web search (`openai-codex-responses`) |
|
|
18
|
+
| GitHub Copilot | OpenAI Responses API web search via Copilot credentials |
|
|
16
19
|
| Anthropic | Messages API web search |
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
GitHub Copilot OpenAI Responses models are supported, including Business and Enterprise seats whose API endpoint is resolved from their authenticated Copilot credentials. This includes models such as `gpt-5.6-sol`.
|
|
22
|
+
|
|
23
|
+
Supports passing up to 20 additional URLs to analyze alongside the query. Successful `web_search` results are collapsed by default in pi; expand the tool call to inspect the full answer and source details.
|
|
19
24
|
|
|
20
25
|
### `url_context`
|
|
21
26
|
|
|
@@ -33,7 +38,7 @@ pi install npm:pi-web-search
|
|
|
33
38
|
|
|
34
39
|
No extra config needed. Select a supported current model in pi and the tools auto-detect the matching provider API.
|
|
35
40
|
|
|
36
|
-
`web_search` will not scan configured models and pick one automatically when the current model does not support native search. To use a dedicated search model, opt in explicitly with
|
|
41
|
+
`web_search` will not scan configured models and pick one automatically when the current model does not support native search. To use a dedicated search model, opt in explicitly with `web-search.json` in pi's agent directory (by default `~/.pi/agent/`; respects `PI_CODING_AGENT_DIR`):
|
|
37
42
|
|
|
38
43
|
```json
|
|
39
44
|
{
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-search",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Provider-native web search for pi
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Provider-native web search for pi: Gemini + URL Context, xAI Grok, OpenAI Responses (Azure/Codex/Copilot), and Anthropic",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"keywords": [
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"web-search",
|
|
12
12
|
"gemini",
|
|
13
13
|
"google",
|
|
14
|
+
"xai",
|
|
15
|
+
"grok",
|
|
14
16
|
"openai",
|
|
15
17
|
"anthropic",
|
|
16
18
|
"url-context",
|
|
@@ -39,6 +41,7 @@
|
|
|
39
41
|
"devDependencies": {
|
|
40
42
|
"@earendil-works/pi-ai": "^0.80.3",
|
|
41
43
|
"@earendil-works/pi-coding-agent": "^0.80.3",
|
|
44
|
+
"@earendil-works/pi-tui": "^0.80.3",
|
|
42
45
|
"@types/node": "^25.9.1",
|
|
43
46
|
"typebox": "^1.1.38",
|
|
44
47
|
"typescript": "^6.0.3"
|
|
@@ -51,6 +54,7 @@
|
|
|
51
54
|
"peerDependencies": {
|
|
52
55
|
"@earendil-works/pi-ai": ">=0.80.3",
|
|
53
56
|
"@earendil-works/pi-coding-agent": ">=0.80.3",
|
|
57
|
+
"@earendil-works/pi-tui": "*",
|
|
54
58
|
"typebox": "*"
|
|
55
59
|
}
|
|
56
60
|
}
|
package/src/api.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { TextEncoder, TextDecoder } from "util";
|
|
|
5
5
|
|
|
6
6
|
// --- Provider Configuration ---
|
|
7
7
|
|
|
8
|
-
type ProviderKind = "google" | "openai" | "anthropic" | "unsupported";
|
|
8
|
+
type ProviderKind = "google" | "openai" | "xai" | "anthropic" | "unsupported";
|
|
9
9
|
|
|
10
10
|
type GoogleRequestBuilder = (model: Model<Api>, body: any) => { url: string; headers: Record<string, string>; body: any };
|
|
11
11
|
|
|
@@ -34,7 +34,12 @@ const GOOGLE_PROVIDERS: Record<string, ProviderConfig> = {
|
|
|
34
34
|
|
|
35
35
|
export function getProviderKind(model: Model<Api>): ProviderKind {
|
|
36
36
|
if (GOOGLE_PROVIDERS[model.provider] || GOOGLE_PROVIDERS[model.api]) return "google";
|
|
37
|
-
if (model.
|
|
37
|
+
if (model.provider === "xai" && model.api === "openai-responses") return "xai";
|
|
38
|
+
if (
|
|
39
|
+
model.api === "openai-responses"
|
|
40
|
+
|| model.api === "azure-openai-responses"
|
|
41
|
+
|| model.api === "openai-codex-responses"
|
|
42
|
+
) return "openai";
|
|
38
43
|
if (model.api === "anthropic-messages") return "anthropic";
|
|
39
44
|
return "unsupported";
|
|
40
45
|
}
|
|
@@ -49,7 +54,7 @@ export function getConfig(model: Model<Api>): ProviderConfig {
|
|
|
49
54
|
// --- Auth Compatibility Layer ---
|
|
50
55
|
|
|
51
56
|
type ResolvedAuth =
|
|
52
|
-
| { ok: true; apiKey?: string; headers?: Record<string, string>; }
|
|
57
|
+
| { ok: true; apiKey?: string; headers?: Record<string, string>; baseUrl?: string; }
|
|
53
58
|
| { ok: false; error: string; };
|
|
54
59
|
|
|
55
60
|
function getEnvAuth(model: Model<Api>): Extract<ResolvedAuth, { ok: true }> | undefined {
|
|
@@ -240,8 +245,47 @@ function isOpenAICodexModel(model: Model<Api>): boolean {
|
|
|
240
245
|
return model.api === "openai-codex-responses";
|
|
241
246
|
}
|
|
242
247
|
|
|
243
|
-
function
|
|
244
|
-
|
|
248
|
+
function resolveGitHubCopilotBaseUrl(
|
|
249
|
+
model: Model<Api>,
|
|
250
|
+
auth: Extract<ResolvedAuth, { ok: true }>,
|
|
251
|
+
): string {
|
|
252
|
+
if (model.provider !== "github-copilot") return model.baseUrl;
|
|
253
|
+
|
|
254
|
+
// Modern pi versions expose the credential-specific Copilot endpoint
|
|
255
|
+
// resolved by the provider. Prefer it so GitHub Enterprise Server and any
|
|
256
|
+
// future provider-owned routing continue to work without token parsing here.
|
|
257
|
+
if (typeof auth.baseUrl === "string" && auth.baseUrl.trim()) {
|
|
258
|
+
return auth.baseUrl;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Compatibility fallback for older pi versions whose extension auth API
|
|
262
|
+
// returned the Copilot token but not its resolved base URL.
|
|
263
|
+
if (!auth.apiKey) return model.baseUrl;
|
|
264
|
+
const proxyEndpoints = auth.apiKey
|
|
265
|
+
.split(";")
|
|
266
|
+
.filter((field) => field.startsWith("proxy-ep="))
|
|
267
|
+
.map((field) => field.slice("proxy-ep=".length));
|
|
268
|
+
if (proxyEndpoints.length !== 1) return model.baseUrl;
|
|
269
|
+
|
|
270
|
+
const proxyHost = proxyEndpoints[0].toLowerCase();
|
|
271
|
+
const labels = proxyHost.split(".");
|
|
272
|
+
const isValidLabel = (label: string) =>
|
|
273
|
+
label.length > 0
|
|
274
|
+
&& label.length <= 63
|
|
275
|
+
&& /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(label);
|
|
276
|
+
const isCopilotProxyHost = proxyHost.length <= 253
|
|
277
|
+
&& labels.length >= 4
|
|
278
|
+
&& labels[0] === "proxy"
|
|
279
|
+
&& labels.at(-2) === "githubcopilot"
|
|
280
|
+
&& labels.at(-1) === "com"
|
|
281
|
+
&& labels.every(isValidLabel);
|
|
282
|
+
if (!isCopilotProxyHost) return model.baseUrl;
|
|
283
|
+
|
|
284
|
+
return `https://api.${labels.slice(1).join(".")}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function resolveOpenAIResponsesUrl(model: Model<Api>, baseUrl = model.baseUrl): string {
|
|
288
|
+
const base = trimTrailingSlash(baseUrl);
|
|
245
289
|
if (!isOpenAICodexModel(model)) return `${base}/responses`;
|
|
246
290
|
if (base.endsWith("/codex/responses")) return base;
|
|
247
291
|
if (base.endsWith("/codex")) return `${base}/responses`;
|
|
@@ -421,6 +465,14 @@ async function resolveGoogleGroundingRedirectUrls(searchResults: SearchResultDet
|
|
|
421
465
|
}
|
|
422
466
|
}
|
|
423
467
|
|
|
468
|
+
function preserveInlineCitations(text: string, citations: Array<{ endIndex?: number; title: string; url: string }>): { text: string; sources: Source[] } {
|
|
469
|
+
const sources: Source[] = [];
|
|
470
|
+
for (const citation of citations) {
|
|
471
|
+
if (citation.url) pushUniqueSource(sources, { title: citation.title, url: citation.url });
|
|
472
|
+
}
|
|
473
|
+
return { text, sources };
|
|
474
|
+
}
|
|
475
|
+
|
|
424
476
|
function applyIndexCitations(text: string, citations: Array<{ endIndex?: number; title: string; url: string }>): { text: string; sources: Source[] } {
|
|
425
477
|
const sources: Source[] = [];
|
|
426
478
|
const insertions = citations
|
|
@@ -639,21 +691,25 @@ async function callOpenAIStream(
|
|
|
639
691
|
}
|
|
640
692
|
const requestHeaders = Object.fromEntries(headers.entries());
|
|
641
693
|
|
|
694
|
+
const isXai = getProviderKind(model) === "xai";
|
|
695
|
+
const searchProvider: ProviderKind = isXai ? "xai" : "openai";
|
|
696
|
+
const providerSourceName = isXai ? "xai" : "openai";
|
|
642
697
|
const requestBody: any = {
|
|
643
698
|
model: model.id,
|
|
644
699
|
input: isCodex
|
|
645
700
|
? [{ role: "user", content: [{ type: "input_text", text: prompt }] }]
|
|
646
|
-
:
|
|
701
|
+
: isXai
|
|
702
|
+
? [{ role: "user", content: prompt }]
|
|
703
|
+
: prompt,
|
|
647
704
|
tools: [{ type: "web_search" }],
|
|
648
|
-
|
|
649
|
-
? ["web_search_call.action.sources"]
|
|
650
|
-
: ["web_search_call.action.sources", "web_search_call.results"],
|
|
705
|
+
...(isCodex || isXai
|
|
706
|
+
? { include: ["web_search_call.action.sources"] }
|
|
707
|
+
: { include: ["web_search_call.action.sources", "web_search_call.results"] }),
|
|
651
708
|
stream: true,
|
|
652
709
|
store: false,
|
|
653
710
|
};
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
}
|
|
711
|
+
// Let the provider choose its supported reasoning default. Some reasoning
|
|
712
|
+
// models reject "none", and supported effort levels vary between models.
|
|
657
713
|
if (isCodex) {
|
|
658
714
|
requestBody.instructions = "Answer the user's request using web search when needed.";
|
|
659
715
|
requestBody.text = { verbosity: "low" };
|
|
@@ -661,7 +717,8 @@ async function callOpenAIStream(
|
|
|
661
717
|
requestBody.parallel_tool_calls = true;
|
|
662
718
|
}
|
|
663
719
|
|
|
664
|
-
const
|
|
720
|
+
const baseUrl = resolveGitHubCopilotBaseUrl(model, auth);
|
|
721
|
+
const response = await fetch(resolveOpenAIResponsesUrl(model, baseUrl), {
|
|
665
722
|
method: "POST",
|
|
666
723
|
headers: requestHeaders,
|
|
667
724
|
body: JSON.stringify(requestBody),
|
|
@@ -669,7 +726,7 @@ async function callOpenAIStream(
|
|
|
669
726
|
});
|
|
670
727
|
|
|
671
728
|
if (!response.ok) {
|
|
672
|
-
throw new Error(
|
|
729
|
+
throw new Error(`${isXai ? "xAI" : "OpenAI"} API error (${response.status}): ${await response.text()}`);
|
|
673
730
|
}
|
|
674
731
|
|
|
675
732
|
let accumulatedText = "";
|
|
@@ -691,7 +748,7 @@ async function callOpenAIStream(
|
|
|
691
748
|
const action = item.action || {};
|
|
692
749
|
const call: NativeSearchCallDetail = {
|
|
693
750
|
id: item.id,
|
|
694
|
-
provider:
|
|
751
|
+
provider: searchProvider,
|
|
695
752
|
status: item.status,
|
|
696
753
|
actionType: action.type,
|
|
697
754
|
raw: item,
|
|
@@ -711,7 +768,7 @@ async function callOpenAIStream(
|
|
|
711
768
|
pushUniqueSearchResult(searchResults, {
|
|
712
769
|
title: source.title || source.display_name || source.name || titleFromUrl(source.url),
|
|
713
770
|
url: source.url,
|
|
714
|
-
source:
|
|
771
|
+
source: `${providerSourceName}.web_search_call.action.sources`,
|
|
715
772
|
type: source.type || "url",
|
|
716
773
|
raw: source,
|
|
717
774
|
});
|
|
@@ -722,7 +779,7 @@ async function callOpenAIStream(
|
|
|
722
779
|
pushUniqueSearchResult(searchResults, {
|
|
723
780
|
title: titleFromUrl(action.url),
|
|
724
781
|
url: action.url,
|
|
725
|
-
source:
|
|
782
|
+
source: `${providerSourceName}.web_search_call.action.${action.type}`,
|
|
726
783
|
type: action.type,
|
|
727
784
|
raw: action,
|
|
728
785
|
});
|
|
@@ -772,21 +829,23 @@ async function callOpenAIStream(
|
|
|
772
829
|
pushNativeSearchEvent(nativeSearchEvents, event.type);
|
|
773
830
|
const call = nativeSearchCalls.find((item) => item.id === event.item_id);
|
|
774
831
|
if (call) call.status = event.type.replace("response.web_search_call.", "");
|
|
775
|
-
else nativeSearchCalls.push({ id: event.item_id, provider:
|
|
832
|
+
else nativeSearchCalls.push({ id: event.item_id, provider: searchProvider, status: event.type.replace("response.web_search_call.", ""), raw: event });
|
|
776
833
|
if (event.type === "response.web_search_call.searching") {
|
|
777
834
|
onUpdate?.({
|
|
778
|
-
content: [{ type: "text", text: accumulatedText ||
|
|
835
|
+
content: [{ type: "text", text: accumulatedText || `Searching the web with ${isXai ? "xAI" : "OpenAI"}...` }],
|
|
779
836
|
details: { streaming: true, searching: true }
|
|
780
837
|
});
|
|
781
838
|
}
|
|
782
839
|
}
|
|
783
840
|
});
|
|
784
841
|
|
|
785
|
-
const cited =
|
|
842
|
+
const cited = isXai
|
|
843
|
+
? preserveInlineCitations(accumulatedText || "No answer available.", citations)
|
|
844
|
+
: applyIndexCitations(accumulatedText || "No answer available.", citations);
|
|
786
845
|
const citationDetails = citations.map((citation) => ({
|
|
787
846
|
title: citation.title,
|
|
788
847
|
url: citation.url,
|
|
789
|
-
source:
|
|
848
|
+
source: `${providerSourceName}.url_citation`,
|
|
790
849
|
type: "citation",
|
|
791
850
|
raw: citation,
|
|
792
851
|
}));
|
|
@@ -800,7 +859,7 @@ async function callOpenAIStream(
|
|
|
800
859
|
return {
|
|
801
860
|
text: cited.text,
|
|
802
861
|
sources: cited.sources.length ? cited.sources.map((source) => ({ ...source, url: normalizeSearchUrl(source.url) })).filter((source) => !isLikelyJunkSearchUrl(source.url)) : derivedSources,
|
|
803
|
-
providerKind:
|
|
862
|
+
providerKind: searchProvider,
|
|
804
863
|
nativeSearchUsed: nativeSearchEvents.length > 0 || nativeSearchCalls.length > 0 || sanitizedSearchResults.length > 0,
|
|
805
864
|
nativeSearchEvents,
|
|
806
865
|
nativeSearchCalls,
|
|
@@ -993,7 +1052,7 @@ export async function callApiStream(
|
|
|
993
1052
|
throw new Error("No prompt text found in request body");
|
|
994
1053
|
}
|
|
995
1054
|
|
|
996
|
-
if (kind === "openai") {
|
|
1055
|
+
if (kind === "openai" || kind === "xai") {
|
|
997
1056
|
return callOpenAIStream(ctx, model, prompt, onUpdate, signal);
|
|
998
1057
|
}
|
|
999
1058
|
if (kind === "anthropic") {
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
3
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
3
4
|
import { getProviderKind } from "./api.ts";
|
|
4
5
|
import { webSearch, WebSearchSchema } from "./web_search.ts";
|
|
5
6
|
import { urlContext, UrlContextSchema } from "./url_context.ts";
|
|
@@ -62,9 +63,30 @@ export default function (pi: ExtensionAPI) {
|
|
|
62
63
|
pi.registerTool({
|
|
63
64
|
name: WEB_SEARCH_TOOL,
|
|
64
65
|
label: "Web Search",
|
|
65
|
-
description: "Search the web using the current supported provider (Google Gemini, OpenAI, or Anthropic). Optionally include URLs to analyze alongside search results.",
|
|
66
|
+
description: "Search the web using the current supported provider (Google Gemini, xAI Grok, OpenAI, or Anthropic). Optionally include URLs to analyze alongside search results.",
|
|
66
67
|
parameters: WebSearchSchema,
|
|
67
|
-
execute: webSearch
|
|
68
|
+
execute: webSearch,
|
|
69
|
+
renderCall(args, theme) {
|
|
70
|
+
const query = args.query || "…";
|
|
71
|
+
const urlCount = args.urls?.length ?? 0;
|
|
72
|
+
const urls = urlCount > 0 ? theme.fg("muted", ` + ${urlCount} URL${urlCount === 1 ? "" : "s"}`) : "";
|
|
73
|
+
return new Text(
|
|
74
|
+
`${theme.fg("toolTitle", theme.bold("web_search"))} ${theme.fg("accent", query)}${urls}`,
|
|
75
|
+
0,
|
|
76
|
+
0,
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
renderResult(result, { expanded }, theme) {
|
|
80
|
+
const output = result.content
|
|
81
|
+
.filter((part) => part.type === "text")
|
|
82
|
+
.map((part) => part.text)
|
|
83
|
+
.join("\n");
|
|
84
|
+
|
|
85
|
+
const isError = Boolean(result.details?.error);
|
|
86
|
+
if (!expanded && !isError) return new Text("", 0, 0);
|
|
87
|
+
|
|
88
|
+
return new Text(theme.fg(isError ? "error" : "toolOutput", output), 0, 0);
|
|
89
|
+
}
|
|
68
90
|
});
|
|
69
91
|
|
|
70
92
|
pi.registerTool({
|
package/src/utils.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { ExtensionContext, AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
3
|
-
import { truncateHead, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { truncateHead, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
|
-
import { homedir } from "node:os";
|
|
6
5
|
import { join } from "node:path";
|
|
7
6
|
import { getProviderKind } from "./api.ts";
|
|
8
7
|
|
|
@@ -18,8 +17,7 @@ export function formatResult(text: string, details: any): AgentToolResult<any> {
|
|
|
18
17
|
|
|
19
18
|
// --- Model Selection ---
|
|
20
19
|
|
|
21
|
-
const SUPPORTED_PROVIDERS = ["google-generative-ai", "openai-responses", "openai-codex-responses", "anthropic-messages"];
|
|
22
|
-
const WEB_SEARCH_CONFIG_PATH = join(homedir(), ".pi", "agent", "web-search.json");
|
|
20
|
+
const SUPPORTED_PROVIDERS = ["google-generative-ai", "xai", "openai-responses", "azure-openai-responses", "openai-codex-responses", "anthropic-messages"];
|
|
23
21
|
|
|
24
22
|
type WebSearchModelConfig =
|
|
25
23
|
| { status: "missing"; path: string; }
|
|
@@ -36,7 +34,7 @@ function describeModel(model: Model<Api>): string {
|
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
function getWebSearchConfigPath(): string {
|
|
39
|
-
return process.env.PI_WEB_SEARCH_CONFIG ||
|
|
37
|
+
return process.env.PI_WEB_SEARCH_CONFIG || join(getAgentDir(), "web-search.json");
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
function readWebSearchModelConfig(): WebSearchModelConfig {
|
package/src/web_search.ts
CHANGED
|
@@ -59,7 +59,14 @@ export async function webSearch(
|
|
|
59
59
|
const cited = applyCitations(result.text, result.groundingMetadata);
|
|
60
60
|
const text = cited.text;
|
|
61
61
|
const sources = result.sources?.length ? result.sources : cited.sources;
|
|
62
|
-
const
|
|
62
|
+
const seenAdditionalResults = new Set<string>();
|
|
63
|
+
const extraSearchResults = (result.searchResults || []).filter((item) => {
|
|
64
|
+
if (!item.url || sources.some((source) => source.url === item.url)) return false;
|
|
65
|
+
const key = `${item.title || ""}\t${item.url}`;
|
|
66
|
+
if (seenAdditionalResults.has(key)) return false;
|
|
67
|
+
seenAdditionalResults.add(key);
|
|
68
|
+
return true;
|
|
69
|
+
});
|
|
63
70
|
|
|
64
71
|
// Handle URL context metadata
|
|
65
72
|
const urlMeta = result.urlContextMetadata?.urlMetadata
|
|
@@ -90,16 +97,11 @@ export async function webSearch(
|
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
if (extraSearchResults.length) {
|
|
93
|
-
|
|
94
|
-
summary += `\n\n## Additional Search Results\n${visibleResults.map((r, i) => {
|
|
100
|
+
summary += `\n\n## Additional Search Results\n${extraSearchResults.map((r, i) => {
|
|
95
101
|
const label = r.title || r.url || `Result ${i + 1}`;
|
|
96
102
|
const url = r.url ? ` - ${r.url}` : "";
|
|
97
|
-
|
|
98
|
-
return `${i + 1}. ${label}${url}${meta ? ` (${meta})` : ""}`;
|
|
103
|
+
return `${i + 1}. ${label}${url}`;
|
|
99
104
|
}).join("\n")}`;
|
|
100
|
-
if (extraSearchResults.length > visibleResults.length) {
|
|
101
|
-
summary += `\n... and ${extraSearchResults.length - visibleResults.length} more results in tool details.`;
|
|
102
|
-
}
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
return formatResult(summary, {
|