@astralyn/pi 0.84.13 → 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 +18 -0
- package/dist/extensions/web-search/constants.d.ts +2 -2
- package/dist/extensions/web-search/constants.d.ts.map +1 -1
- package/dist/extensions/web-search/constants.js +4 -5
- package/dist/extensions/web-search/constants.js.map +1 -1
- 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.js +2 -2
- package/dist/extensions/web-search/index.js.map +1 -1
- package/dist/extensions/web-search/providers/deepseek.d.ts +0 -2
- package/dist/extensions/web-search/providers/deepseek.d.ts.map +1 -1
- package/dist/extensions/web-search/providers/deepseek.js +51 -34
- package/dist/extensions/web-search/providers/deepseek.js.map +1 -1
- package/dist/extensions/web-search/providers/http.d.ts.map +1 -1
- package/dist/extensions/web-search/providers/http.js +68 -6
- package/dist/extensions/web-search/providers/http.js.map +1 -1
- package/dist/extensions/web-search/providers/minimax.d.ts +0 -2
- package/dist/extensions/web-search/providers/minimax.d.ts.map +1 -1
- package/dist/extensions/web-search/providers/minimax.js +33 -52
- package/dist/extensions/web-search/providers/minimax.js.map +1 -1
- package/dist/extensions/web-search/render.d.ts +1 -2
- package/dist/extensions/web-search/render.d.ts.map +1 -1
- package/dist/extensions/web-search/render.js +6 -12
- package/dist/extensions/web-search/render.js.map +1 -1
- 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 +0 -2
- package/dist/extensions/web-search/schema.d.ts.map +1 -1
- package/dist/extensions/web-search/schema.js +4 -25
- package/dist/extensions/web-search/schema.js.map +1 -1
- package/dist/extensions/web-search/types.d.ts +0 -62
- package/dist/extensions/web-search/types.d.ts.map +1 -1
- package/dist/extensions/web-search/types.js.map +1 -1
- package/docs/bundled/extensions/web-search.md +6 -10
- package/docs/docs.json +4 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/extensions/web-search/fusion.d.ts +0 -36
- package/dist/extensions/web-search/fusion.d.ts.map +0 -1
- package/dist/extensions/web-search/fusion.js +0 -272
- package/dist/extensions/web-search/fusion.js.map +0 -1
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
import { type Static, Type } from "typebox";
|
|
5
5
|
export declare const WebSearchParamsSchema: Type.TObject<{
|
|
6
6
|
query: Type.TString;
|
|
7
|
-
allowed_domains: Type.TOptional<Type.TArray<Type.TString>>;
|
|
8
|
-
blocked_domains: Type.TOptional<Type.TArray<Type.TString>>;
|
|
9
7
|
}>;
|
|
10
8
|
export type WebSearchParams = Static<typeof WebSearchParamsSchema>;
|
|
11
9
|
export declare function normalizeWebSearchParams(raw: unknown): WebSearchParams;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,eAAO,MAAM,qBAAqB;;EAMhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEnE,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CAuBtE"}
|
|
@@ -2,17 +2,13 @@
|
|
|
2
2
|
* schema.ts — TypeBox parameter schema and argument normalizer for web_search.
|
|
3
3
|
*/
|
|
4
4
|
import { Type } from "typebox";
|
|
5
|
+
import { MAX_QUERY_LENGTH } from "./results.js";
|
|
5
6
|
export const WebSearchParamsSchema = Type.Object({
|
|
6
7
|
query: Type.String({
|
|
7
8
|
minLength: 1,
|
|
8
|
-
|
|
9
|
+
maxLength: MAX_QUERY_LENGTH,
|
|
10
|
+
description: "The search query. Aim for 3-5 keywords; include a year when freshness matters.",
|
|
9
11
|
}),
|
|
10
|
-
allowed_domains: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
11
|
-
description: "Optional list of domains to include in search results (e.g. ['github.com', 'react.dev']). Cannot be used alongside blocked_domains.",
|
|
12
|
-
})),
|
|
13
|
-
blocked_domains: Type.Optional(Type.Array(Type.String({ minLength: 1 }), {
|
|
14
|
-
description: "Optional list of domains to exclude from search results. Cannot be used alongside allowed_domains.",
|
|
15
|
-
})),
|
|
16
12
|
});
|
|
17
13
|
export function normalizeWebSearchParams(raw) {
|
|
18
14
|
if (typeof raw === "string") {
|
|
@@ -35,23 +31,6 @@ export function normalizeWebSearchParams(raw) {
|
|
|
35
31
|
else if (typeof record.prompt === "string") {
|
|
36
32
|
query = record.prompt.trim();
|
|
37
33
|
}
|
|
38
|
-
|
|
39
|
-
if (Array.isArray(val)) {
|
|
40
|
-
const list = val.map((x) => String(x).trim()).filter((x) => x.length > 0);
|
|
41
|
-
return list.length > 0 ? list : undefined;
|
|
42
|
-
}
|
|
43
|
-
if (typeof val === "string" && val.trim()) {
|
|
44
|
-
return [val.trim()];
|
|
45
|
-
}
|
|
46
|
-
return undefined;
|
|
47
|
-
};
|
|
48
|
-
const allowedDomains = parseDomainArray(record.allowed_domains ?? record.allowedDomains);
|
|
49
|
-
const blockedDomains = parseDomainArray(record.blocked_domains ?? record.blockedDomains);
|
|
50
|
-
return {
|
|
51
|
-
query,
|
|
52
|
-
...(allowedDomains ? { allowed_domains: allowedDomains } : {}),
|
|
53
|
-
// allowed_domains and blocked_domains are mutually exclusive; allowed_domains wins.
|
|
54
|
-
...(blockedDomains && !allowedDomains ? { blocked_domains: blockedDomains } : {}),
|
|
55
|
-
};
|
|
34
|
+
return { query };
|
|
56
35
|
}
|
|
57
36
|
//# sourceMappingURL=schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/extensions/web-search/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/extensions/web-search/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC,MAAM,CAAC;IAChD,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;QAClB,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,gBAAgB;QAC3B,WAAW,EAAE,gFAAgF;KAC7F,CAAC;CACF,CAAC,CAAC;AAIH,MAAM,UAAU,wBAAwB,CAAC,GAAY;IACpD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;IAC9B,CAAC;IAED,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;IAC5C,CAAC;IAED,MAAM,MAAM,GAAG,GAA8B,CAAC;IAE9C,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,CAAC;SAAM,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QACzC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;QACpD,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;IACpC,CAAC;SAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC9C,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,CAAC;AAClB,CAAC","sourcesContent":["/**\n * schema.ts — TypeBox parameter schema and argument normalizer for web_search.\n */\n\nimport { type Static, Type } from \"typebox\";\nimport { MAX_QUERY_LENGTH } from \"./results.ts\";\n\nexport const WebSearchParamsSchema = Type.Object({\n\tquery: Type.String({\n\t\tminLength: 1,\n\t\tmaxLength: MAX_QUERY_LENGTH,\n\t\tdescription: \"The search query. Aim for 3-5 keywords; include a year when freshness matters.\",\n\t}),\n});\n\nexport type WebSearchParams = Static<typeof WebSearchParamsSchema>;\n\nexport function normalizeWebSearchParams(raw: unknown): WebSearchParams {\n\tif (typeof raw === \"string\") {\n\t\treturn { query: raw.trim() };\n\t}\n\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n\t\treturn { query: String(raw ?? \"\").trim() };\n\t}\n\n\tconst record = raw as Record<string, unknown>;\n\n\tlet query = \"\";\n\tif (typeof record.query === \"string\") {\n\t\tquery = record.query.trim();\n\t} else if (typeof record.q === \"string\") {\n\t\tquery = record.q.trim();\n\t} else if (typeof record.search_query === \"string\") {\n\t\tquery = record.search_query.trim();\n\t} else if (typeof record.prompt === \"string\") {\n\t\tquery = record.prompt.trim();\n\t}\n\n\treturn { query };\n}\n"]}
|
|
@@ -40,68 +40,6 @@ export interface ResolvedSearchCredentials {
|
|
|
40
40
|
key: string;
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
export interface MiniMaxOrganicHit {
|
|
44
|
-
title: string;
|
|
45
|
-
link: string;
|
|
46
|
-
snippet?: string;
|
|
47
|
-
date?: string;
|
|
48
|
-
}
|
|
49
|
-
export interface MiniMaxSearchResponse {
|
|
50
|
-
organic?: MiniMaxOrganicHit[];
|
|
51
|
-
related_searches?: {
|
|
52
|
-
query: string;
|
|
53
|
-
}[];
|
|
54
|
-
base_resp?: {
|
|
55
|
-
status_code: number;
|
|
56
|
-
status_msg: string;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
export interface DeepSeekServerToolUseBlock {
|
|
60
|
-
type: "server_tool_use";
|
|
61
|
-
id: string;
|
|
62
|
-
name: "web_search";
|
|
63
|
-
input?: {
|
|
64
|
-
query?: string;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
export interface DeepSeekWebSearchResultItem {
|
|
68
|
-
type: "web_search_result";
|
|
69
|
-
title: string;
|
|
70
|
-
url: string;
|
|
71
|
-
page_age?: string | null;
|
|
72
|
-
encrypted_content?: string;
|
|
73
|
-
}
|
|
74
|
-
export interface DeepSeekWebSearchToolResultBlock {
|
|
75
|
-
type: "web_search_tool_result";
|
|
76
|
-
tool_use_id: string;
|
|
77
|
-
content?: DeepSeekWebSearchResultItem[];
|
|
78
|
-
}
|
|
79
|
-
export interface DeepSeekTextBlock {
|
|
80
|
-
type: "text";
|
|
81
|
-
text?: string;
|
|
82
|
-
citations?: {
|
|
83
|
-
type: string;
|
|
84
|
-
title?: string;
|
|
85
|
-
url?: string;
|
|
86
|
-
cited_text?: string;
|
|
87
|
-
}[];
|
|
88
|
-
}
|
|
89
|
-
export type DeepSeekContentBlock = DeepSeekServerToolUseBlock | DeepSeekWebSearchToolResultBlock | DeepSeekTextBlock | {
|
|
90
|
-
type: string;
|
|
91
|
-
[key: string]: unknown;
|
|
92
|
-
};
|
|
93
|
-
export interface DeepSeekMessagesResponse {
|
|
94
|
-
id?: string;
|
|
95
|
-
type?: string;
|
|
96
|
-
role?: string;
|
|
97
|
-
model?: string;
|
|
98
|
-
content?: DeepSeekContentBlock[];
|
|
99
|
-
stop_reason?: string;
|
|
100
|
-
error?: {
|
|
101
|
-
type?: string;
|
|
102
|
-
message?: string;
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
43
|
export interface ProviderSearchResult {
|
|
106
44
|
source: SearchEngineSource;
|
|
107
45
|
hits: WebSearchHit[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AACxE,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,UAAU,CAAC;AAExD,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;IACzC,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACzC,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC,QAAQ,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3B;AAED,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AACxE,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,UAAU,CAAC;AAExD,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;IACzC,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACzC,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC,QAAQ,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/extensions/web-search/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * types.ts — Type definitions for web_search extension providers and fusion engine.\n */\n\nexport type SearchEngineType = \"minimax\" | \"deepseek\" | \"dual\" | \"none\";\nexport type SearchEngineSource = \"MiniMax\" | \"DeepSeek\";\n\nexport interface WebSearchHit {\n\ttitle: string;\n\turl: string;\n\tsnippet?: string;\n\tdate?: string;\n\tsources: SearchEngineSource[];\n}\n\nexport interface WebSearchDetails {\n\tquery: string;\n\tdurationMs: number;\n\tstatus: \"success\" | \"error\" | \"disabled\";\n\tengine: SearchEngineType;\n\ttotalHits: number;\n\thits: WebSearchHit[];\n\trelatedSearches?: string[];\n\tdeepseekSynthesis?: string;\n\terrorMessage?: string;\n}\n\n/**\n * MiniMax search credential. `host` is always present alongside `key` so\n * callers never guard the two separately.\n */\nexport interface MiniMaxSearchCredential {\n\tkey: string;\n\thost: string;\n}\n\n/**\n * Resolved credentials: presence of a group means that engine is configured,\n * so impossible states (mode without key, key without host) are unrepresentable.\n * Use `configuredEngine()` from auth.ts to derive the SearchEngineType label.\n */\nexport interface ResolvedSearchCredentials {\n\tminimax?: MiniMaxSearchCredential;\n\tdeepseek?: { key: string };\n}\n\nexport interface
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/extensions/web-search/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * types.ts — Type definitions for web_search extension providers and fusion engine.\n */\n\nexport type SearchEngineType = \"minimax\" | \"deepseek\" | \"dual\" | \"none\";\nexport type SearchEngineSource = \"MiniMax\" | \"DeepSeek\";\n\nexport interface WebSearchHit {\n\ttitle: string;\n\turl: string;\n\tsnippet?: string;\n\tdate?: string;\n\tsources: SearchEngineSource[];\n}\n\nexport interface WebSearchDetails {\n\tquery: string;\n\tdurationMs: number;\n\tstatus: \"success\" | \"error\" | \"disabled\";\n\tengine: SearchEngineType;\n\ttotalHits: number;\n\thits: WebSearchHit[];\n\trelatedSearches?: string[];\n\tdeepseekSynthesis?: string;\n\terrorMessage?: string;\n}\n\n/**\n * MiniMax search credential. `host` is always present alongside `key` so\n * callers never guard the two separately.\n */\nexport interface MiniMaxSearchCredential {\n\tkey: string;\n\thost: string;\n}\n\n/**\n * Resolved credentials: presence of a group means that engine is configured,\n * so impossible states (mode without key, key without host) are unrepresentable.\n * Use `configuredEngine()` from auth.ts to derive the SearchEngineType label.\n */\nexport interface ResolvedSearchCredentials {\n\tminimax?: MiniMaxSearchCredential;\n\tdeepseek?: { key: string };\n}\n\nexport interface ProviderSearchResult {\n\tsource: SearchEngineSource;\n\thits: WebSearchHit[];\n\trelatedSearches?: string[];\n\tsynthesisText?: string;\n}\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`web_search` is a Pi-native tool for querying live web information through MiniMax and DeepSeek search engines.
|
|
4
4
|
|
|
5
|
-
When both engines are configured, searches run concurrently and results are fused: URLs are normalized (tracking parameters and fragments stripped), duplicates merged, and
|
|
5
|
+
When both engines are configured, searches run concurrently and results are fused with reciprocal-rank scoring: URLs are normalized (tracking parameters and fragments stripped), duplicates are merged, and high-ranked results from both engines remain represented.
|
|
6
6
|
|
|
7
7
|
| Engine mode | Requirement |
|
|
8
8
|
|---|---|
|
|
@@ -25,26 +25,22 @@ When no key is configured, `web_search` is removed from the model's tool set and
|
|
|
25
25
|
|
|
26
26
|
### Parameters
|
|
27
27
|
|
|
28
|
-
- `query`: the search query — 3–5 keywords work best.
|
|
29
|
-
- `allowed_domains`: optional list of domains to include (e.g. `["github.com", "react.dev"]`).
|
|
30
|
-
- `blocked_domains`: optional list of domains to exclude.
|
|
31
|
-
|
|
32
|
-
`allowed_domains` and `blocked_domains` are mutually exclusive; if both are supplied, `allowed_domains` wins. Domain filters apply both in the upstream query and as a local post-filter on result URLs (subdomains included).
|
|
28
|
+
- `query`: the search query, up to 500 characters — 3–5 keywords work best; include a year when freshness matters.
|
|
33
29
|
|
|
34
30
|
### Output
|
|
35
31
|
|
|
36
|
-
The model-facing payload is Markdown containing up to 12
|
|
32
|
+
The model-facing payload is Markdown containing up to 12 HTTP(S) source results. Titles and snippets are bounded to 200 characters each, source URLs to 2048 characters, DeepSeek synthesis to 6000 characters, and up to 8 related searches to 200 characters each. Results found by both providers are tagged as such; this means the URL appeared in both result sets, not that its claims were independently verified. When structured source URLs are present, the tool result reminds the model to cite the relevant URLs without prescribing a heading or response format.
|
|
37
33
|
|
|
38
34
|
## Limits
|
|
39
35
|
|
|
40
36
|
- With no configured key the tool is hidden from the model (see Credentials); the disabled message only appears when the tool was force-enabled via `/tools` without keys.
|
|
41
37
|
- Engine failures are independent: if one engine errors, the other's results are still returned; the tool errors only when every configured engine fails.
|
|
42
38
|
- MiniMax search uses the Coding Plan Search endpoint (`POST /v1/coding_plan/search`), which requires a Coding Plan key — a standard MiniMax API key may be rejected upstream.
|
|
43
|
-
- DeepSeek search goes through the Anthropic-compatible endpoint (`/anthropic/v1/messages`, model `claude-sonnet-search`) with the server-side `web_search_20250305` tool; the
|
|
44
|
-
- Requests time out after 60 seconds (MiniMax) or 90 seconds (DeepSeek) and surface as Pi tool errors
|
|
39
|
+
- DeepSeek search goes through the Anthropic-compatible endpoint (`/anthropic/v1/messages`, model `claude-sonnet-search`) with the server-side `web_search_20250305` tool; the request uses `max_tokens: 1024`, followed by the local output bound above.
|
|
40
|
+
- Requests time out after 60 seconds (MiniMax) or 90 seconds (DeepSeek) and surface as Pi tool errors. Successful upstream response bodies are limited to 2 MiB, error bodies to 200 bytes, and model-facing error messages to 500 characters.
|
|
45
41
|
|
|
46
42
|
## Implementation notes
|
|
47
43
|
|
|
48
|
-
The extension keeps presentation Pi-native: the call line shows the query
|
|
44
|
+
The extension keeps presentation Pi-native: the call line shows the query, 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 with the configured expand hint, and expanding renders the result sections from `details` — without the model-facing agent directives — falling back to the raw payload for legacy entries.
|
|
49
45
|
|
|
50
46
|
Credential resolution never inspects `getProviderAuthStatus` — that probe returns a truthy `AuthStatus` object even when nothing is configured and carries no key. Only `getAuth` returns usable key material.
|
package/docs/docs.json
CHANGED
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astralyn/pi",
|
|
3
|
-
"version": "0.84.
|
|
3
|
+
"version": "0.84.14",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@astralyn/pi",
|
|
9
|
-
"version": "0.84.
|
|
9
|
+
"version": "0.84.14",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@earendil-works/pi-agent-core": "0.84.4",
|
package/package.json
CHANGED
|
@@ -1,36 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,272 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"]}
|