pi-web-search 1.0.2 → 1.2.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 +78 -6
- package/package.json +23 -9
- package/src/api.ts +848 -108
- package/src/index.ts +69 -7
- package/src/url_context.ts +49 -8
- package/src/utils.ts +49 -49
- package/src/web_search.ts +42 -12
- package/dist/api.d.ts +0 -26
- package/dist/api.js +0 -182
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -18
- package/dist/url_context.d.ts +0 -8
- package/dist/url_context.js +0 -102
- package/dist/utils.d.ts +0 -6
- package/dist/utils.js +0 -63
- package/dist/web_search.d.ts +0 -8
- package/dist/web_search.js +0 -75
- package/tsconfig.json +0 -14
package/dist/url_context.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import { callApiStream, getConfig, applyCitations } from "./api.js";
|
|
3
|
-
import { getModel, missingConfigResult, errorResult, formatResult } from "./utils.js";
|
|
4
|
-
export const UrlContextSchema = Type.Object({
|
|
5
|
-
query: Type.String({ description: "Question or task to perform on the URLs" }),
|
|
6
|
-
urls: Type.Array(Type.String(), {
|
|
7
|
-
description: "Public URLs to analyze (web pages, documents, images, YouTube videos, etc).",
|
|
8
|
-
minItems: 1,
|
|
9
|
-
maxItems: 20
|
|
10
|
-
}),
|
|
11
|
-
});
|
|
12
|
-
const YOUTUBE_REGEX = /^(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
|
|
13
|
-
export async function urlContext(id, params, signal, onUpdate, ctx) {
|
|
14
|
-
const model = await getModel(ctx);
|
|
15
|
-
if (!model)
|
|
16
|
-
return missingConfigResult(ctx);
|
|
17
|
-
const count = params.urls.length;
|
|
18
|
-
onUpdate?.({ content: [{ type: "text", text: `Analyzing ${count} URL${count > 1 ? 's' : ''}...` }], details: {} });
|
|
19
|
-
try {
|
|
20
|
-
const config = getConfig(model);
|
|
21
|
-
let contents = [];
|
|
22
|
-
let tools = [{ [config.urlContextTool]: {} }];
|
|
23
|
-
// Special handling for YouTube videos on Gemini
|
|
24
|
-
if (model.api === "google-generative-ai") {
|
|
25
|
-
const youtubeUrls = [];
|
|
26
|
-
const otherUrls = [];
|
|
27
|
-
for (const url of params.urls) {
|
|
28
|
-
if (YOUTUBE_REGEX.test(url)) {
|
|
29
|
-
youtubeUrls.push(url);
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
otherUrls.push(url);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
// If we have YouTube URLs, construct file_data parts
|
|
36
|
-
if (youtubeUrls.length > 0) {
|
|
37
|
-
const parts = [];
|
|
38
|
-
for (const url of youtubeUrls) {
|
|
39
|
-
parts.push({
|
|
40
|
-
file_data: { file_uri: url, mime_type: "video/mp4" }
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
let prompt = params.query;
|
|
44
|
-
if (otherUrls.length > 0) {
|
|
45
|
-
prompt += `\n\nURLs:\n${otherUrls.join("\n")}`;
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
// If no other URLs, we might not need the tool, but keep it just in case
|
|
49
|
-
// or maybe the tool is required for grounding even with video?
|
|
50
|
-
// "google_search_retrieval" tool might confuse if there are no URLs to retrieve.
|
|
51
|
-
// But if we remove the tool, we might lose grounding capabilities (like search).
|
|
52
|
-
// Let's keep the tool enabled.
|
|
53
|
-
}
|
|
54
|
-
parts.push({ text: prompt });
|
|
55
|
-
contents = [{ role: "user", parts }];
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
// No YouTube URLs, standard behavior
|
|
59
|
-
const combinedPrompt = `${params.query}\n\nURLs:\n${params.urls.join("\n")}`;
|
|
60
|
-
contents = [{ role: "user", parts: [{ text: combinedPrompt }] }];
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
// Not Gemini, standard behavior
|
|
65
|
-
const combinedPrompt = `${params.query}\n\nURLs:\n${params.urls.join("\n")}`;
|
|
66
|
-
contents = [{ role: "user", parts: [{ text: combinedPrompt }] }];
|
|
67
|
-
}
|
|
68
|
-
const result = await callApiStream(ctx, model, {
|
|
69
|
-
contents,
|
|
70
|
-
tools
|
|
71
|
-
}, onUpdate);
|
|
72
|
-
const { text, sources } = applyCitations(result.text, result.groundingMetadata);
|
|
73
|
-
// Handle both camelCase and snake_case metadata
|
|
74
|
-
const urlMeta = result.urlContextMetadata?.urlMetadata
|
|
75
|
-
|| result.urlContextMetadata?.url_metadata || [];
|
|
76
|
-
const retrieved = urlMeta
|
|
77
|
-
.filter((m) => (m.urlRetrievalStatus || m.url_retrieval_status) === "URL_RETRIEVAL_STATUS_SUCCESS")
|
|
78
|
-
.map((m) => m.retrievedUrl || m.retrieved_url || m.url);
|
|
79
|
-
const failed = urlMeta
|
|
80
|
-
.filter((m) => (m.urlRetrievalStatus || m.url_retrieval_status) !== "URL_RETRIEVAL_STATUS_SUCCESS")
|
|
81
|
-
.map((m) => ({
|
|
82
|
-
url: m.retrievedUrl || m.retrieved_url || m.url,
|
|
83
|
-
status: m.urlRetrievalStatus || m.url_retrieval_status
|
|
84
|
-
}));
|
|
85
|
-
let summary = text;
|
|
86
|
-
if (failed.length > 0) {
|
|
87
|
-
summary += `\n\n## URL Status\n✅ Retrieved: ${retrieved.length}\n❌ Failed: ${failed.length}`;
|
|
88
|
-
failed.forEach((f) => { summary += `\n- ${f.url}: ${f.status}`; });
|
|
89
|
-
}
|
|
90
|
-
if (sources.length > 0 && !summary.includes("## Sources")) {
|
|
91
|
-
summary += `\n\n## Sources\n${sources.map((s, i) => `${i + 1}. [${s.title}](${s.url})`).join("\n")}`;
|
|
92
|
-
}
|
|
93
|
-
return formatResult(summary, {
|
|
94
|
-
retrieved,
|
|
95
|
-
failed: failed.length > 0 ? failed : undefined,
|
|
96
|
-
model: model.id
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
catch (e) {
|
|
100
|
-
return errorResult(e);
|
|
101
|
-
}
|
|
102
|
-
}
|
package/dist/utils.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { ExtensionContext, AgentToolResult } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { type Model } from "@mariozechner/pi-ai";
|
|
3
|
-
export declare function formatResult(text: string, details: any): AgentToolResult<any>;
|
|
4
|
-
export declare function getModel(ctx: ExtensionContext): Promise<Model<any> | undefined>;
|
|
5
|
-
export declare function missingConfigResult(ctx: ExtensionContext): AgentToolResult<any>;
|
|
6
|
-
export declare function errorResult(e: Error): AgentToolResult<any>;
|
package/dist/utils.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { truncateHead, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
// --- Formatting ---
|
|
3
|
-
export function formatResult(text, details) {
|
|
4
|
-
const { content, truncated } = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
|
|
5
|
-
return {
|
|
6
|
-
content: [{ type: "text", text: content + (truncated ? "\n\n[Truncated]" : "") }],
|
|
7
|
-
details
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
// --- Model Selection ---
|
|
11
|
-
export async function getModel(ctx) {
|
|
12
|
-
// flash first, big first: 3-flash -> 2.5-flash -> 2.0-flash
|
|
13
|
-
// provider priority: google-gemini-cli -> google-antigravity -> google -> google-generative-ai
|
|
14
|
-
const models = ctx.modelRegistry.getAvailable();
|
|
15
|
-
const flashModels = [
|
|
16
|
-
/gemini-3.*flash/i,
|
|
17
|
-
/gemini-2\.5.*flash/i,
|
|
18
|
-
/gemini-2\.0.*flash/i,
|
|
19
|
-
/gemini.*flash/i,
|
|
20
|
-
];
|
|
21
|
-
const providers = [
|
|
22
|
-
"google-gemini-cli",
|
|
23
|
-
"google-antigravity",
|
|
24
|
-
"google",
|
|
25
|
-
"google-generative-ai",
|
|
26
|
-
];
|
|
27
|
-
// Filter to only Google-compatible models (those with supported api/provider)
|
|
28
|
-
const googleModels = models.filter(m => providers.includes(m.provider) ||
|
|
29
|
-
m.api === "google-generative-ai" ||
|
|
30
|
-
m.api === "google-gemini-cli");
|
|
31
|
-
// Try each flash pattern in priority order
|
|
32
|
-
for (const pattern of flashModels) {
|
|
33
|
-
const matching = googleModels.filter(m => pattern.test(m.id));
|
|
34
|
-
if (matching.length === 0)
|
|
35
|
-
continue;
|
|
36
|
-
// Among matches, pick by provider priority
|
|
37
|
-
for (const provider of providers) {
|
|
38
|
-
const model = matching.find(m => m.provider === provider);
|
|
39
|
-
if (model)
|
|
40
|
-
return model;
|
|
41
|
-
}
|
|
42
|
-
// Fall back to first match if no priority provider found
|
|
43
|
-
return matching[0];
|
|
44
|
-
}
|
|
45
|
-
// No flash model found, try any Google model by provider priority
|
|
46
|
-
for (const provider of providers) {
|
|
47
|
-
const model = googleModels.find(m => m.provider === provider);
|
|
48
|
-
if (model)
|
|
49
|
-
return model;
|
|
50
|
-
}
|
|
51
|
-
// Return first available Google model if any
|
|
52
|
-
return googleModels[0];
|
|
53
|
-
}
|
|
54
|
-
// --- Error Results ---
|
|
55
|
-
export function missingConfigResult(ctx) {
|
|
56
|
-
const msg = ctx.model && ["google-gemini-cli", "google-antigravity"].includes(ctx.model.provider)
|
|
57
|
-
? `Provider ${ctx.model.provider} requires valid OAuth credentials.`
|
|
58
|
-
: "No Google Gemini configuration found. Please configure GEMINI_API_KEY.";
|
|
59
|
-
return { content: [{ type: "text", text: `Failed: ${msg}` }], details: { error: "missing_config" } };
|
|
60
|
-
}
|
|
61
|
-
export function errorResult(e) {
|
|
62
|
-
return { content: [{ type: "text", text: `Error: ${e.message}` }], details: { error: true } };
|
|
63
|
-
}
|
package/dist/web_search.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ExtensionContext, AgentToolUpdateCallback } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { type Static } from "@sinclair/typebox";
|
|
3
|
-
export declare const WebSearchSchema: import("@sinclair/typebox").TObject<{
|
|
4
|
-
query: import("@sinclair/typebox").TString;
|
|
5
|
-
urls: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
6
|
-
}>;
|
|
7
|
-
export type WebSearchInput = Static<typeof WebSearchSchema>;
|
|
8
|
-
export declare function webSearch(id: string, params: WebSearchInput, signal: AbortSignal, onUpdate: AgentToolUpdateCallback | undefined, ctx: ExtensionContext): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<any>>;
|
package/dist/web_search.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import { callApiStream, getConfig, applyCitations } from "./api.js";
|
|
3
|
-
import { getModel, missingConfigResult, errorResult, formatResult } from "./utils.js";
|
|
4
|
-
export const WebSearchSchema = Type.Object({
|
|
5
|
-
query: Type.String({ description: "The search query or question to answer" }),
|
|
6
|
-
urls: Type.Optional(Type.Array(Type.String(), {
|
|
7
|
-
description: "Additional URLs to analyze along with search (up to 20)",
|
|
8
|
-
maxItems: 20
|
|
9
|
-
})),
|
|
10
|
-
});
|
|
11
|
-
export async function webSearch(id, params, signal, onUpdate, ctx) {
|
|
12
|
-
const model = await getModel(ctx);
|
|
13
|
-
if (!model)
|
|
14
|
-
return missingConfigResult(ctx);
|
|
15
|
-
const hasUrls = params.urls && params.urls.length > 0;
|
|
16
|
-
const urlCount = hasUrls ? params.urls.length : 0;
|
|
17
|
-
onUpdate?.({
|
|
18
|
-
content: [{
|
|
19
|
-
type: "text",
|
|
20
|
-
text: hasUrls
|
|
21
|
-
? `Searching and analyzing ${urlCount} URL(s)...`
|
|
22
|
-
: `Searching for "${params.query}"...`
|
|
23
|
-
}],
|
|
24
|
-
details: {}
|
|
25
|
-
});
|
|
26
|
-
try {
|
|
27
|
-
const config = getConfig(model);
|
|
28
|
-
// Build prompt: include URLs if provided
|
|
29
|
-
const prompt = hasUrls
|
|
30
|
-
? `${params.query}\n\nAlso analyze these URLs:\n${params.urls.join("\n")}`
|
|
31
|
-
: params.query;
|
|
32
|
-
// Enable google_search, add url_context if URLs provided
|
|
33
|
-
const tools = hasUrls
|
|
34
|
-
? [{ [config.searchTool]: {} }, { [config.urlContextTool]: {} }]
|
|
35
|
-
: [{ [config.searchTool]: {} }];
|
|
36
|
-
const result = await callApiStream(ctx, model, {
|
|
37
|
-
contents: [{ role: "user", parts: [{ text: prompt }] }],
|
|
38
|
-
tools
|
|
39
|
-
}, onUpdate);
|
|
40
|
-
const { text, sources } = applyCitations(result.text, result.groundingMetadata);
|
|
41
|
-
// Handle URL context metadata
|
|
42
|
-
const urlMeta = result.urlContextMetadata?.urlMetadata
|
|
43
|
-
|| result.urlContextMetadata?.url_metadata || [];
|
|
44
|
-
const retrieved = urlMeta
|
|
45
|
-
.filter((m) => (m.urlRetrievalStatus || m.url_retrieval_status) === "URL_RETRIEVAL_STATUS_SUCCESS")
|
|
46
|
-
.map((m) => m.retrievedUrl || m.retrieved_url || m.url);
|
|
47
|
-
const failed = urlMeta
|
|
48
|
-
.filter((m) => (m.urlRetrievalStatus || m.url_retrieval_status) !== "URL_RETRIEVAL_STATUS_SUCCESS")
|
|
49
|
-
.map((m) => ({
|
|
50
|
-
url: m.retrievedUrl || m.retrieved_url || m.url,
|
|
51
|
-
status: m.urlRetrievalStatus || m.url_retrieval_status
|
|
52
|
-
}));
|
|
53
|
-
let summary = text;
|
|
54
|
-
// Add URL status if there were failures
|
|
55
|
-
if (failed.length > 0) {
|
|
56
|
-
summary += `\n\n## URL Status\n✅ Retrieved: ${retrieved.length}\n❌ Failed: ${failed.length}`;
|
|
57
|
-
failed.forEach((f) => { summary += `\n- ${f.url}: ${f.status}`; });
|
|
58
|
-
}
|
|
59
|
-
// Add sources
|
|
60
|
-
if (sources.length > 0) {
|
|
61
|
-
summary += `\n\n## Sources\n${sources.map((s, i) => `${i + 1}. [${s.title}](${s.url})`).join("\n")}`;
|
|
62
|
-
}
|
|
63
|
-
return formatResult(summary, {
|
|
64
|
-
sources,
|
|
65
|
-
searchQueries: result.groundingMetadata?.webSearchQueries,
|
|
66
|
-
retrieved: retrieved.length > 0 ? retrieved : undefined,
|
|
67
|
-
failed: failed.length > 0 ? failed : undefined,
|
|
68
|
-
model: model.id,
|
|
69
|
-
grounded: sources.length > 0
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
catch (e) {
|
|
73
|
-
return errorResult(e);
|
|
74
|
-
}
|
|
75
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"declaration": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src/**/*"]
|
|
14
|
-
}
|