@apmantza/greedysearch-pi 1.5.1 → 1.6.1

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 CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.6.1 (2026-03-31)
4
+
5
+ ### Features
6
+ - **Single-engine full answers by default** — when using `engine: "perplexity"`, `engine: "bing"`, `engine: "google"`, or `engine: "gemini"`, the full answer is now returned by default instead of truncated previews. Multi-engine (`engine: "all"`) still uses truncated previews (~300 chars) to save tokens during synthesis. Explicit `fullAnswer: true/false` always overrides.
7
+
8
+ ### Code Quality
9
+ - **Major refactoring** — extracted 438 lines from `index.ts` (856 → 418 lines) into modular formatters:
10
+ - `src/formatters/coding.ts` — coding task formatting
11
+ - `src/formatters/results.ts` — search and deep research formatting
12
+ - `src/formatters/sources.ts` — source utilities (URL, label, consensus, formatting)
13
+ - `src/formatters/synthesis.ts` — synthesis rendering
14
+ - `src/utils/helpers.ts` — shared formatting utilities
15
+ - **Complexity reduced** — cognitive complexity dropped from 360 to ~60, maintainability index improved from 11.2 to ~40+
16
+ - **Eliminated code duplication** — removed 6 duplicate blocks, consolidated 4+ single-use helper functions
17
+
18
+ ### Documentation
19
+ - Clarified `greedy_search` is WEB SEARCH ONLY — removed "NOT for codebase search" from tool description (still in skill documentation)
20
+
21
+ ## v1.6.0 (2026-03-29)
22
+
23
+ ### Breaking Changes (Backward Compatible)
24
+ - **Merged deep_research into greedy_search** — new `depth` parameter with three levels:
25
+ - `fast`: single engine (~15-30s)
26
+ - `standard`: 3 engines + synthesis (~30-90s, default for `engine: "all"`)
27
+ - `deep`: 3 engines + source fetching + synthesis + confidence (~60-180s)
28
+ - **Simpler mental model** — one tool with clear speed/quality tradeoffs instead of separate tools with overlapping flags
29
+ - **Deprecated flags still work** — `--synthesize` maps to `depth: "standard"`, `--deep-research` maps to `depth: "deep"`
30
+ - **deep_research tool aliased** — still works, calls `greedy_search` with `depth: "deep"`
31
+
32
+ ### Documentation
33
+ - Updated README with new `depth` parameter and examples
34
+ - Updated skill documentation (SKILL.md) to reflect simplified API
35
+
3
36
  ## v1.5.1 (2026-03-29)
4
37
 
5
38
  - **Fixed npm package** — added `.pi-lens/` and test files to `.npmignore` to reduce package size
package/README.md CHANGED
@@ -24,10 +24,10 @@ pi install git:github.com/apmantza/GreedySearch-pi
24
24
 
25
25
  ## Quick Start
26
26
 
27
- Once installed, Pi gains a `greedy_search` tool. The model will use it automatically for questions about current libraries, error messages, version-specific docs, etc.
27
+ Once installed, Pi gains a `greedy_search` tool with three depth levels.
28
28
 
29
29
  ```
30
- greedy_search({ query: "What's new in React 19?", engine: "all" })
30
+ greedy_search({ query: "What's new in React 19?", depth: "standard" })
31
31
  ```
32
32
 
33
33
  ## Parameters
@@ -35,19 +35,27 @@ greedy_search({ query: "What's new in React 19?", engine: "all" })
35
35
  | Parameter | Type | Default | Description |
36
36
  |-----------|------|---------|-------------|
37
37
  | `query` | string | required | The search question |
38
- | `engine` | string | `"all"` | Engine to use (see below) |
39
- | `synthesize` | boolean | `false` | Synthesize results into one answer via Gemini |
38
+ | `engine` | string | `"all"` | `all`, `perplexity`, `bing`, `google`, `gemini` |
39
+ | `depth` | string | `"standard"` | `fast` (1 engine), `standard` (3 engines + synthesis), `deep` (3 + fetch + synthesis + confidence) |
40
40
  | `fullAnswer` | boolean | `false` | Return complete answer (~3000+ chars) vs truncated preview (~300 chars) |
