@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
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ This file records `@astralyn/pi` releases beginning with the first Fork-owned re
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.84.13] - 2026-09-01
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added a bundled `web_search` extension: a Pi-native tool for querying live web information through MiniMax and DeepSeek search engines. When both engines are configured, searches run concurrently and results are fused — URLs normalized (tracking parameters and fragments stripped), duplicates merged, and hits verified by both engines ranked first. The model-facing payload is Markdown containing up to 12 verified sources (snippets bounded to 200 characters each, dual-engine hits tagged), a DeepSeek synthesis section when available, and up to 8 related searches, followed by a requirement to cite a `Sources:` section. Optional `allowed_domains`/`blocked_domains` filters apply both in the upstream query and as a local post-filter on result URLs (subdomains included); when both are supplied, `allowed_domains` wins.
|
|
12
|
+
- Engine failures are independent: if one engine errors, the other's results are still returned, and the tool errors only when every configured engine fails. Requests time out after 60 seconds (MiniMax) or 90 seconds (DeepSeek), and upstream error bodies are truncated to 200 characters. MiniMax search uses the Coding Plan Search endpoint (`POST /v1/coding_plan/search`), which requires a Coding Plan key; DeepSeek search goes through the Anthropic-compatible endpoint with the server-side `web_search_20250305` tool.
|
|
13
|
+
- Keys resolve per provider through the runtime's canonical auth chain (`ModelRuntime.getAuth`: runtime API key → `auth.json` → `models.json` → environment), with a direct `auth.json` fallback (provider IDs `minimax-cn`, `minimax`, `deepseek`, including command-configured keys such as `"!op read ..."`) and then the `MINIMAX_CN_API_KEY`, `MINIMAX_API_KEY`, and `DEEPSEEK_API_KEY` environment variables. The MiniMax CN account is preferred and selects the `api.minimaxi.com` search host; the global account uses `api.minimax.io`, overridable via `MINIMAX_API_HOST`. Configure with `/login minimax-cn` or `/login deepseek`.
|
|
14
|
+
- When no key is configured, `web_search` is removed from the model's tool set and system prompt entirely — the model never sees or calls it. This is re-evaluated on every session start (`startup`, `/new`, `/reload`, resume, fork), so a mid-session `/login` takes effect from the next session onward. The tool stays registered: it remains listed in `/tools` (where it can be force-enabled, in which case calls fail with the disabled message), and historical sessions render normally.
|
|
15
|
+
- Presentation is Pi-native: the call line shows the query and active domain filters, in-flight renders show the engine label with elapsed time past 2s, the collapsed result shows hit count, engines, duration, and a top-domain preview, and expanding renders the result sections from `details` — without the model-facing agent directives — falling back to the raw payload for legacy entries.
|
|
16
|
+
|
|
7
17
|
## [0.84.12] - 2026-08-31
|
|
8
18
|
|
|
9
19
|
### Upstream sync: Pi v0.84.4
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAWnE,eAAO,MAAM,iBAAiB,EAAE,eAAe,EAU9C,CAAC"}
|
package/dist/extensions/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import routerExtension from "./router/index.js";
|
|
|
6
6
|
import statuslineExtension from "./statusline/index.js";
|
|
7
7
|
import subagentExtension from "./subagent/index.js";
|
|
8
8
|
import todoExtension from "./todo/index.js";
|
|
9
|
+
import webSearchExtension from "./web-search/index.js";
|
|
9
10
|
export const builtInExtensions = [
|
|
10
11
|
{ name: "llama.cpp", factory: llamaExtension, hidden: true },
|
|
11
12
|
{ name: "background", factory: backgroundExtension, hidden: true },
|
|
@@ -15,5 +16,6 @@ export const builtInExtensions = [
|
|
|
15
16
|
{ name: "statusline", factory: statuslineExtension, hidden: true },
|
|
16
17
|
{ name: "subagent", factory: subagentExtension, hidden: true },
|
|
17
18
|
{ name: "todo", factory: todoExtension, hidden: true },
|
|
19
|
+
{ name: "web_search", factory: webSearchExtension, hidden: true },
|
|
18
20
|
];
|
|
19
21
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AACA,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,aAAa,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AACA,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAC5C,OAAO,kBAAkB,MAAM,uBAAuB,CAAC;AAEvD,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IACnD,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE;IAC5D,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,IAAI,EAAE;IAClE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9D,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9D,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE;IAC1D,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,IAAI,EAAE;IAClE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9D,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE;IACtD,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,IAAI,EAAE;CACjE,CAAC","sourcesContent":["import type { InlineExtension } from \"../core/extensions/types.ts\";\nimport backgroundExtension from \"./background/index.ts\";\nimport deepwikiExtension from \"./deepwiki/index.ts\";\nimport llamaExtension from \"./llama/index.ts\";\nimport questionExtension from \"./question/index.ts\";\nimport routerExtension from \"./router/index.ts\";\nimport statuslineExtension from \"./statusline/index.ts\";\nimport subagentExtension from \"./subagent/index.ts\";\nimport todoExtension from \"./todo/index.ts\";\nimport webSearchExtension from \"./web-search/index.ts\";\n\nexport const builtInExtensions: InlineExtension[] = [\n\t{ name: \"llama.cpp\", factory: llamaExtension, hidden: true },\n\t{ name: \"background\", factory: backgroundExtension, hidden: true },\n\t{ name: \"deepwiki\", factory: deepwikiExtension, hidden: true },\n\t{ name: \"question\", factory: questionExtension, hidden: true },\n\t{ name: \"router\", factory: routerExtension, hidden: true },\n\t{ name: \"statusline\", factory: statuslineExtension, hidden: true },\n\t{ name: \"subagent\", factory: subagentExtension, hidden: true },\n\t{ name: \"todo\", factory: todoExtension, hidden: true },\n\t{ name: \"web_search\", factory: webSearchExtension, hidden: true },\n];\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auth.ts — Credential discovery for web_search, plus the engine label derived from it.
|
|
3
|
+
*/
|
|
4
|
+
import type { ModelRuntime } from "../../core/model-runtime.ts";
|
|
5
|
+
import type { ResolvedSearchCredentials, SearchEngineType } from "./types.ts";
|
|
6
|
+
/** The engine set implied by the configured credentials. */
|
|
7
|
+
export declare function configuredEngine(credentials: ResolvedSearchCredentials): SearchEngineType;
|
|
8
|
+
/**
|
|
9
|
+
* Discover and resolve current active search engine credentials.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveSearchCredentials(modelRuntime?: ModelRuntime, authPath?: string): Promise<ResolvedSearchCredentials>;
|
|
12
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,OAAO,KAAK,EAA2B,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAkFvG,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,yBAAyB,GAAG,gBAAgB,CAKzF;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAC7C,YAAY,CAAC,EAAE,YAAY,EAC3B,QAAQ,GAAE,MAAsB,GAC9B,OAAO,CAAC,yBAAyB,CAAC,CAMpC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auth.ts — Credential discovery for web_search, plus the engine label derived from it.
|
|
3
|
+
*/
|
|
4
|
+
import { getAuthPath } from "../../config.js";
|
|
5
|
+
import { readStoredCredential } from "../../core/auth-storage.js";
|
|
6
|
+
import { isCommandConfigValue, resolveConfigValue } from "../../core/resolve-config-value.js";
|
|
7
|
+
const MINIMAX_CN_HOST = "https://api.minimaxi.com";
|
|
8
|
+
const MINIMAX_GLOBAL_HOST = "https://api.minimax.io";
|
|
9
|
+
function trimValue(value) {
|
|
10
|
+
const trimmed = value?.trim();
|
|
11
|
+
return trimmed || undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a provider API key through the runtime's canonical auth chain
|
|
15
|
+
* (runtime key → auth.json → models.json → environment). Returns undefined
|
|
16
|
+
* when the provider is unknown to the runtime or nothing is configured.
|
|
17
|
+
*/
|
|
18
|
+
async function runtimeApiKey(modelRuntime, providerId) {
|
|
19
|
+
if (!modelRuntime)
|
|
20
|
+
return undefined;
|
|
21
|
+
try {
|
|
22
|
+
return trimValue((await modelRuntime.getAuth(providerId))?.auth.apiKey);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Direct auth.json read for contexts without a ModelRuntime. Resolves
|
|
30
|
+
* command-configured keys (e.g. "!op read ...") like AuthStorage does.
|
|
31
|
+
*/
|
|
32
|
+
function storedApiKey(providerId, authPath) {
|
|
33
|
+
const credential = readStoredCredential(providerId, authPath);
|
|
34
|
+
if (!credential || credential.type !== "api_key" || typeof credential.key !== "string")
|
|
35
|
+
return undefined;
|
|
36
|
+
const key = isCommandConfigValue(credential.key)
|
|
37
|
+
? resolveConfigValue(credential.key, credential.env)
|
|
38
|
+
: credential.key;
|
|
39
|
+
return trimValue(key);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve one provider's API key. The runtime chain already covers auth.json,
|
|
43
|
+
* models.json, and environment variables; the direct file/env read is the
|
|
44
|
+
* fallback for contexts without a ModelRuntime (or a provider it doesn't know).
|
|
45
|
+
*/
|
|
46
|
+
async function resolveProviderKey(modelRuntime, authPath, providerId, envVar) {
|
|
47
|
+
return ((await runtimeApiKey(modelRuntime, providerId)) ??
|
|
48
|
+
storedApiKey(providerId, authPath) ??
|
|
49
|
+
trimValue(process.env[envVar]));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve MiniMax search credentials, preferring the CN account over global.
|
|
53
|
+
*/
|
|
54
|
+
async function resolveMiniMaxCredential(modelRuntime, authPath) {
|
|
55
|
+
const cnKey = await resolveProviderKey(modelRuntime, authPath, "minimax-cn", "MINIMAX_CN_API_KEY");
|
|
56
|
+
if (cnKey)
|
|
57
|
+
return { key: cnKey, host: MINIMAX_CN_HOST };
|
|
58
|
+
const globalKey = await resolveProviderKey(modelRuntime, authPath, "minimax", "MINIMAX_API_KEY");
|
|
59
|
+
if (globalKey)
|
|
60
|
+
return { key: globalKey, host: trimValue(process.env.MINIMAX_API_HOST) ?? MINIMAX_GLOBAL_HOST };
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve DeepSeek search credentials.
|
|
65
|
+
*/
|
|
66
|
+
async function resolveDeepSeekCredential(modelRuntime, authPath) {
|
|
67
|
+
const key = await resolveProviderKey(modelRuntime, authPath, "deepseek", "DEEPSEEK_API_KEY");
|
|
68
|
+
return key ? { key } : undefined;
|
|
69
|
+
}
|
|
70
|
+
/** The engine set implied by the configured credentials. */
|
|
71
|
+
export function configuredEngine(credentials) {
|
|
72
|
+
if (credentials.minimax && credentials.deepseek)
|
|
73
|
+
return "dual";
|
|
74
|
+
if (credentials.minimax)
|
|
75
|
+
return "minimax";
|
|
76
|
+
if (credentials.deepseek)
|
|
77
|
+
return "deepseek";
|
|
78
|
+
return "none";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Discover and resolve current active search engine credentials.
|
|
82
|
+
*/
|
|
83
|
+
export async function resolveSearchCredentials(modelRuntime, authPath = getAuthPath()) {
|
|
84
|
+
const [minimax, deepseek] = await Promise.all([
|
|
85
|
+
resolveMiniMaxCredential(modelRuntime, authPath),
|
|
86
|
+
resolveDeepSeekCredential(modelRuntime, authPath),
|
|
87
|
+
]);
|
|
88
|
+
return { minimax, deepseek };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/extensions/web-search/auth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * auth.ts — Credential discovery for web_search, plus the engine label derived from it.\n */\n\nimport { getAuthPath } from \"../../config.ts\";\nimport { readStoredCredential } from \"../../core/auth-storage.ts\";\nimport type { ModelRuntime } from \"../../core/model-runtime.ts\";\nimport { isCommandConfigValue, resolveConfigValue } from \"../../core/resolve-config-value.ts\";\nimport type { MiniMaxSearchCredential, ResolvedSearchCredentials, SearchEngineType } from \"./types.ts\";\n\nconst MINIMAX_CN_HOST = \"https://api.minimaxi.com\";\nconst MINIMAX_GLOBAL_HOST = \"https://api.minimax.io\";\n\nfunction trimValue(value: string | undefined): string | undefined {\n\tconst trimmed = value?.trim();\n\treturn trimmed || undefined;\n}\n\n/**\n * Resolve a provider API key through the runtime's canonical auth chain\n * (runtime key → auth.json → models.json → environment). Returns undefined\n * when the provider is unknown to the runtime or nothing is configured.\n */\nasync function runtimeApiKey(modelRuntime: ModelRuntime | undefined, providerId: string): Promise<string | undefined> {\n\tif (!modelRuntime) return undefined;\n\ttry {\n\t\treturn trimValue((await modelRuntime.getAuth(providerId))?.auth.apiKey);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Direct auth.json read for contexts without a ModelRuntime. Resolves\n * command-configured keys (e.g. \"!op read ...\") like AuthStorage does.\n */\nfunction storedApiKey(providerId: string, authPath: string): string | undefined {\n\tconst credential = readStoredCredential(providerId, authPath);\n\tif (!credential || credential.type !== \"api_key\" || typeof credential.key !== \"string\") return undefined;\n\tconst key = isCommandConfigValue(credential.key)\n\t\t? resolveConfigValue(credential.key, credential.env)\n\t\t: credential.key;\n\treturn trimValue(key);\n}\n\n/**\n * Resolve one provider's API key. The runtime chain already covers auth.json,\n * models.json, and environment variables; the direct file/env read is the\n * fallback for contexts without a ModelRuntime (or a provider it doesn't know).\n */\nasync function resolveProviderKey(\n\tmodelRuntime: ModelRuntime | undefined,\n\tauthPath: string,\n\tproviderId: string,\n\tenvVar: string,\n): Promise<string | undefined> {\n\treturn (\n\t\t(await runtimeApiKey(modelRuntime, providerId)) ??\n\t\tstoredApiKey(providerId, authPath) ??\n\t\ttrimValue(process.env[envVar])\n\t);\n}\n\n/**\n * Resolve MiniMax search credentials, preferring the CN account over global.\n */\nasync function resolveMiniMaxCredential(\n\tmodelRuntime: ModelRuntime | undefined,\n\tauthPath: string,\n): Promise<MiniMaxSearchCredential | undefined> {\n\tconst cnKey = await resolveProviderKey(modelRuntime, authPath, \"minimax-cn\", \"MINIMAX_CN_API_KEY\");\n\tif (cnKey) return { key: cnKey, host: MINIMAX_CN_HOST };\n\n\tconst globalKey = await resolveProviderKey(modelRuntime, authPath, \"minimax\", \"MINIMAX_API_KEY\");\n\tif (globalKey) return { key: globalKey, host: trimValue(process.env.MINIMAX_API_HOST) ?? MINIMAX_GLOBAL_HOST };\n\n\treturn undefined;\n}\n\n/**\n * Resolve DeepSeek search credentials.\n */\nasync function resolveDeepSeekCredential(\n\tmodelRuntime: ModelRuntime | undefined,\n\tauthPath: string,\n): Promise<{ key: string } | undefined> {\n\tconst key = await resolveProviderKey(modelRuntime, authPath, \"deepseek\", \"DEEPSEEK_API_KEY\");\n\treturn key ? { key } : undefined;\n}\n\n/** The engine set implied by the configured credentials. */\nexport function configuredEngine(credentials: ResolvedSearchCredentials): SearchEngineType {\n\tif (credentials.minimax && credentials.deepseek) return \"dual\";\n\tif (credentials.minimax) return \"minimax\";\n\tif (credentials.deepseek) return \"deepseek\";\n\treturn \"none\";\n}\n\n/**\n * Discover and resolve current active search engine credentials.\n */\nexport async function resolveSearchCredentials(\n\tmodelRuntime?: ModelRuntime,\n\tauthPath: string = getAuthPath(),\n): Promise<ResolvedSearchCredentials> {\n\tconst [minimax, deepseek] = await Promise.all([\n\t\tresolveMiniMaxCredential(modelRuntime, authPath),\n\t\tresolveDeepSeekCredential(modelRuntime, authPath),\n\t]);\n\treturn { minimax, deepseek };\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* constants.ts — web_search tool identity, descriptions, and model-facing guidelines.
|
|
3
|
+
*/
|
|
4
|
+
import type { SearchEngineType } from "./types.ts";
|
|
5
|
+
export declare const WEB_SEARCH_TOOL_NAME = "web_search";
|
|
6
|
+
export declare const WEB_SEARCH_LABEL = "Web Search";
|
|
7
|
+
export declare const WEB_SEARCH_DESCRIPTION = "Search the web for real-time information, documentation, libraries, technical references, and current events beyond the model's knowledge cutoff. Supports domain filtering with allowed_domains and blocked_domains.";
|
|
8
|
+
export declare const WEB_SEARCH_PROMPT_SNIPPET = "Search the web for real-time information, documentation, and external references";
|
|
9
|
+
export declare const WEB_SEARCH_DISABLED_MESSAGE = "Web search is disabled: Neither MiniMax nor DeepSeek API Key was found in auth.json or environment variables. Please add credentials via auth.json or environment variables (MINIMAX_API_KEY, DEEPSEEK_API_KEY).";
|
|
10
|
+
/** Canonical display label for the contributing engine set ("" when none). */
|
|
11
|
+
export declare function getEngineLabel(engine: SearchEngineType | undefined): string;
|
|
12
|
+
export declare function getWebSearchPromptGuidelines(): string[];
|
|
13
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAE7C,eAAO,MAAM,sBAAsB,0NACqL,CAAC;AAEzN,eAAO,MAAM,yBAAyB,qFAC6C,CAAC;AAEpF,eAAO,MAAM,2BAA2B,qNAC2K,CAAC;AAEpN,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,MAAM,CAK3E;AAED,wBAAgB,4BAA4B,IAAI,MAAM,EAAE,CAOvD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* constants.ts — web_search tool identity, descriptions, and model-facing guidelines.
|
|
3
|
+
*/
|
|
4
|
+
export const WEB_SEARCH_TOOL_NAME = "web_search";
|
|
5
|
+
export const WEB_SEARCH_LABEL = "Web Search";
|
|
6
|
+
export const WEB_SEARCH_DESCRIPTION = "Search the web for real-time information, documentation, libraries, technical references, and current events beyond the model's knowledge cutoff. Supports domain filtering with allowed_domains and blocked_domains.";
|
|
7
|
+
export const WEB_SEARCH_PROMPT_SNIPPET = "Search the web for real-time information, documentation, and external references";
|
|
8
|
+
export const WEB_SEARCH_DISABLED_MESSAGE = "Web search is disabled: Neither MiniMax nor DeepSeek API Key was found in auth.json or environment variables. Please add credentials via auth.json or environment variables (MINIMAX_API_KEY, DEEPSEEK_API_KEY).";
|
|
9
|
+
/** Canonical display label for the contributing engine set ("" when none). */
|
|
10
|
+
export function getEngineLabel(engine) {
|
|
11
|
+
if (engine === "dual")
|
|
12
|
+
return "MiniMax & DeepSeek";
|
|
13
|
+
if (engine === "minimax")
|
|
14
|
+
return "MiniMax";
|
|
15
|
+
if (engine === "deepseek")
|
|
16
|
+
return "DeepSeek";
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
export function getWebSearchPromptGuidelines() {
|
|
20
|
+
const currentYearMonth = new Date().toISOString().slice(0, 7);
|
|
21
|
+
return [
|
|
22
|
+
"Use `web_search` to query live web documentation, external technical issues, release notes, and real-time facts beyond your training cutoff.",
|
|
23
|
+
"CRITICAL REQUIREMENT: After answering the user's question, you MUST include a 'Sources:' section at the end of your response listing all relevant URLs from the search results as markdown hyperlinks: [Title](URL).",
|
|
24
|
+
`IMPORTANT: The current date is ${currentYearMonth}. Use the current year in search queries when seeking recent documentation, releases, or current events.`,
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/extensions/web-search/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAAC;AACjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAE7C,MAAM,CAAC,MAAM,sBAAsB,GAClC,uNAAuN,CAAC;AAEzN,MAAM,CAAC,MAAM,yBAAyB,GACrC,kFAAkF,CAAC;AAEpF,MAAM,CAAC,MAAM,2BAA2B,GACvC,kNAAkN,CAAC;AAEpN,8EAA8E;AAC9E,MAAM,UAAU,cAAc,CAAC,MAAoC;IAClE,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,oBAAoB,CAAC;IACnD,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,MAAM,KAAK,UAAU;QAAE,OAAO,UAAU,CAAC;IAC7C,OAAO,EAAE,CAAC;AACX,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC3C,MAAM,gBAAgB,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9D,OAAO;QACN,8IAA8I;QAC9I,sNAAsN;QACtN,kCAAkC,gBAAgB,0GAA0G;KAC5J,CAAC;AACH,CAAC","sourcesContent":["/**\n * constants.ts — web_search tool identity, descriptions, and model-facing guidelines.\n */\n\nimport type { SearchEngineType } from \"./types.ts\";\n\nexport const WEB_SEARCH_TOOL_NAME = \"web_search\";\nexport const WEB_SEARCH_LABEL = \"Web Search\";\n\nexport const WEB_SEARCH_DESCRIPTION =\n\t\"Search the web for real-time information, documentation, libraries, technical references, and current events beyond the model's knowledge cutoff. Supports domain filtering with allowed_domains and blocked_domains.\";\n\nexport const WEB_SEARCH_PROMPT_SNIPPET =\n\t\"Search the web for real-time information, documentation, and external references\";\n\nexport const WEB_SEARCH_DISABLED_MESSAGE =\n\t\"Web search is disabled: Neither MiniMax nor DeepSeek API Key was found in auth.json or environment variables. Please add credentials via auth.json or environment variables (MINIMAX_API_KEY, DEEPSEEK_API_KEY).\";\n\n/** Canonical display label for the contributing engine set (\"\" when none). */\nexport function getEngineLabel(engine: SearchEngineType | undefined): string {\n\tif (engine === \"dual\") return \"MiniMax & DeepSeek\";\n\tif (engine === \"minimax\") return \"MiniMax\";\n\tif (engine === \"deepseek\") return \"DeepSeek\";\n\treturn \"\";\n}\n\nexport function getWebSearchPromptGuidelines(): string[] {\n\tconst currentYearMonth = new Date().toISOString().slice(0, 7);\n\treturn [\n\t\t\"Use `web_search` to query live web documentation, external technical issues, release notes, and real-time facts beyond your training cutoff.\",\n\t\t\"CRITICAL REQUIREMENT: After answering the user's question, you MUST include a 'Sources:' section at the end of your response listing all relevant URLs from the search results as markdown hyperlinks: [Title](URL).\",\n\t\t`IMPORTANT: The current date is ${currentYearMonth}. Use the current year in search queries when seeking recent documentation, releases, or current events.`,\n\t];\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fusion.ts — Dual search engine concurrency dispatcher, URL deduplication, ranking, and formatter.
|
|
3
|
+
*/
|
|
4
|
+
import type { AgentToolUpdateCallback } from "../../core/extensions/types.ts";
|
|
5
|
+
import type { WebSearchParams } from "./schema.ts";
|
|
6
|
+
import type { ProviderSearchResult, ResolvedSearchCredentials, WebSearchDetails, WebSearchHit } from "./types.ts";
|
|
7
|
+
/**
|
|
8
|
+
* Normalize URL by removing common tracking parameters and fragments.
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeUrl(rawUrl: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Deduplicate and rank search hits from multiple search engines.
|
|
13
|
+
*/
|
|
14
|
+
export declare function fuseSearchHits(results: ProviderSearchResult[]): {
|
|
15
|
+
hits: WebSearchHit[];
|
|
16
|
+
relatedSearches?: string[];
|
|
17
|
+
deepseekSynthesis?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Sources, synthesis, and related-searches sections. Shared by the model-facing
|
|
21
|
+
* payload (formatSearchOutput) and the expanded TUI view, which adds no agent
|
|
22
|
+
* directives of its own.
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatResultsMarkdown(details: WebSearchDetails): string;
|
|
25
|
+
/**
|
|
26
|
+
* Format search results into a clean, structured Markdown payload for the Main Agent.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatSearchOutput(query: string, details: WebSearchDetails): string;
|
|
29
|
+
/**
|
|
30
|
+
* Execute web search across MiniMax and/or DeepSeek based on configured credentials.
|
|
31
|
+
*/
|
|
32
|
+
export declare function executeWebSearch(params: WebSearchParams, credentials: ResolvedSearchCredentials, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<WebSearchDetails>): Promise<{
|
|
33
|
+
formattedOutput: string;
|
|
34
|
+
details: WebSearchDetails;
|
|
35
|
+
}>;
|
|
36
|
+
//# sourceMappingURL=fusion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fusion.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/fusion.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAK9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EACX,oBAAoB,EACpB,yBAAyB,EAEzB,gBAAgB,EAChB,YAAY,EACZ,MAAM,YAAY,CAAC;AAMpB;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA6BnD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG;IAChE,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAwDA;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAiCvE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAmBnF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACrC,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,yBAAyB,EACtC,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,uBAAuB,CAAC,gBAAgB,CAAC,GAClD,OAAO,CAAC;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAkIjE"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fusion.ts — Dual search engine concurrency dispatcher, URL deduplication, ranking, and formatter.
|
|
3
|
+
*/
|
|
4
|
+
import { configuredEngine } from "./auth.js";
|
|
5
|
+
import { getEngineLabel, WEB_SEARCH_DISABLED_MESSAGE } from "./constants.js";
|
|
6
|
+
import { searchDeepSeek } from "./providers/deepseek.js";
|
|
7
|
+
import { searchMiniMax } from "./providers/minimax.js";
|
|
8
|
+
const MAX_OUTPUT_HITS = 12;
|
|
9
|
+
const MAX_SNIPPET_LENGTH = 200;
|
|
10
|
+
const MAX_RELATED_SEARCHES = 8;
|
|
11
|
+
/**
|
|
12
|
+
* Normalize URL by removing common tracking parameters and fragments.
|
|
13
|
+
*/
|
|
14
|
+
export function normalizeUrl(rawUrl) {
|
|
15
|
+
try {
|
|
16
|
+
const u = new URL(rawUrl);
|
|
17
|
+
// Remove hash
|
|
18
|
+
u.hash = "";
|
|
19
|
+
// Remove tracking query parameters
|
|
20
|
+
const trackingParams = [
|
|
21
|
+
"utm_source",
|
|
22
|
+
"utm_medium",
|
|
23
|
+
"utm_campaign",
|
|
24
|
+
"utm_term",
|
|
25
|
+
"utm_content",
|
|
26
|
+
"ref",
|
|
27
|
+
"fbclid",
|
|
28
|
+
"gclid",
|
|
29
|
+
"trk",
|
|
30
|
+
"gi",
|
|
31
|
+
];
|
|
32
|
+
for (const p of trackingParams) {
|
|
33
|
+
u.searchParams.delete(p);
|
|
34
|
+
}
|
|
35
|
+
let href = u.toString();
|
|
36
|
+
if (href.endsWith("/") && u.pathname !== "/") {
|
|
37
|
+
href = href.slice(0, -1);
|
|
38
|
+
}
|
|
39
|
+
return href;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return rawUrl.trim();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Deduplicate and rank search hits from multiple search engines.
|
|
47
|
+
*/
|
|
48
|
+
export function fuseSearchHits(results) {
|
|
49
|
+
const hitMap = new Map();
|
|
50
|
+
const relatedSet = new Set();
|
|
51
|
+
let deepseekSynthesis;
|
|
52
|
+
for (const result of results) {
|
|
53
|
+
if (result.relatedSearches) {
|
|
54
|
+
for (const r of result.relatedSearches) {
|
|
55
|
+
if (relatedSet.size >= MAX_RELATED_SEARCHES)
|
|
56
|
+
break;
|
|
57
|
+
if (r?.trim())
|
|
58
|
+
relatedSet.add(r.trim());
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (result.synthesisText) {
|
|
62
|
+
deepseekSynthesis = result.synthesisText;
|
|
63
|
+
}
|
|
64
|
+
for (const hit of result.hits) {
|
|
65
|
+
const normalized = normalizeUrl(hit.url);
|
|
66
|
+
const existing = hitMap.get(normalized);
|
|
67
|
+
if (existing) {
|
|
68
|
+
for (const s of hit.sources) {
|
|
69
|
+
if (!existing.sources.includes(s)) {
|
|
70
|
+
existing.sources.push(s);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Prefer longer snippet or date if missing
|
|
74
|
+
if ((!existing.snippet || existing.snippet.length < (hit.snippet?.length || 0)) && hit.snippet) {
|
|
75
|
+
existing.snippet = hit.snippet;
|
|
76
|
+
}
|
|
77
|
+
if (!existing.date && hit.date) {
|
|
78
|
+
existing.date = hit.date;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
hitMap.set(normalized, {
|
|
83
|
+
...hit,
|
|
84
|
+
url: normalized,
|
|
85
|
+
sources: [...hit.sources],
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Sort hits: items supported by multiple engines first, then preserve discovery order
|
|
91
|
+
const fused = Array.from(hitMap.values()).sort((a, b) => {
|
|
92
|
+
if (b.sources.length !== a.sources.length) {
|
|
93
|
+
return b.sources.length - a.sources.length;
|
|
94
|
+
}
|
|
95
|
+
return 0;
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
hits: fused.slice(0, MAX_OUTPUT_HITS),
|
|
99
|
+
relatedSearches: relatedSet.size > 0 ? Array.from(relatedSet) : undefined,
|
|
100
|
+
deepseekSynthesis,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Sources, synthesis, and related-searches sections. Shared by the model-facing
|
|
105
|
+
* payload (formatSearchOutput) and the expanded TUI view, which adds no agent
|
|
106
|
+
* directives of its own.
|
|
107
|
+
*/
|
|
108
|
+
export function formatResultsMarkdown(details) {
|
|
109
|
+
const parts = [];
|
|
110
|
+
if (details.hits.length > 0) {
|
|
111
|
+
parts.push(`## Verified Web Sources (${details.hits.length} found via ${getEngineLabel(details.engine)})\n`);
|
|
112
|
+
details.hits.forEach((hit, idx) => {
|
|
113
|
+
const sourceTag = hit.sources.length > 1 ? ` — *(verified by ${hit.sources.join(" & ")})*` : "";
|
|
114
|
+
parts.push(`${idx + 1}. **[${hit.title}](${hit.url})**${sourceTag}`);
|
|
115
|
+
if (hit.snippet) {
|
|
116
|
+
const truncated = hit.snippet.length > MAX_SNIPPET_LENGTH
|
|
117
|
+
? `${hit.snippet.slice(0, MAX_SNIPPET_LENGTH).trim()}...`
|
|
118
|
+
: hit.snippet.trim();
|
|
119
|
+
parts.push(` - ${truncated}`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
parts.push("");
|
|
123
|
+
}
|
|
124
|
+
if (details.deepseekSynthesis) {
|
|
125
|
+
parts.push("## Key Technical Insights & Synthesis\n");
|
|
126
|
+
parts.push(details.deepseekSynthesis.trim());
|
|
127
|
+
parts.push("");
|
|
128
|
+
}
|
|
129
|
+
if (details.relatedSearches && details.relatedSearches.length > 0) {
|
|
130
|
+
parts.push("## Related Searches\n");
|
|
131
|
+
parts.push(details.relatedSearches.map((r) => `- ${r}`).join("\n"));
|
|
132
|
+
parts.push("");
|
|
133
|
+
}
|
|
134
|
+
return parts.join("\n");
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Format search results into a clean, structured Markdown payload for the Main Agent.
|
|
138
|
+
*/
|
|
139
|
+
export function formatSearchOutput(query, details) {
|
|
140
|
+
if (details.status === "disabled") {
|
|
141
|
+
return WEB_SEARCH_DISABLED_MESSAGE;
|
|
142
|
+
}
|
|
143
|
+
if (details.status === "error") {
|
|
144
|
+
return `Web search failed for "${query}": ${details.errorMessage || "Unknown search error"}`;
|
|
145
|
+
}
|
|
146
|
+
if (details.hits.length === 0 && !details.deepseekSynthesis) {
|
|
147
|
+
return `No search results found for "${query}". Try rephrasing with different keywords.`;
|
|
148
|
+
}
|
|
149
|
+
return [
|
|
150
|
+
`# Web Search Results for: "${query}"\n`,
|
|
151
|
+
formatResultsMarkdown(details),
|
|
152
|
+
"---",
|
|
153
|
+
"**CRITICAL REQUIREMENT FOR MAIN AGENT:** Use the above search results to inform your response. You MUST include a `Sources:` section at the end of your response listing the relevant URLs as markdown links: `[Title](URL)`.",
|
|
154
|
+
].join("\n");
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Execute web search across MiniMax and/or DeepSeek based on configured credentials.
|
|
158
|
+
*/
|
|
159
|
+
export async function executeWebSearch(params, credentials, signal, onUpdate) {
|
|
160
|
+
const startTime = Date.now();
|
|
161
|
+
const query = params.query.trim();
|
|
162
|
+
const engine = configuredEngine(credentials);
|
|
163
|
+
if (!query) {
|
|
164
|
+
const details = {
|
|
165
|
+
query: "",
|
|
166
|
+
durationMs: 0,
|
|
167
|
+
status: "error",
|
|
168
|
+
engine,
|
|
169
|
+
totalHits: 0,
|
|
170
|
+
hits: [],
|
|
171
|
+
errorMessage: "Search query must not be empty.",
|
|
172
|
+
};
|
|
173
|
+
return {
|
|
174
|
+
formattedOutput: formatSearchOutput(query, details),
|
|
175
|
+
details,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (engine === "none") {
|
|
179
|
+
const details = {
|
|
180
|
+
query,
|
|
181
|
+
durationMs: 0,
|
|
182
|
+
status: "disabled",
|
|
183
|
+
engine: "none",
|
|
184
|
+
totalHits: 0,
|
|
185
|
+
hits: [],
|
|
186
|
+
errorMessage: WEB_SEARCH_DISABLED_MESSAGE,
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
formattedOutput: formatSearchOutput(query, details),
|
|
190
|
+
details,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// Send initial progress update
|
|
194
|
+
onUpdate?.({
|
|
195
|
+
content: [{ type: "text", text: `Searching for "${query}" via ${getEngineLabel(engine)}...` }],
|
|
196
|
+
details: {
|
|
197
|
+
query,
|
|
198
|
+
durationMs: 0,
|
|
199
|
+
status: "success",
|
|
200
|
+
engine,
|
|
201
|
+
totalHits: 0,
|
|
202
|
+
hits: [],
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
const searchPromises = [];
|
|
206
|
+
if (credentials.minimax) {
|
|
207
|
+
searchPromises.push(searchMiniMax({
|
|
208
|
+
query,
|
|
209
|
+
apiKey: credentials.minimax.key,
|
|
210
|
+
apiHost: credentials.minimax.host,
|
|
211
|
+
allowedDomains: params.allowed_domains,
|
|
212
|
+
blockedDomains: params.blocked_domains,
|
|
213
|
+
signal,
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
if (credentials.deepseek) {
|
|
217
|
+
searchPromises.push(searchDeepSeek({
|
|
218
|
+
query,
|
|
219
|
+
apiKey: credentials.deepseek.key,
|
|
220
|
+
allowedDomains: params.allowed_domains,
|
|
221
|
+
blockedDomains: params.blocked_domains,
|
|
222
|
+
signal,
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
const settledResults = await Promise.allSettled(searchPromises);
|
|
226
|
+
const durationMs = Date.now() - startTime;
|
|
227
|
+
const successfulResults = [];
|
|
228
|
+
const errors = [];
|
|
229
|
+
for (const res of settledResults) {
|
|
230
|
+
if (res.status === "fulfilled") {
|
|
231
|
+
successfulResults.push(res.value);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
errors.push(res.reason instanceof Error ? res.reason.message : String(res.reason));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// If all engines failed
|
|
238
|
+
if (successfulResults.length === 0) {
|
|
239
|
+
const details = {
|
|
240
|
+
query,
|
|
241
|
+
durationMs,
|
|
242
|
+
status: "error",
|
|
243
|
+
engine,
|
|
244
|
+
totalHits: 0,
|
|
245
|
+
hits: [],
|
|
246
|
+
errorMessage: errors.join(" | ") || "All search engines failed",
|
|
247
|
+
};
|
|
248
|
+
return {
|
|
249
|
+
formattedOutput: formatSearchOutput(query, details),
|
|
250
|
+
details,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const { hits, relatedSearches, deepseekSynthesis } = fuseSearchHits(successfulResults);
|
|
254
|
+
// Badge reflects the engines that actually responded, not just configuration.
|
|
255
|
+
const respondedSources = new Set(successfulResults.map((r) => r.source));
|
|
256
|
+
const contributingEngine = respondedSources.size > 1 ? "dual" : respondedSources.has("MiniMax") ? "minimax" : "deepseek";
|
|
257
|
+
const details = {
|
|
258
|
+
query,
|
|
259
|
+
durationMs,
|
|
260
|
+
status: "success",
|
|
261
|
+
engine: contributingEngine,
|
|
262
|
+
totalHits: hits.length,
|
|
263
|
+
hits,
|
|
264
|
+
relatedSearches,
|
|
265
|
+
deepseekSynthesis,
|
|
266
|
+
};
|
|
267
|
+
return {
|
|
268
|
+
formattedOutput: formatSearchOutput(query, details),
|
|
269
|
+
details,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=fusion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fusion.js","sourceRoot":"","sources":["../../../src/extensions/web-search/fusion.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * fusion.ts — Dual search engine concurrency dispatcher, URL deduplication, ranking, and formatter.\n */\n\nimport type { AgentToolUpdateCallback } from \"../../core/extensions/types.ts\";\nimport { configuredEngine } from \"./auth.ts\";\nimport { getEngineLabel, WEB_SEARCH_DISABLED_MESSAGE } from \"./constants.ts\";\nimport { searchDeepSeek } from \"./providers/deepseek.ts\";\nimport { searchMiniMax } from \"./providers/minimax.ts\";\nimport type { WebSearchParams } from \"./schema.ts\";\nimport type {\n\tProviderSearchResult,\n\tResolvedSearchCredentials,\n\tSearchEngineType,\n\tWebSearchDetails,\n\tWebSearchHit,\n} from \"./types.ts\";\n\nconst MAX_OUTPUT_HITS = 12;\nconst MAX_SNIPPET_LENGTH = 200;\nconst MAX_RELATED_SEARCHES = 8;\n\n/**\n * Normalize URL by removing common tracking parameters and fragments.\n */\nexport function normalizeUrl(rawUrl: string): string {\n\ttry {\n\t\tconst u = new URL(rawUrl);\n\t\t// Remove hash\n\t\tu.hash = \"\";\n\t\t// Remove tracking query parameters\n\t\tconst trackingParams = [\n\t\t\t\"utm_source\",\n\t\t\t\"utm_medium\",\n\t\t\t\"utm_campaign\",\n\t\t\t\"utm_term\",\n\t\t\t\"utm_content\",\n\t\t\t\"ref\",\n\t\t\t\"fbclid\",\n\t\t\t\"gclid\",\n\t\t\t\"trk\",\n\t\t\t\"gi\",\n\t\t];\n\t\tfor (const p of trackingParams) {\n\t\t\tu.searchParams.delete(p);\n\t\t}\n\t\tlet href = u.toString();\n\t\tif (href.endsWith(\"/\") && u.pathname !== \"/\") {\n\t\t\thref = href.slice(0, -1);\n\t\t}\n\t\treturn href;\n\t} catch {\n\t\treturn rawUrl.trim();\n\t}\n}\n\n/**\n * Deduplicate and rank search hits from multiple search engines.\n */\nexport function fuseSearchHits(results: ProviderSearchResult[]): {\n\thits: WebSearchHit[];\n\trelatedSearches?: string[];\n\tdeepseekSynthesis?: string;\n} {\n\tconst hitMap = new Map<string, WebSearchHit>();\n\tconst relatedSet = new Set<string>();\n\tlet deepseekSynthesis: string | undefined;\n\n\tfor (const result of results) {\n\t\tif (result.relatedSearches) {\n\t\t\tfor (const r of result.relatedSearches) {\n\t\t\t\tif (relatedSet.size >= MAX_RELATED_SEARCHES) break;\n\t\t\t\tif (r?.trim()) relatedSet.add(r.trim());\n\t\t\t}\n\t\t}\n\t\tif (result.synthesisText) {\n\t\t\tdeepseekSynthesis = result.synthesisText;\n\t\t}\n\n\t\tfor (const hit of result.hits) {\n\t\t\tconst normalized = normalizeUrl(hit.url);\n\t\t\tconst existing = hitMap.get(normalized);\n\n\t\t\tif (existing) {\n\t\t\t\tfor (const s of hit.sources) {\n\t\t\t\t\tif (!existing.sources.includes(s)) {\n\t\t\t\t\t\texisting.sources.push(s);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Prefer longer snippet or date if missing\n\t\t\t\tif ((!existing.snippet || existing.snippet.length < (hit.snippet?.length || 0)) && hit.snippet) {\n\t\t\t\t\texisting.snippet = hit.snippet;\n\t\t\t\t}\n\t\t\t\tif (!existing.date && hit.date) {\n\t\t\t\t\texisting.date = hit.date;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\thitMap.set(normalized, {\n\t\t\t\t\t...hit,\n\t\t\t\t\turl: normalized,\n\t\t\t\t\tsources: [...hit.sources],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sort hits: items supported by multiple engines first, then preserve discovery order\n\tconst fused = Array.from(hitMap.values()).sort((a, b) => {\n\t\tif (b.sources.length !== a.sources.length) {\n\t\t\treturn b.sources.length - a.sources.length;\n\t\t}\n\t\treturn 0;\n\t});\n\n\treturn {\n\t\thits: fused.slice(0, MAX_OUTPUT_HITS),\n\t\trelatedSearches: relatedSet.size > 0 ? Array.from(relatedSet) : undefined,\n\t\tdeepseekSynthesis,\n\t};\n}\n\n/**\n * Sources, synthesis, and related-searches sections. Shared by the model-facing\n * payload (formatSearchOutput) and the expanded TUI view, which adds no agent\n * directives of its own.\n */\nexport function formatResultsMarkdown(details: WebSearchDetails): string {\n\tconst parts: string[] = [];\n\n\tif (details.hits.length > 0) {\n\t\tparts.push(`## Verified Web Sources (${details.hits.length} found via ${getEngineLabel(details.engine)})\\n`);\n\n\t\tdetails.hits.forEach((hit, idx) => {\n\t\t\tconst sourceTag = hit.sources.length > 1 ? ` — *(verified by ${hit.sources.join(\" & \")})*` : \"\";\n\t\t\tparts.push(`${idx + 1}. **[${hit.title}](${hit.url})**${sourceTag}`);\n\t\t\tif (hit.snippet) {\n\t\t\t\tconst truncated =\n\t\t\t\t\thit.snippet.length > MAX_SNIPPET_LENGTH\n\t\t\t\t\t\t? `${hit.snippet.slice(0, MAX_SNIPPET_LENGTH).trim()}...`\n\t\t\t\t\t\t: hit.snippet.trim();\n\t\t\t\tparts.push(` - ${truncated}`);\n\t\t\t}\n\t\t});\n\t\tparts.push(\"\");\n\t}\n\n\tif (details.deepseekSynthesis) {\n\t\tparts.push(\"## Key Technical Insights & Synthesis\\n\");\n\t\tparts.push(details.deepseekSynthesis.trim());\n\t\tparts.push(\"\");\n\t}\n\n\tif (details.relatedSearches && details.relatedSearches.length > 0) {\n\t\tparts.push(\"## Related Searches\\n\");\n\t\tparts.push(details.relatedSearches.map((r) => `- ${r}`).join(\"\\n\"));\n\t\tparts.push(\"\");\n\t}\n\n\treturn parts.join(\"\\n\");\n}\n\n/**\n * Format search results into a clean, structured Markdown payload for the Main Agent.\n */\nexport function formatSearchOutput(query: string, details: WebSearchDetails): string {\n\tif (details.status === \"disabled\") {\n\t\treturn WEB_SEARCH_DISABLED_MESSAGE;\n\t}\n\n\tif (details.status === \"error\") {\n\t\treturn `Web search failed for \"${query}\": ${details.errorMessage || \"Unknown search error\"}`;\n\t}\n\n\tif (details.hits.length === 0 && !details.deepseekSynthesis) {\n\t\treturn `No search results found for \"${query}\". Try rephrasing with different keywords.`;\n\t}\n\n\treturn [\n\t\t`# Web Search Results for: \"${query}\"\\n`,\n\t\tformatResultsMarkdown(details),\n\t\t\"---\",\n\t\t\"**CRITICAL REQUIREMENT FOR MAIN AGENT:** Use the above search results to inform your response. You MUST include a `Sources:` section at the end of your response listing the relevant URLs as markdown links: `[Title](URL)`.\",\n\t].join(\"\\n\");\n}\n\n/**\n * Execute web search across MiniMax and/or DeepSeek based on configured credentials.\n */\nexport async function executeWebSearch(\n\tparams: WebSearchParams,\n\tcredentials: ResolvedSearchCredentials,\n\tsignal?: AbortSignal,\n\tonUpdate?: AgentToolUpdateCallback<WebSearchDetails>,\n): Promise<{ formattedOutput: string; details: WebSearchDetails }> {\n\tconst startTime = Date.now();\n\tconst query = params.query.trim();\n\tconst engine = configuredEngine(credentials);\n\n\tif (!query) {\n\t\tconst details: WebSearchDetails = {\n\t\t\tquery: \"\",\n\t\t\tdurationMs: 0,\n\t\t\tstatus: \"error\",\n\t\t\tengine,\n\t\t\ttotalHits: 0,\n\t\t\thits: [],\n\t\t\terrorMessage: \"Search query must not be empty.\",\n\t\t};\n\t\treturn {\n\t\t\tformattedOutput: formatSearchOutput(query, details),\n\t\t\tdetails,\n\t\t};\n\t}\n\n\tif (engine === \"none\") {\n\t\tconst details: WebSearchDetails = {\n\t\t\tquery,\n\t\t\tdurationMs: 0,\n\t\t\tstatus: \"disabled\",\n\t\t\tengine: \"none\",\n\t\t\ttotalHits: 0,\n\t\t\thits: [],\n\t\t\terrorMessage: WEB_SEARCH_DISABLED_MESSAGE,\n\t\t};\n\t\treturn {\n\t\t\tformattedOutput: formatSearchOutput(query, details),\n\t\t\tdetails,\n\t\t};\n\t}\n\n\t// Send initial progress update\n\tonUpdate?.({\n\t\tcontent: [{ type: \"text\", text: `Searching for \"${query}\" via ${getEngineLabel(engine)}...` }],\n\t\tdetails: {\n\t\t\tquery,\n\t\t\tdurationMs: 0,\n\t\t\tstatus: \"success\",\n\t\t\tengine,\n\t\t\ttotalHits: 0,\n\t\t\thits: [],\n\t\t},\n\t});\n\n\tconst searchPromises: Promise<ProviderSearchResult>[] = [];\n\n\tif (credentials.minimax) {\n\t\tsearchPromises.push(\n\t\t\tsearchMiniMax({\n\t\t\t\tquery,\n\t\t\t\tapiKey: credentials.minimax.key,\n\t\t\t\tapiHost: credentials.minimax.host,\n\t\t\t\tallowedDomains: params.allowed_domains,\n\t\t\t\tblockedDomains: params.blocked_domains,\n\t\t\t\tsignal,\n\t\t\t}),\n\t\t);\n\t}\n\n\tif (credentials.deepseek) {\n\t\tsearchPromises.push(\n\t\t\tsearchDeepSeek({\n\t\t\t\tquery,\n\t\t\t\tapiKey: credentials.deepseek.key,\n\t\t\t\tallowedDomains: params.allowed_domains,\n\t\t\t\tblockedDomains: params.blocked_domains,\n\t\t\t\tsignal,\n\t\t\t}),\n\t\t);\n\t}\n\n\tconst settledResults = await Promise.allSettled(searchPromises);\n\tconst durationMs = Date.now() - startTime;\n\n\tconst successfulResults: ProviderSearchResult[] = [];\n\tconst errors: string[] = [];\n\n\tfor (const res of settledResults) {\n\t\tif (res.status === \"fulfilled\") {\n\t\t\tsuccessfulResults.push(res.value);\n\t\t} else {\n\t\t\terrors.push(res.reason instanceof Error ? res.reason.message : String(res.reason));\n\t\t}\n\t}\n\n\t// If all engines failed\n\tif (successfulResults.length === 0) {\n\t\tconst details: WebSearchDetails = {\n\t\t\tquery,\n\t\t\tdurationMs,\n\t\t\tstatus: \"error\",\n\t\t\tengine,\n\t\t\ttotalHits: 0,\n\t\t\thits: [],\n\t\t\terrorMessage: errors.join(\" | \") || \"All search engines failed\",\n\t\t};\n\t\treturn {\n\t\t\tformattedOutput: formatSearchOutput(query, details),\n\t\t\tdetails,\n\t\t};\n\t}\n\n\tconst { hits, relatedSearches, deepseekSynthesis } = fuseSearchHits(successfulResults);\n\n\t// Badge reflects the engines that actually responded, not just configuration.\n\tconst respondedSources = new Set(successfulResults.map((r) => r.source));\n\tconst contributingEngine: SearchEngineType =\n\t\trespondedSources.size > 1 ? \"dual\" : respondedSources.has(\"MiniMax\") ? \"minimax\" : \"deepseek\";\n\n\tconst details: WebSearchDetails = {\n\t\tquery,\n\t\tdurationMs,\n\t\tstatus: \"success\",\n\t\tengine: contributingEngine,\n\t\ttotalHits: hits.length,\n\t\thits,\n\t\trelatedSearches,\n\t\tdeepseekSynthesis,\n\t};\n\n\treturn {\n\t\tformattedOutput: formatSearchOutput(query, details),\n\t\tdetails,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* web_search — Pi-native Web Search extension combining MiniMax and DeepSeek search engines.
|
|
3
|
+
*/
|
|
4
|
+
import type { ExtensionAPI } from "../../core/extensions/types.ts";
|
|
5
|
+
export default function webSearch(pi: ExtensionAPI): void;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAqB,MAAM,gCAAgC,CAAC;AAuCtF,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA6CxD"}
|