prism-mcp-server 1.5.0

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 (47) hide show
  1. package/.gitmodules +3 -0
  2. package/Dockerfile +30 -0
  3. package/LICENSE +21 -0
  4. package/README.md +970 -0
  5. package/benchmark.ts +172 -0
  6. package/call_chrome_mcp.py +96 -0
  7. package/docker-compose.yml +67 -0
  8. package/execute_via_chrome_mcp.py +133 -0
  9. package/gmail_auth_test.py +29 -0
  10. package/gmail_list_latest_5.py +27 -0
  11. package/index.ts +34 -0
  12. package/list_chrome_tools.py +70 -0
  13. package/package.json +64 -0
  14. package/patch_cgc_mcp.py +90 -0
  15. package/repomix-output.xml +9 -0
  16. package/run_server.sh +9 -0
  17. package/server.json +78 -0
  18. package/src/config.ts +85 -0
  19. package/src/server.ts +627 -0
  20. package/src/tools/compactionHandler.ts +313 -0
  21. package/src/tools/definitions.ts +367 -0
  22. package/src/tools/handlers.ts +261 -0
  23. package/src/tools/index.ts +38 -0
  24. package/src/tools/sessionMemoryDefinitions.ts +437 -0
  25. package/src/tools/sessionMemoryHandlers.ts +774 -0
  26. package/src/utils/braveApi.ts +375 -0
  27. package/src/utils/embeddingApi.ts +97 -0
  28. package/src/utils/executor.ts +105 -0
  29. package/src/utils/googleAi.ts +107 -0
  30. package/src/utils/keywordExtractor.ts +207 -0
  31. package/src/utils/supabaseApi.ts +194 -0
  32. package/supabase/migrations/015_session_memory.sql +145 -0
  33. package/supabase/migrations/016_knowledge_accumulation.sql +315 -0
  34. package/supabase/migrations/017_ledger_compaction.sql +74 -0
  35. package/supabase/migrations/018_semantic_search.sql +110 -0
  36. package/supabase/migrations/019_concurrency_control.sql +320 -0
  37. package/supabase/migrations/020_multi_tenant_rls.sql +459 -0
  38. package/test_cross_mcp.js +393 -0
  39. package/test_mcp_schema.js +83 -0
  40. package/tests/test_knowledge_system.js +319 -0
  41. package/tsconfig.json +16 -0
  42. package/vertex-ai/test_claude_vertex.py +78 -0
  43. package/vertex-ai/test_gemini_vertex.py +39 -0
  44. package/vertex-ai/test_hybrid_search_pipeline.ts +296 -0
  45. package/vertex-ai/test_pipeline_benchmark.ts +251 -0
  46. package/vertex-ai/test_realworld_comparison.ts +290 -0
  47. package/vertex-ai/verify_discovery_engine.ts +72 -0
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * Hybrid Search Pipeline Test
4
+ *
5
+ * Validates the combined MCP + Vertex AI Discovery Engine search pipeline:
6
+ * 1. Brave Search (real-time web) via MCP server
7
+ * 2. Discovery Engine (curated enterprise index) via Vertex AI
8
+ * 3. code_mode_transform (context reduction via sandboxed JS)
9
+ * 4. Gemini analysis (LLM post-processing of merged results)
10
+ *
11
+ * Usage:
12
+ * npx ts-node vertex-ai/test_hybrid_search_pipeline.ts
13
+ *
14
+ * Prerequisites:
15
+ * - BRAVE_API_KEY environment variable
16
+ * - GCP ADC configured (gcloud auth application-default login)
17
+ * - DISCOVERY_ENGINE_* environment variables set
18
+ * - GEMINI_API_KEY or GOOGLE_API_KEY environment variable
19
+ */
20
+
21
+ import { SearchServiceClient } from '@google-cloud/discoveryengine';
22
+ import { GoogleGenerativeAI } from '@google/generative-ai';
23
+
24
+ // ─── Configuration ───────────────────────────────────────────────
25
+
26
+ const BRAVE_API_KEY = process.env.BRAVE_API_KEY || '';
27
+ const GEMINI_API_KEY = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || '';
28
+
29
+ const DE_PROJECT_ID = process.env.DISCOVERY_ENGINE_PROJECT_ID || process.env.GCP_PROJECT_ID || '';
30
+ const DE_LOCATION = process.env.DISCOVERY_ENGINE_LOCATION || 'global';
31
+ const DE_COLLECTION = process.env.DISCOVERY_ENGINE_COLLECTION || 'default_collection';
32
+ const DE_ENGINE_ID = process.env.DISCOVERY_ENGINE_ENGINE_ID || '';
33
+ const DE_SERVING_CONFIG = process.env.DISCOVERY_ENGINE_SERVING_CONFIG || 'default_serving_config';
34
+
35
+ const TEST_QUERY = 'machine learning model optimization techniques';
36
+
37
+ // ─── Interfaces ──────────────────────────────────────────────────
38
+
39
+ interface SearchResult {
40
+ source: 'brave' | 'discovery_engine';
41
+ title: string;
42
+ url: string;
43
+ snippet: string;
44
+ }
45
+
46
+ interface PipelineMetrics {
47
+ stage: string;
48
+ latencyMs: number;
49
+ inputSizeKB: number;
50
+ outputSizeKB: number;
51
+ reductionPct: number;
52
+ }
53
+
54
+ // ─── Stage 1: Brave Web Search ───────────────────────────────────
55
+
56
+ async function braveWebSearch(query: string, count: number = 5): Promise<{ results: SearchResult[]; rawSize: number; latencyMs: number }> {
57
+ if (!BRAVE_API_KEY) {
58
+ console.log(' ⚠️ BRAVE_API_KEY not set — skipping Brave Search stage');
59
+ return { results: [], rawSize: 0, latencyMs: 0 };
60
+ }
61
+
62
+ const start = Date.now();
63
+ const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${count}`;
64
+
65
+ const response = await fetch(url, {
66
+ headers: {
67
+ 'Accept': 'application/json',
68
+ 'Accept-Encoding': 'gzip',
69
+ 'X-Subscription-Token': BRAVE_API_KEY,
70
+ },
71
+ });
72
+
73
+ const data = await response.json();
74
+ const latencyMs = Date.now() - start;
75
+ const rawJson = JSON.stringify(data);
76
+ const rawSize = Buffer.byteLength(rawJson, 'utf8');
77
+
78
+ const results: SearchResult[] = (data.web?.results || []).map((r: any) => ({
79
+ source: 'brave' as const,
80
+ title: r.title || '',
81
+ url: r.url || '',
82
+ snippet: (r.description || '').substring(0, 200),
83
+ }));
84
+
85
+ return { results, rawSize, latencyMs };
86
+ }
87
+
88
+ // ─── Stage 2: Discovery Engine Search ────────────────────────────
89
+
90
+ async function discoveryEngineSearch(query: string, pageSize: number = 5): Promise<{ results: SearchResult[]; rawSize: number; latencyMs: number }> {
91
+ if (!DE_PROJECT_ID || !DE_ENGINE_ID) {
92
+ console.log(' ⚠️ Discovery Engine env vars not set — skipping DE stage');
93
+ return { results: [], rawSize: 0, latencyMs: 0 };
94
+ }
95
+
96
+ const start = Date.now();
97
+ const client = new SearchServiceClient();
98
+
99
+ const servingConfig = `projects/${DE_PROJECT_ID}/locations/${DE_LOCATION}/collections/${DE_COLLECTION}/engines/${DE_ENGINE_ID}/servingConfigs/${DE_SERVING_CONFIG}`;
100
+
101
+ try {
102
+ const [response] = await client.search({
103
+ servingConfig,
104
+ query,
105
+ pageSize,
106
+ }, { autoPaginate: false });
107
+
108
+ const latencyMs = Date.now() - start;
109
+
110
+ // With autoPaginate: false, response is an array of ISearchResult
111
+ const resultItems = Array.isArray(response) ? response : (response as any).results || [];
112
+ const rawJson = JSON.stringify(resultItems);
113
+ const rawSize = Buffer.byteLength(rawJson, 'utf8');
114
+
115
+ // Helper to extract string from protobuf Value
116
+ const getField = (structData: any, field: string): string => {
117
+ if (!structData) return '';
118
+ // Direct access (already decoded)
119
+ if (typeof structData[field] === 'string') return structData[field];
120
+ // Protobuf fields format
121
+ if (structData.fields?.[field]?.stringValue) return structData.fields[field].stringValue;
122
+ return '';
123
+ };
124
+
125
+ const results: SearchResult[] = resultItems.map((r: any) => ({
126
+ source: 'discovery_engine' as const,
127
+ title: getField(r.document?.derivedStructData, 'title') ||
128
+ getField(r.document?.derivedStructData, 'displayLink') ||
129
+ r.document?.name?.split('/').pop() || 'Untitled',
130
+ url: getField(r.document?.derivedStructData, 'link') ||
131
+ getField(r.document?.derivedStructData, 'htmlFormattedUrl') || '',
132
+ snippet: getField(r.document?.derivedStructData, 'snippet') ||
133
+ getField(r.document?.derivedStructData, 'htmlSnippet') || '',
134
+ }));
135
+
136
+ return { results, rawSize, latencyMs };
137
+ } catch (error: any) {
138
+ console.log(` ⚠️ Discovery Engine error: ${error.message}`);
139
+ return { results: [], rawSize: 0, latencyMs: Date.now() - start };
140
+ }
141
+ }
142
+
143
+ // ─── Stage 3: Context Reduction (simulates code_mode_transform) ──
144
+
145
+ function contextReduction(braveResults: SearchResult[], deResults: SearchResult[]): { merged: SearchResult[]; metrics: PipelineMetrics } {
146
+ const start = Date.now();
147
+
148
+ // Merge and deduplicate by URL
149
+ const allResults = [...braveResults, ...deResults];
150
+ const seen = new Set<string>();
151
+ const merged: SearchResult[] = [];
152
+
153
+ for (const result of allResults) {
154
+ const key = result.url.toLowerCase().replace(/\/$/, '');
155
+ if (!seen.has(key) && result.url) {
156
+ seen.add(key);
157
+ merged.push(result);
158
+ }
159
+ }
160
+
161
+ const inputJson = JSON.stringify(allResults);
162
+ const outputJson = JSON.stringify(merged.map(r => ({
163
+ source: r.source,
164
+ title: r.title,
165
+ url: r.url,
166
+ })));
167
+
168
+ const inputSizeKB = Buffer.byteLength(inputJson, 'utf8') / 1024;
169
+ const outputSizeKB = Buffer.byteLength(outputJson, 'utf8') / 1024;
170
+
171
+ return {
172
+ merged,
173
+ metrics: {
174
+ stage: 'context_reduction',
175
+ latencyMs: Date.now() - start,
176
+ inputSizeKB,
177
+ outputSizeKB,
178
+ reductionPct: inputSizeKB > 0 ? Number((100 - (outputSizeKB / inputSizeKB) * 100).toFixed(1)) : 0,
179
+ },
180
+ };
181
+ }
182
+
183
+ // ─── Stage 4: Gemini Analysis ────────────────────────────────────
184
+
185
+ async function geminiAnalysis(mergedResults: SearchResult[], query: string): Promise<{ analysis: string; latencyMs: number }> {
186
+ if (!GEMINI_API_KEY) {
187
+ console.log(' ⚠️ GEMINI_API_KEY not set — skipping Gemini analysis stage');
188
+ return { analysis: '[Skipped - no API key]', latencyMs: 0 };
189
+ }
190
+
191
+ const start = Date.now();
192
+ const genAI = new GoogleGenerativeAI(GEMINI_API_KEY);
193
+ const model = genAI.getGenerativeModel({ model: 'gemini-2.5-flash' });
194
+
195
+ const sourceSummary = mergedResults.map((r, i) =>
196
+ `[${i + 1}] (${r.source}) ${r.title}\n URL: ${r.url}`
197
+ ).join('\n');
198
+
199
+ const prompt = `Given the following search results from a hybrid pipeline (web search + enterprise Discovery Engine) for the query "${query}", provide a brief analytical summary highlighting the most relevant findings and how the two sources complement each other:\n\n${sourceSummary}\n\nKeep the summary concise (3-5 sentences).`;
200
+
201
+ try {
202
+ const result = await model.generateContent(prompt);
203
+ const text = result.response.text();
204
+ return { analysis: text, latencyMs: Date.now() - start };
205
+ } catch (error: any) {
206
+ return { analysis: `[Error: ${error.message}]`, latencyMs: Date.now() - start };
207
+ }
208
+ }
209
+
210
+ // ─── Main Pipeline ───────────────────────────────────────────────
211
+
212
+ async function runHybridPipeline() {
213
+ console.log('╔══════════════════════════════════════════════════════════╗');
214
+ console.log('║ Hybrid Search Pipeline Test: MCP + Vertex AI ║');
215
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
216
+
217
+ console.log(`Query: "${TEST_QUERY}"\n`);
218
+
219
+ const metrics: PipelineMetrics[] = [];
220
+
221
+ // Stage 1: Brave Search
222
+ console.log('── Stage 1: Brave Web Search (MCP) ──');
223
+ const brave = await braveWebSearch(TEST_QUERY);
224
+ console.log(` Results: ${brave.results.length} | Latency: ${brave.latencyMs}ms | Raw: ${(brave.rawSize / 1024).toFixed(1)}KB`);
225
+ metrics.push({
226
+ stage: 'brave_web_search',
227
+ latencyMs: brave.latencyMs,
228
+ inputSizeKB: 0,
229
+ outputSizeKB: brave.rawSize / 1024,
230
+ reductionPct: 0,
231
+ });
232
+
233
+ // Stage 2: Discovery Engine
234
+ console.log('\n── Stage 2: Vertex AI Discovery Engine ──');
235
+ const de = await discoveryEngineSearch(TEST_QUERY);
236
+ console.log(` Results: ${de.results.length} | Latency: ${de.latencyMs}ms | Raw: ${(de.rawSize / 1024).toFixed(1)}KB`);
237
+ metrics.push({
238
+ stage: 'discovery_engine',
239
+ latencyMs: de.latencyMs,
240
+ inputSizeKB: 0,
241
+ outputSizeKB: de.rawSize / 1024,
242
+ reductionPct: 0,
243
+ });
244
+
245
+ // Stage 3: Context Reduction
246
+ console.log('\n── Stage 3: Context Reduction (code_mode_transform) ──');
247
+ const { merged, metrics: reductionMetrics } = contextReduction(brave.results, de.results);
248
+ console.log(` Merged: ${merged.length} unique results (from ${brave.results.length + de.results.length} total)`);
249
+ console.log(` Reduction: ${reductionMetrics.inputSizeKB.toFixed(1)}KB → ${reductionMetrics.outputSizeKB.toFixed(1)}KB (${reductionMetrics.reductionPct}%)`);
250
+ metrics.push(reductionMetrics);
251
+
252
+ // Stage 4: Gemini Analysis
253
+ console.log('\n── Stage 4: Gemini LLM Analysis ──');
254
+ const analysis = await geminiAnalysis(merged, TEST_QUERY);
255
+ console.log(` Latency: ${analysis.latencyMs}ms`);
256
+ console.log(` Analysis:\n${analysis.analysis.split('\n').map(l => ` ${l}`).join('\n')}`);
257
+ metrics.push({
258
+ stage: 'gemini_analysis',
259
+ latencyMs: analysis.latencyMs,
260
+ inputSizeKB: 0,
261
+ outputSizeKB: Buffer.byteLength(analysis.analysis, 'utf8') / 1024,
262
+ reductionPct: 0,
263
+ });
264
+
265
+ // Summary
266
+ const totalLatency = metrics.reduce((sum, m) => sum + m.latencyMs, 0);
267
+
268
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
269
+ console.log('║ PIPELINE SUMMARY ║');
270
+ console.log('╠══════════════════════════════════════════════════════════╣');
271
+ console.log(`║ Total latency: ${totalLatency}ms `);
272
+ console.log(`║ Sources queried: ${[brave.results.length > 0 ? 'Brave' : null, de.results.length > 0 ? 'Discovery Engine' : null].filter(Boolean).join(' + ') || 'None'}`);
273
+ console.log(`║ Unique results: ${merged.length} `);
274
+ console.log(`║ Context reduction: ${reductionMetrics.reductionPct}% `);
275
+ console.log('╚══════════════════════════════════════════════════════════╝');
276
+
277
+ // Detailed results table
278
+ console.log('\n── Merged Results ──');
279
+ merged.forEach((r, i) => {
280
+ console.log(` [${i + 1}] (${r.source === 'brave' ? '🌐 Brave' : '🔍 DE'}) ${r.title}`);
281
+ console.log(` ${r.url}`);
282
+ });
283
+
284
+ // Pass/fail
285
+ const passed = brave.results.length > 0 || de.results.length > 0;
286
+ console.log(`\n${passed ? '✅ PIPELINE TEST PASSED' : '⚠️ No results from either source — check API keys and env vars'}`);
287
+
288
+ return passed;
289
+ }
290
+
291
+ runHybridPipeline()
292
+ .then(passed => process.exit(passed ? 0 : 1))
293
+ .catch(err => {
294
+ console.error('Pipeline error:', err);
295
+ process.exit(1);
296
+ });
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * Pipeline Performance Benchmark
4
+ *
5
+ * Measures and compares performance characteristics of the hybrid search pipeline
6
+ * across different configurations:
7
+ * - MCP-only (Brave Search + code_mode_transform)
8
+ * - Discovery Engine-only (Vertex AI Search)
9
+ * - Hybrid (both sources merged + LLM analysis)
10
+ *
11
+ * Metrics collected:
12
+ * - Query latency (ms)
13
+ * - Payload size (KB before/after context reduction)
14
+ * - Token estimation (chars / 4 ≈ tokens)
15
+ * - Context window efficiency (% reduction)
16
+ *
17
+ * Usage:
18
+ * npx ts-node vertex-ai/test_pipeline_benchmark.ts
19
+ *
20
+ * Prerequisites:
21
+ * - BRAVE_API_KEY environment variable
22
+ * - GCP ADC configured for Discovery Engine
23
+ */
24
+
25
+ import { SearchServiceClient } from '@google-cloud/discoveryengine';
26
+
27
+ // ─── Configuration ───────────────────────────────────────────────
28
+
29
+ const BRAVE_API_KEY = process.env.BRAVE_API_KEY || '';
30
+ const DE_PROJECT_ID = process.env.DISCOVERY_ENGINE_PROJECT_ID || process.env.GCP_PROJECT_ID || '';
31
+ const DE_ENGINE_ID = process.env.DISCOVERY_ENGINE_ENGINE_ID || '';
32
+ const DE_LOCATION = process.env.DISCOVERY_ENGINE_LOCATION || 'global';
33
+ const DE_COLLECTION = process.env.DISCOVERY_ENGINE_COLLECTION || 'default_collection';
34
+ const DE_SERVING_CONFIG = process.env.DISCOVERY_ENGINE_SERVING_CONFIG || 'default_serving_config';
35
+
36
+ const BENCHMARK_QUERIES = [
37
+ 'transformer architecture attention mechanism',
38
+ 'kubernetes pod autoscaling best practices',
39
+ 'python async await concurrency patterns',
40
+ 'vertex ai discovery engine structured search',
41
+ 'mcp model context protocol integration',
42
+ ];
43
+
44
+ const ITERATIONS = 3; // Number of runs per query for averaging
45
+
46
+ // ─── Types ───────────────────────────────────────────────────────
47
+
48
+ interface BenchmarkResult {
49
+ source: string;
50
+ query: string;
51
+ avgLatencyMs: number;
52
+ avgRawSizeKB: number;
53
+ avgReducedSizeKB: number;
54
+ avgReductionPct: number;
55
+ estimatedTokensBefore: number;
56
+ estimatedTokensAfter: number;
57
+ successRate: number;
58
+ }
59
+
60
+ // ─── Brave Search Benchmark ─────────────────────────────────────
61
+
62
+ async function benchmarkBrave(query: string): Promise<{ latencyMs: number; rawSizeKB: number; reducedSizeKB: number; success: boolean }> {
63
+ if (!BRAVE_API_KEY) return { latencyMs: 0, rawSizeKB: 0, reducedSizeKB: 0, success: false };
64
+
65
+ const start = Date.now();
66
+ try {
67
+ const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=10`;
68
+ const response = await fetch(url, {
69
+ headers: {
70
+ 'Accept': 'application/json',
71
+ 'Accept-Encoding': 'gzip',
72
+ 'X-Subscription-Token': BRAVE_API_KEY,
73
+ },
74
+ });
75
+
76
+ const data = await response.json();
77
+ const latencyMs = Date.now() - start;
78
+
79
+ const rawJson = JSON.stringify(data);
80
+ const rawSizeKB = Buffer.byteLength(rawJson, 'utf8') / 1024;
81
+
82
+ // Simulate code_mode_transform: extract only title + url + description
83
+ const reduced = (data.web?.results || []).map((r: any) => ({
84
+ title: r.title,
85
+ url: r.url,
86
+ desc: (r.description || '').substring(0, 150),
87
+ }));
88
+ const reducedJson = JSON.stringify(reduced);
89
+ const reducedSizeKB = Buffer.byteLength(reducedJson, 'utf8') / 1024;
90
+
91
+ return { latencyMs, rawSizeKB, reducedSizeKB, success: true };
92
+ } catch {
93
+ return { latencyMs: Date.now() - start, rawSizeKB: 0, reducedSizeKB: 0, success: false };
94
+ }
95
+ }
96
+
97
+ // ─── Discovery Engine Benchmark ─────────────────────────────────
98
+
99
+ async function benchmarkDiscoveryEngine(query: string): Promise<{ latencyMs: number; rawSizeKB: number; reducedSizeKB: number; success: boolean }> {
100
+ if (!DE_PROJECT_ID || !DE_ENGINE_ID) return { latencyMs: 0, rawSizeKB: 0, reducedSizeKB: 0, success: false };
101
+
102
+ const start = Date.now();
103
+ try {
104
+ const client = new SearchServiceClient();
105
+ const servingConfig = `projects/${DE_PROJECT_ID}/locations/${DE_LOCATION}/collections/${DE_COLLECTION}/engines/${DE_ENGINE_ID}/servingConfigs/${DE_SERVING_CONFIG}`;
106
+
107
+ const [response] = await client.search({ servingConfig, query, pageSize: 10 }, { autoPaginate: false });
108
+ const latencyMs = Date.now() - start;
109
+
110
+ const resultItems = Array.isArray(response) ? response : (response as any).results || [];
111
+ const rawJson = JSON.stringify(resultItems);
112
+ const rawSizeKB = Buffer.byteLength(rawJson, 'utf8') / 1024;
113
+
114
+ // Helper for protobuf Value extraction
115
+ const getField = (sd: any, f: string): string => {
116
+ if (!sd) return '';
117
+ if (typeof sd[f] === 'string') return sd[f];
118
+ if (sd.fields?.[f]?.stringValue) return sd.fields[f].stringValue;
119
+ return '';
120
+ };
121
+
122
+ const reduced = resultItems.map((r: any) => ({
123
+ title: getField(r.document?.derivedStructData, 'title') || getField(r.document?.derivedStructData, 'displayLink') || '',
124
+ url: getField(r.document?.derivedStructData, 'link') || getField(r.document?.derivedStructData, 'htmlFormattedUrl') || '',
125
+ }));
126
+ const reducedJson = JSON.stringify(reduced);
127
+ const reducedSizeKB = Buffer.byteLength(reducedJson, 'utf8') / 1024;
128
+
129
+ return { latencyMs, rawSizeKB, reducedSizeKB, success: true };
130
+ } catch {
131
+ return { latencyMs: Date.now() - start, rawSizeKB: 0, reducedSizeKB: 0, success: false };
132
+ }
133
+ }
134
+
135
+ // ─── Run Benchmark ───────────────────────────────────────────────
136
+
137
+ async function runBenchmarks() {
138
+ console.log('╔══════════════════════════════════════════════════════════════╗');
139
+ console.log('║ Pipeline Performance Benchmark: MCP vs Vertex AI ║');
140
+ console.log('╚══════════════════════════════════════════════════════════════╝\n');
141
+
142
+ const braveResults: BenchmarkResult[] = [];
143
+ const deResults: BenchmarkResult[] = [];
144
+
145
+ for (const query of BENCHMARK_QUERIES) {
146
+ console.log(`\n── Query: "${query}" ──`);
147
+
148
+ // Benchmark Brave Search
149
+ const braveRuns: Awaited<ReturnType<typeof benchmarkBrave>>[] = [];
150
+ for (let i = 0; i < ITERATIONS; i++) {
151
+ braveRuns.push(await benchmarkBrave(query));
152
+ if (i < ITERATIONS - 1) await sleep(500); // Rate limit courtesy
153
+ }
154
+
155
+ const braveSuccessful = braveRuns.filter(r => r.success);
156
+ if (braveSuccessful.length > 0) {
157
+ const result: BenchmarkResult = {
158
+ source: 'Brave Search',
159
+ query,
160
+ avgLatencyMs: avg(braveSuccessful.map(r => r.latencyMs)),
161
+ avgRawSizeKB: avg(braveSuccessful.map(r => r.rawSizeKB)),
162
+ avgReducedSizeKB: avg(braveSuccessful.map(r => r.reducedSizeKB)),
163
+ avgReductionPct: avg(braveSuccessful.map(r => r.rawSizeKB > 0 ? (1 - r.reducedSizeKB / r.rawSizeKB) * 100 : 0)),
164
+ estimatedTokensBefore: Math.round(avg(braveSuccessful.map(r => r.rawSizeKB * 1024 / 4))),
165
+ estimatedTokensAfter: Math.round(avg(braveSuccessful.map(r => r.reducedSizeKB * 1024 / 4))),
166
+ successRate: braveSuccessful.length / ITERATIONS,
167
+ };
168
+ braveResults.push(result);
169
+ console.log(` 🌐 Brave: ${result.avgLatencyMs.toFixed(0)}ms | ${result.avgRawSizeKB.toFixed(1)}KB → ${result.avgReducedSizeKB.toFixed(1)}KB (${result.avgReductionPct.toFixed(0)}% reduction)`);
170
+ } else {
171
+ console.log(' 🌐 Brave: skipped (no API key)');
172
+ }
173
+
174
+ // Benchmark Discovery Engine
175
+ const deRuns: Awaited<ReturnType<typeof benchmarkDiscoveryEngine>>[] = [];
176
+ for (let i = 0; i < ITERATIONS; i++) {
177
+ deRuns.push(await benchmarkDiscoveryEngine(query));
178
+ if (i < ITERATIONS - 1) await sleep(300);
179
+ }
180
+
181
+ const deSuccessful = deRuns.filter(r => r.success);
182
+ if (deSuccessful.length > 0) {
183
+ const result: BenchmarkResult = {
184
+ source: 'Discovery Engine',
185
+ query,
186
+ avgLatencyMs: avg(deSuccessful.map(r => r.latencyMs)),
187
+ avgRawSizeKB: avg(deSuccessful.map(r => r.rawSizeKB)),
188
+ avgReducedSizeKB: avg(deSuccessful.map(r => r.reducedSizeKB)),
189
+ avgReductionPct: avg(deSuccessful.map(r => r.rawSizeKB > 0 ? (1 - r.reducedSizeKB / r.rawSizeKB) * 100 : 0)),
190
+ estimatedTokensBefore: Math.round(avg(deSuccessful.map(r => r.rawSizeKB * 1024 / 4))),
191
+ estimatedTokensAfter: Math.round(avg(deSuccessful.map(r => r.reducedSizeKB * 1024 / 4))),
192
+ successRate: deSuccessful.length / ITERATIONS,
193
+ };
194
+ deResults.push(result);
195
+ console.log(` 🔍 DE: ${result.avgLatencyMs.toFixed(0)}ms | ${result.avgRawSizeKB.toFixed(1)}KB → ${result.avgReducedSizeKB.toFixed(1)}KB (${result.avgReductionPct.toFixed(0)}% reduction)`);
196
+ } else {
197
+ console.log(' 🔍 DE: skipped (env vars not set)');
198
+ }
199
+ }
200
+
201
+ // Summary
202
+ console.log('\n╔══════════════════════════════════════════════════════════════╗');
203
+ console.log('║ AGGREGATE RESULTS ║');
204
+ console.log('╠══════════════════════════════════════════════════════════════╣');
205
+
206
+ if (braveResults.length > 0) {
207
+ console.log('║ 🌐 Brave Search (MCP) ║');
208
+ console.log(`║ Avg latency: ${avg(braveResults.map(r => r.avgLatencyMs)).toFixed(0)}ms`);
209
+ console.log(`║ Avg raw payload: ${avg(braveResults.map(r => r.avgRawSizeKB)).toFixed(1)}KB`);
210
+ console.log(`║ Avg reduced: ${avg(braveResults.map(r => r.avgReducedSizeKB)).toFixed(1)}KB`);
211
+ console.log(`║ Avg reduction: ${avg(braveResults.map(r => r.avgReductionPct)).toFixed(0)}%`);
212
+ console.log(`║ Token savings: ~${avg(braveResults.map(r => r.estimatedTokensBefore - r.estimatedTokensAfter)).toFixed(0)} tokens/query`);
213
+ }
214
+
215
+ if (deResults.length > 0) {
216
+ console.log('║ ║');
217
+ console.log('║ 🔍 Discovery Engine (Vertex AI) ║');
218
+ console.log(`║ Avg latency: ${avg(deResults.map(r => r.avgLatencyMs)).toFixed(0)}ms`);
219
+ console.log(`║ Avg raw payload: ${avg(deResults.map(r => r.avgRawSizeKB)).toFixed(1)}KB`);
220
+ console.log(`║ Avg reduced: ${avg(deResults.map(r => r.avgReducedSizeKB)).toFixed(1)}KB`);
221
+ console.log(`║ Avg reduction: ${avg(deResults.map(r => r.avgReductionPct)).toFixed(0)}%`);
222
+ console.log(`║ Token savings: ~${avg(deResults.map(r => r.estimatedTokensBefore - r.estimatedTokensAfter)).toFixed(0)} tokens/query`);
223
+ }
224
+
225
+ if (braveResults.length > 0 && deResults.length > 0) {
226
+ const braveLatency = avg(braveResults.map(r => r.avgLatencyMs));
227
+ const deLatency = avg(deResults.map(r => r.avgLatencyMs));
228
+ const latencyDiff = ((braveLatency - deLatency) / braveLatency * 100).toFixed(0);
229
+ console.log('║ ║');
230
+ console.log(`║ ⚡ DE is ${latencyDiff}% ${Number(latencyDiff) > 0 ? 'faster' : 'slower'} than Brave (pre-indexed vs live search)`);
231
+ }
232
+
233
+ console.log('╚══════════════════════════════════════════════════════════════╝');
234
+ }
235
+
236
+ // ─── Utilities ───────────────────────────────────────────────────
237
+
238
+ function avg(nums: number[]): number {
239
+ return nums.length > 0 ? nums.reduce((a, b) => a + b, 0) / nums.length : 0;
240
+ }
241
+
242
+ function sleep(ms: number): Promise<void> {
243
+ return new Promise(r => setTimeout(r, ms));
244
+ }
245
+
246
+ // ─── Entry ───────────────────────────────────────────────────────
247
+
248
+ runBenchmarks().catch(err => {
249
+ console.error('Benchmark error:', err);
250
+ process.exit(1);
251
+ });