41
41
 
42
- ## Engines
42
+ ## Depth Levels
43
43
 
44
- | Engine | Alias | Latency | Best for |
45
- |--------|-------|---------|----------|
46
- | `all` | | 30-90s | Highest confidence all 3 engines in parallel (default) |
47
- | `perplexity` | `p` | 15-30s | Technical Q&A, code explanations, documentation |
48
- | `bing` | `b` | 15-30s | Recent news, Microsoft ecosystem |
49
- | `google` | `g` | 15-30s | Broad coverage, multiple perspectives |
50
- | `gemini` | `gem` | 15-30s | Google's AI with different training data |
44
+ | Depth | Engines | Synthesis | Source Fetch | Time | Best For |
45
+ |-------|---------|-----------|--------------|------|----------|
46
+ | `fast` | 1 | | | 15-30s | Quick lookup, single perspective |
47
+ | `standard` | 3 | ✅ | — | 30-90s | Default balanced speed/quality |
48
+ | `deep` | 3 | ✅ | ✅ (top 5) | 60-180s | Research that matters architecture decisions |
49
+
50
+ ## Engines (for fast mode)
51
+
52
+ | Engine | Alias | Best for |
53
+ |--------|-------|----------|
54
+ | `all` | — | All 3 engines — but for fast single-engine, pick one below |
55
+ | `perplexity` | `p` | Technical Q&A, code explanations, documentation |
56
+ | `bing` | `b` | Recent news, Microsoft ecosystem |
57
+ | `google` | `g` | Broad coverage, multiple perspectives |
58
+ | `gemini` | `gem` | Google's AI with different training data |
51
59
 
52
60
  ## Streaming Progress
53
61
 
@@ -60,24 +68,21 @@ When using `engine: "all"`, the tool streams progress as each engine completes:
60
68
  **Searching...** ✅ perplexity done · ✅ bing done · ✅ google done
61
69
  ```
62
70
 
63
- ## Synthesis Mode
71
+ ## Deep Research Mode
64
72
 
65
- For complex research questions, use `synthesize: true` with `engine: "all"`:
73
+ For research that matters — architecture decisions, library comparisons use `depth: "deep"`:
66
74
 
67
75
  ```
68
- greedy_search({ query: "best auth patterns for SaaS in 2026", engine: "all", synthesize: true })
76
+ greedy_search({ query: "best auth patterns for SaaS in 2026", depth: "deep" })
69
77
  ```
70
78
 
71
- This deduplicates sources across engines, builds a normalized source registry, and feeds that context to Gemini for one clean synthesized answer. Adds ~30s but returns agreement summaries, caveats, key claims, and better-labeled top sources.
79
+ Deep mode: 3 engines + source fetching (top 5) + synthesis + confidence scores. ~60-180s but returns grounded synthesis with fetched evidence.
72
80
 
73
- **Use synthesis when:**
74
- - You need one definitive answer, not multiple perspectives
75
- - You're researching a topic to write about or make a decision
76
- - Token efficiency matters (one answer vs three)
81
+ **Standard vs Deep:**
82
+ - `standard` (default): 3 engines + synthesis. Good for most research.
83
+ - `deep`: Same + fetches source content for grounded answers. Use when the answer really matters.
77
84
 
78
- **Skip synthesis when:**
79
- - You want to see where engines disagree
80
- - Speed matters
85
+ **Legacy:** `deep_research` tool still works — aliases to `greedy_search` with `depth: "deep"`.
81
86
 
82
87
  ## Full vs Short Answers
83
88
 
@@ -89,28 +94,28 @@ greedy_search({ query: "explain the React compiler", engine: "perplexity", fullA
89
94
 
90
95
  ## Examples
91
96
 
92
- **Quick technical lookup:**
97
+ **Quick lookup (fast):**
93
98
 
94
99
  ```
