@astralyn/pi 0.84.11 → 0.84.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cli/startup-ui.d.ts.map +1 -1
  3. package/dist/cli/startup-ui.js +2 -1
  4. package/dist/cli/startup-ui.js.map +1 -1
  5. package/dist/core/agent-session.d.ts +62 -8
  6. package/dist/core/agent-session.d.ts.map +1 -1
  7. package/dist/core/agent-session.js +395 -78
  8. package/dist/core/agent-session.js.map +1 -1
  9. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  10. package/dist/core/compaction/branch-summarization.js +4 -3
  11. package/dist/core/compaction/branch-summarization.js.map +1 -1
  12. package/dist/core/compaction/compaction.d.ts +5 -0
  13. package/dist/core/compaction/compaction.d.ts.map +1 -1
  14. package/dist/core/compaction/compaction.js +19 -5
  15. package/dist/core/compaction/compaction.js.map +1 -1
  16. package/dist/core/extensions/index.d.ts +1 -1
  17. package/dist/core/extensions/index.d.ts.map +1 -1
  18. package/dist/core/extensions/index.js.map +1 -1
  19. package/dist/core/extensions/runner.d.ts +5 -0
  20. package/dist/core/extensions/runner.d.ts.map +1 -1
  21. package/dist/core/extensions/runner.js +44 -1
  22. package/dist/core/extensions/runner.js.map +1 -1
  23. package/dist/core/extensions/types.d.ts +18 -10
  24. package/dist/core/extensions/types.d.ts.map +1 -1
  25. package/dist/core/extensions/types.js.map +1 -1
  26. package/dist/core/session-manager.d.ts.map +1 -1
  27. package/dist/core/session-manager.js +4 -2
  28. package/dist/core/session-manager.js.map +1 -1
  29. package/dist/core/settings-manager.d.ts +8 -1
  30. package/dist/core/settings-manager.d.ts.map +1 -1
  31. package/dist/core/settings-manager.js +17 -0
  32. package/dist/core/settings-manager.js.map +1 -1
  33. package/dist/core/tools/bash.d.ts +5 -0
  34. package/dist/core/tools/bash.d.ts.map +1 -1
  35. package/dist/core/tools/bash.js +5 -1
  36. package/dist/core/tools/bash.js.map +1 -1
  37. package/dist/extensions/background/actions.d.ts +21 -0
  38. package/dist/extensions/background/actions.d.ts.map +1 -0
  39. package/dist/extensions/background/actions.js +264 -0
  40. package/dist/extensions/background/actions.js.map +1 -0
  41. package/dist/extensions/background/constants.d.ts +24 -0
  42. package/dist/extensions/background/constants.d.ts.map +1 -0
  43. package/dist/extensions/background/constants.js +24 -0
  44. package/dist/extensions/background/constants.js.map +1 -0
  45. package/dist/extensions/background/index.d.ts +6 -115
  46. package/dist/extensions/background/index.d.ts.map +1 -1
  47. package/dist/extensions/background/index.js +38 -509
  48. package/dist/extensions/background/index.js.map +1 -1
  49. package/dist/extensions/background/manager.d.ts +2 -1
  50. package/dist/extensions/background/manager.d.ts.map +1 -1
  51. package/dist/extensions/background/manager.js +8 -10
  52. package/dist/extensions/background/manager.js.map +1 -1
  53. package/dist/extensions/background/notify.d.ts +16 -0
  54. package/dist/extensions/background/notify.d.ts.map +1 -0
  55. package/dist/extensions/background/notify.js +114 -0
  56. package/dist/extensions/background/notify.js.map +1 -0
  57. package/dist/extensions/background/output-file.d.ts +58 -0
  58. package/dist/extensions/background/output-file.d.ts.map +1 -0
  59. package/dist/extensions/background/output-file.js +122 -0
  60. package/dist/extensions/background/output-file.js.map +1 -0
  61. package/dist/extensions/background/registry.d.ts +40 -72
  62. package/dist/extensions/background/registry.d.ts.map +1 -1
  63. package/dist/extensions/background/registry.js +71 -184
  64. package/dist/extensions/background/registry.js.map +1 -1
  65. package/dist/extensions/background/render.d.ts +3 -10
  66. package/dist/extensions/background/render.d.ts.map +1 -1
  67. package/dist/extensions/background/render.js +20 -60
  68. package/dist/extensions/background/render.js.map +1 -1
  69. package/dist/extensions/background/schema.d.ts +34 -0
  70. package/dist/extensions/background/schema.d.ts.map +1 -0
  71. package/dist/extensions/background/schema.js +89 -0
  72. package/dist/extensions/background/schema.js.map +1 -0
  73. package/dist/extensions/background/task-view.d.ts +48 -0
  74. package/dist/extensions/background/task-view.d.ts.map +1 -0
  75. package/dist/extensions/background/task-view.js +90 -0
  76. package/dist/extensions/background/task-view.js.map +1 -0
  77. package/dist/extensions/background/text.d.ts +15 -0
  78. package/dist/extensions/background/text.d.ts.map +1 -0
  79. package/dist/extensions/background/text.js +31 -0
  80. package/dist/extensions/background/text.js.map +1 -0
  81. package/dist/extensions/background/types.d.ts +74 -0
  82. package/dist/extensions/background/types.d.ts.map +1 -0
  83. package/dist/extensions/background/types.js +17 -0
  84. package/dist/extensions/background/types.js.map +1 -0
  85. package/dist/extensions/index.d.ts.map +1 -1
  86. package/dist/extensions/index.js +2 -0
  87. package/dist/extensions/index.js.map +1 -1
  88. package/dist/extensions/llama/client.d.ts +6 -0
  89. package/dist/extensions/llama/client.d.ts.map +1 -1
  90. package/dist/extensions/llama/client.js +7 -0
  91. package/dist/extensions/llama/client.js.map +1 -1
  92. package/dist/extensions/llama/provider.d.ts +3 -1
  93. package/dist/extensions/llama/provider.d.ts.map +1 -1
  94. package/dist/extensions/llama/provider.js +27 -6
  95. package/dist/extensions/llama/provider.js.map +1 -1
  96. package/dist/extensions/subagent/activity.d.ts +5 -0
  97. package/dist/extensions/subagent/activity.d.ts.map +1 -1
  98. package/dist/extensions/subagent/activity.js +5 -0
  99. package/dist/extensions/subagent/activity.js.map +1 -1
  100. package/dist/extensions/subagent/agents-command.d.ts.map +1 -1
  101. package/dist/extensions/subagent/agents-command.js +6 -391
  102. package/dist/extensions/subagent/agents-command.js.map +1 -1
  103. package/dist/extensions/subagent/constants.d.ts.map +1 -1
  104. package/dist/extensions/subagent/constants.js +1 -0
  105. package/dist/extensions/subagent/constants.js.map +1 -1
  106. package/dist/extensions/subagent/render.d.ts.map +1 -1
  107. package/dist/extensions/subagent/render.js +15 -1
  108. package/dist/extensions/subagent/render.js.map +1 -1
  109. package/dist/extensions/subagent/resolve.d.ts.map +1 -1
  110. package/dist/extensions/subagent/resolve.js +2 -4
  111. package/dist/extensions/subagent/resolve.js.map +1 -1
  112. package/dist/extensions/subagent/runner.d.ts +1 -1
  113. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  114. package/dist/extensions/subagent/runner.js +8 -8
  115. package/dist/extensions/subagent/runner.js.map +1 -1
  116. package/dist/extensions/subagent/sdk-runner.d.ts +11 -0
  117. package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
  118. package/dist/extensions/subagent/sdk-runner.js +90 -79
  119. package/dist/extensions/subagent/sdk-runner.js.map +1 -1
  120. package/dist/extensions/subagent/state.d.ts.map +1 -1
  121. package/dist/extensions/subagent/state.js +145 -126
  122. package/dist/extensions/subagent/state.js.map +1 -1
  123. package/dist/extensions/subagent/text.d.ts.map +1 -1
  124. package/dist/extensions/subagent/text.js +4 -0
  125. package/dist/extensions/subagent/text.js.map +1 -1
  126. package/dist/extensions/subagent/ui/agents-dialog.d.ts +4 -0
  127. package/dist/extensions/subagent/ui/agents-dialog.d.ts.map +1 -0
  128. package/dist/extensions/subagent/ui/agents-dialog.js +84 -0
  129. package/dist/extensions/subagent/ui/agents-dialog.js.map +1 -0
  130. package/dist/extensions/subagent/ui/choice-menu.d.ts +39 -0
  131. package/dist/extensions/subagent/ui/choice-menu.d.ts.map +1 -0
  132. package/dist/extensions/subagent/ui/choice-menu.js +93 -0
  133. package/dist/extensions/subagent/ui/choice-menu.js.map +1 -0
  134. package/dist/extensions/subagent/ui/model-picker.d.ts +47 -0
  135. package/dist/extensions/subagent/ui/model-picker.d.ts.map +1 -0
  136. package/dist/extensions/subagent/ui/model-picker.js +142 -0
  137. package/dist/extensions/subagent/ui/model-picker.js.map +1 -0
  138. package/dist/extensions/subagent/ui/profile-settings-menu.d.ts +41 -0
  139. package/dist/extensions/subagent/ui/profile-settings-menu.d.ts.map +1 -0
  140. package/dist/extensions/subagent/ui/profile-settings-menu.js +98 -0
  141. package/dist/extensions/subagent/ui/profile-settings-menu.js.map +1 -0
  142. package/dist/extensions/web-search/auth.d.ts +12 -0
  143. package/dist/extensions/web-search/auth.d.ts.map +1 -0
  144. package/dist/extensions/web-search/auth.js +90 -0
  145. package/dist/extensions/web-search/auth.js.map +1 -0
  146. package/dist/extensions/web-search/constants.d.ts +13 -0
  147. package/dist/extensions/web-search/constants.d.ts.map +1 -0
  148. package/dist/extensions/web-search/constants.js +27 -0
  149. package/dist/extensions/web-search/constants.js.map +1 -0
  150. package/dist/extensions/web-search/fusion.d.ts +36 -0
  151. package/dist/extensions/web-search/fusion.d.ts.map +1 -0
  152. package/dist/extensions/web-search/fusion.js +272 -0
  153. package/dist/extensions/web-search/fusion.js.map +1 -0
  154. package/dist/extensions/web-search/index.d.ts +6 -0
  155. package/dist/extensions/web-search/index.d.ts.map +1 -0
  156. package/dist/extensions/web-search/index.js +70 -0
  157. package/dist/extensions/web-search/index.js.map +1 -0
  158. package/dist/extensions/web-search/providers/deepseek.d.ts +15 -0
  159. package/dist/extensions/web-search/providers/deepseek.d.ts.map +1 -0
  160. package/dist/extensions/web-search/providers/deepseek.js +63 -0
  161. package/dist/extensions/web-search/providers/deepseek.js.map +1 -0
  162. package/dist/extensions/web-search/providers/http.d.ts +9 -0
  163. package/dist/extensions/web-search/providers/http.d.ts.map +1 -0
  164. package/dist/extensions/web-search/providers/http.js +25 -0
  165. package/dist/extensions/web-search/providers/http.js.map +1 -0
  166. package/dist/extensions/web-search/providers/minimax.d.ts +14 -0
  167. package/dist/extensions/web-search/providers/minimax.d.ts.map +1 -0
  168. package/dist/extensions/web-search/providers/minimax.js +69 -0
  169. package/dist/extensions/web-search/providers/minimax.js.map +1 -0
  170. package/dist/extensions/web-search/render.d.ts +11 -0
  171. package/dist/extensions/web-search/render.d.ts.map +1 -0
  172. package/dist/extensions/web-search/render.js +109 -0
  173. package/dist/extensions/web-search/render.js.map +1 -0
  174. package/dist/extensions/web-search/schema.d.ts +12 -0
  175. package/dist/extensions/web-search/schema.d.ts.map +1 -0
  176. package/dist/extensions/web-search/schema.js +57 -0
  177. package/dist/extensions/web-search/schema.js.map +1 -0
  178. package/dist/extensions/web-search/types.d.ts +111 -0
  179. package/dist/extensions/web-search/types.d.ts.map +1 -0
  180. package/dist/extensions/web-search/types.js +5 -0
  181. package/dist/extensions/web-search/types.js.map +1 -0
  182. package/dist/index.d.ts +2 -1
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.js +1 -0
  185. package/dist/index.js.map +1 -1
  186. package/dist/main.d.ts.map +1 -1
  187. package/dist/main.js +2 -0
  188. package/dist/main.js.map +1 -1
  189. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  190. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  191. package/dist/modes/interactive/components/settings-selector.js +10 -0
  192. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  193. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  194. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  195. package/dist/modes/interactive/interactive-mode.js +71 -58
  196. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  197. package/dist/modes/rpc/rpc-client.d.ts +7 -0
  198. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  199. package/dist/modes/rpc/rpc-client.js +7 -0
  200. package/dist/modes/rpc/rpc-client.js.map +1 -1
  201. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  202. package/dist/modes/rpc/rpc-mode.js +3 -0
  203. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  204. package/dist/modes/rpc/rpc-types.d.ts +12 -0
  205. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  206. package/dist/modes/rpc/rpc-types.js.map +1 -1
  207. package/dist/utils/shell.d.ts.map +1 -1
  208. package/dist/utils/shell.js +6 -4
  209. package/dist/utils/shell.js.map +1 -1
  210. package/docs/bundled/README.md +1 -0
  211. package/docs/bundled/extensions/web-search.md +50 -0
  212. package/docs/compaction.md +5 -12
  213. package/docs/environment-variables.md +3 -0
  214. package/docs/extensions.md +21 -5
  215. package/docs/keybindings.md +1 -1
  216. package/docs/quickstart.md +1 -1
  217. package/docs/rpc.md +23 -0
  218. package/docs/settings.md +7 -0
  219. package/docs/terminal-setup.md +12 -0
  220. package/docs/usage.md +4 -3
  221. package/npm-shrinkwrap.json +29 -29
  222. package/package.json +6 -6
  223. package/dist/core/compaction-controller.d.ts +0 -90
  224. package/dist/core/compaction-controller.d.ts.map +0 -1
  225. package/dist/core/compaction-controller.js +0 -471
  226. package/dist/core/compaction-controller.js.map +0 -1
