@astralyn/pi 0.84.12 → 0.84.14
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 +28 -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 +26 -0
- package/dist/extensions/web-search/constants.js.map +1 -0
- package/dist/extensions/web-search/execute.d.ts +13 -0
- package/dist/extensions/web-search/execute.d.ts.map +1 -0
- package/dist/extensions/web-search/execute.js +101 -0
- package/dist/extensions/web-search/execute.js.map +1 -0
- package/dist/extensions/web-search/format.d.ts +12 -0
- package/dist/extensions/web-search/format.d.ts.map +1 -0
- package/dist/extensions/web-search/format.js +88 -0
- package/dist/extensions/web-search/format.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 +13 -0
- package/dist/extensions/web-search/providers/deepseek.d.ts.map +1 -0
- package/dist/extensions/web-search/providers/deepseek.js +80 -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 +87 -0
- package/dist/extensions/web-search/providers/http.js.map +1 -0
- package/dist/extensions/web-search/providers/minimax.d.ts +12 -0
- package/dist/extensions/web-search/providers/minimax.d.ts.map +1 -0
- package/dist/extensions/web-search/providers/minimax.js +50 -0
- package/dist/extensions/web-search/providers/minimax.js.map +1 -0
- package/dist/extensions/web-search/render.d.ts +10 -0
- package/dist/extensions/web-search/render.d.ts.map +1 -0
- package/dist/extensions/web-search/render.js +103 -0
- package/dist/extensions/web-search/render.js.map +1 -0
- package/dist/extensions/web-search/results.d.ts +28 -0
- package/dist/extensions/web-search/results.d.ts.map +1 -0
- package/dist/extensions/web-search/results.js +154 -0
- package/dist/extensions/web-search/results.js.map +1 -0
- package/dist/extensions/web-search/schema.d.ts +10 -0
- package/dist/extensions/web-search/schema.d.ts.map +1 -0
- package/dist/extensions/web-search/schema.js +36 -0
- package/dist/extensions/web-search/schema.js.map +1 -0
- package/dist/extensions/web-search/types.d.ts +49 -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 +46 -0
- package/docs/docs.json +4 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,34 @@ This file records `@astralyn/pi` releases beginning with the first Fork-owned re
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.84.14] - 2026-09-01
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Simplified `web_search` tool metadata so its capability description, session-wide routing guidance, and conditional result instructions live in their canonical prompt layers.
|
|
12
|
+
- Reworked dual-provider result fusion to use reciprocal-rank scoring, preserving high-ranked unique results from both engines instead of allowing the first provider to fill the output cap.
|
|
13
|
+
- Reworded search-result trust labels: provider overlap is now described as `found by MiniMax & DeepSeek`, and generated analysis is explicitly labeled `DeepSeek Search Synthesis`, avoiding claims of independent verification or authority.
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
|
|
17
|
+
- Removed the `allowed_domains` and `blocked_domains` parameters from `web_search`, together with provider-specific query rewriting and local URL filtering.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Scoped `web_search` citation guidance to successful tool results instead of injecting it into every session through `promptGuidelines`. Result payloads now ask naturally for relevant URL citations only when structured sources exist, without requiring a `Sources:` heading or Markdown link format.
|
|
22
|
+
- Preserved caller cancellation across concurrent searches, fixed URL normalization around trailing path/query slashes, upgraded fallback URL titles during deduplication, and restored the warning renderer for disabled results.
|
|
23
|
+
- Added strict local bounds and Markdown-safe formatting for provider-controlled titles, URLs, snippets, synthesis, related searches, and errors; oversized or malformed source responses no longer flow unbounded into the model context.
|
|
24
|
+
|
|
25
|
+
## [0.84.13] - 2026-09-01
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- 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.
|
|
30
|
+
- 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.
|
|
31
|
+
- 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`.
|
|
32
|
+
- 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.
|
|
33
|
+
- 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.
|
|
34
|
+
|
|
7
35
|
## [0.84.12] - 2026-08-31
|
|
8
36
|
|
|
9
37
|
### 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 live web through configured MiniMax and DeepSeek providers. When both are available, searches run concurrently and results are deduplicated and fused. Returns up to 12 source URLs with bounded titles and snippets, plus related searches and optional synthesis; partial provider failures still return successful results.";
|
|
8
|
+
export declare const WEB_SEARCH_PROMPT_SNIPPET = "Search the live web for current information";
|
|
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,8UACyS,CAAC;AAE7U,eAAO,MAAM,yBAAyB,gDAAgD,CAAC;AAEvF,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,CAMvD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
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 live web through configured MiniMax and DeepSeek providers. When both are available, searches run concurrently and results are deduplicated and fused. Returns up to 12 source URLs with bounded titles and snippets, plus related searches and optional synthesis; partial provider failures still return successful results.";
|
|
7
|
+
export const WEB_SEARCH_PROMPT_SNIPPET = "Search the live web for current information";
|
|
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` for current information, recent releases, live documentation, and other freshness-sensitive facts beyond your training cutoff.",
|
|
23
|
+
`When using \`web_search\` for freshness-sensitive topics, account for the current date (${currentYearMonth}) and include the year in the query when useful.`,
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
//# 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,2UAA2U,CAAC;AAE7U,MAAM,CAAC,MAAM,yBAAyB,GAAG,6CAA6C,CAAC;AAEvF,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,iJAAiJ;QACjJ,2FAA2F,gBAAgB,kDAAkD;KAC7J,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 live web through configured MiniMax and DeepSeek providers. When both are available, searches run concurrently and results are deduplicated and fused. Returns up to 12 source URLs with bounded titles and snippets, plus related searches and optional synthesis; partial provider failures still return successful results.\";\n\nexport const WEB_SEARCH_PROMPT_SNIPPET = \"Search the live web for current information\";\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` for current information, recent releases, live documentation, and other freshness-sensitive facts beyond your training cutoff.\",\n\t\t`When using \\`web_search\\` for freshness-sensitive topics, account for the current date (${currentYearMonth}) and include the year in the query when useful.`,\n\t];\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider orchestration and execution-state construction for web_search.
|
|
3
|
+
*/
|
|
4
|
+
import type { AgentToolUpdateCallback } from "../../core/extensions/types.ts";
|
|
5
|
+
import type { WebSearchParams } from "./schema.ts";
|
|
6
|
+
import type { ResolvedSearchCredentials, WebSearchDetails } from "./types.ts";
|
|
7
|
+
export interface WebSearchExecution {
|
|
8
|
+
formattedOutput: string;
|
|
9
|
+
details: WebSearchDetails;
|
|
10
|
+
}
|
|
11
|
+
/** Execute all configured search providers and combine their successful results. */
|
|
12
|
+
export declare function executeWebSearch(params: WebSearchParams, credentials: ResolvedSearchCredentials, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<WebSearchDetails>): Promise<WebSearchExecution>;
|
|
13
|
+
//# sourceMappingURL=execute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/execute.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAO9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAwB,yBAAyB,EAAoB,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEtH,MAAM,WAAW,kBAAkB;IAClC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,gBAAgB,CAAC;CAC1B;AAED,oFAAoF;AACpF,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,kBAAkB,CAAC,CAmG7B"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider orchestration and execution-state construction for web_search.
|
|
3
|
+
*/
|
|
4
|
+
import { configuredEngine } from "./auth.js";
|
|
5
|
+
import { getEngineLabel, WEB_SEARCH_DISABLED_MESSAGE } from "./constants.js";
|
|
6
|
+
import { formatSearchOutput } from "./format.js";
|
|
7
|
+
import { searchDeepSeek } from "./providers/deepseek.js";
|
|
8
|
+
import { searchMiniMax } from "./providers/minimax.js";
|
|
9
|
+
import { boundSingleLineText, fuseSearchHits, MAX_ERROR_MESSAGE_LENGTH, MAX_QUERY_LENGTH } from "./results.js";
|
|
10
|
+
/** Execute all configured search providers and combine their successful results. */
|
|
11
|
+
export async function executeWebSearch(params, credentials, signal, onUpdate) {
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
const query = boundSingleLineText(params.query, MAX_QUERY_LENGTH) ?? "";
|
|
14
|
+
const engine = configuredEngine(credentials);
|
|
15
|
+
if (!query) {
|
|
16
|
+
const details = {
|
|
17
|
+
query: "",
|
|
18
|
+
durationMs: 0,
|
|
19
|
+
status: "error",
|
|
20
|
+
engine,
|
|
21
|
+
totalHits: 0,
|
|
22
|
+
hits: [],
|
|
23
|
+
errorMessage: "Search query must not be empty.",
|
|
24
|
+
};
|
|
25
|
+
return { formattedOutput: formatSearchOutput(query, details), details };
|
|
26
|
+
}
|
|
27
|
+
if (engine === "none") {
|
|
28
|
+
const details = {
|
|
29
|
+
query,
|
|
30
|
+
durationMs: 0,
|
|
31
|
+
status: "disabled",
|
|
32
|
+
engine: "none",
|
|
33
|
+
totalHits: 0,
|
|
34
|
+
hits: [],
|
|
35
|
+
errorMessage: WEB_SEARCH_DISABLED_MESSAGE,
|
|
36
|
+
};
|
|
37
|
+
return { formattedOutput: formatSearchOutput(query, details), details };
|
|
38
|
+
}
|
|
39
|
+
signal?.throwIfAborted();
|
|
40
|
+
onUpdate?.({
|
|
41
|
+
content: [{ type: "text", text: `Searching for "${query}" via ${getEngineLabel(engine)}...` }],
|
|
42
|
+
details: {
|
|
43
|
+
query,
|
|
44
|
+
durationMs: 0,
|
|
45
|
+
status: "success",
|
|
46
|
+
engine,
|
|
47
|
+
totalHits: 0,
|
|
48
|
+
hits: [],
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const searches = [];
|
|
52
|
+
if (credentials.minimax) {
|
|
53
|
+
searches.push(searchMiniMax({
|
|
54
|
+
query,
|
|
55
|
+
apiKey: credentials.minimax.key,
|
|
56
|
+
apiHost: credentials.minimax.host,
|
|
57
|
+
signal,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
if (credentials.deepseek) {
|
|
61
|
+
searches.push(searchDeepSeek({ query, apiKey: credentials.deepseek.key, signal }));
|
|
62
|
+
}
|
|
63
|
+
const settledResults = await Promise.allSettled(searches);
|
|
64
|
+
signal?.throwIfAborted();
|
|
65
|
+
const durationMs = Date.now() - startTime;
|
|
66
|
+
const successfulResults = [];
|
|
67
|
+
const errors = [];
|
|
68
|
+
for (const result of settledResults) {
|
|
69
|
+
if (result.status === "fulfilled")
|
|
70
|
+
successfulResults.push(result.value);
|
|
71
|
+
else
|
|
72
|
+
errors.push(result.reason instanceof Error ? result.reason.message : String(result.reason));
|
|
73
|
+
}
|
|
74
|
+
if (successfulResults.length === 0) {
|
|
75
|
+
const details = {
|
|
76
|
+
query,
|
|
77
|
+
durationMs,
|
|
78
|
+
status: "error",
|
|
79
|
+
engine,
|
|
80
|
+
totalHits: 0,
|
|
81
|
+
hits: [],
|
|
82
|
+
errorMessage: boundSingleLineText(errors.join(" | "), MAX_ERROR_MESSAGE_LENGTH) ?? "All search engines failed",
|
|
83
|
+
};
|
|
84
|
+
return { formattedOutput: formatSearchOutput(query, details), details };
|
|
85
|
+
}
|
|
86
|
+
const { hits, relatedSearches, deepseekSynthesis } = fuseSearchHits(successfulResults);
|
|
87
|
+
const respondedSources = new Set(successfulResults.map((result) => result.source));
|
|
88
|
+
const contributingEngine = respondedSources.size > 1 ? "dual" : respondedSources.has("MiniMax") ? "minimax" : "deepseek";
|
|
89
|
+
const details = {
|
|
90
|
+
query,
|
|
91
|
+
durationMs,
|
|
92
|
+
status: "success",
|
|
93
|
+
engine: contributingEngine,
|
|
94
|
+
totalHits: hits.length,
|
|
95
|
+
hits,
|
|
96
|
+
relatedSearches,
|
|
97
|
+
deepseekSynthesis,
|
|
98
|
+
};
|
|
99
|
+
return { formattedOutput: formatSearchOutput(query, details), details };
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=execute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/extensions/web-search/execute.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Provider orchestration and execution-state construction for web_search.\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 { formatSearchOutput } from \"./format.ts\";\nimport { searchDeepSeek } from \"./providers/deepseek.ts\";\nimport { searchMiniMax } from \"./providers/minimax.ts\";\nimport { boundSingleLineText, fuseSearchHits, MAX_ERROR_MESSAGE_LENGTH, MAX_QUERY_LENGTH } from \"./results.ts\";\nimport type { WebSearchParams } from \"./schema.ts\";\nimport type { ProviderSearchResult, ResolvedSearchCredentials, SearchEngineType, WebSearchDetails } from \"./types.ts\";\n\nexport interface WebSearchExecution {\n\tformattedOutput: string;\n\tdetails: WebSearchDetails;\n}\n\n/** Execute all configured search providers and combine their successful results. */\nexport async function executeWebSearch(\n\tparams: WebSearchParams,\n\tcredentials: ResolvedSearchCredentials,\n\tsignal?: AbortSignal,\n\tonUpdate?: AgentToolUpdateCallback<WebSearchDetails>,\n): Promise<WebSearchExecution> {\n\tconst startTime = Date.now();\n\tconst query = boundSingleLineText(params.query, MAX_QUERY_LENGTH) ?? \"\";\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 { formattedOutput: formatSearchOutput(query, details), details };\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 { formattedOutput: formatSearchOutput(query, details), details };\n\t}\n\n\tsignal?.throwIfAborted();\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 searches: Promise<ProviderSearchResult>[] = [];\n\tif (credentials.minimax) {\n\t\tsearches.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\tsignal,\n\t\t\t}),\n\t\t);\n\t}\n\tif (credentials.deepseek) {\n\t\tsearches.push(searchDeepSeek({ query, apiKey: credentials.deepseek.key, signal }));\n\t}\n\n\tconst settledResults = await Promise.allSettled(searches);\n\tsignal?.throwIfAborted();\n\tconst durationMs = Date.now() - startTime;\n\tconst successfulResults: ProviderSearchResult[] = [];\n\tconst errors: string[] = [];\n\n\tfor (const result of settledResults) {\n\t\tif (result.status === \"fulfilled\") successfulResults.push(result.value);\n\t\telse errors.push(result.reason instanceof Error ? result.reason.message : String(result.reason));\n\t}\n\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: boundSingleLineText(errors.join(\" | \"), MAX_ERROR_MESSAGE_LENGTH) ?? \"All search engines failed\",\n\t\t};\n\t\treturn { formattedOutput: formatSearchOutput(query, details), details };\n\t}\n\n\tconst { hits, relatedSearches, deepseekSynthesis } = fuseSearchHits(successfulResults);\n\tconst respondedSources = new Set(successfulResults.map((result) => result.source));\n\tconst contributingEngine: SearchEngineType =\n\t\trespondedSources.size > 1 ? \"dual\" : respondedSources.has(\"MiniMax\") ? \"minimax\" : \"deepseek\";\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 { formattedOutput: formatSearchOutput(query, details), details };\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing and expanded-TUI Markdown formatting for web_search results.
|
|
3
|
+
*/
|
|
4
|
+
import type { WebSearchDetails } from "./types.ts";
|
|
5
|
+
/**
|
|
6
|
+
* Sources, synthesis, and related-search sections shared by model output and
|
|
7
|
+
* the expanded TUI. This function adds no model instructions.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatResultsMarkdown(details: WebSearchDetails): string;
|
|
10
|
+
/** Format a structured result payload for the main agent. */
|
|
11
|
+
export declare function formatSearchOutput(query: string, details: WebSearchDetails): string;
|
|
12
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgBH,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,YAAY,CAAC;AAuCjE;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAiCvE;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAwBnF"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing and expanded-TUI Markdown formatting for web_search results.
|
|
3
|
+
*/
|
|
4
|
+
import { getEngineLabel, WEB_SEARCH_DISABLED_MESSAGE } from "./constants.js";
|
|
5
|
+
import { boundMultilineText, boundSingleLineText, MAX_ERROR_MESSAGE_LENGTH, MAX_OUTPUT_HITS, MAX_QUERY_LENGTH, MAX_RELATED_SEARCH_LENGTH, MAX_RELATED_SEARCHES, MAX_SNIPPET_LENGTH, MAX_SYNTHESIS_LENGTH, MAX_TITLE_LENGTH, normalizeUrl, } from "./results.js";
|
|
6
|
+
function escapeMarkdownText(text) {
|
|
7
|
+
return text
|
|
8
|
+
.replace(/\\/g, "\\\\")
|
|
9
|
+
.replace(/\[/g, "\\[")
|
|
10
|
+
.replace(/\]/g, "\\]")
|
|
11
|
+
.replace(/</g, "\\<")
|
|
12
|
+
.replace(/>/g, "\\>");
|
|
13
|
+
}
|
|
14
|
+
function formatHit(hit) {
|
|
15
|
+
const url = normalizeUrl(hit.url);
|
|
16
|
+
if (!url)
|
|
17
|
+
return undefined;
|
|
18
|
+
const sources = Array.isArray(hit.sources)
|
|
19
|
+
? [...new Set(hit.sources.filter((source) => source === "MiniMax" || source === "DeepSeek"))]
|
|
20
|
+
: [];
|
|
21
|
+
return {
|
|
22
|
+
title: escapeMarkdownText(boundSingleLineText(hit.title, MAX_TITLE_LENGTH) ?? url),
|
|
23
|
+
url,
|
|
24
|
+
snippet: boundSingleLineText(hit.snippet, MAX_SNIPPET_LENGTH),
|
|
25
|
+
sources,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function usableHits(details) {
|
|
29
|
+
return details.hits
|
|
30
|
+
.map(formatHit)
|
|
31
|
+
.filter((hit) => hit !== undefined)
|
|
32
|
+
.slice(0, MAX_OUTPUT_HITS);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Sources, synthesis, and related-search sections shared by model output and
|
|
36
|
+
* the expanded TUI. This function adds no model instructions.
|
|
37
|
+
*/
|
|
38
|
+
export function formatResultsMarkdown(details) {
|
|
39
|
+
const parts = [];
|
|
40
|
+
const hits = usableHits(details);
|
|
41
|
+
if (hits.length > 0) {
|
|
42
|
+
parts.push(`## Web Sources (${hits.length} found via ${getEngineLabel(details.engine)})\n`);
|
|
43
|
+
hits.forEach((hit, index) => {
|
|
44
|
+
const sourceTag = hit.sources.length > 1 ? ` — *(found by ${hit.sources.join(" & ")})*` : "";
|
|
45
|
+
parts.push(`${index + 1}. **[${hit.title}](<${hit.url}>)**${sourceTag}`);
|
|
46
|
+
if (hit.snippet)
|
|
47
|
+
parts.push(` - ${escapeMarkdownText(hit.snippet)}`);
|
|
48
|
+
});
|
|
49
|
+
parts.push("");
|
|
50
|
+
}
|
|
51
|
+
const synthesis = boundMultilineText(details.deepseekSynthesis, MAX_SYNTHESIS_LENGTH);
|
|
52
|
+
if (synthesis) {
|
|
53
|
+
parts.push("## DeepSeek Search Synthesis\n");
|
|
54
|
+
parts.push(escapeMarkdownText(synthesis));
|
|
55
|
+
parts.push("");
|
|
56
|
+
}
|
|
57
|
+
const relatedSearches = details.relatedSearches
|
|
58
|
+
?.map((related) => boundSingleLineText(related, MAX_RELATED_SEARCH_LENGTH))
|
|
59
|
+
.filter((related) => related !== undefined)
|
|
60
|
+
.slice(0, MAX_RELATED_SEARCHES);
|
|
61
|
+
if (relatedSearches && relatedSearches.length > 0) {
|
|
62
|
+
parts.push("## Related Searches\n");
|
|
63
|
+
parts.push(relatedSearches.map((related) => `- ${escapeMarkdownText(related)}`).join("\n"));
|
|
64
|
+
parts.push("");
|
|
65
|
+
}
|
|
66
|
+
return parts.join("\n");
|
|
67
|
+
}
|
|
68
|
+
/** Format a structured result payload for the main agent. */
|
|
69
|
+
export function formatSearchOutput(query, details) {
|
|
70
|
+
if (details.status === "disabled")
|
|
71
|
+
return WEB_SEARCH_DISABLED_MESSAGE;
|
|
72
|
+
const boundedQuery = boundSingleLineText(query, MAX_QUERY_LENGTH) ?? "";
|
|
73
|
+
if (details.status === "error") {
|
|
74
|
+
const errorMessage = boundSingleLineText(details.errorMessage, MAX_ERROR_MESSAGE_LENGTH) ?? "Unknown search error";
|
|
75
|
+
return `Web search failed for "${boundedQuery}": ${errorMessage}`;
|
|
76
|
+
}
|
|
77
|
+
const hasSources = usableHits(details).length > 0;
|
|
78
|
+
const hasSynthesis = boundMultilineText(details.deepseekSynthesis, MAX_SYNTHESIS_LENGTH) !== undefined;
|
|
79
|
+
if (!hasSources && !hasSynthesis) {
|
|
80
|
+
return `No search results found for "${boundedQuery}". Try rephrasing with different keywords.`;
|
|
81
|
+
}
|
|
82
|
+
const parts = [`# Web Search Results for: "${boundedQuery}"\n`, formatResultsMarkdown(details)];
|
|
83
|
+
if (hasSources) {
|
|
84
|
+
parts.push("---", "Use these search results to answer the user, and cite the relevant source URLs in your response.");
|
|
85
|
+
}
|
|
86
|
+
return parts.join("\n");
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../src/extensions/web-search/format.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model-facing and expanded-TUI Markdown formatting for web_search results.\n */\n\nimport { getEngineLabel, WEB_SEARCH_DISABLED_MESSAGE } from \"./constants.ts\";\nimport {\n\tboundMultilineText,\n\tboundSingleLineText,\n\tMAX_ERROR_MESSAGE_LENGTH,\n\tMAX_OUTPUT_HITS,\n\tMAX_QUERY_LENGTH,\n\tMAX_RELATED_SEARCH_LENGTH,\n\tMAX_RELATED_SEARCHES,\n\tMAX_SNIPPET_LENGTH,\n\tMAX_SYNTHESIS_LENGTH,\n\tMAX_TITLE_LENGTH,\n\tnormalizeUrl,\n} from \"./results.ts\";\nimport type { WebSearchDetails, WebSearchHit } from \"./types.ts\";\n\ninterface FormattedHit {\n\ttitle: string;\n\turl: string;\n\tsnippet?: string;\n\tsources: WebSearchHit[\"sources\"];\n}\n\nfunction escapeMarkdownText(text: string): string {\n\treturn text\n\t\t.replace(/\\\\/g, \"\\\\\\\\\")\n\t\t.replace(/\\[/g, \"\\\\[\")\n\t\t.replace(/\\]/g, \"\\\\]\")\n\t\t.replace(/</g, \"\\\\<\")\n\t\t.replace(/>/g, \"\\\\>\");\n}\n\nfunction formatHit(hit: WebSearchHit): FormattedHit | undefined {\n\tconst url = normalizeUrl(hit.url);\n\tif (!url) return undefined;\n\tconst sources = Array.isArray(hit.sources)\n\t\t? [...new Set(hit.sources.filter((source) => source === \"MiniMax\" || source === \"DeepSeek\"))]\n\t\t: [];\n\treturn {\n\t\ttitle: escapeMarkdownText(boundSingleLineText(hit.title, MAX_TITLE_LENGTH) ?? url),\n\t\turl,\n\t\tsnippet: boundSingleLineText(hit.snippet, MAX_SNIPPET_LENGTH),\n\t\tsources,\n\t};\n}\n\nfunction usableHits(details: WebSearchDetails): FormattedHit[] {\n\treturn details.hits\n\t\t.map(formatHit)\n\t\t.filter((hit): hit is FormattedHit => hit !== undefined)\n\t\t.slice(0, MAX_OUTPUT_HITS);\n}\n\n/**\n * Sources, synthesis, and related-search sections shared by model output and\n * the expanded TUI. This function adds no model instructions.\n */\nexport function formatResultsMarkdown(details: WebSearchDetails): string {\n\tconst parts: string[] = [];\n\tconst hits = usableHits(details);\n\n\tif (hits.length > 0) {\n\t\tparts.push(`## Web Sources (${hits.length} found via ${getEngineLabel(details.engine)})\\n`);\n\n\t\thits.forEach((hit, index) => {\n\t\t\tconst sourceTag = hit.sources.length > 1 ? ` — *(found by ${hit.sources.join(\" & \")})*` : \"\";\n\t\t\tparts.push(`${index + 1}. **[${hit.title}](<${hit.url}>)**${sourceTag}`);\n\t\t\tif (hit.snippet) parts.push(` - ${escapeMarkdownText(hit.snippet)}`);\n\t\t});\n\t\tparts.push(\"\");\n\t}\n\n\tconst synthesis = boundMultilineText(details.deepseekSynthesis, MAX_SYNTHESIS_LENGTH);\n\tif (synthesis) {\n\t\tparts.push(\"## DeepSeek Search Synthesis\\n\");\n\t\tparts.push(escapeMarkdownText(synthesis));\n\t\tparts.push(\"\");\n\t}\n\n\tconst relatedSearches = details.relatedSearches\n\t\t?.map((related) => boundSingleLineText(related, MAX_RELATED_SEARCH_LENGTH))\n\t\t.filter((related): related is string => related !== undefined)\n\t\t.slice(0, MAX_RELATED_SEARCHES);\n\tif (relatedSearches && relatedSearches.length > 0) {\n\t\tparts.push(\"## Related Searches\\n\");\n\t\tparts.push(relatedSearches.map((related) => `- ${escapeMarkdownText(related)}`).join(\"\\n\"));\n\t\tparts.push(\"\");\n\t}\n\n\treturn parts.join(\"\\n\");\n}\n\n/** Format a structured result payload for the main agent. */\nexport function formatSearchOutput(query: string, details: WebSearchDetails): string {\n\tif (details.status === \"disabled\") return WEB_SEARCH_DISABLED_MESSAGE;\n\n\tconst boundedQuery = boundSingleLineText(query, MAX_QUERY_LENGTH) ?? \"\";\n\tif (details.status === \"error\") {\n\t\tconst errorMessage =\n\t\t\tboundSingleLineText(details.errorMessage, MAX_ERROR_MESSAGE_LENGTH) ?? \"Unknown search error\";\n\t\treturn `Web search failed for \"${boundedQuery}\": ${errorMessage}`;\n\t}\n\n\tconst hasSources = usableHits(details).length > 0;\n\tconst hasSynthesis = boundMultilineText(details.deepseekSynthesis, MAX_SYNTHESIS_LENGTH) !== undefined;\n\tif (!hasSources && !hasSynthesis) {\n\t\treturn `No search results found for \"${boundedQuery}\". Try rephrasing with different keywords.`;\n\t}\n\n\tconst parts = [`# Web Search Results for: \"${boundedQuery}\"\\n`, formatResultsMarkdown(details)];\n\tif (hasSources) {\n\t\tparts.push(\n\t\t\t\"---\",\n\t\t\t\"Use these search results to answer the user, and cite the relevant source URLs in your response.\",\n\t\t);\n\t}\n\treturn parts.join(\"\\n\");\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"}
|
|
@@ -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 "./execute.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 === "error",
|
|
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 \"./execute.ts\";\nimport { renderWebSearchCall, renderWebSearchResult } from \"./render.ts\";\nimport { normalizeWebSearchParams, 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 === \"error\",\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\treturn renderWebSearchCall(args, 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"]}
|