@astralyn/pi 0.84.13 → 0.84.15

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.
Files changed (202) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/core/agent-session.d.ts.map +1 -1
  3. package/dist/core/agent-session.js +12 -6
  4. package/dist/core/agent-session.js.map +1 -1
  5. package/dist/core/extensions/types.d.ts +4 -1
  6. package/dist/core/extensions/types.d.ts.map +1 -1
  7. package/dist/core/extensions/types.js.map +1 -1
  8. package/dist/core/tools/bash.d.ts +1 -0
  9. package/dist/core/tools/bash.d.ts.map +1 -1
  10. package/dist/core/tools/bash.js +4 -1
  11. package/dist/core/tools/bash.js.map +1 -1
  12. package/dist/core/tools/find.d.ts +2 -0
  13. package/dist/core/tools/find.d.ts.map +1 -1
  14. package/dist/core/tools/find.js +18 -15
  15. package/dist/core/tools/find.js.map +1 -1
  16. package/dist/extensions/background/actions.d.ts.map +1 -1
  17. package/dist/extensions/background/actions.js +3 -2
  18. package/dist/extensions/background/actions.js.map +1 -1
  19. package/dist/extensions/background/manager.d.ts.map +1 -1
  20. package/dist/extensions/background/manager.js +8 -9
  21. package/dist/extensions/background/manager.js.map +1 -1
  22. package/dist/extensions/background/registry.d.ts +4 -1
  23. package/dist/extensions/background/registry.d.ts.map +1 -1
  24. package/dist/extensions/background/registry.js +30 -17
  25. package/dist/extensions/background/registry.js.map +1 -1
  26. package/dist/extensions/background/task-view.d.ts +6 -0
  27. package/dist/extensions/background/task-view.d.ts.map +1 -1
  28. package/dist/extensions/background/task-view.js +11 -6
  29. package/dist/extensions/background/task-view.js.map +1 -1
  30. package/dist/extensions/deepwiki/client.d.ts.map +1 -1
  31. package/dist/extensions/deepwiki/client.js +3 -0
  32. package/dist/extensions/deepwiki/client.js.map +1 -1
  33. package/dist/extensions/deepwiki/execute.d.ts +2 -1
  34. package/dist/extensions/deepwiki/execute.d.ts.map +1 -1
  35. package/dist/extensions/deepwiki/execute.js +17 -2
  36. package/dist/extensions/deepwiki/execute.js.map +1 -1
  37. package/dist/extensions/deepwiki/http.d.ts +1 -0
  38. package/dist/extensions/deepwiki/http.d.ts.map +1 -1
  39. package/dist/extensions/deepwiki/http.js +16 -2
  40. package/dist/extensions/deepwiki/http.js.map +1 -1
  41. package/dist/extensions/deepwiki/schema.d.ts +1 -1
  42. package/dist/extensions/deepwiki/schema.d.ts.map +1 -1
  43. package/dist/extensions/deepwiki/schema.js +8 -2
  44. package/dist/extensions/deepwiki/schema.js.map +1 -1
  45. package/dist/extensions/question/dialog.d.ts.map +1 -1
  46. package/dist/extensions/question/dialog.js +80 -9
  47. package/dist/extensions/question/dialog.js.map +1 -1
  48. package/dist/extensions/question/index.d.ts.map +1 -1
  49. package/dist/extensions/question/index.js +8 -0
  50. package/dist/extensions/question/index.js.map +1 -1
  51. package/dist/extensions/question/schema.d.ts +2 -2
  52. package/dist/extensions/question/schema.d.ts.map +1 -1
  53. package/dist/extensions/question/schema.js +41 -11
  54. package/dist/extensions/question/schema.js.map +1 -1
  55. package/dist/extensions/question/types.d.ts +1 -1
  56. package/dist/extensions/question/types.d.ts.map +1 -1
  57. package/dist/extensions/question/types.js.map +1 -1
  58. package/dist/extensions/router/probe.d.ts.map +1 -1
  59. package/dist/extensions/router/probe.js +10 -29
  60. package/dist/extensions/router/probe.js.map +1 -1
  61. package/dist/extensions/router/register.d.ts.map +1 -1
  62. package/dist/extensions/router/register.js +16 -6
  63. package/dist/extensions/router/register.js.map +1 -1
  64. package/dist/extensions/router/store.d.ts.map +1 -1
  65. package/dist/extensions/router/store.js +108 -51
  66. package/dist/extensions/router/store.js.map +1 -1
  67. package/dist/extensions/router/ui.d.ts +6 -0
  68. package/dist/extensions/router/ui.d.ts.map +1 -1
  69. package/dist/extensions/router/ui.js +14 -6
  70. package/dist/extensions/router/ui.js.map +1 -1
  71. package/dist/extensions/statusline/index.d.ts.map +1 -1
  72. package/dist/extensions/statusline/index.js +40 -43
  73. package/dist/extensions/statusline/index.js.map +1 -1
  74. package/dist/extensions/subagent/activity.d.ts +7 -1
  75. package/dist/extensions/subagent/activity.d.ts.map +1 -1
  76. package/dist/extensions/subagent/activity.js +69 -6
  77. package/dist/extensions/subagent/activity.js.map +1 -1
  78. package/dist/extensions/subagent/agents-command.js +1 -1
  79. package/dist/extensions/subagent/agents-command.js.map +1 -1
  80. package/dist/extensions/subagent/agents.d.ts.map +1 -1
  81. package/dist/extensions/subagent/agents.js +0 -3
  82. package/dist/extensions/subagent/agents.js.map +1 -1
  83. package/dist/extensions/subagent/budget.d.ts.map +1 -1
  84. package/dist/extensions/subagent/budget.js +29 -2
  85. package/dist/extensions/subagent/budget.js.map +1 -1
  86. package/dist/extensions/subagent/cancellation.d.ts +4 -4
  87. package/dist/extensions/subagent/cancellation.d.ts.map +1 -1
  88. package/dist/extensions/subagent/cancellation.js +31 -13
  89. package/dist/extensions/subagent/cancellation.js.map +1 -1
  90. package/dist/extensions/subagent/constants.d.ts +1 -0
  91. package/dist/extensions/subagent/constants.d.ts.map +1 -1
  92. package/dist/extensions/subagent/constants.js +1 -0
  93. package/dist/extensions/subagent/constants.js.map +1 -1
  94. package/dist/extensions/subagent/index.d.ts.map +1 -1
  95. package/dist/extensions/subagent/index.js +1 -0
  96. package/dist/extensions/subagent/index.js.map +1 -1
  97. package/dist/extensions/subagent/render.d.ts +8 -1
  98. package/dist/extensions/subagent/render.d.ts.map +1 -1
  99. package/dist/extensions/subagent/render.js +148 -178
  100. package/dist/extensions/subagent/render.js.map +1 -1
  101. package/dist/extensions/subagent/runner.d.ts +1 -0
  102. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  103. package/dist/extensions/subagent/runner.js +53 -44
  104. package/dist/extensions/subagent/runner.js.map +1 -1
  105. package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
  106. package/dist/extensions/subagent/sdk-runner.js +0 -3
  107. package/dist/extensions/subagent/sdk-runner.js.map +1 -1
  108. package/dist/extensions/subagent/settings.d.ts +1 -1
  109. package/dist/extensions/subagent/settings.d.ts.map +1 -1
  110. package/dist/extensions/subagent/settings.js +74 -25
  111. package/dist/extensions/subagent/settings.js.map +1 -1
  112. package/dist/extensions/subagent/state.d.ts +1 -0
  113. package/dist/extensions/subagent/state.d.ts.map +1 -1
  114. package/dist/extensions/subagent/state.js +46 -16
  115. package/dist/extensions/subagent/state.js.map +1 -1
  116. package/dist/extensions/subagent/types.d.ts +0 -7
  117. package/dist/extensions/subagent/types.d.ts.map +1 -1
  118. package/dist/extensions/subagent/types.js.map +1 -1
  119. package/dist/extensions/subagent/ui/agents-dialog.js +1 -1
  120. package/dist/extensions/subagent/ui/agents-dialog.js.map +1 -1
  121. package/dist/extensions/subagent/ui/model-list.d.ts.map +1 -1
  122. package/dist/extensions/subagent/ui/model-list.js +3 -4
  123. package/dist/extensions/subagent/ui/model-list.js.map +1 -1
  124. package/dist/extensions/subagent/ui/model-picker.d.ts.map +1 -1
  125. package/dist/extensions/subagent/ui/model-picker.js +2 -1
  126. package/dist/extensions/subagent/ui/model-picker.js.map +1 -1
  127. package/dist/extensions/web-search/constants.d.ts +2 -2
  128. package/dist/extensions/web-search/constants.d.ts.map +1 -1
  129. package/dist/extensions/web-search/constants.js +4 -5
  130. package/dist/extensions/web-search/constants.js.map +1 -1
  131. package/dist/extensions/web-search/execute.d.ts +13 -0
  132. package/dist/extensions/web-search/execute.d.ts.map +1 -0
  133. package/dist/extensions/web-search/execute.js +101 -0
  134. package/dist/extensions/web-search/execute.js.map +1 -0
  135. package/dist/extensions/web-search/format.d.ts +12 -0
  136. package/dist/extensions/web-search/format.d.ts.map +1 -0
  137. package/dist/extensions/web-search/format.js +109 -0
  138. package/dist/extensions/web-search/format.js.map +1 -0
  139. package/dist/extensions/web-search/index.d.ts.map +1 -1
  140. package/dist/extensions/web-search/index.js +12 -9
  141. package/dist/extensions/web-search/index.js.map +1 -1
  142. package/dist/extensions/web-search/providers/deepseek.d.ts +0 -2
  143. package/dist/extensions/web-search/providers/deepseek.d.ts.map +1 -1
  144. package/dist/extensions/web-search/providers/deepseek.js +82 -34
  145. package/dist/extensions/web-search/providers/deepseek.js.map +1 -1
  146. package/dist/extensions/web-search/providers/http.d.ts +2 -7
  147. package/dist/extensions/web-search/providers/http.d.ts.map +1 -1
  148. package/dist/extensions/web-search/providers/http.js +30 -13
  149. package/dist/extensions/web-search/providers/http.js.map +1 -1
  150. package/dist/extensions/web-search/providers/minimax.d.ts +0 -2
  151. package/dist/extensions/web-search/providers/minimax.d.ts.map +1 -1
  152. package/dist/extensions/web-search/providers/minimax.js +43 -50
  153. package/dist/extensions/web-search/providers/minimax.js.map +1 -1
  154. package/dist/extensions/web-search/render.d.ts +1 -2
  155. package/dist/extensions/web-search/render.d.ts.map +1 -1
  156. package/dist/extensions/web-search/render.js +22 -17
  157. package/dist/extensions/web-search/render.js.map +1 -1
  158. package/dist/extensions/web-search/results.d.ts +35 -0
  159. package/dist/extensions/web-search/results.d.ts.map +1 -0
  160. package/dist/extensions/web-search/results.js +172 -0
  161. package/dist/extensions/web-search/results.js.map +1 -0
  162. package/dist/extensions/web-search/schema.d.ts +0 -2
  163. package/dist/extensions/web-search/schema.d.ts.map +1 -1
  164. package/dist/extensions/web-search/schema.js +4 -25
  165. package/dist/extensions/web-search/schema.js.map +1 -1
  166. package/dist/extensions/web-search/types.d.ts +0 -62
  167. package/dist/extensions/web-search/types.d.ts.map +1 -1
  168. package/dist/extensions/web-search/types.js.map +1 -1
  169. package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  170. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  171. package/dist/modes/interactive/components/tool-execution.js +7 -0
  172. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  173. package/dist/modes/interactive/components/tool-group.d.ts.map +1 -1
  174. package/dist/modes/interactive/components/tool-group.js +10 -0
  175. package/dist/modes/interactive/components/tool-group.js.map +1 -1
  176. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  177. package/dist/modes/interactive/interactive-mode.js +3 -40
  178. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  179. package/dist/modes/interactive/theme/theme.d.ts +0 -4
  180. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  181. package/dist/modes/interactive/theme/theme.js +16 -23
  182. package/dist/modes/interactive/theme/theme.js.map +1 -1
  183. package/dist/utils/http-response.d.ts +10 -0
  184. package/dist/utils/http-response.d.ts.map +1 -0
  185. package/dist/utils/http-response.js +68 -0
  186. package/dist/utils/http-response.js.map +1 -0
  187. package/docs/bundled/extensions/background.md +2 -1
  188. package/docs/bundled/extensions/deepwiki.md +7 -5
  189. package/docs/bundled/extensions/question.md +6 -4
  190. package/docs/bundled/extensions/router.md +1 -1
  191. package/docs/bundled/extensions/statusline.md +1 -0
  192. package/docs/bundled/extensions/subagent.md +29 -39
  193. package/docs/bundled/extensions/web-search.md +8 -12
  194. package/docs/development.md +7 -2
  195. package/docs/docs.json +4 -0
  196. package/docs/extensions.md +2 -2
  197. package/npm-shrinkwrap.json +2 -2
  198. package/package.json +1 -1
  199. package/dist/extensions/web-search/fusion.d.ts +0 -36
  200. package/dist/extensions/web-search/fusion.d.ts.map +0 -1
  201. package/dist/extensions/web-search/fusion.js +0 -272
  202. package/dist/extensions/web-search/fusion.js.map +0 -1
@@ -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"]}