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.
@@ -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
- }
@@ -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>>;
@@ -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
- }