95
- greedy_search({ query: "How to use async await in Python", engine: "perplexity" })
100
+ greedy_search({ query: "How to use async await in Python", depth: "fast", engine: "perplexity" })
96
101
  ```
97
102
 
98
- **Compare tools (see where engines agree/disagree):**
103
+ **Compare tools (standard):**
99
104
 
100
105
  ```
101
- greedy_search({ query: "Prisma vs Drizzle in 2026", engine: "all" })
106
+ greedy_search({ query: "Prisma vs Drizzle in 2026", depth: "standard" })
102
107
  ```
103
108
 
104
- **Research with synthesis:**
109
+ **Deep research (architecture decision):**
105
110
 
106
111
  ```
107
- greedy_search({ query: "Best practices for monorepo structure", engine: "all", synthesize: true })
112
+ greedy_search({ query: "Best practices for monorepo structure", depth: "deep" })
108
113
  ```
109
114
 
110
115
  **Debug an error:**
111
116
 
112
117
  ```
113
- greedy_search({ query: "Error: Cannot find module 'react-dom/client' Next.js 15", engine: "all" })
118
+ greedy_search({ query: "Error: Cannot find module 'react-dom/client' Next.js 15", depth: "standard" })
114
119
  ```
115
120
 
116
121
  ## Requirements
@@ -200,6 +205,17 @@ Sources are now extracted by regex-parsing Markdown links (`[title](url)`) from
200
205
 
201
206
  ## Changelog
202
207
 
208
+ ### v1.6.1 (2026-03-31)
209
+ - **Single-engine full answers by default** — `engine: "google"` (or any single engine) now returns complete answers instead of truncated previews. Multi-engine (`all`) still truncates to save tokens during synthesis.
210
+ - **Codebase refactored** — extracted 438 lines from `index.ts` into modular formatters (`src/formatters/`) reducing cognitive complexity from 360 to ~60 and maintainability index from 11.2 to ~40+
211
+ - **Removed codebase search confusion** — clarified that `greedy_search` is WEB SEARCH ONLY (not for searching local code)
212
+
213
+ ### v1.6.0 (2026-03-29)
214
+ - **Merged deep_research into greedy_search** — new `depth` parameter: `fast` (1 engine), `standard` (3 engines + synthesis), `deep` (3 engines + fetch + synthesis + confidence)
215
+ - **Simpler API** — one tool with clear speed/quality tradeoffs instead of separate tools with overlapping flags
216
+ - **Backward compatible** — `deep_research` still works as alias, `--synthesize` and `--deep-research` flags still function
217
+ - **Updated documentation** — README and skill docs now use `depth` parameter throughout
218
+
203
219
  ### v1.5.1 (2026-03-29)
204
220
  - Fixed npm package — added `.pi-lens/` and test files to `.npmignore`
205
221
 
@@ -8,6 +8,8 @@
8
8
  //
9
9
  // Output (stdout): JSON { answer, sources, query, url }
10
10
  // Errors go to stderr only — stdout is always clean JSON for piping.
11
+ //
12
+ // TODO: Refactor - this file has 42 lines duplicated with google-ai.mjs (line 28)
11
13
 
12
14
  import {
13
15
  cdp,
package/index.ts CHANGED
@@ -15,6 +15,10 @@ import { fileURLToPath } from "node:url";
15
15
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
16
16
  import { Type } from "@sinclair/typebox";
17
17
 
18
+ // Formatters extracted to reduce file complexity
19
+ import { formatCodingTask } from "./src/formatters/coding.js";
20
+ import { formatResults, formatDeepResearch } from "./src/formatters/results.js";
21
+
18
22
  const __dir = dirname(fileURLToPath(import.meta.url));
19
23
 
20
24
  const ALL_ENGINES = ["perplexity", "bing", "google"] as const;
@@ -77,349 +81,6 @@ function runSearch(
77
81
  });
78
82
  }
79
83
 
80
- function formatEngineName(engine: string): string {
81
- if (engine === "bing") return "Bing Copilot";
82
- if (engine === "google") return "Google AI";
83
- return engine.charAt(0).toUpperCase() + engine.slice(1);
84
- }
85
-
86
- function humanizeSourceType(sourceType: string): string {
87
- if (!sourceType) return "";
88
- if (sourceType === "official-docs") return "official docs";
89
- return sourceType.replace(/-/g, " ");
90
- }
91
-
92
- function sourceUrl(source: Record<string, unknown>): string {
93
- return String(source.displayUrl || source.canonicalUrl || source.url || "");
94
- }
95
-
96
- function sourceLabel(source: Record<string, unknown>): string {
97
- return String(
98
- source.title || source.domain || sourceUrl(source) || "Untitled source",
99
- );
100
- }
101
-
102
- function sourceConsensus(source: Record<string, unknown>): number {
103
- if (typeof source.engineCount === "number") return source.engineCount;
104
- const engines = Array.isArray(source.engines)
105
- ? (source.engines as string[])
106
- : [];
107
- return engines.length;
108
- }
109
-
110
- function formatAgreementLevel(level: string): string {
111
- if (!level) return "Mixed";
112
- return level.charAt(0).toUpperCase() + level.slice(1);
113
- }
114
-
115
- function getSourceMap(
116
- sources: Array<Record<string, unknown>>,
117
- ): Map<string, Record<string, unknown>> {
118
- return new Map(
119
- sources
120
- .map((source) => [String(source.id || ""), source] as const)
121
- .filter(([id]) => id),
122
- );
123
- }
124
-
125
- function formatSourceLine(source: Record<string, unknown>): string {
126
- const id = String(source.id || "?");
127
- const url = sourceUrl(source);
128
- const title = sourceLabel(source);
129
- const domain = String(source.domain || "");
130
- const engines = Array.isArray(source.engines)
131
- ? (source.engines as string[])
132
- : [];
133
- const consensus = sourceConsensus(source);
134
- const typeLabel = humanizeSourceType(String(source.sourceType || ""));
135
- const fetch = source.fetch as Record<string, unknown> | undefined;
136
- const fetchStatus = fetch?.ok
137
- ? `fetched ${fetch.status || 200}`
138
- : fetch?.attempted
139
- ? "fetch failed"
140
- : "";
141
- const pieces = [
142
- `${id} - [${title}](${url})`,
143
- domain,
144
- typeLabel,
145
- engines.length
146
- ? `cited by ${engines.map(formatEngineName).join(", ")} (${consensus}/3)`
147
- : `${consensus}/3`,
148
- fetchStatus,
149
- ].filter(Boolean);
150
- return `- ${pieces.join(" - ")}`;
151
- }
152
-
153
- function renderSourceEvidence(
154
- lines: string[],
155
- source: Record<string, unknown>,
156
- ): void {
157
- const fetch = source.fetch as Record<string, unknown> | undefined;
158
- if (!fetch?.attempted) return;
159
-
160
- const snippet = String(fetch.snippet || "").trim();
161
- const lastModified = String(fetch.lastModified || "").trim();
162
- if (snippet) lines.push(` Evidence: ${snippet}`);
163
- if (lastModified) lines.push(` Last-Modified: ${lastModified}`);
164
- if (fetch.error) lines.push(` Fetch error: ${String(fetch.error)}`);
165
- }
166
-
167
- function pickSources(
168
- sources: Array<Record<string, unknown>>,
169
- recommendedIds: string[] = [],
170
- max = 6,
171
- ): Array<Record<string, unknown>> {
172
- if (!sources.length) return [];
173
- const sourceMap = getSourceMap(sources);
174
- const recommended = recommendedIds
175
- .map((id) => sourceMap.get(id))
176
- .filter((source): source is Record<string, unknown> => Boolean(source));
177
- if (recommended.length > 0) return recommended.slice(0, max);
178
- return sources.slice(0, max);
179
- }
180
-
181
- function renderSynthesis(
182
- lines: string[],
183
- synthesis: Record<string, unknown>,
184
- sources: Array<Record<string, unknown>>,
185
- maxSources = 6,
186
- ): void {
187
- if (synthesis.answer) {
188
- lines.push("## Answer");
189
- lines.push(String(synthesis.answer));
190
- lines.push("");
191
- }
192
-
193
- const agreement = synthesis.agreement as Record<string, unknown> | undefined;
194
- const agreementSummary = String(agreement?.summary || "").trim();
195
- const agreementLevel = String(agreement?.level || "").trim();
196
- if (agreementSummary || agreementLevel) {
197
- lines.push("## Consensus");
198
- lines.push(
199
- `- ${formatAgreementLevel(agreementLevel)}${agreementSummary ? ` - ${agreementSummary}` : ""}`,
200
- );
201
- lines.push("");
202
- }
203
-
204
- const differences = Array.isArray(synthesis.differences)
205
- ? (synthesis.differences as string[])
206
- : [];
207
- if (differences.length > 0) {
208
- lines.push("## Where Engines Differ");
209
- for (const difference of differences) lines.push(`- ${difference}`);
210
- lines.push("");
211
- }
212
-
213
- const caveats = Array.isArray(synthesis.caveats)
214
- ? (synthesis.caveats as string[])
215
- : [];
216
- if (caveats.length > 0) {
217
- lines.push("## Caveats");
218
- for (const caveat of caveats) lines.push(`- ${caveat}`);
219
- lines.push("");
220
- }
221
-
222
- const claims = Array.isArray(synthesis.claims)
223
- ? (synthesis.claims as Array<Record<string, unknown>>)
224
- : [];
225
- if (claims.length > 0) {
226
- lines.push("## Key Claims");
227
- for (const claim of claims) {
228
- const sourceIds = Array.isArray(claim.sourceIds)
229
- ? (claim.sourceIds as string[])
230
- : [];
231
- const support = String(claim.support || "moderate");
232
- lines.push(
233
- `- ${String(claim.claim || "")} [${support}${sourceIds.length ? `; ${sourceIds.join(", ")}` : ""}]`,
234
- );
235
- }
236
- lines.push("");
237
- }
238
-
239
- const recommendedIds = Array.isArray(synthesis.recommendedSources)
240
- ? (synthesis.recommendedSources as string[])
241
- : [];
242
- const topSources = pickSources(sources, recommendedIds, maxSources);
243
- if (topSources.length > 0) {
244
- lines.push("## Top Sources");
245
- for (const source of topSources) lines.push(formatSourceLine(source));
246
- lines.push("");
247
- }
248
- }
249
-
250
- function formatResults(engine: string, data: Record<string, unknown>): string {
251
- const lines: string[] = [];
252
-
253
- if (engine === "all") {
254
- const synthesis = data._synthesis as Record<string, unknown> | undefined;
255
- const dedupedSources = data._sources as
256
- | Array<Record<string, unknown>>
257
- | undefined;
258
- if (synthesis?.answer) {
259
- renderSynthesis(lines, synthesis, dedupedSources || [], 6);
260
- lines.push(
261
- "*Synthesized from Perplexity, Bing Copilot, and Google AI*\n",
262
- );
263
- return lines.join("\n").trim();
264
- }
265
-
266
- for (const [eng, result] of Object.entries(data)) {
267
- if (eng.startsWith("_")) continue;
268
- lines.push(`\n## ${formatEngineName(eng)}`);
269
- const r = result as Record<string, unknown>;
270
- if (r.error) {
271
- lines.push(`Error: ${r.error}`);
272
- } else {
273
- if (r.answer) lines.push(String(r.answer));
274
- if (Array.isArray(r.sources) && r.sources.length > 0) {
275
- lines.push("\nSources:");
276
- for (const s of r.sources.slice(0, 3)) {
277
- const src = s as Record<string, string>;
278
- lines.push(`- [${src.title || src.url}](${src.url})`);
279
- }
280
- }
281
- }
282
- }
283
- } else {
284
- if (data.error) {
285
- lines.push(`Error: ${data.error}`);
286
- } else {
287
- if (data.answer) lines.push(String(data.answer));
288
- if (Array.isArray(data.sources) && data.sources.length > 0) {
289
- lines.push("\nSources:");
290
- for (const s of data.sources.slice(0, 5)) {
291
- const src = s as Record<string, string>;
292
- lines.push(`- [${src.title || src.url}](${src.url})`);
293
- }
294
- }
295
- }
296
- }
297
-
298
- return lines.join("\n").trim();
299
- }
300
-
301
- function formatDeepResearch(data: Record<string, unknown>): string {
302
- const lines: string[] = [];
303
- const confidence = data._confidence as Record<string, unknown> | undefined;
304
- const dedupedSources = data._sources as
305
- | Array<Record<string, unknown>>
306
- | undefined;
307
- const synthesis = data._synthesis as Record<string, unknown> | undefined;
308
-
309
- lines.push("# Deep Research Report\n");
310
-
311
- if (confidence) {
312
- const enginesResponded = (confidence.enginesResponded as string[]) || [];
313
- const enginesFailed = (confidence.enginesFailed as string[]) || [];
314
- const agreementLevel = String(confidence.agreementLevel || "mixed");
315
- const firstPartySourceCount = Number(confidence.firstPartySourceCount || 0);
316
- const sourceTypeBreakdown = confidence.sourceTypeBreakdown as
317
- | Record<string, number>
318
- | undefined;
319
-
320
- lines.push("## Confidence\n");
321
- lines.push(`- Agreement: ${formatAgreementLevel(agreementLevel)}`);
322
- lines.push(
323
- `- Engines responded: ${enginesResponded.map(formatEngineName).join(", ") || "none"}`,
324
- );
325
- if (enginesFailed.length > 0) {
326
- lines.push(
327
- `- Engines failed: ${enginesFailed.map(formatEngineName).join(", ")}`,
328
- );
329
- }
330
- lines.push(
331
- `- Top source consensus: ${confidence.topSourceConsensus || 0}/3 engines`,
332
- );
333
- lines.push(`- Total unique sources: ${confidence.sourcesCount || 0}`);
334
- lines.push(`- Official sources: ${confidence.officialSourceCount || 0}`);
335
- lines.push(`- First-party sources: ${firstPartySourceCount}`);
336
- lines.push(
337
- `- Fetch success rate: ${confidence.fetchedSourceSuccessRate || 0}`,
338
- );
339
- if (sourceTypeBreakdown && Object.keys(sourceTypeBreakdown).length > 0) {
340
- lines.push(
341
- `- Source mix: ${Object.entries(sourceTypeBreakdown)
342
- .map(([type, count]) => `${humanizeSourceType(type)} ${count}`)
343
- .join(", ")}`,
344
- );
345
- }
346
- lines.push("");
347
- }
348
-
349
- if (synthesis?.answer)
350
- renderSynthesis(lines, synthesis, dedupedSources || [], 8);
351
-
352
- lines.push("## Engine Perspectives\n");
353
- for (const engine of ["perplexity", "bing", "google"]) {
354
- const r = data[engine] as Record<string, unknown> | undefined;
355
- if (!r) continue;
356
- lines.push(`### ${formatEngineName(engine)}`);
357
- if (r.error) {
358
- lines.push(`⚠️ Error: ${r.error}`);
359
- } else if (r.answer) {
360
- lines.push(String(r.answer).slice(0, 2000));
361
- }
362
- lines.push("");
363
- }
364
-
365
- if (dedupedSources && dedupedSources.length > 0) {
366
- lines.push("## Source Registry\n");
367
- for (const source of dedupedSources) {
368
- lines.push(formatSourceLine(source));
369
- renderSourceEvidence(lines, source);
370
- }
371
- lines.push("");
372
- }
373
-
374
- return lines.join("\n").trim();
375
- }
376
-
377
- function formatCodingTask(
378
- data: Record<string, unknown> | Record<string, Record<string, unknown>>,
379
- ): string {
380
- const lines: string[] = [];
381
-
382
- // Check if it's multi-engine result
383
- const hasMultipleEngines = "gemini" in data || "copilot" in data;
384
-
385
- if (hasMultipleEngines) {
386
- // Multi-engine result
387
- for (const [engineName, result] of Object.entries(data)) {
388
- const r = result as Record<string, unknown>;
389
- lines.push(
390
- `## ${engineName.charAt(0).toUpperCase() + engineName.slice(1)}\n`,
391
- );
392
-
393
- if (r.error) {
394
- lines.push(`⚠️ Error: ${r.error}\n`);
395
- } else {
396
- if (r.explanation) lines.push(String(r.explanation));
397
- if (Array.isArray(r.code) && r.code.length > 0) {
398
- for (const block of r.code) {
399
- const b = block as { language: string; code: string };
400
- lines.push(`\n\`\`\`${b.language}\n${b.code}\n\`\`\`\n`);
401
- }
402
- }
403
- if (r.url) lines.push(`*Source: ${r.url}*`);
404
- }
405
- lines.push("");
406
- }
407
- } else {
408
- // Single engine result
409
- const r = data as Record<string, unknown>;
410
- if (r.explanation) lines.push(String(r.explanation));
411
- if (Array.isArray(r.code) && r.code.length > 0) {
412
- for (const block of r.code) {
413
- const b = block as { language: string; code: string };
414
- lines.push(`\n\`\`\`${b.language}\n${b.code}\n\`\`\`\n`);
415
- }
416
- }
417
- if (r.url) lines.push(`*Source: ${r.url}*`);
418
- }
419
-
420
- return lines.join("\n").trim();
421
- }
422
-
423
84
  export default function greedySearchExtension(pi: ExtensionAPI) {
424
85
  pi.on("session_start", async (_event, ctx) => {
425
86
  if (!cdpAvailable()) {
@@ -434,10 +95,10 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
434
95
  name: "greedy_search",
435
96
  label: "Greedy Search",
436
97
  description:
437
- "Search the web using AI-powered engines (Perplexity, Bing Copilot, Google AI) in parallel. " +
438
- "Optionally synthesize results with Gemini deduplicates sources by consensus and returns one grounded answer. " +
439
- "Reports streaming progress as each engine completes. " +
440
- "Use for current information, library docs, error messages, best practices, or any question where training data may be stale.",
98
+ "WEB SEARCH ONLY searches live web via Perplexity, Bing Copilot, and Google AI in parallel. " +
99
+ "Optionally synthesizes results with Gemini, deduplicates sources by consensus. " +
100
+ "Use for: library docs, recent framework changes, error messages, best practices, current events. " +
101
+ "Reports streaming progress as each engine completes.",
441
102
  promptSnippet: "Multi-engine AI web search with streaming progress",
442
103
  parameters: Type.Object({
443
104
  query: Type.String({ description: "The search query" }),
@@ -456,12 +117,13 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
456
117
  default: "all",
457
118
  },
458
119
  ),
459
- synthesize: Type.Optional(
460
- Type.Boolean({
120
+ depth: Type.Union(
121
+ [Type.Literal("fast"), Type.Literal("standard"), Type.Literal("deep")],
122
+ {
461
123
  description:
462
- 'When true and engine is "all", deduplicates sources across engines and feeds them to Gemini for a single grounded synthesis. Adds ~30s but saves tokens and improves answer quality.',
463
- default: false,
464
- }),
124
+ "Search depth: fast (single engine, ~15-30s), standard (3 engines + synthesis, ~30-90s), deep (3 engines + source fetching + synthesis + confidence, ~60-180s). Default: standard.",
125
+ default: "standard",
126
+ },
465
127
  ),
466
128
  fullAnswer: Type.Optional(
467
129
  Type.Boolean({
@@ -475,12 +137,12 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
475
137
  const {
476
138
  query,
477
139
  engine = "all",
478
- synthesize = false,
479
- fullAnswer = false,
140
+ depth = "standard",
141
+ fullAnswer: fullAnswerParam,
480
142
  } = params as {
481
143
  query: string;
482
144
  engine: string;
483
- synthesize?: boolean;
145
+ depth?: "fast" | "standard" | "deep";
484
146
  fullAnswer?: boolean;
485
147
  };
486
148
 
@@ -497,10 +159,13 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
497
159
  }
498
160
 
499
161
  const flags: string[] = [];
162
+ // Default to full answer for single-engine queries (unless explicitly set to false)
163
+ // For multi-engine, default to truncated to save tokens during synthesis
164
+ const fullAnswer = fullAnswerParam ?? (engine !== "all");
500
165
  if (fullAnswer) flags.push("--full");
501
- if (synthesize && engine === "all") flags.push("--synthesize");
166
+ if (depth === "deep") flags.push("--deep");
167
+ else if (depth === "standard" && engine === "all") flags.push("--synthesize");
502
168
 
503
- // Track progress for "all" engine mode
504
169
  const completed = new Set<string>();
505
170
 
506
171
  const onProgress = (eng: string, _status: "done" | "error") => {
@@ -510,7 +175,8 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
510
175
  if (completed.has(e)) parts.push(`✅ ${e} done`);
511
176
  else parts.push(`⏳ ${e}`);
512
177
  }
513
- if (synthesize && completed.size >= 3) parts.push("🔄 synthesizing");
178
+ if (depth !== "fast" && completed.size >= 3)
179
+ parts.push("🔄 synthesizing");
514
180
 
515
181
  onUpdate?.({
516
182
  content: [
@@ -546,15 +212,11 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
546
212
  // ─── deep_research ─────────────────────────────────────────────────────────
547
213
  pi.registerTool({
548
214
  name: "deep_research",
549
- label: "Deep Research",
215
+ label: "Deep Research (legacy)",
550
216
  description:
551
- "Comprehensive multi-engine research with source fetching and synthesis. " +
552
- "Runs Perplexity, Bing Copilot, and Google AI in parallel with full answers, " +
553
- "deduplicates and ranks sources by consensus, fetches content from top sources, " +
554
- "and synthesizes via Gemini. Returns a structured research document with confidence scores. " +
555
- "Use for architecture decisions, library comparisons, best practices, or any research where the answer matters.",
556
- promptSnippet:
557
- "Deep multi-engine research with source deduplication and synthesis",
217
+ "DEPRECATED Use greedy_search with depth: 'deep' instead. " +
218
+ "Comprehensive multi-engine research with source fetching and synthesis.",
219
+ promptSnippet: "Deep multi-engine research (legacy alias to greedy_search)",
558
220
  parameters: Type.Object({
559
221
  query: Type.String({ description: "The research question" }),
560
222
  }),
@@ -590,11 +252,10 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
590
252
  };
591
253
 
592
254
  try {
593
- // Run deep research (includes full answers, synthesis, and source fetching)
594
255
  const data = await runSearch(
595
256
  "all",
596
257
  query,
597
- ["--deep-research"],
258
+ ["--deep"],
598
259
  signal,
599
260
  onProgress,
600
261
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apmantza/greedysearch-pi",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "Pi extension: multi-engine AI search (Perplexity, Bing Copilot, Google AI) via browser automation — NO API KEYS needed. Extracts answers with sources, optional Gemini synthesis. Grounded AI answers from real browser interactions.",
5
5
  "type": "module",
6
6
  "keywords": [