@@ -0,0 +1,272 @@
1
+ /**
2
+ * fusion.ts — Dual search engine concurrency dispatcher, URL deduplication, ranking, and formatter.
3
+ */
4
+ import { configuredEngine } from "./auth.js";
5
+ import { getEngineLabel, WEB_SEARCH_DISABLED_MESSAGE } from "./constants.js";
6
+ import { searchDeepSeek } from "./providers/deepseek.js";
7
+ import { searchMiniMax } from "./providers/minimax.js";
8
+ const MAX_OUTPUT_HITS = 12;
9
+ const MAX_SNIPPET_LENGTH = 200;
10
+ const MAX_RELATED_SEARCHES = 8;
11
+ /**
12
+ * Normalize URL by removing common tracking parameters and fragments.
13
+ */
14
+ export function normalizeUrl(rawUrl) {
15
+ try {
16
+ const u = new URL(rawUrl);
17
+ // Remove hash
18
+ u.hash = "";
19
+ // Remove tracking query parameters
20
+ const trackingParams = [
21
+ "utm_source",
22
+ "utm_medium",
23
+ "utm_campaign",
24
+ "utm_term",
25
+ "utm_content",
26
+ "ref",
27
+ "fbclid",
28
+ "gclid",
29
+ "trk",
30
+ "gi",
31
+ ];
32
+ for (const p of trackingParams) {
33
+ u.searchParams.delete(p);
34
+ }
35
+ let href = u.toString();
36
+ if (href.endsWith("/") && u.pathname !== "/") {
37
+ href = href.slice(0, -1);
38
+ }
39
+ return href;
40
+ }
41
+ catch {
42
+ return rawUrl.trim();
43
+ }
44
+ }
45
+ /**
46
+ * Deduplicate and rank search hits from multiple search engines.
47
+ */
48
+ export function fuseSearchHits(results) {
49
+ const hitMap = new Map();
50
+ const relatedSet = new Set();
51
+ let deepseekSynthesis;
52
+ for (const result of results) {
53
+ if (result.relatedSearches) {
54
+ for (const r of result.relatedSearches) {
55
+ if (relatedSet.size >= MAX_RELATED_SEARCHES)
56
+ break;
57
+ if (r?.trim())
58
+ relatedSet.add(r.trim());
59
+ }
60
+ }
61
+ if (result.synthesisText) {
62
+ deepseekSynthesis = result.synthesisText;
63
+ }
64
+ for (const hit of result.hits) {
65
+ const normalized = normalizeUrl(hit.url);
66
+ const existing = hitMap.get(normalized);
67
+ if (existing) {
68
+ for (const s of hit.sources) {
69
+ if (!existing.sources.includes(s)) {
70
+ existing.sources.push(s);
71
+ }
72
+ }
73
+ // Prefer longer snippet or date if missing
74
+ if ((!existing.snippet || existing.snippet.length < (hit.snippet?.length || 0)) && hit.snippet) {
75
+ existing.snippet = hit.snippet;
76
+ }
77
+ if (!existing.date && hit.date) {
78
+ existing.date = hit.date;
79
+ }
80
+ }
81
+ else {
82
+ hitMap.set(normalized, {
83
+ ...hit,
84
+ url: normalized,
85
+ sources: [...hit.sources],
86
+ });
87
+ }
88
+ }
89
+ }
90
+ // Sort hits: items supported by multiple engines first, then preserve discovery order
91
+ const fused = Array.from(hitMap.values()).sort((a, b) => {
92
+ if (b.sources.length !== a.sources.length) {
93
+ return b.sources.length - a.sources.length;
94
+ }
95
+ return 0;
96
+ });
97
+ return {
98
+ hits: fused.slice(0, MAX_OUTPUT_HITS),
99
+ relatedSearches: relatedSet.size > 0 ? Array.from(relatedSet) : undefined,
100
+ deepseekSynthesis,
101
+ };
102
+ }
103
+ /**
104
+ * Sources, synthesis, and related-searches sections. Shared by the model-facing
105
+ * payload (formatSearchOutput) and the expanded TUI view, which adds no agent
106
+ * directives of its own.
107
+ */
108
+ export function formatResultsMarkdown(details) {
109
+ const parts = [];
110
+ if (details.hits.length > 0) {
111
+ parts.push(`## Verified Web Sources (${details.hits.length} found via ${getEngineLabel(details.engine)})\n`);
112
+ details.hits.forEach((hit, idx) => {
113
+ const sourceTag = hit.sources.length > 1 ? ` — *(verified by ${hit.sources.join(" & ")})*` : "";
114
+ parts.push(`${idx + 1}. **[${hit.title}](${hit.url})**${sourceTag}`);
115
+ if (hit.snippet) {
116
+ const truncated = hit.snippet.length > MAX_SNIPPET_LENGTH
117
+ ? `${hit.snippet.slice(0, MAX_SNIPPET_LENGTH).trim()}...`
118
+ : hit.snippet.trim();
119
+ parts.push(` - ${truncated}`);
120
+ }
121
+ });
122
+ parts.push("");
123
+ }
124
+ if (details.deepseekSynthesis) {
125
+ parts.push("## Key Technical Insights & Synthesis\n");
126
+ parts.push(details.deepseekSynthesis.trim());
127
+ parts.push("");
128
+ }
129
+ if (details.relatedSearches && details.relatedSearches.length > 0) {
130
+ parts.push("## Related Searches\n");
131
+ parts.push(details.relatedSearches.map((r) => `- ${r}`).join("\n"));
132
+ parts.push("");
133
+ }
134
+ return parts.join("\n");
135
+ }
136
+ /**
137
+ * Format search results into a clean, structured Markdown payload for the Main Agent.
138
+ */
139
+ export function formatSearchOutput(query, details) {
140
+ if (details.status === "disabled") {
141
+ return WEB_SEARCH_DISABLED_MESSAGE;
142
+ }
143
+ if (details.status === "error") {
144
+ return `Web search failed for "${query}": ${details.errorMessage || "Unknown search error"}`;
145
+ }
146
+ if (details.hits.length === 0 && !details.deepseekSynthesis) {
147
+ return `No search results found for "${query}". Try rephrasing with different keywords.`;
148
+ }
149
+ return [
150
+ `# Web Search Results for: "${query}"\n`,
151
+ formatResultsMarkdown(details),
152
+ "---",
153
+ "**CRITICAL REQUIREMENT FOR MAIN AGENT:** Use the above search results to inform your response. You MUST include a `Sources:` section at the end of your response listing the relevant URLs as markdown links: `[Title](URL)`.",
154
+ ].join("\n");
155
+ }
156
+ /**
157
+ * Execute web search across MiniMax and/or DeepSeek based on configured credentials.
158
+ */
159
+ export async function executeWebSearch(params, credentials, signal, onUpdate) {
160
+ const startTime = Date.now();
161
+ const query = params.query.trim();
162
+ const engine = configuredEngine(credentials);
163
+ if (!query) {
164
+ const details = {
165
+ query: "",
166
+ durationMs: 0,
167
+ status: "error",
168
+ engine,
169
+ totalHits: 0,
170
+ hits: [],
171
+ errorMessage: "Search query must not be empty.",
172
+ };
173
+ return {
174
+ formattedOutput: formatSearchOutput(query, details),
175
+ details,
176
+ };
177
+ }
178
+ if (engine === "none") {
179
+ const details = {
180
+ query,
181
+ durationMs: 0,
182
+ status: "disabled",
183
+ engine: "none",
184
+ totalHits: 0,
185
+ hits: [],
186
+ errorMessage: WEB_SEARCH_DISABLED_MESSAGE,
187
+ };
188
+ return {
189
+ formattedOutput: formatSearchOutput(query, details),
190
+ details,
191
+ };
192
+ }
193
+ // Send initial progress update
194
+ onUpdate?.({
195
+ content: [{ type: "text", text: `Searching for "${query}" via ${getEngineLabel(engine)}...` }],
196
+ details: {
197
+ query,
198
+ durationMs: 0,
199
+ status: "success",
200
+ engine,
201
+ totalHits: 0,
202
+ hits: [],
203
+ },
204
+ });
205
+ const searchPromises = [];
206
+ if (credentials.minimax) {
207
+ searchPromises.push(searchMiniMax({
208
+ query,
209
+ apiKey: credentials.minimax.key,
210
+ apiHost: credentials.minimax.host,
211
+ allowedDomains: params.allowed_domains,
212
+ blockedDomains: params.blocked_domains,
213
+ signal,
214
+ }));
215
+ }
216
+ if (credentials.deepseek) {
217
+ searchPromises.push(searchDeepSeek({
218
+ query,
219
+ apiKey: credentials.deepseek.key,
220
+ allowedDomains: params.allowed_domains,
221
+ blockedDomains: params.blocked_domains,
222
+ signal,
223
+ }));
224
+ }
225
+ const settledResults = await Promise.allSettled(searchPromises);
226
+ const durationMs = Date.now() - startTime;
227
+ const successfulResults = [];
228
+ const errors = [];
229
+ for (const res of settledResults) {
230
+ if (res.status === "fulfilled") {
231
+ successfulResults.push(res.value);
232
+ }
233
+ else {
234
+ errors.push(res.reason instanceof Error ? res.reason.message : String(res.reason));
235
+ }
236
+ }
237
+ // If all engines failed
238
+ if (successfulResults.length === 0) {
239
+ const details = {
240
+ query,
241
+ durationMs,
242
+ status: "error",
243
+ engine,
244
+ totalHits: 0,
245
+ hits: [],
246
+ errorMessage: errors.join(" | ") || "All search engines failed",
247
+ };
248
+ return {
249
+ formattedOutput: formatSearchOutput(query, details),
250
+ details,
251
+ };
252
+ }
253
+ const { hits, relatedSearches, deepseekSynthesis } = fuseSearchHits(successfulResults);
254
+ // Badge reflects the engines that actually responded, not just configuration.
255
+ const respondedSources = new Set(successfulResults.map((r) => r.source));
256
+ const contributingEngine = respondedSources.size > 1 ? "dual" : respondedSources.has("MiniMax") ? "minimax" : "deepseek";
257
+ const details = {
258
+ query,
259
+ durationMs,
260
+ status: "success",
261
+ engine: contributingEngine,
262
+ totalHits: hits.length,
263
+ hits,
264
+ relatedSearches,
265
+ deepseekSynthesis,
266
+ };
267
+ return {
268
+ formattedOutput: formatSearchOutput(query, details),
269
+ details,
270
+ };
271
+ }
272
+ //# sourceMappingURL=fusion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fusion.js","sourceRoot":"","sources":["../../../src/extensions/web-search/fusion.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * fusion.ts — Dual search engine concurrency dispatcher, URL deduplication, ranking, and formatter.\n */\n\nimport type { AgentToolUpdateCallback } from \"../../core/extensions/types.ts\";\nimport { configuredEngine } from \"./auth.ts\";\nimport { getEngineLabel, WEB_SEARCH_DISABLED_MESSAGE } from \"./constants.ts\";\nimport { searchDeepSeek } from \"./providers/deepseek.ts\";\nimport { searchMiniMax } from \"./providers/minimax.ts\";\nimport type { WebSearchParams } from \"./schema.ts\";\nimport type {\n\tProviderSearchResult,\n\tResolvedSearchCredentials,\n\tSearchEngineType,\n\tWebSearchDetails,\n\tWebSearchHit,\n} from \"./types.ts\";\n\nconst MAX_OUTPUT_HITS = 12;\nconst MAX_SNIPPET_LENGTH = 200;\nconst MAX_RELATED_SEARCHES = 8;\n\n/**\n * Normalize URL by removing common tracking parameters and fragments.\n */\nexport function normalizeUrl(rawUrl: string): string {\n\ttry {\n\t\tconst u = new URL(rawUrl);\n\t\t// Remove hash\n\t\tu.hash = \"\";\n\t\t// Remove tracking query parameters\n\t\tconst trackingParams = [\n\t\t\t\"utm_source\",\n\t\t\t\"utm_medium\",\n\t\t\t\"utm_campaign\",\n\t\t\t\"utm_term\",\n\t\t\t\"utm_content\",\n\t\t\t\"ref\",\n\t\t\t\"fbclid\",\n\t\t\t\"gclid\",\n\t\t\t\"trk\",\n\t\t\t\"gi\",\n\t\t];\n\t\tfor (const p of trackingParams) {\n\t\t\tu.searchParams.delete(p);\n\t\t}\n\t\tlet href = u.toString();\n\t\tif (href.endsWith(\"/\") && u.pathname !== \"/\") {\n\t\t\thref = href.slice(0, -1);\n\t\t}\n\t\treturn href;\n\t} catch {\n\t\treturn rawUrl.trim();\n\t}\n}\n\n/**\n * Deduplicate and rank search hits from multiple search engines.\n */\nexport function fuseSearchHits(results: ProviderSearchResult[]): {\n\thits: WebSearchHit[];\n\trelatedSearches?: string[];\n\tdeepseekSynthesis?: string;\n} {\n\tconst hitMap = new Map<string, WebSearchHit>();\n\tconst relatedSet = new Set<string>();\n\tlet deepseekSynthesis: string | undefined;\n\n\tfor (const result of results) {\n\t\tif (result.relatedSearches) {\n\t\t\tfor (const r of result.relatedSearches) {\n\t\t\t\tif (relatedSet.size >= MAX_RELATED_SEARCHES) break;\n\t\t\t\tif (r?.trim()) relatedSet.add(r.trim());\n\t\t\t}\n\t\t}\n\t\tif (result.synthesisText) {\n\t\t\tdeepseekSynthesis = result.synthesisText;\n\t\t}\n\n\t\tfor (const hit of result.hits) {\n\t\t\tconst normalized = normalizeUrl(hit.url);\n\t\t\tconst existing = hitMap.get(normalized);\n\n\t\t\tif (existing) {\n\t\t\t\tfor (const s of hit.sources) {\n\t\t\t\t\tif (!existing.sources.includes(s)) {\n\t\t\t\t\t\texisting.sources.push(s);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Prefer longer snippet or date if missing\n\t\t\t\tif ((!existing.snippet || existing.snippet.length < (hit.snippet?.length || 0)) && hit.snippet) {\n\t\t\t\t\texisting.snippet = hit.snippet;\n\t\t\t\t}\n\t\t\t\tif (!existing.date && hit.date) {\n\t\t\t\t\texisting.date = hit.date;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\thitMap.set(normalized, {\n\t\t\t\t\t...hit,\n\t\t\t\t\turl: normalized,\n\t\t\t\t\tsources: [...hit.sources],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sort hits: items supported by multiple engines first, then preserve discovery order\n\tconst fused = Array.from(hitMap.values()).sort((a, b) => {\n\t\tif (b.sources.length !== a.sources.length) {\n\t\t\treturn b.sources.length - a.sources.length;\n\t\t}\n\t\treturn 0;\n\t});\n\n\treturn {\n\t\thits: fused.slice(0, MAX_OUTPUT_HITS),\n\t\trelatedSearches: relatedSet.size > 0 ? Array.from(relatedSet) : undefined,\n\t\tdeepseekSynthesis,\n\t};\n}\n\n/**\n * Sources, synthesis, and related-searches sections. Shared by the model-facing\n * payload (formatSearchOutput) and the expanded TUI view, which adds no agent\n * directives of its own.\n */\nexport function formatResultsMarkdown(details: WebSearchDetails): string {\n\tconst parts: string[] = [];\n\n\tif (details.hits.length > 0) {\n\t\tparts.push(`## Verified Web Sources (${details.hits.length} found via ${getEngineLabel(details.engine)})\\n`);\n\n\t\tdetails.hits.forEach((hit, idx) => {\n\t\t\tconst sourceTag = hit.sources.length > 1 ? ` — *(verified by ${hit.sources.join(\" & \")})*` : \"\";\n\t\t\tparts.push(`${idx + 1}. **[${hit.title}](${hit.url})**${sourceTag}`);\n\t\t\tif (hit.snippet) {\n\t\t\t\tconst truncated =\n\t\t\t\t\thit.snippet.length > MAX_SNIPPET_LENGTH\n\t\t\t\t\t\t? `${hit.snippet.slice(0, MAX_SNIPPET_LENGTH).trim()}...`\n\t\t\t\t\t\t: hit.snippet.trim();\n\t\t\t\tparts.push(` - ${truncated}`);\n\t\t\t}\n\t\t});\n\t\tparts.push(\"\");\n\t}\n\n\tif (details.deepseekSynthesis) {\n\t\tparts.push(\"## Key Technical Insights & Synthesis\\n\");\n\t\tparts.push(details.deepseekSynthesis.trim());\n\t\tparts.push(\"\");\n\t}\n\n\tif (details.relatedSearches && details.relatedSearches.length > 0) {\n\t\tparts.push(\"## Related Searches\\n\");\n\t\tparts.push(details.relatedSearches.map((r) => `- ${r}`).join(\"\\n\"));\n\t\tparts.push(\"\");\n\t}\n\n\treturn parts.join(\"\\n\");\n}\n\n/**\n * Format search results into a clean, structured Markdown payload for the Main Agent.\n */\nexport function formatSearchOutput(query: string, details: WebSearchDetails): string {\n\tif (details.status === \"disabled\") {\n\t\treturn WEB_SEARCH_DISABLED_MESSAGE;\n\t}\n\n\tif (details.status === \"error\") {\n\t\treturn `Web search failed for \"${query}\": ${details.errorMessage || \"Unknown search error\"}`;\n\t}\n\n\tif (details.hits.length === 0 && !details.deepseekSynthesis) {\n\t\treturn `No search results found for \"${query}\". Try rephrasing with different keywords.`;\n\t}\n\n\treturn [\n\t\t`# Web Search Results for: \"${query}\"\\n`,\n\t\tformatResultsMarkdown(details),\n\t\t\"---\",\n\t\t\"**CRITICAL REQUIREMENT FOR MAIN AGENT:** Use the above search results to inform your response. You MUST include a `Sources:` section at the end of your response listing the relevant URLs as markdown links: `[Title](URL)`.\",\n\t].join(\"\\n\");\n}\n\n/**\n * Execute web search across MiniMax and/or DeepSeek based on configured credentials.\n */\nexport async function executeWebSearch(\n\tparams: WebSearchParams,\n\tcredentials: ResolvedSearchCredentials,\n\tsignal?: AbortSignal,\n\tonUpdate?: AgentToolUpdateCallback<WebSearchDetails>,\n): Promise<{ formattedOutput: string; details: WebSearchDetails }> {\n\tconst startTime = Date.now();\n\tconst query = params.query.trim();\n\tconst engine = configuredEngine(credentials);\n\n\tif (!query) {\n\t\tconst details: WebSearchDetails = {\n\t\t\tquery: \"\",\n\t\t\tdurationMs: 0,\n\t\t\tstatus: \"error\",\n\t\t\tengine,\n\t\t\ttotalHits: 0,\n\t\t\thits: [],\n\t\t\terrorMessage: \"Search query must not be empty.\",\n\t\t};\n\t\treturn {\n\t\t\tformattedOutput: formatSearchOutput(query, details),\n\t\t\tdetails,\n\t\t};\n\t}\n\n\tif (engine === \"none\") {\n\t\tconst details: WebSearchDetails = {\n\t\t\tquery,\n\t\t\tdurationMs: 0,\n\t\t\tstatus: \"disabled\",\n\t\t\tengine: \"none\",\n\t\t\ttotalHits: 0,\n\t\t\thits: [],\n\t\t\terrorMessage: WEB_SEARCH_DISABLED_MESSAGE,\n\t\t};\n\t\treturn {\n\t\t\tformattedOutput: formatSearchOutput(query, details),\n\t\t\tdetails,\n\t\t};\n\t}\n\n\t// Send initial progress update\n\tonUpdate?.({\n\t\tcontent: [{ type: \"text\", text: `Searching for \"${query}\" via ${getEngineLabel(engine)}...` }],\n\t\tdetails: {\n\t\t\tquery,\n\t\t\tdurationMs: 0,\n\t\t\tstatus: \"success\",\n\t\t\tengine,\n\t\t\ttotalHits: 0,\n\t\t\thits: [],\n\t\t},\n\t});\n\n\tconst searchPromises: Promise<ProviderSearchResult>[] = [];\n\n\tif (credentials.minimax) {\n\t\tsearchPromises.push(\n\t\t\tsearchMiniMax({\n\t\t\t\tquery,\n\t\t\t\tapiKey: credentials.minimax.key,\n\t\t\t\tapiHost: credentials.minimax.host,\n\t\t\t\tallowedDomains: params.allowed_domains,\n\t\t\t\tblockedDomains: params.blocked_domains,\n\t\t\t\tsignal,\n\t\t\t}),\n\t\t);\n\t}\n\n\tif (credentials.deepseek) {\n\t\tsearchPromises.push(\n\t\t\tsearchDeepSeek({\n\t\t\t\tquery,\n\t\t\t\tapiKey: credentials.deepseek.key,\n\t\t\t\tallowedDomains: params.allowed_domains,\n\t\t\t\tblockedDomains: params.blocked_domains,\n\t\t\t\tsignal,\n\t\t\t}),\n\t\t);\n\t}\n\n\tconst settledResults = await Promise.allSettled(searchPromises);\n\tconst durationMs = Date.now() - startTime;\n\n\tconst successfulResults: ProviderSearchResult[] = [];\n\tconst errors: string[] = [];\n\n\tfor (const res of settledResults) {\n\t\tif (res.status === \"fulfilled\") {\n\t\t\tsuccessfulResults.push(res.value);\n\t\t} else {\n\t\t\terrors.push(res.reason instanceof Error ? res.reason.message : String(res.reason));\n\t\t}\n\t}\n\n\t// If all engines failed\n\tif (successfulResults.length === 0) {\n\t\tconst details: WebSearchDetails = {\n\t\t\tquery,\n\t\t\tdurationMs,\n\t\t\tstatus: \"error\",\n\t\t\tengine,\n\t\t\ttotalHits: 0,\n\t\t\thits: [],\n\t\t\terrorMessage: errors.join(\" | \") || \"All search engines failed\",\n\t\t};\n\t\treturn {\n\t\t\tformattedOutput: formatSearchOutput(query, details),\n\t\t\tdetails,\n\t\t};\n\t}\n\n\tconst { hits, relatedSearches, deepseekSynthesis } = fuseSearchHits(successfulResults);\n\n\t// Badge reflects the engines that actually responded, not just configuration.\n\tconst respondedSources = new Set(successfulResults.map((r) => r.source));\n\tconst contributingEngine: SearchEngineType =\n\t\trespondedSources.size > 1 ? \"dual\" : respondedSources.has(\"MiniMax\") ? \"minimax\" : \"deepseek\";\n\n\tconst details: WebSearchDetails = {\n\t\tquery,\n\t\tdurationMs,\n\t\tstatus: \"success\",\n\t\tengine: contributingEngine,\n\t\ttotalHits: hits.length,\n\t\thits,\n\t\trelatedSearches,\n\t\tdeepseekSynthesis,\n\t};\n\n\treturn {\n\t\tformattedOutput: formatSearchOutput(query, details),\n\t\tdetails,\n\t};\n}\n"]}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * web_search — Pi-native Web Search extension combining MiniMax and DeepSeek search engines.
3
+ */
4
+ import type { ExtensionAPI } from "../../core/extensions/types.ts";
5
+ export default function webSearch(pi: ExtensionAPI): void;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/web-search/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAqB,MAAM,gCAAgC,CAAC;AAuCtF,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA6CxD"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * web_search — Pi-native Web Search extension combining MiniMax and DeepSeek search engines.
3
+ */
4
+ import { configuredEngine, resolveSearchCredentials } from "./auth.js";
5
+ import { getWebSearchPromptGuidelines, WEB_SEARCH_DESCRIPTION, WEB_SEARCH_LABEL, WEB_SEARCH_PROMPT_SNIPPET, WEB_SEARCH_TOOL_NAME, } from "./constants.js";
6
+ import { executeWebSearch } from "./fusion.js";
7
+ import { renderWebSearchCall, renderWebSearchResult } from "./render.js";
8
+ import { normalizeWebSearchParams, WebSearchParamsSchema } from "./schema.js";
9
+ function trackQueryElapsed(context, isPartial) {
10
+ const state = context.state;
11
+ if (isPartial) {
12
+ state.startedAt ??= Date.now();
13
+ if (state.refreshTimer === undefined) {
14
+ state.refreshTimer = setTimeout(() => {
15
+ state.refreshTimer = undefined;
16
+ context.invalidate();
17
+ }, 1000);
18
+ state.refreshTimer.unref?.();
19
+ }
20
+ return Date.now() - state.startedAt;
21
+ }
22
+ if (state.refreshTimer !== undefined) {
23
+ clearTimeout(state.refreshTimer);
24
+ state.refreshTimer = undefined;
25
+ }
26
+ return undefined;
27
+ }
28
+ export default function webSearch(pi) {
29
+ pi.registerTool({
30
+ name: WEB_SEARCH_TOOL_NAME,
31
+ label: WEB_SEARCH_LABEL,
32
+ description: WEB_SEARCH_DESCRIPTION,
33
+ promptSnippet: WEB_SEARCH_PROMPT_SNIPPET,
34
+ promptGuidelines: getWebSearchPromptGuidelines(),
35
+ parameters: WebSearchParamsSchema,
36
+ prepareArguments: normalizeWebSearchParams,
37
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
38
+ const credentials = await resolveSearchCredentials(ctx.modelRuntime);
39
+ const execution = await executeWebSearch(params, credentials, signal, onUpdate);
40
+ return {
41
+ content: [{ type: "text", text: execution.formattedOutput }],
42
+ details: execution.details,
43
+ isError: execution.details.status !== "success",
44
+ };
45
+ },
46
+ renderCall(args, theme) {
47
+ return renderWebSearchCall(args, theme);
48
+ },
49
+ renderResult(result, options, theme, context) {
50
+ const elapsedMs = trackQueryElapsed(context, options.isPartial);
51
+ return renderWebSearchResult(result, options, theme, context.isError, elapsedMs);
52
+ },
53
+ });
54
+ // Keep the tool out of the model's tool set (and system prompt) when no search
55
+ // credentials are configured. Re-evaluated on every session start (startup,
56
+ // /new, /reload, resume, fork), so /login takes effect from the next session
57
+ // onward; the tool stays registered for /tools and historical rendering.
58
+ pi.on("session_start", async (_event, ctx) => {
59
+ const credentials = await resolveSearchCredentials(ctx.modelRuntime);
60
+ const active = new Set(pi.getActiveTools());
61
+ if (configuredEngine(credentials) === "none") {
62
+ active.delete(WEB_SEARCH_TOOL_NAME);
63
+ }
64
+ else {
65
+ active.add(WEB_SEARCH_TOOL_NAME);
66
+ }
67
+ pi.setActiveTools([...active]);
68
+ });
69
+ }
70
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/web-search/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * web_search — Pi-native Web Search extension combining MiniMax and DeepSeek search engines.\n */\n\nimport type { ExtensionAPI, ToolRenderContext } from \"../../core/extensions/types.ts\";\nimport { configuredEngine, resolveSearchCredentials } from \"./auth.ts\";\nimport {\n\tgetWebSearchPromptGuidelines,\n\tWEB_SEARCH_DESCRIPTION,\n\tWEB_SEARCH_LABEL,\n\tWEB_SEARCH_PROMPT_SNIPPET,\n\tWEB_SEARCH_TOOL_NAME,\n} from \"./constants.ts\";\nimport { executeWebSearch } from \"./fusion.ts\";\nimport { renderWebSearchCall, renderWebSearchResult } from \"./render.ts\";\nimport { normalizeWebSearchParams, type WebSearchParams, WebSearchParamsSchema } from \"./schema.ts\";\nimport type { WebSearchDetails } from \"./types.ts\";\n\ninterface WebSearchRenderState {\n\tstartedAt?: number;\n\trefreshTimer?: ReturnType<typeof setTimeout>;\n}\n\nfunction trackQueryElapsed(context: ToolRenderContext<WebSearchRenderState>, isPartial: boolean): number | undefined {\n\tconst state = context.state;\n\tif (isPartial) {\n\t\tstate.startedAt ??= Date.now();\n\t\tif (state.refreshTimer === undefined) {\n\t\t\tstate.refreshTimer = setTimeout(() => {\n\t\t\t\tstate.refreshTimer = undefined;\n\t\t\t\tcontext.invalidate();\n\t\t\t}, 1000);\n\t\t\tstate.refreshTimer.unref?.();\n\t\t}\n\t\treturn Date.now() - state.startedAt;\n\t}\n\tif (state.refreshTimer !== undefined) {\n\t\tclearTimeout(state.refreshTimer);\n\t\tstate.refreshTimer = undefined;\n\t}\n\treturn undefined;\n}\n\nexport default function webSearch(pi: ExtensionAPI): void {\n\tpi.registerTool<typeof WebSearchParamsSchema, WebSearchDetails, WebSearchRenderState>({\n\t\tname: WEB_SEARCH_TOOL_NAME,\n\t\tlabel: WEB_SEARCH_LABEL,\n\t\tdescription: WEB_SEARCH_DESCRIPTION,\n\t\tpromptSnippet: WEB_SEARCH_PROMPT_SNIPPET,\n\t\tpromptGuidelines: getWebSearchPromptGuidelines(),\n\t\tparameters: WebSearchParamsSchema,\n\t\tprepareArguments: normalizeWebSearchParams,\n\n\t\tasync execute(_toolCallId, params, signal, onUpdate, ctx) {\n\t\t\tconst credentials = await resolveSearchCredentials(ctx.modelRuntime);\n\t\t\tconst execution = await executeWebSearch(params, credentials, signal, onUpdate);\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: execution.formattedOutput }],\n\t\t\t\tdetails: execution.details,\n\t\t\t\tisError: execution.details.status !== \"success\",\n\t\t\t};\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\treturn renderWebSearchCall(args as WebSearchParams, theme);\n\t\t},\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst elapsedMs = trackQueryElapsed(context, options.isPartial);\n\t\t\treturn renderWebSearchResult(result, options, theme, context.isError, elapsedMs);\n\t\t},\n\t});\n\n\t// Keep the tool out of the model's tool set (and system prompt) when no search\n\t// credentials are configured. Re-evaluated on every session start (startup,\n\t// /new, /reload, resume, fork), so /login takes effect from the next session\n\t// onward; the tool stays registered for /tools and historical rendering.\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tconst credentials = await resolveSearchCredentials(ctx.modelRuntime);\n\t\tconst active = new Set(pi.getActiveTools());\n\t\tif (configuredEngine(credentials) === \"none\") {\n\t\t\tactive.delete(WEB_SEARCH_TOOL_NAME);\n\t\t} else {\n\t\t\tactive.add(WEB_SEARCH_TOOL_NAME);\n\t\t}\n\t\tpi.setActiveTools([...active]);\n\t});\n}\n"]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * deepseek.ts — DeepSeek Messages API search provider (claude-sonnet-search model with web_search_20250305).
3
+ */
4
+ import type { ProviderSearchResult } from "../types.ts";
5
+ export interface DeepSeekSearchOptions {
6
+ query: string;
7
+ apiKey: string;
8
+ baseUrl?: string;
9
+ model?: string;
10
+ signal?: AbortSignal;
11
+ allowedDomains?: string[];
12
+ blockedDomains?: string[];
13
+ }
14
+ export declare function searchDeepSeek(options: DeepSeekSearchOptions): Promise<ProviderSearchResult>;
15
+ //# sourceMappingURL=deepseek.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deepseek.d.ts","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/deepseek.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAA4B,oBAAoB,EAAgB,MAAM,aAAa,CAAC;AAGhG,MAAM,WAAW,qBAAqB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAKD,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAqElG"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * deepseek.ts — DeepSeek Messages API search provider (claude-sonnet-search model with web_search_20250305).
3
+ */
4
+ import { postJson } from "./http.js";
5
+ const DEFAULT_DEEPSEEK_MESSAGES_ENDPOINT = "https://api.deepseek.com/anthropic/v1/messages";
6
+ const DEFAULT_DEEPSEEK_SEARCH_MODEL = "claude-sonnet-search";
7
+ export async function searchDeepSeek(options) {
8
+ const endpoint = options.baseUrl || DEFAULT_DEEPSEEK_MESSAGES_ENDPOINT;
9
+ const model = options.model || DEFAULT_DEEPSEEK_SEARCH_MODEL;
10
+ const toolConfig = {
11
+ type: "web_search_20250305",
12
+ name: "web_search",
13
+ };
14
+ if (options.allowedDomains && options.allowedDomains.length > 0) {
15
+ toolConfig.allowed_domains = options.allowedDomains;
16
+ }
17
+ else if (options.blockedDomains && options.blockedDomains.length > 0) {
18
+ toolConfig.blocked_domains = options.blockedDomains;
19
+ }
20
+ const data = await postJson(endpoint, {
21
+ "Content-Type": "application/json",
22
+ "x-api-key": options.apiKey,
23
+ Authorization: `Bearer ${options.apiKey}`,
24
+ "anthropic-version": "2023-06-01",
25
+ }, {
26
+ model,
27
+ max_tokens: 1024,
28
+ messages: [{ role: "user", content: options.query }],
29
+ tools: [toolConfig],
30
+ }, options.signal, 90_000, "DeepSeek Messages search API");
31
+ if (data.error) {
32
+ throw new Error(`DeepSeek search error: ${data.error.message || data.error.type || "Unknown error"}`);
33
+ }
34
+ const hits = [];
35
+ let synthesisText = "";
36
+ for (const block of data.content || []) {
37
+ if (block.type === "web_search_tool_result") {
38
+ const toolResultBlock = block;
39
+ for (const item of toolResultBlock.content || []) {
40
+ if (item.url && typeof item.url === "string") {
41
+ hits.push({
42
+ title: item.title?.trim() || item.url.trim(),
43
+ url: item.url.trim(),
44
+ date: item.page_age || undefined,
45
+ sources: ["DeepSeek"],
46
+ });
47
+ }
48
+ }
49
+ }
50
+ else if (block.type === "text") {
51
+ const textBlock = block;
52
+ if (textBlock.text) {
53
+ synthesisText += (synthesisText ? "\n\n" : "") + textBlock.text.trim();
54
+ }
55
+ }
56
+ }
57
+ return {
58
+ source: "DeepSeek",
59
+ hits,
60
+ synthesisText: synthesisText || undefined,
61
+ };
62
+ }
63
+ //# sourceMappingURL=deepseek.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deepseek.js","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/deepseek.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * deepseek.ts — DeepSeek Messages API search provider (claude-sonnet-search model with web_search_20250305).\n */\n\nimport type { DeepSeekMessagesResponse, ProviderSearchResult, WebSearchHit } from \"../types.ts\";\nimport { postJson } from \"./http.ts\";\n\nexport interface DeepSeekSearchOptions {\n\tquery: string;\n\tapiKey: string;\n\tbaseUrl?: string;\n\tmodel?: string;\n\tsignal?: AbortSignal;\n\tallowedDomains?: string[];\n\tblockedDomains?: string[];\n}\n\nconst DEFAULT_DEEPSEEK_MESSAGES_ENDPOINT = \"https://api.deepseek.com/anthropic/v1/messages\";\nconst DEFAULT_DEEPSEEK_SEARCH_MODEL = \"claude-sonnet-search\";\n\nexport async function searchDeepSeek(options: DeepSeekSearchOptions): Promise<ProviderSearchResult> {\n\tconst endpoint = options.baseUrl || DEFAULT_DEEPSEEK_MESSAGES_ENDPOINT;\n\tconst model = options.model || DEFAULT_DEEPSEEK_SEARCH_MODEL;\n\n\tconst toolConfig: Record<string, unknown> = {\n\t\ttype: \"web_search_20250305\",\n\t\tname: \"web_search\",\n\t};\n\n\tif (options.allowedDomains && options.allowedDomains.length > 0) {\n\t\ttoolConfig.allowed_domains = options.allowedDomains;\n\t} else if (options.blockedDomains && options.blockedDomains.length > 0) {\n\t\ttoolConfig.blocked_domains = options.blockedDomains;\n\t}\n\n\tconst data = await postJson<DeepSeekMessagesResponse>(\n\t\tendpoint,\n\t\t{\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"x-api-key\": options.apiKey,\n\t\t\tAuthorization: `Bearer ${options.apiKey}`,\n\t\t\t\"anthropic-version\": \"2023-06-01\",\n\t\t},\n\t\t{\n\t\t\tmodel,\n\t\t\tmax_tokens: 1024,\n\t\t\tmessages: [{ role: \"user\", content: options.query }],\n\t\t\ttools: [toolConfig],\n\t\t},\n\t\toptions.signal,\n\t\t90_000,\n\t\t\"DeepSeek Messages search API\",\n\t);\n\n\tif (data.error) {\n\t\tthrow new Error(`DeepSeek search error: ${data.error.message || data.error.type || \"Unknown error\"}`);\n\t}\n\n\tconst hits: WebSearchHit[] = [];\n\tlet synthesisText = \"\";\n\n\tfor (const block of data.content || []) {\n\t\tif (block.type === \"web_search_tool_result\") {\n\t\t\tconst toolResultBlock = block as {\n\t\t\t\tcontent?: { type?: string; title?: string; url?: string; page_age?: string }[];\n\t\t\t};\n\t\t\tfor (const item of toolResultBlock.content || []) {\n\t\t\t\tif (item.url && typeof item.url === \"string\") {\n\t\t\t\t\thits.push({\n\t\t\t\t\t\ttitle: item.title?.trim() || item.url.trim(),\n\t\t\t\t\t\turl: item.url.trim(),\n\t\t\t\t\t\tdate: item.page_age || undefined,\n\t\t\t\t\t\tsources: [\"DeepSeek\"],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (block.type === \"text\") {\n\t\t\tconst textBlock = block as { text?: string };\n\t\t\tif (textBlock.text) {\n\t\t\t\tsynthesisText += (synthesisText ? \"\\n\\n\" : \"\") + textBlock.text.trim();\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tsource: \"DeepSeek\",\n\t\thits,\n\t\tsynthesisText: synthesisText || undefined,\n\t};\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * http.ts — Shared JSON POST plumbing for search providers: caller signal plus
3
+ * per-request timeout, bounded upstream error bodies, clear parse errors.
4
+ */
5
+ /**
6
+ * POST a JSON body and return the parsed response.
7
+ */
8
+ export declare function postJson<T>(url: string, headers: Record<string, string>, body: unknown, signal: AbortSignal | undefined, timeoutMs: number, errorLabel: string): Promise<T>;
9
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/http.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;GAEG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * http.ts — Shared JSON POST plumbing for search providers: caller signal plus
3
+ * per-request timeout, bounded upstream error bodies, clear parse errors.
4
+ */
5
+ /** Error bodies are surfaced to the model; keep them bounded. */
6
+ const MAX_ERROR_BODY_LENGTH = 200;
7
+ /**
8
+ * POST a JSON body and return the parsed response.
9
+ */
10
+ export async function postJson(url, headers, body, signal, timeoutMs, errorLabel) {
11
+ const response = await fetch(url, {
12
+ method: "POST",
13
+ headers,
14
+ body: JSON.stringify(body),
15
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),
16
+ });
17
+ if (!response.ok) {
18
+ const errorText = await response.text().catch(() => "");
19
+ throw new Error(`${errorLabel} returned HTTP ${response.status} ${response.statusText}: ${errorText.slice(0, MAX_ERROR_BODY_LENGTH)}`);
20
+ }
21
+ return (await response.json().catch(() => {
22
+ throw new Error(`${errorLabel} returned invalid JSON`);
23
+ }));
24
+ }
25
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/http.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iEAAiE;AACjE,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC7B,GAAW,EACX,OAA+B,EAC/B,IAAa,EACb,MAA+B,EAC/B,SAAiB,EACjB,UAAkB;IAElB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,OAAO;QACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;KAC3G,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QAClB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACxD,MAAM,IAAI,KAAK,CACd,GAAG,UAAU,kBAAkB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,EAAE,CACrH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;QACxC,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,wBAAwB,CAAC,CAAC;IACxD,CAAC,CAAC,CAAM,CAAC;AACV,CAAC","sourcesContent":["/**\n * http.ts — Shared JSON POST plumbing for search providers: caller signal plus\n * per-request timeout, bounded upstream error bodies, clear parse errors.\n */\n\n/** Error bodies are surfaced to the model; keep them bounded. */\nconst MAX_ERROR_BODY_LENGTH = 200;\n\n/**\n * POST a JSON body and return the parsed response.\n */\nexport async function postJson<T>(\n\turl: string,\n\theaders: Record<string, string>,\n\tbody: unknown,\n\tsignal: AbortSignal | undefined,\n\ttimeoutMs: number,\n\terrorLabel: string,\n): Promise<T> {\n\tconst response = await fetch(url, {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify(body),\n\t\tsignal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),\n\t});\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text().catch(() => \"\");\n\t\tthrow new Error(\n\t\t\t`${errorLabel} returned HTTP ${response.status} ${response.statusText}: ${errorText.slice(0, MAX_ERROR_BODY_LENGTH)}`,\n\t\t);\n\t}\n\n\treturn (await response.json().catch(() => {\n\t\tthrow new Error(`${errorLabel} returned invalid JSON`);\n\t})) as T;\n}\n"]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * minimax.ts — MiniMax Coding Plan Search REST API provider.
3
+ */
4
+ import type { ProviderSearchResult } from "../types.ts";
5
+ export interface MiniMaxSearchOptions {
6
+ query: string;
7
+ apiKey: string;
8
+ apiHost?: string;
9
+ signal?: AbortSignal;
10
+ allowedDomains?: string[];
11
+ blockedDomains?: string[];
12
+ }
13
+ export declare function searchMiniMax(options: MiniMaxSearchOptions): Promise<ProviderSearchResult>;
14
+ //# sourceMappingURL=minimax.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"minimax.d.ts","sourceRoot":"","sources":["../../../../src/extensions/web-search/providers/minimax.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAyB,oBAAoB,EAAgB,MAAM,aAAa,CAAC;AAG7F,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AA+BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA+ChG"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * minimax.ts — MiniMax Coding Plan Search REST API provider.
3
+ */
4
+ import { postJson } from "./http.js";
5
+ function buildSearchQuery(query, allowedDomains, blockedDomains) {
6
+ let q = query.trim();
7
+ if (allowedDomains && allowedDomains.length > 0) {
8
+ const siteFilter = allowedDomains.map((d) => `site:${d.trim()}`).join(" OR ");
9
+ q = `${q} (${siteFilter})`;
10
+ }
11
+ else if (blockedDomains && blockedDomains.length > 0) {
12
+ const blockFilter = blockedDomains.map((d) => `-site:${d.trim()}`).join(" ");
13
+ q = `${q} ${blockFilter}`;
14
+ }
15
+ return q;
16
+ }
17
+ function matchesDomainFilters(url, allowedDomains, blockedDomains) {
18
+ try {
19
+ const parsed = new URL(url);
20
+ const hostname = parsed.hostname.toLowerCase();
21
+ if (allowedDomains && allowedDomains.length > 0) {
22
+ return allowedDomains.some((d) => hostname === d.toLowerCase() || hostname.endsWith(`.${d.toLowerCase()}`));
23
+ }
24
+ if (blockedDomains && blockedDomains.length > 0) {
25
+ return !blockedDomains.some((d) => hostname === d.toLowerCase() || hostname.endsWith(`.${d.toLowerCase()}`));
26
+ }
27
+ return true;
28
+ }
29
+ catch {
30
+ return true;
31
+ }
32
+ }
33
+ export async function searchMiniMax(options) {
34
+ const host = options.apiHost?.replace(/\/+$/, "") || "https://api.minimaxi.com";
35
+ const effectiveQuery = buildSearchQuery(options.query, options.allowedDomains, options.blockedDomains);
36
+ const data = await postJson(`${host}/v1/coding_plan/search`, {
37
+ "Content-Type": "application/json",
38
+ Authorization: `Bearer ${options.apiKey}`,
39
+ }, { q: effectiveQuery }, options.signal, 60_000, "MiniMax search API");
40
+ if (data.base_resp && data.base_resp.status_code !== 0) {
41
+ throw new Error(`MiniMax search failed: [${data.base_resp.status_code}] ${data.base_resp.status_msg}`);
42
+ }
43
+ const rawHits = data.organic || [];
44
+ const hits = [];
45
+ for (const item of rawHits) {
46
+ if (!item.link || !item.link.trim())
47
+ continue;
48
+ const cleanLink = item.link.trim();
49
+ if (!matchesDomainFilters(cleanLink, options.allowedDomains, options.blockedDomains)) {
50
+ continue;
51
+ }
52
+ hits.push({
53
+ title: item.title?.trim() || cleanLink,
54
+ url: cleanLink,
55
+ snippet: item.snippet?.trim() || undefined,
56
+ date: item.date?.trim() || undefined,
57
+ sources: ["MiniMax"],
58
+ });
59
+ }
60
+ const relatedSearches = data.related_searches
61
+ ?.map((r) => r.query?.trim())
62
+ .filter((q) => Boolean(q && q.length > 0));
63
+ return {
64
+ source: "MiniMax",
65
+ hits,
66
+ relatedSearches: relatedSearches && relatedSearches.length > 0 ? relatedSearches : undefined,
67
+ };
68
+ }
69
+ //# sourceMappingURL=minimax.js.map