@astralyn/pi 0.84.12 → 0.84.13
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/CHANGELOG.md +10 -0
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/web-search/auth.d.ts +12 -0
- package/dist/extensions/web-search/auth.d.ts.map +1 -0
- package/dist/extensions/web-search/auth.js +90 -0
- package/dist/extensions/web-search/auth.js.map +1 -0
- package/dist/extensions/web-search/constants.d.ts +13 -0
- package/dist/extensions/web-search/constants.d.ts.map +1 -0
- package/dist/extensions/web-search/constants.js +27 -0
- package/dist/extensions/web-search/constants.js.map +1 -0
- package/dist/extensions/web-search/fusion.d.ts +36 -0
- package/dist/extensions/web-search/fusion.d.ts.map +1 -0
- package/dist/extensions/web-search/fusion.js +272 -0
- package/dist/extensions/web-search/fusion.js.map +1 -0
- package/dist/extensions/web-search/index.d.ts +6 -0
- package/dist/extensions/web-search/index.d.ts.map +1 -0
- package/dist/extensions/web-search/index.js +70 -0
- package/dist/extensions/web-search/index.js.map +1 -0
- package/dist/extensions/web-search/providers/deepseek.d.ts +15 -0
- package/dist/extensions/web-search/providers/deepseek.d.ts.map +1 -0
- package/dist/extensions/web-search/providers/deepseek.js +63 -0
- package/dist/extensions/web-search/providers/deepseek.js.map +1 -0
- package/dist/extensions/web-search/providers/http.d.ts +9 -0
- package/dist/extensions/web-search/providers/http.d.ts.map +1 -0
- package/dist/extensions/web-search/providers/http.js +25 -0
- package/dist/extensions/web-search/providers/http.js.map +1 -0
- package/dist/extensions/web-search/providers/minimax.d.ts +14 -0
- package/dist/extensions/web-search/providers/minimax.d.ts.map +1 -0
- package/dist/extensions/web-search/providers/minimax.js +69 -0
- package/dist/extensions/web-search/providers/minimax.js.map +1 -0
- package/dist/extensions/web-search/render.d.ts +11 -0
- package/dist/extensions/web-search/render.d.ts.map +1 -0
- package/dist/extensions/web-search/render.js +109 -0
- package/dist/extensions/web-search/render.js.map +1 -0
- package/dist/extensions/web-search/schema.d.ts +12 -0
- package/dist/extensions/web-search/schema.d.ts.map +1 -0
- package/dist/extensions/web-search/schema.js +57 -0
- package/dist/extensions/web-search/schema.js.map +1 -0
- package/dist/extensions/web-search/types.d.ts +111 -0
- package/dist/extensions/web-search/types.d.ts.map +1 -0
- package/dist/extensions/web-search/types.js +5 -0
- package/dist/extensions/web-search/types.js.map +1 -0
- package/docs/bundled/README.md +1 -0
- package/docs/bundled/extensions/web-search.md +50 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* web_search — Pi-native Web Search extension combining MiniMax and DeepSeek search engines.
|
|
3
|
+
*/
|
|
4
|
+
import { configuredEngine, resolveSearchCredentials } from "./auth.js";
|
|
5
|
+
import { getWebSearchPromptGuidelines, WEB_SEARCH_DESCRIPTION, WEB_SEARCH_LABEL, WEB_SEARCH_PROMPT_SNIPPET, WEB_SEARCH_TOOL_NAME, } from "./constants.js";
|
|
6
|
+
import { executeWebSearch } from "./fusion.js";
|
|
7
|
+
import { renderWebSearchCall, renderWebSearchResult } from "./render.js";
|
|
8
|
+
import { normalizeWebSearchParams, WebSearchParamsSchema } from "./schema.js";
|
|
9
|
+
function trackQueryElapsed(context, isPartial) {
|
|
10
|
+
const state = context.state;
|
|
11
|
+
if (isPartial) {
|
|
12
|
+
state.startedAt ??= Date.now();
|
|
13
|
+
if (state.refreshTimer === undefined) {
|
|
14
|
+
state.refreshTimer = setTimeout(() => {
|
|
15
|
+
state.refreshTimer = undefined;
|
|
16
|
+
context.invalidate();
|
|
17
|
+
}, 1000);
|
|
18
|
+
state.refreshTimer.unref?.();
|
|
19
|
+
}
|
|
20
|
+
return Date.now() - state.startedAt;
|
|
21
|
+
}
|
|
22
|
+
if (state.refreshTimer !== undefined) {
|
|
23
|
+
clearTimeout(state.refreshTimer);
|
|
24
|
+
state.refreshTimer = undefined;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
export default function webSearch(pi) {
|
|
29
|
+
pi.registerTool({
|
|
30
|
+
name: WEB_SEARCH_TOOL_NAME,
|
|
31
|
+
label: WEB_SEARCH_LABEL,
|
|
32
|
+
description: WEB_SEARCH_DESCRIPTION,
|
|
33
|
+
promptSnippet: WEB_SEARCH_PROMPT_SNIPPET,
|
|
34
|
+
promptGuidelines: getWebSearchPromptGuidelines(),
|
|
35
|
+
parameters: WebSearchParamsSchema,
|
|
36
|
+
prepareArguments: normalizeWebSearchParams,
|
|
37
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
38
|
+
const credentials = await resolveSearchCredentials(ctx.modelRuntime);
|
|
39
|
+
const execution = await executeWebSearch(params, credentials, signal, onUpdate);
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: execution.formattedOutput }],
|
|
42
|
+
details: execution.details,
|
|
43
|
+
isError: execution.details.status !== "success",
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
renderCall(args, theme) {
|
|
47
|
+
return renderWebSearchCall(args, theme);
|
|
48
|
+
},
|
|
49
|
+
renderResult(result, options, theme, context) {
|
|
50
|
+
const elapsedMs = trackQueryElapsed(context, options.isPartial);
|
|
51
|
+
return renderWebSearchResult(result, options, theme, context.isError, elapsedMs);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
// Keep the tool out of the model's tool set (and system prompt) when no search
|
|
55
|
+
// credentials are configured. Re-evaluated on every session start (startup,
|
|
56
|
+
// /new, /reload, resume, fork), so /login takes effect from the next session
|
|
57
|
+
// onward; the tool stays registered for /tools and historical rendering.
|
|
58
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
59
|
+
const credentials = await resolveSearchCredentials(ctx.modelRuntime);
|
|
60
|
+
const active = new Set(pi.getActiveTools());
|
|
61
|
+
if (configuredEngine(credentials) === "none") {
|
|
62
|
+
active.delete(WEB_SEARCH_TOOL_NAME);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
active.add(WEB_SEARCH_TOOL_NAME);
|
|
66
|
+
}
|
|
67
|
+
pi.setActiveTools([...active]);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/web-search/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * web_search — Pi-native Web Search extension combining MiniMax and DeepSeek search engines.\n */\n\nimport type { ExtensionAPI, ToolRenderContext } from \"../../core/extensions/types.ts\";\nimport { configuredEngine, resolveSearchCredentials } from \"./auth.ts\";\nimport {\n\tgetWebSearchPromptGuidelines,\n\tWEB_SEARCH_DESCRIPTION,\n\tWEB_SEARCH_LABEL,\n\tWEB_SEARCH_PROMPT_SNIPPET,\n\tWEB_SEARCH_TOOL_NAME,\n} from \"./constants.ts\";\nimport { executeWebSearch } from \"./fusion.ts\";\nimport { renderWebSearchCall, renderWebSearchResult } from \"./render.ts\";\nimport { normalizeWebSearchParams, type WebSearchParams, WebSearchParamsSchema } from \"./schema.ts\";\nimport type { WebSearchDetails } from \"./types.ts\";\n\ninterface WebSearchRenderState {\n\tstartedAt?: number;\n\trefreshTimer?: ReturnType<typeof setTimeout>;\n}\n\nfunction trackQueryElapsed(context: ToolRenderContext<WebSearchRenderState>, isPartial: boolean): number | undefined {\n\tconst state = context.state;\n\tif (isPartial) {\n\t\tstate.startedAt ??= Date.now();\n\t\tif (state.refreshTimer === undefined) {\n\t\t\tstate.refreshTimer = setTimeout(() => {\n\t\t\t\tstate.refreshTimer = undefined;\n\t\t\t\tcontext.invalidate();\n\t\t\t}, 1000);\n\t\t\tstate.refreshTimer.unref?.();\n\t\t}\n\t\treturn Date.now() - state.startedAt;\n\t}\n\tif (state.refreshTimer !== undefined) {\n\t\tclearTimeout(state.refreshTimer);\n\t\tstate.refreshTimer = undefined;\n\t}\n\treturn undefined;\n}\n\nexport default function webSearch(pi: ExtensionAPI): void {\n\tpi.registerTool<typeof WebSearchParamsSchema, WebSearchDetails, WebSearchRenderState>({\n\t\tname: WEB_SEARCH_TOOL_NAME,\n\t\tlabel: WEB_SEARCH_LABEL,\n\t\tdescription: WEB_SEARCH_DESCRIPTION,\n\t\tpromptSnippet: WEB_SEARCH_PROMPT_SNIPPET,\n\t\tpromptGuidelines: getWebSearchPromptGuidelines(),\n\t\tparameters: WebSearchParamsSchema,\n\t\tprepareArguments: normalizeWebSearchParams,\n\n\t\tasync execute(_toolCallId, params, signal, onUpdate, ctx) {\n\t\t\tconst credentials = await resolveSearchCredentials(ctx.modelRuntime);\n\t\t\tconst execution = await executeWebSearch(params, credentials, signal, onUpdate);\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: execution.formattedOutput }],\n\t\t\t\tdetails: execution.details,\n\t\t\t\tisError: execution.details.status !== \"success\",\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\treturn renderWebSearchCall(args as WebSearchParams, theme);\n\t\t},\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst elapsedMs = trackQueryElapsed(context, options.isPartial);\n\t\t\treturn renderWebSearchResult(result, options, theme, context.isError, elapsedMs);\n\t\t},\n\t});\n\n\t// Keep the tool out of the model's tool set (and system prompt) when no search\n\t// credentials are configured. Re-evaluated on every session start (startup,\n\t// /new, /reload, resume, fork), so /login takes effect from the next session\n\t// onward; the tool stays registered for /tools and historical rendering.\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tconst credentials = await resolveSearchCredentials(ctx.modelRuntime);\n\t\tconst active = new Set(pi.getActiveTools());\n\t\tif (configuredEngine(credentials) === \"none\") {\n\t\t\tactive.delete(WEB_SEARCH_TOOL_NAME);\n\t\t} else {\n\t\t\tactive.add(WEB_SEARCH_TOOL_NAME);\n\t\t}\n\t\tpi.setActiveTools([...active]);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deepseek.ts — DeepSeek Messages API search provider (claude-sonnet-search model with web_search_20250305).
|
|
3
|
+
*/
|
|
4
|
+
import type { ProviderSearchResult } from "../types.ts";
|
|
5
|
+
export interface DeepSeekSearchOptions {
|
|
6
|
+
query: string;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
allowedDomains?: string[];
|
|
12
|
+
blockedDomains?: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function searchDeepSeek(options: DeepSeekSearchOptions): Promise<ProviderSearchResult>;
|
|
15
|
+
//# sourceMappingURL=deepseek.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deepseek.d.ts","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/deepseek.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAA4B,oBAAoB,EAAgB,MAAM,aAAa,CAAC;AAGhG,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAKD,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAqElG"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deepseek.ts — DeepSeek Messages API search provider (claude-sonnet-search model with web_search_20250305).
|
|
3
|
+
*/
|
|
4
|
+
import { postJson } from "./http.js";
|
|
5
|
+
const DEFAULT_DEEPSEEK_MESSAGES_ENDPOINT = "https://api.deepseek.com/anthropic/v1/messages";
|
|
6
|
+
const DEFAULT_DEEPSEEK_SEARCH_MODEL = "claude-sonnet-search";
|
|
7
|
+
export async function searchDeepSeek(options) {
|
|
8
|
+
const endpoint = options.baseUrl || DEFAULT_DEEPSEEK_MESSAGES_ENDPOINT;
|
|
9
|
+
const model = options.model || DEFAULT_DEEPSEEK_SEARCH_MODEL;
|
|
10
|
+
const toolConfig = {
|
|
11
|
+
type: "web_search_20250305",
|
|
12
|
+
name: "web_search",
|
|
13
|
+
};
|
|
14
|
+
if (options.allowedDomains && options.allowedDomains.length > 0) {
|
|
15
|
+
toolConfig.allowed_domains = options.allowedDomains;
|
|
16
|
+
}
|
|
17
|
+
else if (options.blockedDomains && options.blockedDomains.length > 0) {
|
|
18
|
+
toolConfig.blocked_domains = options.blockedDomains;
|
|
19
|
+
}
|
|
20
|
+
const data = await postJson(endpoint, {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
"x-api-key": options.apiKey,
|
|
23
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
24
|
+
"anthropic-version": "2023-06-01",
|
|
25
|
+
}, {
|
|
26
|
+
model,
|
|
27
|
+
max_tokens: 1024,
|
|
28
|
+
messages: [{ role: "user", content: options.query }],
|
|
29
|
+
tools: [toolConfig],
|
|
30
|
+
}, options.signal, 90_000, "DeepSeek Messages search API");
|
|
31
|
+
if (data.error) {
|
|
32
|
+
throw new Error(`DeepSeek search error: ${data.error.message || data.error.type || "Unknown error"}`);
|
|
33
|
+
}
|
|
34
|
+
const hits = [];
|
|
35
|
+
let synthesisText = "";
|
|
36
|
+
for (const block of data.content || []) {
|
|
37
|
+
if (block.type === "web_search_tool_result") {
|
|
38
|
+
const toolResultBlock = block;
|
|
39
|
+
for (const item of toolResultBlock.content || []) {
|
|
40
|
+
if (item.url && typeof item.url === "string") {
|
|
41
|
+
hits.push({
|
|
42
|
+
title: item.title?.trim() || item.url.trim(),
|
|
43
|
+
url: item.url.trim(),
|
|
44
|
+
date: item.page_age || undefined,
|
|
45
|
+
sources: ["DeepSeek"],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (block.type === "text") {
|
|
51
|
+
const textBlock = block;
|
|
52
|
+
if (textBlock.text) {
|
|
53
|
+
synthesisText += (synthesisText ? "\n\n" : "") + textBlock.text.trim();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
source: "DeepSeek",
|
|
59
|
+
hits,
|
|
60
|
+
synthesisText: synthesisText || undefined,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=deepseek.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deepseek.js","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/deepseek.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * deepseek.ts — DeepSeek Messages API search provider (claude-sonnet-search model with web_search_20250305).\n */\n\nimport type { DeepSeekMessagesResponse, ProviderSearchResult, WebSearchHit } from \"../types.ts\";\nimport { postJson } from \"./http.ts\";\n\nexport interface DeepSeekSearchOptions {\n\tquery: string;\n\tapiKey: string;\n\tbaseUrl?: string;\n\tmodel?: string;\n\tsignal?: AbortSignal;\n\tallowedDomains?: string[];\n\tblockedDomains?: string[];\n}\n\nconst DEFAULT_DEEPSEEK_MESSAGES_ENDPOINT = \"https://api.deepseek.com/anthropic/v1/messages\";\nconst DEFAULT_DEEPSEEK_SEARCH_MODEL = \"claude-sonnet-search\";\n\nexport async function searchDeepSeek(options: DeepSeekSearchOptions): Promise<ProviderSearchResult> {\n\tconst endpoint = options.baseUrl || DEFAULT_DEEPSEEK_MESSAGES_ENDPOINT;\n\tconst model = options.model || DEFAULT_DEEPSEEK_SEARCH_MODEL;\n\n\tconst toolConfig: Record<string, unknown> = {\n\t\ttype: \"web_search_20250305\",\n\t\tname: \"web_search\",\n\t};\n\n\tif (options.allowedDomains && options.allowedDomains.length > 0) {\n\t\ttoolConfig.allowed_domains = options.allowedDomains;\n\t} else if (options.blockedDomains && options.blockedDomains.length > 0) {\n\t\ttoolConfig.blocked_domains = options.blockedDomains;\n\t}\n\n\tconst data = await postJson<DeepSeekMessagesResponse>(\n\t\tendpoint,\n\t\t{\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"x-api-key\": options.apiKey,\n\t\t\tAuthorization: `Bearer ${options.apiKey}`,\n\t\t\t\"anthropic-version\": \"2023-06-01\",\n\t\t},\n\t\t{\n\t\t\tmodel,\n\t\t\tmax_tokens: 1024,\n\t\t\tmessages: [{ role: \"user\", content: options.query }],\n\t\t\ttools: [toolConfig],\n\t\t},\n\t\toptions.signal,\n\t\t90_000,\n\t\t\"DeepSeek Messages search API\",\n\t);\n\n\tif (data.error) {\n\t\tthrow new Error(`DeepSeek search error: ${data.error.message || data.error.type || \"Unknown error\"}`);\n\t}\n\n\tconst hits: WebSearchHit[] = [];\n\tlet synthesisText = \"\";\n\n\tfor (const block of data.content || []) {\n\t\tif (block.type === \"web_search_tool_result\") {\n\t\t\tconst toolResultBlock = block as {\n\t\t\t\tcontent?: { type?: string; title?: string; url?: string; page_age?: string }[];\n\t\t\t};\n\t\t\tfor (const item of toolResultBlock.content || []) {\n\t\t\t\tif (item.url && typeof item.url === \"string\") {\n\t\t\t\t\thits.push({\n\t\t\t\t\t\ttitle: item.title?.trim() || item.url.trim(),\n\t\t\t\t\t\turl: item.url.trim(),\n\t\t\t\t\t\tdate: item.page_age || undefined,\n\t\t\t\t\t\tsources: [\"DeepSeek\"],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (block.type === \"text\") {\n\t\t\tconst textBlock = block as { text?: string };\n\t\t\tif (textBlock.text) {\n\t\t\t\tsynthesisText += (synthesisText ? \"\\n\\n\" : \"\") + textBlock.text.trim();\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tsource: \"DeepSeek\",\n\t\thits,\n\t\tsynthesisText: synthesisText || undefined,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http.ts — Shared JSON POST plumbing for search providers: caller signal plus
|
|
3
|
+
* per-request timeout, bounded upstream error bodies, clear parse errors.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* POST a JSON body and return the parsed response.
|
|
7
|
+
*/
|
|
8
|
+
export declare function postJson<T>(url: string, headers: Record<string, string>, body: unknown, signal: AbortSignal | undefined, timeoutMs: number, errorLabel: string): Promise<T>;
|
|
9
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/http.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;GAEG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http.ts — Shared JSON POST plumbing for search providers: caller signal plus
|
|
3
|
+
* per-request timeout, bounded upstream error bodies, clear parse errors.
|
|
4
|
+
*/
|
|
5
|
+
/** Error bodies are surfaced to the model; keep them bounded. */
|
|
6
|
+
const MAX_ERROR_BODY_LENGTH = 200;
|
|
7
|
+
/**
|
|
8
|
+
* POST a JSON body and return the parsed response.
|
|
9
|
+
*/
|
|
10
|
+
export async function postJson(url, headers, body, signal, timeoutMs, errorLabel) {
|
|
11
|
+
const response = await fetch(url, {
|
|
12
|
+
method: "POST",
|
|
13
|
+
headers,
|
|
14
|
+
body: JSON.stringify(body),
|
|
15
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const errorText = await response.text().catch(() => "");
|
|
19
|
+
throw new Error(`${errorLabel} returned HTTP ${response.status} ${response.statusText}: ${errorText.slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
20
|
+
}
|
|
21
|
+
return (await response.json().catch(() => {
|
|
22
|
+
throw new Error(`${errorLabel} returned invalid JSON`);
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/http.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iEAAiE;AACjE,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC7B,GAAW,EACX,OAA+B,EAC/B,IAAa,EACb,MAA+B,EAC/B,SAAiB,EACjB,UAAkB;IAElB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,OAAO;QACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;KAC3G,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QAClB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACxD,MAAM,IAAI,KAAK,CACd,GAAG,UAAU,kBAAkB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,EAAE,CACrH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;QACxC,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,wBAAwB,CAAC,CAAC;IACxD,CAAC,CAAC,CAAM,CAAC;AACV,CAAC","sourcesContent":["/**\n * http.ts — Shared JSON POST plumbing for search providers: caller signal plus\n * per-request timeout, bounded upstream error bodies, clear parse errors.\n */\n\n/** Error bodies are surfaced to the model; keep them bounded. */\nconst MAX_ERROR_BODY_LENGTH = 200;\n\n/**\n * POST a JSON body and return the parsed response.\n */\nexport async function postJson<T>(\n\turl: string,\n\theaders: Record<string, string>,\n\tbody: unknown,\n\tsignal: AbortSignal | undefined,\n\ttimeoutMs: number,\n\terrorLabel: string,\n): Promise<T> {\n\tconst response = await fetch(url, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify(body),\n\t\tsignal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),\n\t});\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text().catch(() => \"\");\n\t\tthrow new Error(\n\t\t\t`${errorLabel} returned HTTP ${response.status} ${response.statusText}: ${errorText.slice(0, MAX_ERROR_BODY_LENGTH)}`,\n\t\t);\n\t}\n\n\treturn (await response.json().catch(() => {\n\t\tthrow new Error(`${errorLabel} returned invalid JSON`);\n\t})) as T;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* minimax.ts — MiniMax Coding Plan Search REST API provider.
|
|
3
|
+
*/
|
|
4
|
+
import type { ProviderSearchResult } from "../types.ts";
|
|
5
|
+
export interface MiniMaxSearchOptions {
|
|
6
|
+
query: string;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
apiHost?: string;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
allowedDomains?: string[];
|
|
11
|
+
blockedDomains?: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function searchMiniMax(options: MiniMaxSearchOptions): Promise<ProviderSearchResult>;
|
|
14
|
+
//# sourceMappingURL=minimax.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"minimax.d.ts","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/minimax.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAyB,oBAAoB,EAAgB,MAAM,aAAa,CAAC;AAG7F,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AA+BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA+ChG"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* minimax.ts — MiniMax Coding Plan Search REST API provider.
|
|
3
|
+
*/
|
|
4
|
+
import { postJson } from "./http.js";
|
|
5
|
+
function buildSearchQuery(query, allowedDomains, blockedDomains) {
|
|
6
|
+
let q = query.trim();
|
|
7
|
+
if (allowedDomains && allowedDomains.length > 0) {
|
|
8
|
+
const siteFilter = allowedDomains.map((d) => `site:${d.trim()}`).join(" OR ");
|
|
9
|
+
q = `${q} (${siteFilter})`;
|
|
10
|
+
}
|
|
11
|
+
else if (blockedDomains && blockedDomains.length > 0) {
|
|
12
|
+
const blockFilter = blockedDomains.map((d) => `-site:${d.trim()}`).join(" ");
|
|
13
|
+
q = `${q} ${blockFilter}`;
|
|
14
|
+
}
|
|
15
|
+
return q;
|
|
16
|
+
}
|
|
17
|
+
function matchesDomainFilters(url, allowedDomains, blockedDomains) {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = new URL(url);
|
|
20
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
21
|
+
if (allowedDomains && allowedDomains.length > 0) {
|
|
22
|
+
return allowedDomains.some((d) => hostname === d.toLowerCase() || hostname.endsWith(`.${d.toLowerCase()}`));
|
|
23
|
+
}
|
|
24
|
+
if (blockedDomains && blockedDomains.length > 0) {
|
|
25
|
+
return !blockedDomains.some((d) => hostname === d.toLowerCase() || hostname.endsWith(`.${d.toLowerCase()}`));
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function searchMiniMax(options) {
|
|
34
|
+
const host = options.apiHost?.replace(/\/+$/, "") || "https://api.minimaxi.com";
|
|
35
|
+
const effectiveQuery = buildSearchQuery(options.query, options.allowedDomains, options.blockedDomains);
|
|
36
|
+
const data = await postJson(`${host}/v1/coding_plan/search`, {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
39
|
+
}, { q: effectiveQuery }, options.signal, 60_000, "MiniMax search API");
|
|
40
|
+
if (data.base_resp && data.base_resp.status_code !== 0) {
|
|
41
|
+
throw new Error(`MiniMax search failed: [${data.base_resp.status_code}] ${data.base_resp.status_msg}`);
|
|
42
|
+
}
|
|
43
|
+
const rawHits = data.organic || [];
|
|
44
|
+
const hits = [];
|
|
45
|
+
for (const item of rawHits) {
|
|
46
|
+
if (!item.link || !item.link.trim())
|
|
47
|
+
continue;
|
|
48
|
+
const cleanLink = item.link.trim();
|
|
49
|
+
if (!matchesDomainFilters(cleanLink, options.allowedDomains, options.blockedDomains)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
hits.push({
|
|
53
|
+
title: item.title?.trim() || cleanLink,
|
|
54
|
+
url: cleanLink,
|
|
55
|
+
snippet: item.snippet?.trim() || undefined,
|
|
56
|
+
date: item.date?.trim() || undefined,
|
|
57
|
+
sources: ["MiniMax"],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const relatedSearches = data.related_searches
|
|
61
|
+
?.map((r) => r.query?.trim())
|
|
62
|
+
.filter((q) => Boolean(q && q.length > 0));
|
|
63
|
+
return {
|
|
64
|
+
source: "MiniMax",
|
|
65
|
+
hits,
|
|
66
|
+
relatedSearches: relatedSearches && relatedSearches.length > 0 ? relatedSearches : undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=minimax.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"minimax.js","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/minimax.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * minimax.ts — MiniMax Coding Plan Search REST API provider.\n */\n\nimport type { MiniMaxSearchResponse, ProviderSearchResult, WebSearchHit } from \"../types.ts\";\nimport { postJson } from \"./http.ts\";\n\nexport interface MiniMaxSearchOptions {\n\tquery: string;\n\tapiKey: string;\n\tapiHost?: string;\n\tsignal?: AbortSignal;\n\tallowedDomains?: string[];\n\tblockedDomains?: string[];\n}\n\nfunction buildSearchQuery(query: string, allowedDomains?: string[], blockedDomains?: string[]): string {\n\tlet q = query.trim();\n\tif (allowedDomains && allowedDomains.length > 0) {\n\t\tconst siteFilter = allowedDomains.map((d) => `site:${d.trim()}`).join(\" OR \");\n\t\tq = `${q} (${siteFilter})`;\n\t} else if (blockedDomains && blockedDomains.length > 0) {\n\t\tconst blockFilter = blockedDomains.map((d) => `-site:${d.trim()}`).join(\" \");\n\t\tq = `${q} ${blockFilter}`;\n\t}\n\treturn q;\n}\n\nfunction matchesDomainFilters(url: string, allowedDomains?: string[], blockedDomains?: string[]): boolean {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tconst hostname = parsed.hostname.toLowerCase();\n\n\t\tif (allowedDomains && allowedDomains.length > 0) {\n\t\t\treturn allowedDomains.some((d) => hostname === d.toLowerCase() || hostname.endsWith(`.${d.toLowerCase()}`));\n\t\t}\n\t\tif (blockedDomains && blockedDomains.length > 0) {\n\t\t\treturn !blockedDomains.some((d) => hostname === d.toLowerCase() || hostname.endsWith(`.${d.toLowerCase()}`));\n\t\t}\n\t\treturn true;\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nexport async function searchMiniMax(options: MiniMaxSearchOptions): Promise<ProviderSearchResult> {\n\tconst host = options.apiHost?.replace(/\\/+$/, \"\") || \"https://api.minimaxi.com\";\n\tconst effectiveQuery = buildSearchQuery(options.query, options.allowedDomains, options.blockedDomains);\n\n\tconst data = await postJson<MiniMaxSearchResponse>(\n\t\t`${host}/v1/coding_plan/search`,\n\t\t{\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAuthorization: `Bearer ${options.apiKey}`,\n\t\t},\n\t\t{ q: effectiveQuery },\n\t\toptions.signal,\n\t\t60_000,\n\t\t\"MiniMax search API\",\n\t);\n\n\tif (data.base_resp && data.base_resp.status_code !== 0) {\n\t\tthrow new Error(`MiniMax search failed: [${data.base_resp.status_code}] ${data.base_resp.status_msg}`);\n\t}\n\n\tconst rawHits = data.organic || [];\n\tconst hits: WebSearchHit[] = [];\n\n\tfor (const item of rawHits) {\n\t\tif (!item.link || !item.link.trim()) continue;\n\t\tconst cleanLink = item.link.trim();\n\t\tif (!matchesDomainFilters(cleanLink, options.allowedDomains, options.blockedDomains)) {\n\t\t\tcontinue;\n\t\t}\n\t\thits.push({\n\t\t\ttitle: item.title?.trim() || cleanLink,\n\t\t\turl: cleanLink,\n\t\t\tsnippet: item.snippet?.trim() || undefined,\n\t\t\tdate: item.date?.trim() || undefined,\n\t\t\tsources: [\"MiniMax\"],\n\t\t});\n\t}\n\n\tconst relatedSearches = data.related_searches\n\t\t?.map((r) => r.query?.trim())\n\t\t.filter((q): q is string => Boolean(q && q.length > 0));\n\n\treturn {\n\t\tsource: \"MiniMax\",\n\t\thits,\n\t\trelatedSearches: relatedSearches && relatedSearches.length > 0 ? relatedSearches : undefined,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render.ts — TUI rendering for web_search tool (call, collapsed summary, expanded view).
|
|
3
|
+
*/
|
|
4
|
+
import { type Component } from "@earendil-works/pi-tui";
|
|
5
|
+
import type { AgentToolResult, ToolRenderResultOptions } from "../../core/extensions/types.ts";
|
|
6
|
+
import { type Theme } from "../../modes/interactive/theme/theme.ts";
|
|
7
|
+
import type { WebSearchParams } from "./schema.ts";
|
|
8
|
+
import type { WebSearchDetails } from "./types.ts";
|
|
9
|
+
export declare function renderWebSearchCall(args: WebSearchParams, theme: Theme): Component;
|
|
10
|
+
export declare function renderWebSearchResult(result: AgentToolResult<WebSearchDetails>, options: ToolRenderResultOptions, theme: Theme, isError: boolean, elapsedMs?: number): Component;
|
|
11
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/render.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAqC,MAAM,wBAAwB,CAAC;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE/F,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAGtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,YAAY,CAAC;AAsCjE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,GAAG,SAAS,CAYlF;AASD,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,eAAe,CAAC,gBAAgB,CAAC,EACzC,OAAO,EAAE,uBAAuB,EAChC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,MAAM,GAChB,SAAS,CA6DX"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render.ts — TUI rendering for web_search tool (call, collapsed summary, expanded view).
|
|
3
|
+
*/
|
|
4
|
+
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
5
|
+
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
6
|
+
import { getMarkdownTheme } from "../../modes/interactive/theme/theme.js";
|
|
7
|
+
import { getEngineLabel, WEB_SEARCH_LABEL } from "./constants.js";
|
|
8
|
+
import { formatResultsMarkdown } from "./fusion.js";
|
|
9
|
+
/** Matches the bash/deepwiki progress display: sub-2s calls stay quiet. */
|
|
10
|
+
const ELAPSED_DISPLAY_THRESHOLD_MS = 2000;
|
|
11
|
+
function singleLine(text) {
|
|
12
|
+
return typeof text === "string" ? text.replace(/\s+/g, " ").trim() : "";
|
|
13
|
+
}
|
|
14
|
+
function truncateText(text, maxLength) {
|
|
15
|
+
if (text.length <= maxLength)
|
|
16
|
+
return text;
|
|
17
|
+
return `${text.slice(0, maxLength - 3)}...`;
|
|
18
|
+
}
|
|
19
|
+
function firstText(result) {
|
|
20
|
+
for (const part of result.content ?? []) {
|
|
21
|
+
if (part.type === "text" && typeof part.text === "string")
|
|
22
|
+
return part.text;
|
|
23
|
+
}
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
/** " · github.com, react.dev, +3" from the first-seen unique hit domains. */
|
|
27
|
+
function domainSummary(hits) {
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
for (const hit of hits) {
|
|
30
|
+
try {
|
|
31
|
+
const host = new URL(hit.url).hostname.replace(/^www\./, "");
|
|
32
|
+
if (host)
|
|
33
|
+
seen.add(host);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// ignore malformed URLs
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (seen.size === 0)
|
|
40
|
+
return "";
|
|
41
|
+
const domains = [...seen];
|
|
42
|
+
const shown = domains.slice(0, 2).join(", ");
|
|
43
|
+
return domains.length > 2 ? ` · ${shown}, +${domains.length - 2}` : ` · ${shown}`;
|
|
44
|
+
}
|
|
45
|
+
export function renderWebSearchCall(args, theme) {
|
|
46
|
+
let line = theme.fg("toolTitle", theme.bold(`${WEB_SEARCH_LABEL} `));
|
|
47
|
+
const query = singleLine(args.query || "");
|
|
48
|
+
line += theme.fg("accent", `"${truncateText(query, 60)}"`);
|
|
49
|
+
if (args.allowed_domains && args.allowed_domains.length > 0) {
|
|
50
|
+
line += ` ${theme.fg("muted", `[sites: ${args.allowed_domains.join(", ")}]`)}`;
|
|
51
|
+
}
|
|
52
|
+
else if (args.blocked_domains && args.blocked_domains.length > 0) {
|
|
53
|
+
line += ` ${theme.fg("muted", `[-sites: ${args.blocked_domains.join(", ")}]`)}`;
|
|
54
|
+
}
|
|
55
|
+
return new Text(line, 0, 0);
|
|
56
|
+
}
|
|
57
|
+
function markdownBlock(text) {
|
|
58
|
+
const container = new Container();
|
|
59
|
+
container.addChild(new Spacer(1));
|
|
60
|
+
container.addChild(new Markdown(text, 1, 0, getMarkdownTheme()));
|
|
61
|
+
return container;
|
|
62
|
+
}
|
|
63
|
+
export function renderWebSearchResult(result, options, theme, isError, elapsedMs) {
|
|
64
|
+
const details = result.details;
|
|
65
|
+
if (options.isPartial) {
|
|
66
|
+
// The query stays on the call line above; no need to repeat it here.
|
|
67
|
+
const engine = getEngineLabel(details?.engine);
|
|
68
|
+
const label = engine ? `Searching via ${engine}...` : "Searching the web...";
|
|
69
|
+
const suffix = elapsedMs !== undefined && elapsedMs >= ELAPSED_DISPLAY_THRESHOLD_MS
|
|
70
|
+
? theme.fg("muted", ` (${Math.round(elapsedMs / 1000)}s)`)
|
|
71
|
+
: "";
|
|
72
|
+
return new Text(`${theme.fg("warning", label)}${suffix}`, 0, 0);
|
|
73
|
+
}
|
|
74
|
+
const text = firstText(result);
|
|
75
|
+
const errorMessage = details?.errorMessage;
|
|
76
|
+
if (isError || details?.status === "error") {
|
|
77
|
+
const line = truncateText(errorMessage ?? (singleLine(text) || "Search request failed"), 160);
|
|
78
|
+
return new Text(theme.fg("error", `failed · ${line}`), 0, 0);
|
|
79
|
+
}
|
|
80
|
+
if (details?.status === "disabled") {
|
|
81
|
+
// Only reachable when the tool was force-enabled without credentials, or in
|
|
82
|
+
// historical sessions: the session_start hook keeps it hidden otherwise.
|
|
83
|
+
return new Text(theme.fg("warning", "disabled · no MiniMax/DeepSeek key — /login minimax-cn or export MINIMAX_API_KEY / DEEPSEEK_API_KEY"), 0, 0);
|
|
84
|
+
}
|
|
85
|
+
if (!options.expanded) {
|
|
86
|
+
const hint = `${theme.fg("muted", " (")}${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
|
|
87
|
+
if (!details) {
|
|
88
|
+
// Legacy entries without details: show a bounded slice of the payload.
|
|
89
|
+
const line = truncateText(singleLine(text), 100) || "done";
|
|
90
|
+
return new Text(`${theme.fg("toolOutput", line)}${hint}`, 0, 0);
|
|
91
|
+
}
|
|
92
|
+
const hitCount = details.totalHits ?? details.hits.length;
|
|
93
|
+
const engine = getEngineLabel(details.engine);
|
|
94
|
+
const engineLabel = engine ? ` via ${engine}` : "";
|
|
95
|
+
const duration = details.durationMs ? ` · ${(details.durationMs / 1000).toFixed(1)}s` : "";
|
|
96
|
+
const countText = hitCount === 1 ? "1 result" : hitCount === 0 ? "no results" : `${hitCount} results`;
|
|
97
|
+
const summary = theme.fg("toolOutput", truncateText(`${countText}${engineLabel}${duration}${domainSummary(details.hits)}`, 100));
|
|
98
|
+
return new Text(`${summary}${hint}`, 0, 0);
|
|
99
|
+
}
|
|
100
|
+
// Expanded: structured sections from details, without the model-facing agent
|
|
101
|
+
// directives; fall back to the raw payload for legacy entries without details.
|
|
102
|
+
if (details) {
|
|
103
|
+
const body = formatResultsMarkdown(details);
|
|
104
|
+
if (body.trim())
|
|
105
|
+
return markdownBlock(body);
|
|
106
|
+
}
|
|
107
|
+
return markdownBlock(text);
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/extensions/web-search/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * render.ts — TUI rendering for web_search tool (call, collapsed summary, expanded view).\n */\n\nimport { type Component, Container, Markdown, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { AgentToolResult, ToolRenderResultOptions } from \"../../core/extensions/types.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getMarkdownTheme, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { getEngineLabel, WEB_SEARCH_LABEL } from \"./constants.ts\";\nimport { formatResultsMarkdown } from \"./fusion.ts\";\nimport type { WebSearchParams } from \"./schema.ts\";\nimport type { WebSearchDetails, WebSearchHit } from \"./types.ts\";\n\n/** Matches the bash/deepwiki progress display: sub-2s calls stay quiet. */\nconst ELAPSED_DISPLAY_THRESHOLD_MS = 2000;\n\nfunction singleLine(text: unknown): string {\n\treturn typeof text === \"string\" ? text.replace(/\\s+/g, \" \").trim() : \"\";\n}\n\nfunction truncateText(text: string, maxLength: number): string {\n\tif (text.length <= maxLength) return text;\n\treturn `${text.slice(0, maxLength - 3)}...`;\n}\n\nfunction firstText(result: AgentToolResult<WebSearchDetails>): string {\n\tfor (const part of result.content ?? []) {\n\t\tif (part.type === \"text\" && typeof part.text === \"string\") return part.text;\n\t}\n\treturn \"\";\n}\n\n/** \" · github.com, react.dev, +3\" from the first-seen unique hit domains. */\nfunction domainSummary(hits: WebSearchHit[]): string {\n\tconst seen = new Set<string>();\n\tfor (const hit of hits) {\n\t\ttry {\n\t\t\tconst host = new URL(hit.url).hostname.replace(/^www\\./, \"\");\n\t\t\tif (host) seen.add(host);\n\t\t} catch {\n\t\t\t// ignore malformed URLs\n\t\t}\n\t}\n\tif (seen.size === 0) return \"\";\n\tconst domains = [...seen];\n\tconst shown = domains.slice(0, 2).join(\", \");\n\treturn domains.length > 2 ? ` · ${shown}, +${domains.length - 2}` : ` · ${shown}`;\n}\n\nexport function renderWebSearchCall(args: WebSearchParams, theme: Theme): Component {\n\tlet line = theme.fg(\"toolTitle\", theme.bold(`${WEB_SEARCH_LABEL} `));\n\tconst query = singleLine(args.query || \"\");\n\tline += theme.fg(\"accent\", `\"${truncateText(query, 60)}\"`);\n\n\tif (args.allowed_domains && args.allowed_domains.length > 0) {\n\t\tline += ` ${theme.fg(\"muted\", `[sites: ${args.allowed_domains.join(\", \")}]`)}`;\n\t} else if (args.blocked_domains && args.blocked_domains.length > 0) {\n\t\tline += ` ${theme.fg(\"muted\", `[-sites: ${args.blocked_domains.join(\", \")}]`)}`;\n\t}\n\n\treturn new Text(line, 0, 0);\n}\n\nfunction markdownBlock(text: string): Component {\n\tconst container = new Container();\n\tcontainer.addChild(new Spacer(1));\n\tcontainer.addChild(new Markdown(text, 1, 0, getMarkdownTheme()));\n\treturn container;\n}\n\nexport function renderWebSearchResult(\n\tresult: AgentToolResult<WebSearchDetails>,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tisError: boolean,\n\telapsedMs?: number,\n): Component {\n\tconst details = result.details;\n\n\tif (options.isPartial) {\n\t\t// The query stays on the call line above; no need to repeat it here.\n\t\tconst engine = getEngineLabel(details?.engine);\n\t\tconst label = engine ? `Searching via ${engine}...` : \"Searching the web...\";\n\t\tconst suffix =\n\t\t\telapsedMs !== undefined && elapsedMs >= ELAPSED_DISPLAY_THRESHOLD_MS\n\t\t\t\t? theme.fg(\"muted\", ` (${Math.round(elapsedMs / 1000)}s)`)\n\t\t\t\t: \"\";\n\t\treturn new Text(`${theme.fg(\"warning\", label)}${suffix}`, 0, 0);\n\t}\n\n\tconst text = firstText(result);\n\tconst errorMessage = details?.errorMessage;\n\n\tif (isError || details?.status === \"error\") {\n\t\tconst line = truncateText(errorMessage ?? (singleLine(text) || \"Search request failed\"), 160);\n\t\treturn new Text(theme.fg(\"error\", `failed · ${line}`), 0, 0);\n\t}\n\n\tif (details?.status === \"disabled\") {\n\t\t// Only reachable when the tool was force-enabled without credentials, or in\n\t\t// historical sessions: the session_start hook keeps it hidden otherwise.\n\t\treturn new Text(\n\t\t\ttheme.fg(\n\t\t\t\t\"warning\",\n\t\t\t\t\"disabled · no MiniMax/DeepSeek key — /login minimax-cn or export MINIMAX_API_KEY / DEEPSEEK_API_KEY\",\n\t\t\t),\n\t\t\t0,\n\t\t\t0,\n\t\t);\n\t}\n\n\tif (!options.expanded) {\n\t\tconst hint = `${theme.fg(\"muted\", \" (\")}${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\tif (!details) {\n\t\t\t// Legacy entries without details: show a bounded slice of the payload.\n\t\t\tconst line = truncateText(singleLine(text), 100) || \"done\";\n\t\t\treturn new Text(`${theme.fg(\"toolOutput\", line)}${hint}`, 0, 0);\n\t\t}\n\t\tconst hitCount = details.totalHits ?? details.hits.length;\n\t\tconst engine = getEngineLabel(details.engine);\n\t\tconst engineLabel = engine ? ` via ${engine}` : \"\";\n\t\tconst duration = details.durationMs ? ` · ${(details.durationMs / 1000).toFixed(1)}s` : \"\";\n\t\tconst countText = hitCount === 1 ? \"1 result\" : hitCount === 0 ? \"no results\" : `${hitCount} results`;\n\t\tconst summary = theme.fg(\n\t\t\t\"toolOutput\",\n\t\t\ttruncateText(`${countText}${engineLabel}${duration}${domainSummary(details.hits)}`, 100),\n\t\t);\n\t\treturn new Text(`${summary}${hint}`, 0, 0);\n\t}\n\n\t// Expanded: structured sections from details, without the model-facing agent\n\t// directives; fall back to the raw payload for legacy entries without details.\n\tif (details) {\n\t\tconst body = formatResultsMarkdown(details);\n\t\tif (body.trim()) return markdownBlock(body);\n\t}\n\treturn markdownBlock(text);\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schema.ts — TypeBox parameter schema and argument normalizer for web_search.
|
|
3
|
+
*/
|
|
4
|
+
import { type Static, Type } from "typebox";
|
|
5
|
+
export declare const WebSearchParamsSchema: Type.TObject<{
|
|
6
|
+
query: Type.TString;
|
|
7
|
+
allowed_domains: Type.TOptional<Type.TArray<Type.TString>>;
|
|
8
|
+
blocked_domains: Type.TOptional<Type.TArray<Type.TString>>;
|
|
9
|
+
}>;
|
|
10
|
+
export type WebSearchParams = Static<typeof WebSearchParamsSchema>;
|
|
11
|
+
export declare function normalizeWebSearchParams(raw: unknown): WebSearchParams;
|
|
12
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,qBAAqB;;;;EAiBhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEnE,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CA0CtE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* schema.ts — TypeBox parameter schema and argument normalizer for web_search.
|
|
3
|
+
*/
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
export const WebSearchParamsSchema = Type.Object({
|
|
6
|
+
query: Type.String({
|
|
7
|
+
minLength: 1,
|
|
8
|
+
description: "The search query. Aim for 3-5 keywords for best results.",
|
|
9
|
+
}),
|
|
10
|
+
allowed_domains: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
11
|
+
description: "Optional list of domains to include in search results (e.g. ['github.com', 'react.dev']). Cannot be used alongside blocked_domains.",
|
|
12
|
+
})),
|
|
13
|
+
blocked_domains: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
14
|
+
description: "Optional list of domains to exclude from search results. Cannot be used alongside allowed_domains.",
|
|
15
|
+
})),
|
|
16
|
+
});
|
|
17
|
+
export function normalizeWebSearchParams(raw) {
|
|
18
|
+
if (typeof raw === "string") {
|
|
19
|
+
return { query: raw.trim() };
|
|
20
|
+
}
|
|
21
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
22
|
+
return { query: String(raw ?? "").trim() };
|
|
23
|
+
}
|
|
24
|
+
const record = raw;
|
|
25
|
+
let query = "";
|
|
26
|
+
if (typeof record.query === "string") {
|
|
27
|
+
query = record.query.trim();
|
|
28
|
+
}
|
|
29
|
+
else if (typeof record.q === "string") {
|
|
30
|
+
query = record.q.trim();
|
|
31
|
+
}
|
|
32
|
+
else if (typeof record.search_query === "string") {
|
|
33
|
+
query = record.search_query.trim();
|
|
34
|
+
}
|
|
35
|
+
else if (typeof record.prompt === "string") {
|
|
36
|
+
query = record.prompt.trim();
|
|
37
|
+
}
|
|
38
|
+
const parseDomainArray = (val) => {
|
|
39
|
+
if (Array.isArray(val)) {
|
|
40
|
+
const list = val.map((x) => String(x).trim()).filter((x) => x.length > 0);
|
|
41
|
+
return list.length > 0 ? list : undefined;
|
|
42
|
+
}
|
|
43
|
+
if (typeof val === "string" && val.trim()) {
|
|
44
|
+
return [val.trim()];
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
};
|
|
48
|
+
const allowedDomains = parseDomainArray(record.allowed_domains ?? record.allowedDomains);
|
|
49
|
+
const blockedDomains = parseDomainArray(record.blocked_domains ?? record.blockedDomains);
|
|
50
|
+
return {
|
|
51
|
+
query,
|
|
52
|
+
...(allowedDomains ? { allowed_domains: allowedDomains } : {}),
|
|
53
|
+
// allowed_domains and blocked_domains are mutually exclusive; allowed_domains wins.
|
|
54
|
+
...(blockedDomains && !allowedDomains ? { blocked_domains: blockedDomains } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/extensions/web-search/schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * schema.ts — TypeBox parameter schema and argument normalizer for web_search.\n */\n\nimport { type Static, Type } from \"typebox\";\n\nexport const WebSearchParamsSchema = Type.Object({\n\tquery: Type.String({\n\t\tminLength: 1,\n\t\tdescription: \"The search query. Aim for 3-5 keywords for best results.\",\n\t}),\n\tallowed_domains: Type.Optional(\n\t\tType.Array(Type.String({ minLength: 1 }), {\n\t\t\tdescription:\n\t\t\t\t\"Optional list of domains to include in search results (e.g. ['github.com', 'react.dev']). Cannot be used alongside blocked_domains.\",\n\t\t}),\n\t),\n\tblocked_domains: Type.Optional(\n\t\tType.Array(Type.String({ minLength: 1 }), {\n\t\t\tdescription:\n\t\t\t\t\"Optional list of domains to exclude from search results. Cannot be used alongside allowed_domains.\",\n\t\t}),\n\t),\n});\n\nexport type WebSearchParams = Static<typeof WebSearchParamsSchema>;\n\nexport function normalizeWebSearchParams(raw: unknown): WebSearchParams {\n\tif (typeof raw === \"string\") {\n\t\treturn { query: raw.trim() };\n\t}\n\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n\t\treturn { query: String(raw ?? \"\").trim() };\n\t}\n\n\tconst record = raw as Record<string, unknown>;\n\n\tlet query = \"\";\n\tif (typeof record.query === \"string\") {\n\t\tquery = record.query.trim();\n\t} else if (typeof record.q === \"string\") {\n\t\tquery = record.q.trim();\n\t} else if (typeof record.search_query === \"string\") {\n\t\tquery = record.search_query.trim();\n\t} else if (typeof record.prompt === \"string\") {\n\t\tquery = record.prompt.trim();\n\t}\n\n\tconst parseDomainArray = (val: unknown): string[] | undefined => {\n\t\tif (Array.isArray(val)) {\n\t\t\tconst list = val.map((x) => String(x).trim()).filter((x) => x.length > 0);\n\t\t\treturn list.length > 0 ? list : undefined;\n\t\t}\n\t\tif (typeof val === \"string\" && val.trim()) {\n\t\t\treturn [val.trim()];\n\t\t}\n\t\treturn undefined;\n\t};\n\n\tconst allowedDomains = parseDomainArray(record.allowed_domains ?? record.allowedDomains);\n\tconst blockedDomains = parseDomainArray(record.blocked_domains ?? record.blockedDomains);\n\n\treturn {\n\t\tquery,\n\t\t...(allowedDomains ? { allowed_domains: allowedDomains } : {}),\n\t\t// allowed_domains and blocked_domains are mutually exclusive; allowed_domains wins.\n\t\t...(blockedDomains && !allowedDomains ? { blocked_domains: blockedDomains } : {}),\n\t};\n}\n"]}
|