pi-web-search 1.0.0 → 1.0.2
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 +1 -2
- package/dist/index.js +1 -9
- package/dist/url_context.js +51 -4
- package/package.json +3 -9
- package/src/index.ts +1 -10
- package/src/url_context.ts +55 -4
- package/dist/advanced_search.d.ts +0 -8
- package/dist/advanced_search.js +0 -76
- package/dist/youtube_video.d.ts +0 -10
- package/dist/youtube_video.js +0 -54
- package/run.sh +0 -9
- package/src/youtube_video.ts +0 -64
package/README.md
CHANGED
|
@@ -5,8 +5,7 @@ A pi extension that provides web search and content analysis capabilities powere
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **🔍 Web Search** - Google search with citations
|
|
8
|
-
- **📄 URL Analysis** - Analyze web pages, PDFs, and
|
|
9
|
-
- **🎥 YouTube** - Video summaries and Q&A
|
|
8
|
+
- **📄 URL Analysis** - Analyze web pages, PDFs, images, and YouTube videos (up to 20 URLs)
|
|
10
9
|
|
|
11
10
|
## Installation
|
|
12
11
|
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { webSearch, WebSearchSchema } from "./web_search.js";
|
|
2
2
|
import { urlContext, UrlContextSchema } from "./url_context.js";
|
|
3
|
-
import { youtubeVideo, YoutubeVideoSchema } from "./youtube_video.js";
|
|
4
3
|
export default function (pi) {
|
|
5
4
|
pi.registerTool({
|
|
6
5
|
name: "web_search",
|
|
@@ -12,15 +11,8 @@ export default function (pi) {
|
|
|
12
11
|
pi.registerTool({
|
|
13
12
|
name: "url_context",
|
|
14
13
|
label: "URL Context",
|
|
15
|
-
description: "Analyze
|
|
14
|
+
description: "Analyze the content of up to 20 public URLs. Supports web pages, documents, images, and YouTube videos.",
|
|
16
15
|
parameters: UrlContextSchema,
|
|
17
16
|
execute: urlContext
|
|
18
17
|
});
|
|
19
|
-
pi.registerTool({
|
|
20
|
-
name: "youtube_video",
|
|
21
|
-
label: "YouTube Video",
|
|
22
|
-
description: "Analyze YouTube videos. Summarize, answer questions, find timestamps. Supports video clipping (start/end offsets). Preview feature.",
|
|
23
|
-
parameters: YoutubeVideoSchema,
|
|
24
|
-
execute: youtubeVideo
|
|
25
|
-
});
|
|
26
18
|
}
|
package/dist/url_context.js
CHANGED
|
@@ -4,11 +4,12 @@ import { getModel, missingConfigResult, errorResult, formatResult } from "./util
|
|
|
4
4
|
export const UrlContextSchema = Type.Object({
|
|
5
5
|
query: Type.String({ description: "Question or task to perform on the URLs" }),
|
|
6
6
|
urls: Type.Array(Type.String(), {
|
|
7
|
-
description: "URLs to analyze (
|
|
7
|
+
description: "Public URLs to analyze (web pages, documents, images, YouTube videos, etc).",
|
|
8
8
|
minItems: 1,
|
|
9
9
|
maxItems: 20
|
|
10
10
|
}),
|
|
11
11
|
});
|
|
12
|
+
const YOUTUBE_REGEX = /^(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
|
|
12
13
|
export async function urlContext(id, params, signal, onUpdate, ctx) {
|
|
13
14
|
const model = await getModel(ctx);
|
|
14
15
|
if (!model)
|
|
@@ -17,10 +18,56 @@ export async function urlContext(id, params, signal, onUpdate, ctx) {
|
|
|
17
18
|
onUpdate?.({ content: [{ type: "text", text: `Analyzing ${count} URL${count > 1 ? 's' : ''}...` }], details: {} });
|
|
18
19
|
try {
|
|
19
20
|
const config = getConfig(model);
|
|
20
|
-
|
|
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
|
+
}
|
|
21
68
|
const result = await callApiStream(ctx, model, {
|
|
22
|
-
contents
|
|
23
|
-
tools
|
|
69
|
+
contents,
|
|
70
|
+
tools
|
|
24
71
|
}, onUpdate);
|
|
25
72
|
const { text, sources } = applyCitations(result.text, result.groundingMetadata);
|
|
26
73
|
// Handle both camelCase and snake_case metadata
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-search",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Web search and content analysis extension for pi, powered by Gemini API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,15 +24,9 @@
|
|
|
24
24
|
"url": "https://github.com/ttttmr/pi-web-search/issues"
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/ttttmr/pi-web-search#readme",
|
|
27
|
-
"scripts": {
|
|
28
|
-
"build": "tsc",
|
|
29
|
-
"prepublishOnly": "npm run build"
|
|
30
|
-
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"@mariozechner/pi-coding-agent": "^0.51.5",
|
|
33
|
-
"@sinclair/typebox": "^0.32.15"
|
|
34
|
-
},
|
|
35
27
|
"devDependencies": {
|
|
28
|
+
"@mariozechner/pi-coding-agent": "^0.51.5",
|
|
29
|
+
"@sinclair/typebox": "^0.32.15",
|
|
36
30
|
"typescript": "^5.0.0",
|
|
37
31
|
"@types/node": "^20.0.0"
|
|
38
32
|
},
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { webSearch, WebSearchSchema } from "./web_search.js";
|
|
3
3
|
import { urlContext, UrlContextSchema } from "./url_context.js";
|
|
4
|
-
import { youtubeVideo, YoutubeVideoSchema } from "./youtube_video.js";
|
|
5
4
|
|
|
6
5
|
export default function (pi: ExtensionAPI) {
|
|
7
6
|
pi.registerTool({
|
|
@@ -15,16 +14,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
15
14
|
pi.registerTool({
|
|
16
15
|
name: "url_context",
|
|
17
16
|
label: "URL Context",
|
|
18
|
-
description: "Analyze
|
|
17
|
+
description: "Analyze the content of up to 20 public URLs. Supports web pages, documents, images, and YouTube videos.",
|
|
19
18
|
parameters: UrlContextSchema,
|
|
20
19
|
execute: urlContext
|
|
21
20
|
});
|
|
22
|
-
|
|
23
|
-
pi.registerTool({
|
|
24
|
-
name: "youtube_video",
|
|
25
|
-
label: "YouTube Video",
|
|
26
|
-
description: "Analyze YouTube videos. Summarize, answer questions, find timestamps. Supports video clipping (start/end offsets). Preview feature.",
|
|
27
|
-
parameters: YoutubeVideoSchema,
|
|
28
|
-
execute: youtubeVideo
|
|
29
|
-
});
|
|
30
21
|
}
|
package/src/url_context.ts
CHANGED
|
@@ -6,13 +6,15 @@ import { getModel, missingConfigResult, errorResult, formatResult } from "./util
|
|
|
6
6
|
export const UrlContextSchema = Type.Object({
|
|
7
7
|
query: Type.String({ description: "Question or task to perform on the URLs" }),
|
|
8
8
|
urls: Type.Array(Type.String(), {
|
|
9
|
-
description: "URLs to analyze (
|
|
9
|
+
description: "Public URLs to analyze (web pages, documents, images, YouTube videos, etc).",
|
|
10
10
|
minItems: 1,
|
|
11
11
|
maxItems: 20
|
|
12
12
|
}),
|
|
13
13
|
});
|
|
14
14
|
export type UrlContextInput = Static<typeof UrlContextSchema>;
|
|
15
15
|
|
|
16
|
+
const YOUTUBE_REGEX = /^(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
|
|
17
|
+
|
|
16
18
|
export async function urlContext(
|
|
17
19
|
id: string,
|
|
18
20
|
params: UrlContextInput,
|
|
@@ -28,11 +30,60 @@ export async function urlContext(
|
|
|
28
30
|
|
|
29
31
|
try {
|
|
30
32
|
const config = getConfig(model);
|
|
31
|
-
|
|
33
|
+
|
|
34
|
+
let contents: any[] = [];
|
|
35
|
+
let tools: any[] | undefined = [{ [config.urlContextTool]: {} }];
|
|
36
|
+
|
|
37
|
+
// Special handling for YouTube videos on Gemini
|
|
38
|
+
if (model.api === "google-generative-ai") {
|
|
39
|
+
const youtubeUrls: string[] = [];
|
|
40
|
+
const otherUrls: string[] = [];
|
|
41
|
+
|
|
42
|
+
for (const url of params.urls) {
|
|
43
|
+
if (YOUTUBE_REGEX.test(url)) {
|
|
44
|
+
youtubeUrls.push(url);
|
|
45
|
+
} else {
|
|
46
|
+
otherUrls.push(url);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If we have YouTube URLs, construct file_data parts
|
|
51
|
+
if (youtubeUrls.length > 0) {
|
|
52
|
+
const parts: any[] = [];
|
|
53
|
+
|
|
54
|
+
for (const url of youtubeUrls) {
|
|
55
|
+
parts.push({
|
|
56
|
+
file_data: { file_uri: url, mime_type: "video/mp4" }
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let prompt = params.query;
|
|
61
|
+
if (otherUrls.length > 0) {
|
|
62
|
+
prompt += `\n\nURLs:\n${otherUrls.join("\n")}`;
|
|
63
|
+
} else {
|
|
64
|
+
// If no other URLs, we might not need the tool, but keep it just in case
|
|
65
|
+
// or maybe the tool is required for grounding even with video?
|
|
66
|
+
// "google_search_retrieval" tool might confuse if there are no URLs to retrieve.
|
|
67
|
+
// But if we remove the tool, we might lose grounding capabilities (like search).
|
|
68
|
+
// Let's keep the tool enabled.
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
parts.push({ text: prompt });
|
|
72
|
+
contents = [{ role: "user", parts }];
|
|
73
|
+
} else {
|
|
74
|
+
// No YouTube URLs, standard behavior
|
|
75
|
+
const combinedPrompt = `${params.query}\n\nURLs:\n${params.urls.join("\n")}`;
|
|
76
|
+
contents = [{ role: "user", parts: [{ text: combinedPrompt }] }];
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
// Not Gemini, standard behavior
|
|
80
|
+
const combinedPrompt = `${params.query}\n\nURLs:\n${params.urls.join("\n")}`;
|
|
81
|
+
contents = [{ role: "user", parts: [{ text: combinedPrompt }] }];
|
|
82
|
+
}
|
|
32
83
|
|
|
33
84
|
const result = await callApiStream(ctx, model, {
|
|
34
|
-
contents
|
|
35
|
-
tools
|
|
85
|
+
contents,
|
|
86
|
+
tools
|
|
36
87
|
}, onUpdate);
|
|
37
88
|
|
|
38
89
|
const { text, sources } = applyCitations(result.text, result.groundingMetadata);
|
|
@@ -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 AdvancedSearchSchema: 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 AdvancedSearchInput = Static<typeof AdvancedSearchSchema>;
|
|
8
|
-
export declare function advancedSearch(id: string, params: AdvancedSearchInput, signal: AbortSignal, onUpdate: AgentToolUpdateCallback | undefined, ctx: ExtensionContext): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<any>>;
|
package/dist/advanced_search.js
DELETED
|
@@ -1,76 +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 AdvancedSearchSchema = 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 include in context (up to 20)",
|
|
8
|
-
maxItems: 20
|
|
9
|
-
})),
|
|
10
|
-
});
|
|
11
|
-
export async function advancedSearch(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 in the query if provided
|
|
29
|
-
const prompt = hasUrls
|
|
30
|
-
? `${params.query}\n\nAlso analyze these URLs:\n${params.urls.join("\n")}`
|
|
31
|
-
: params.query;
|
|
32
|
-
// Enable both tools: google_search + url_context
|
|
33
|
-
// Gemini will decide when to use each
|
|
34
|
-
const tools = hasUrls
|
|
35
|
-
? [{ [config.searchTool]: {} }, { [config.urlContextTool]: {} }]
|
|
36
|
-
: [{ [config.searchTool]: {} }];
|
|
37
|
-
const result = await callApiStream(ctx, model, {
|
|
38
|
-
contents: [{ role: "user", parts: [{ text: prompt }] }],
|
|
39
|
-
tools
|
|
40
|
-
}, onUpdate);
|
|
41
|
-
const { text, sources } = applyCitations(result.text, result.groundingMetadata);
|
|
42
|
-
// Handle URL context metadata
|
|
43
|
-
const urlMeta = result.urlContextMetadata?.urlMetadata
|
|
44
|
-
|| result.urlContextMetadata?.url_metadata || [];
|
|
45
|
-
const retrieved = urlMeta
|
|
46
|
-
.filter((m) => (m.urlRetrievalStatus || m.url_retrieval_status) === "URL_RETRIEVAL_STATUS_SUCCESS")
|
|
47
|
-
.map((m) => m.retrievedUrl || m.retrieved_url || m.url);
|
|
48
|
-
const failed = urlMeta
|
|
49
|
-
.filter((m) => (m.urlRetrievalStatus || m.url_retrieval_status) !== "URL_RETRIEVAL_STATUS_SUCCESS")
|
|
50
|
-
.map((m) => ({
|
|
51
|
-
url: m.retrievedUrl || m.retrieved_url || m.url,
|
|
52
|
-
status: m.urlRetrievalStatus || m.url_retrieval_status
|
|
53
|
-
}));
|
|
54
|
-
let summary = text;
|
|
55
|
-
// Add URL status if there were failures
|
|
56
|
-
if (failed.length > 0) {
|
|
57
|
-
summary += `\n\n## URL Status\n✅ Retrieved: ${retrieved.length}\n❌ Failed: ${failed.length}`;
|
|
58
|
-
failed.forEach((f) => { summary += `\n- ${f.url}: ${f.status}`; });
|
|
59
|
-
}
|
|
60
|
-
// Add sources
|
|
61
|
-
if (sources.length > 0) {
|
|
62
|
-
summary += `\n\n## Sources\n${sources.map((s, i) => `${i + 1}. [${s.title}](${s.url})`).join("\n")}`;
|
|
63
|
-
}
|
|
64
|
-
return formatResult(summary, {
|
|
65
|
-
sources,
|
|
66
|
-
searchQueries: result.groundingMetadata?.webSearchQueries,
|
|
67
|
-
retrieved: retrieved.length > 0 ? retrieved : undefined,
|
|
68
|
-
failed: failed.length > 0 ? failed : undefined,
|
|
69
|
-
model: model.id,
|
|
70
|
-
grounded: sources.length > 0
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
catch (e) {
|
|
74
|
-
return errorResult(e);
|
|
75
|
-
}
|
|
76
|
-
}
|
package/dist/youtube_video.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ExtensionContext, AgentToolUpdateCallback } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { type Static } from "@sinclair/typebox";
|
|
3
|
-
export declare const YoutubeVideoSchema: import("@sinclair/typebox").TObject<{
|
|
4
|
-
video_url: import("@sinclair/typebox").TString;
|
|
5
|
-
query: import("@sinclair/typebox").TString;
|
|
6
|
-
start_offset: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
7
|
-
end_offset: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
8
|
-
}>;
|
|
9
|
-
export type YoutubeVideoInput = Static<typeof YoutubeVideoSchema>;
|
|
10
|
-
export declare function youtubeVideo(id: string, params: YoutubeVideoInput, signal: AbortSignal, onUpdate: AgentToolUpdateCallback | undefined, ctx: ExtensionContext): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<any>>;
|
package/dist/youtube_video.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import { callApiStream } from "./api.js";
|
|
3
|
-
import { getModel, missingConfigResult, errorResult, formatResult } from "./utils.js";
|
|
4
|
-
import { urlContext } from "./url_context.js";
|
|
5
|
-
export const YoutubeVideoSchema = Type.Object({
|
|
6
|
-
video_url: Type.String({ description: "YouTube video URL" }),
|
|
7
|
-
query: Type.String({ description: "Question or task about the video" }),
|
|
8
|
-
start_offset: Type.Optional(Type.String({ description: "Start time (e.g., '120s' or '2:00')" })),
|
|
9
|
-
end_offset: Type.Optional(Type.String({ description: "End time (e.g., '300s' or '5:00')" })),
|
|
10
|
-
});
|
|
11
|
-
export async function youtubeVideo(id, params, signal, onUpdate, ctx) {
|
|
12
|
-
const model = await getModel(ctx);
|
|
13
|
-
if (!model)
|
|
14
|
-
return missingConfigResult(ctx);
|
|
15
|
-
// Only google-generative-ai supports native video; others use URL context
|
|
16
|
-
if (model.api !== "google-generative-ai") {
|
|
17
|
-
let query = params.query;
|
|
18
|
-
if (params.start_offset || params.end_offset) {
|
|
19
|
-
query += "\n\nFocus on the video section";
|
|
20
|
-
if (params.start_offset)
|
|
21
|
-
query += ` from ${params.start_offset}`;
|
|
22
|
-
if (params.end_offset)
|
|
23
|
-
query += ` to ${params.end_offset}`;
|
|
24
|
-
query += ".";
|
|
25
|
-
}
|
|
26
|
-
return urlContext(id, { query, urls: [params.video_url] }, signal, onUpdate, ctx);
|
|
27
|
-
}
|
|
28
|
-
onUpdate?.({ content: [{ type: "text", text: `Analyzing YouTube video...` }], details: {} });
|
|
29
|
-
try {
|
|
30
|
-
const videoPart = {
|
|
31
|
-
file_data: { file_uri: params.video_url, mime_type: "video/mp4" }
|
|
32
|
-
};
|
|
33
|
-
if (params.start_offset || params.end_offset) {
|
|
34
|
-
videoPart.video_metadata = {};
|
|
35
|
-
if (params.start_offset)
|
|
36
|
-
videoPart.video_metadata.start_offset = params.start_offset;
|
|
37
|
-
if (params.end_offset)
|
|
38
|
-
videoPart.video_metadata.end_offset = params.end_offset;
|
|
39
|
-
}
|
|
40
|
-
const result = await callApiStream(ctx, model, {
|
|
41
|
-
contents: [{ role: "user", parts: [videoPart, { text: params.query }] }]
|
|
42
|
-
}, onUpdate);
|
|
43
|
-
return formatResult(result.text, {
|
|
44
|
-
videoUrl: params.video_url,
|
|
45
|
-
clipping: (params.start_offset || params.end_offset)
|
|
46
|
-
? { start: params.start_offset, end: params.end_offset }
|
|
47
|
-
: undefined,
|
|
48
|
-
model: model.id
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
catch (e) {
|
|
52
|
-
return errorResult(e);
|
|
53
|
-
}
|
|
54
|
-
}
|
package/run.sh
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
pi --no-skills --no-extensions -e ./src/index.ts --models "google-antigravity/*" '搜索今天btc价格'
|
|
2
|
-
pi --no-skills --no-extensions -e ./src/index.ts --models "google-gemini-cli/*" '搜索今天btc价格'
|
|
3
|
-
pi --no-skills --no-extensions -e ./src/index.ts --models "google/*" '搜索今天btc价格'
|
|
4
|
-
|
|
5
|
-
pi --no-skills --no-extensions -e ./src/index.ts --provider google-antigravity --model claude-sonnet-4-5 '搜索今天btc价格'
|
|
6
|
-
# ok
|
|
7
|
-
pi --no-skills --no-extensions -e ./src/index.ts --provider google-gemini-cli --model gemini-3-flash-preview '搜索今天btc价格'
|
|
8
|
-
# ok
|
|
9
|
-
pi --no-skills --no-extensions -e ./src/index.ts --provider google --model gemini-2.5-flash '搜索今天btc价格'
|
package/src/youtube_video.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { ExtensionContext, AgentToolUpdateCallback } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { Type, type Static } from "@sinclair/typebox";
|
|
3
|
-
import { callApiStream } from "./api.js";
|
|
4
|
-
import { getModel, missingConfigResult, errorResult, formatResult } from "./utils.js";
|
|
5
|
-
import { urlContext } from "./url_context.js";
|
|
6
|
-
|
|
7
|
-
export const YoutubeVideoSchema = Type.Object({
|
|
8
|
-
video_url: Type.String({ description: "YouTube video URL" }),
|
|
9
|
-
query: Type.String({ description: "Question or task about the video" }),
|
|
10
|
-
start_offset: Type.Optional(Type.String({ description: "Start time (e.g., '120s' or '2:00')" })),
|
|
11
|
-
end_offset: Type.Optional(Type.String({ description: "End time (e.g., '300s' or '5:00')" })),
|
|
12
|
-
});
|
|
13
|
-
export type YoutubeVideoInput = Static<typeof YoutubeVideoSchema>;
|
|
14
|
-
|
|
15
|
-
export async function youtubeVideo(
|
|
16
|
-
id: string,
|
|
17
|
-
params: YoutubeVideoInput,
|
|
18
|
-
signal: AbortSignal,
|
|
19
|
-
onUpdate: AgentToolUpdateCallback | undefined,
|
|
20
|
-
ctx: ExtensionContext
|
|
21
|
-
) {
|
|
22
|
-
const model = await getModel(ctx);
|
|
23
|
-
if (!model) return missingConfigResult(ctx);
|
|
24
|
-
|
|
25
|
-
// Only google-generative-ai supports native video; others use URL context
|
|
26
|
-
if (model.api !== "google-generative-ai") {
|
|
27
|
-
let query = params.query;
|
|
28
|
-
if (params.start_offset || params.end_offset) {
|
|
29
|
-
query += "\n\nFocus on the video section";
|
|
30
|
-
if (params.start_offset) query += ` from ${params.start_offset}`;
|
|
31
|
-
if (params.end_offset) query += ` to ${params.end_offset}`;
|
|
32
|
-
query += ".";
|
|
33
|
-
}
|
|
34
|
-
return urlContext(id, { query, urls: [params.video_url] }, signal, onUpdate, ctx);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
onUpdate?.({ content: [{ type: "text", text: `Analyzing YouTube video...` }], details: {} });
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const videoPart: any = {
|
|
41
|
-
file_data: { file_uri: params.video_url, mime_type: "video/mp4" }
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
if (params.start_offset || params.end_offset) {
|
|
45
|
-
videoPart.video_metadata = {};
|
|
46
|
-
if (params.start_offset) videoPart.video_metadata.start_offset = params.start_offset;
|
|
47
|
-
if (params.end_offset) videoPart.video_metadata.end_offset = params.end_offset;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const result = await callApiStream(ctx, model, {
|
|
51
|
-
contents: [{ role: "user", parts: [videoPart, { text: params.query }] }]
|
|
52
|
-
}, onUpdate);
|
|
53
|
-
|
|
54
|
-
return formatResult(result.text, {
|
|
55
|
-
videoUrl: params.video_url,
|
|
56
|
-
clipping: (params.start_offset || params.end_offset)
|
|
57
|
-
? { start: params.start_offset, end: params.end_offset }
|
|
58
|
-
: undefined,
|
|
59
|
-
model: model.id
|
|
60
|
-
});
|
|
61
|
-
} catch (e: any) {
|
|
62
|
-
return errorResult(e);
|
|
63
|
-
}
|
|
64
|
-
}
|