mcp-researchpowerpack 7.0.11 → 7.0.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.
- package/dist/index.js +4662 -21
- package/dist/index.js.map +4 -4
- package/dist/mcp-use.json +2 -2
- package/dist/src/clients/jina.js +202 -16
- package/dist/src/clients/jina.js.map +3 -3
- package/dist/src/clients/kernel.js +254 -7
- package/dist/src/clients/kernel.js.map +4 -4
- package/dist/src/clients/reddit.js +326 -23
- package/dist/src/clients/reddit.js.map +4 -4
- package/dist/src/clients/scraper.js +345 -22
- package/dist/src/clients/scraper.js.map +4 -4
- package/dist/src/clients/search.js +316 -20
- package/dist/src/clients/search.js.map +4 -4
- package/dist/src/config/index.js +39 -10
- package/dist/src/config/index.js.map +3 -3
- package/dist/src/effect/errors.js +130 -5
- package/dist/src/effect/errors.js.map +3 -3
- package/dist/src/effect/runtime.js +1893 -4
- package/dist/src/effect/runtime.js.map +4 -4
- package/dist/src/effect/services.js +2124 -22
- package/dist/src/effect/services.js.map +4 -4
- package/dist/src/schemas/scrape-links.js +6 -5
- package/dist/src/schemas/scrape-links.js.map +1 -1
- package/dist/src/schemas/start-research.js +2 -1
- package/dist/src/schemas/start-research.js.map +1 -1
- package/dist/src/schemas/web-search.js +9 -8
- package/dist/src/schemas/web-search.js.map +1 -1
- package/dist/src/services/llm-processor.js +406 -25
- package/dist/src/services/llm-processor.js.map +4 -4
- package/dist/src/services/markdown-cleaner.js +6 -5
- package/dist/src/services/markdown-cleaner.js.map +1 -1
- package/dist/src/tools/mcp-helpers.js +2 -1
- package/dist/src/tools/mcp-helpers.js.map +1 -1
- package/dist/src/tools/registry.js +4629 -3
- package/dist/src/tools/registry.js.map +4 -4
- package/dist/src/tools/scrape.js +2610 -80
- package/dist/src/tools/scrape.js.map +4 -4
- package/dist/src/tools/search.js +2388 -59
- package/dist/src/tools/search.js.map +4 -4
- package/dist/src/tools/start-research.js +2030 -23
- package/dist/src/tools/start-research.js.map +4 -4
- package/dist/src/tools/utils.js +98 -7
- package/dist/src/tools/utils.js.map +3 -3
- package/dist/src/utils/concurrency.js +1 -0
- package/dist/src/utils/concurrency.js.map +1 -1
- package/dist/src/utils/content-extractor.js +27 -2
- package/dist/src/utils/content-extractor.js.map +3 -3
- package/dist/src/utils/content-quality.js +4 -3
- package/dist/src/utils/content-quality.js.map +1 -1
- package/dist/src/utils/errors.js +26 -3
- package/dist/src/utils/errors.js.map +3 -3
- package/dist/src/utils/logger.js +1 -0
- package/dist/src/utils/logger.js.map +1 -1
- package/dist/src/utils/markdown-formatter.js +1 -0
- package/dist/src/utils/markdown-formatter.js.map +1 -1
- package/dist/src/utils/query-relax.js +9 -8
- package/dist/src/utils/query-relax.js.map +1 -1
- package/dist/src/utils/response.js +3 -2
- package/dist/src/utils/response.js.map +1 -1
- package/dist/src/utils/retry.js +5 -4
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/sanitize.js +4 -3
- package/dist/src/utils/sanitize.js.map +1 -1
- package/dist/src/utils/source-type.js +4 -3
- package/dist/src/utils/source-type.js.map +1 -1
- package/dist/src/utils/url-aggregator.js +112 -11
- package/dist/src/utils/url-aggregator.js.map +3 -3
- package/dist/src/version.js +7 -6
- package/dist/src/version.js.map +1 -1
- package/package.json +3 -3
package/dist/src/tools/utils.js
CHANGED
|
@@ -1,10 +1,101 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
// src/utils/logger.ts
|
|
2
|
+
import { Logger } from "mcp-use";
|
|
3
|
+
function getLogger(name) {
|
|
4
|
+
return Logger.get(name);
|
|
5
|
+
}
|
|
6
|
+
function mcpLog(level, message, loggerName) {
|
|
7
|
+
const logger = getLogger(loggerName ?? "research-powerpack");
|
|
8
|
+
switch (level) {
|
|
9
|
+
case "debug":
|
|
10
|
+
logger.debug(message);
|
|
11
|
+
break;
|
|
12
|
+
case "info":
|
|
13
|
+
logger.info(message);
|
|
14
|
+
break;
|
|
15
|
+
case "warning":
|
|
16
|
+
logger.warn(message);
|
|
17
|
+
break;
|
|
18
|
+
case "error":
|
|
19
|
+
logger.error(message);
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/utils/response.ts
|
|
25
|
+
var SECONDS_MS = 1e3;
|
|
26
|
+
var MINUTES_MS = 6e4;
|
|
27
|
+
function formatSuccess(opts) {
|
|
28
|
+
const parts = [];
|
|
29
|
+
parts.push(`\u2713 ${opts.title}`);
|
|
30
|
+
parts.push("");
|
|
31
|
+
parts.push(opts.summary);
|
|
32
|
+
if (opts.data) {
|
|
33
|
+
parts.push("");
|
|
34
|
+
parts.push("---");
|
|
35
|
+
parts.push(opts.data);
|
|
36
|
+
}
|
|
37
|
+
if (opts.nextSteps?.length) {
|
|
38
|
+
parts.push("");
|
|
39
|
+
parts.push("---");
|
|
40
|
+
parts.push("**Next Steps:**");
|
|
41
|
+
opts.nextSteps.forEach((step) => parts.push(`\u2192 ${step}`));
|
|
42
|
+
}
|
|
43
|
+
if (opts.metadata && Object.keys(opts.metadata).length > 0) {
|
|
44
|
+
parts.push("");
|
|
45
|
+
parts.push("---");
|
|
46
|
+
const metaStr = Object.entries(opts.metadata).map(([k, v]) => `${k}: ${v}`).join(" | ");
|
|
47
|
+
parts.push(`*${metaStr}*`);
|
|
48
|
+
}
|
|
49
|
+
return parts.join("\n");
|
|
50
|
+
}
|
|
51
|
+
function formatError(opts) {
|
|
52
|
+
const parts = [];
|
|
53
|
+
const prefix = opts.toolName ? `[${opts.toolName}] ` : "";
|
|
54
|
+
parts.push(`\u274C ${prefix}${opts.code}: ${opts.message}`);
|
|
55
|
+
if (opts.retryable) {
|
|
56
|
+
parts.push("*Retryable.*");
|
|
57
|
+
}
|
|
58
|
+
if (opts.howToFix?.length) {
|
|
59
|
+
parts.push("");
|
|
60
|
+
parts.push("**How to Fix:**");
|
|
61
|
+
opts.howToFix.forEach((step, i) => parts.push(`${i + 1}. ${step}`));
|
|
62
|
+
}
|
|
63
|
+
if (opts.alternatives?.length) {
|
|
64
|
+
parts.push("");
|
|
65
|
+
parts.push("**Alternatives:**");
|
|
66
|
+
opts.alternatives.forEach((alt, i) => parts.push(`${i + 1}. ${alt}`));
|
|
67
|
+
}
|
|
68
|
+
return parts.join("\n");
|
|
69
|
+
}
|
|
70
|
+
function formatBatchHeader(opts) {
|
|
71
|
+
const parts = [];
|
|
72
|
+
const successRate = opts.totalItems > 0 ? opts.successful / opts.totalItems : 0;
|
|
73
|
+
const emoji = successRate === 1 ? "\u2713" : successRate >= 0.5 ? "\u26A0\uFE0F" : "\u274C";
|
|
74
|
+
parts.push(`${emoji} ${opts.title}`);
|
|
75
|
+
parts.push("");
|
|
76
|
+
parts.push(`\u2022 Total: ${opts.totalItems}`);
|
|
77
|
+
parts.push(`\u2022 Successful: ${opts.successful}`);
|
|
78
|
+
if (opts.failed > 0) {
|
|
79
|
+
parts.push(`\u2022 Failed: ${opts.failed}`);
|
|
80
|
+
}
|
|
81
|
+
if (opts.tokensPerItem) {
|
|
82
|
+
parts.push(`\u2022 Tokens/item: ~${opts.tokensPerItem.toLocaleString()}`);
|
|
83
|
+
}
|
|
84
|
+
if (opts.batches) {
|
|
85
|
+
parts.push(`\u2022 Batches: ${opts.batches}`);
|
|
86
|
+
}
|
|
87
|
+
if (opts.extras) {
|
|
88
|
+
Object.entries(opts.extras).forEach(([key, val]) => {
|
|
89
|
+
parts.push(`\u2022 ${key}: ${val}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return parts.join("\n");
|
|
93
|
+
}
|
|
94
|
+
function formatDuration(ms) {
|
|
95
|
+
if (ms < SECONDS_MS) return `${ms}ms`;
|
|
96
|
+
if (ms < MINUTES_MS) return `${(ms / SECONDS_MS).toFixed(1)}s`;
|
|
97
|
+
return `${(ms / MINUTES_MS).toFixed(1)}m`;
|
|
98
|
+
}
|
|
8
99
|
export {
|
|
9
100
|
formatBatchHeader,
|
|
10
101
|
formatDuration,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../src/
|
|
4
|
-
"sourcesContent": ["/**\n *
|
|
5
|
-
"mappings": "
|
|
3
|
+
"sources": ["../../../src/utils/logger.ts", "../../../src/utils/response.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Server logging utility.\n *\n * This server is HTTP-only, so logging must never depend on a transport-bound\n * MCP server instance. All logs flow through mcp-use's Logger, which writes to\n * stderr in Node and console in the browser.\n */\n\nimport { Logger } from 'mcp-use';\n\nexport type LogLevel = 'debug' | 'info' | 'warning' | 'error';\n\nfunction getLogger(name: string) {\n return Logger.get(name);\n}\n\n/**\n * Structured log helper backed by mcp-use's Logger.\n *\n * @param level - Log level.\n * @param message - Message to emit.\n * @param loggerName - Tool/component name for context (falls back to \"research-powerpack\").\n */\nexport function mcpLog(level: LogLevel, message: string, loggerName?: string): void {\n const logger = getLogger(loggerName ?? 'research-powerpack');\n\n switch (level) {\n case 'debug':\n logger.debug(message);\n break;\n case 'info':\n logger.info(message);\n break;\n case 'warning':\n logger.warn(message);\n break;\n case 'error':\n logger.error(message);\n break;\n }\n}\n", "/**\n * MCP Response Formatters\n */\n\n/** Duration thresholds in milliseconds */\nconst SECONDS_MS = 1_000 as const;\nconst MINUTES_MS = 60_000 as const;\n\n// ============================================================================\n// Success Response Formatter\n// ============================================================================\n\nexport interface SuccessOptions {\n /** Title/header for the response */\n readonly title: string;\n /** Summary section (70% of content) */\n readonly summary: string;\n /** Optional data section (20% of content) */\n readonly data?: string;\n /** Optional next steps (10% of content) */\n readonly nextSteps?: string[];\n /** Optional metadata footer */\n readonly metadata?: Record<string, string | number>;\n}\n\n/**\n * Format a successful response using 70/20/10 pattern\n */\nexport function formatSuccess(opts: SuccessOptions): string {\n const parts: string[] = [];\n\n // Title\n parts.push(`\u2713 ${opts.title}`);\n parts.push('');\n\n // Summary (70%)\n parts.push(opts.summary);\n\n // Data section (20%)\n if (opts.data) {\n parts.push('');\n parts.push('---');\n parts.push(opts.data);\n }\n\n // Next steps (10%)\n if (opts.nextSteps?.length) {\n parts.push('');\n parts.push('---');\n parts.push('**Next Steps:**');\n opts.nextSteps.forEach(step => parts.push(`\u2192 ${step}`));\n }\n\n // Metadata footer\n if (opts.metadata && Object.keys(opts.metadata).length > 0) {\n parts.push('');\n parts.push('---');\n const metaStr = Object.entries(opts.metadata)\n .map(([k, v]) => `${k}: ${v}`)\n .join(' | ');\n parts.push(`*${metaStr}*`);\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Error Response Formatter\n// ============================================================================\n\nexport interface ErrorOptions {\n /** Error code (e.g., RATE_LIMITED, TIMEOUT) */\n readonly code: string;\n /** Human-readable error message */\n readonly message: string;\n /** Is this error retryable? */\n readonly retryable?: boolean;\n /** How to fix the error */\n readonly howToFix?: string[];\n /** Alternative actions */\n readonly alternatives?: string[];\n /** Tool name for context */\n readonly toolName?: string;\n}\n\n/**\n * Format an error response with recovery guidance\n * Designed to keep agents moving \u2014 every error includes actionable alternatives\n */\nexport function formatError(opts: ErrorOptions): string {\n const parts: string[] = [];\n\n // Error header\n const prefix = opts.toolName ? `[${opts.toolName}] ` : '';\n parts.push(`\u274C ${prefix}${opts.code}: ${opts.message}`);\n\n // Retryable hint\n if (opts.retryable) {\n parts.push('*Retryable.*');\n }\n\n // How to fix\n if (opts.howToFix?.length) {\n parts.push('');\n parts.push('**How to Fix:**');\n opts.howToFix.forEach((step, i) => parts.push(`${i + 1}. ${step}`));\n }\n\n // Alternatives\n if (opts.alternatives?.length) {\n parts.push('');\n parts.push('**Alternatives:**');\n opts.alternatives.forEach((alt, i) => parts.push(`${i + 1}. ${alt}`));\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Batch Header Formatter\n// ============================================================================\n\nexport interface BatchHeaderOptions {\n /** Batch operation title */\n readonly title: string;\n /** Total items attempted */\n readonly totalItems: number;\n /** Successfully processed count */\n readonly successful: number;\n /** Failed count */\n readonly failed: number;\n /** Optional tokens per item */\n readonly tokensPerItem?: number;\n /** Optional batch count */\n readonly batches?: number;\n /** Extra stats to include */\n readonly extras?: Record<string, string | number>;\n}\n\n/**\n * Format a batch operation header with stats\n */\nexport function formatBatchHeader(opts: BatchHeaderOptions): string {\n const parts: string[] = [];\n\n // Title with emoji based on success rate\n const successRate = opts.totalItems > 0 ? opts.successful / opts.totalItems : 0;\n const emoji = successRate === 1 ? '\u2713' : successRate >= 0.5 ? '\u26A0\uFE0F' : '\u274C';\n parts.push(`${emoji} ${opts.title}`);\n parts.push('');\n\n // Stats\n parts.push(`\u2022 Total: ${opts.totalItems}`);\n parts.push(`\u2022 Successful: ${opts.successful}`);\n if (opts.failed > 0) {\n parts.push(`\u2022 Failed: ${opts.failed}`);\n }\n if (opts.tokensPerItem) {\n parts.push(`\u2022 Tokens/item: ~${opts.tokensPerItem.toLocaleString()}`);\n }\n if (opts.batches) {\n parts.push(`\u2022 Batches: ${opts.batches}`);\n }\n\n // Extra stats\n if (opts.extras) {\n Object.entries(opts.extras).forEach(([key, val]) => {\n parts.push(`\u2022 ${key}: ${val}`);\n });\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Duration Formatter\n// ============================================================================\n\n/**\n * Format duration in human-readable form\n */\nexport function formatDuration(ms: number): string {\n if (ms < SECONDS_MS) return `${ms}ms`;\n if (ms < MINUTES_MS) return `${(ms / SECONDS_MS).toFixed(1)}s`;\n return `${(ms / MINUTES_MS).toFixed(1)}m`;\n}\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/concurrency.ts"],
|
|
4
4
|
"sourcesContent": ["/**\n * Thin wrappers around the `p-map` library for bounded parallel execution.\n * Keeps the internal API stable (`pMap` / `pMapSettled`) while delegating\n * scheduling and AbortSignal handling to `p-map@7`.\n */\n\nimport pMapLib from 'p-map';\n\n/**\n * Run `mapper` over `items` with at most `concurrency` in flight.\n * Rejects with the first error the mapper throws \u2014 matches the prior\n * hand-rolled semantics. Results preserve input order.\n */\nexport async function pMap<T, R>(\n items: readonly T[],\n mapper: (item: T, index: number) => Promise<R>,\n concurrency: number = 6,\n signal?: AbortSignal,\n): Promise<R[]> {\n if (items.length === 0) return [];\n const limit = Math.max(1, Math.min(concurrency, items.length));\n return pMapLib(items, mapper, { concurrency: limit, signal });\n}\n\n/**\n * Like `pMap` but never rejects \u2014 mapper errors surface as\n * `{ status: 'rejected', reason }` entries. Useful when one per-item\n * failure must not cancel the others (e.g. per-URL scraping).\n */\nexport async function pMapSettled<T, R>(\n items: readonly T[],\n mapper: (item: T, index: number) => Promise<R>,\n concurrency: number = 6,\n signal?: AbortSignal,\n): Promise<PromiseSettledResult<R>[]> {\n if (items.length === 0) return [];\n const limit = Math.max(1, Math.min(concurrency, items.length));\n return pMapLib(\n items,\n async (item, index): Promise<PromiseSettledResult<R>> => {\n try {\n const value = await mapper(item, index);\n return { status: 'fulfilled', value };\n } catch (reason) {\n return { status: 'rejected', reason };\n }\n },\n { concurrency: limit, signal, stopOnError: false },\n );\n}\n"],
|
|
5
|
-
"mappings": "AAMA,OAAO,aAAa;AAOpB,eAAsB,KACpB,OACA,QACA,cAAsB,GACtB,QACc;AACd,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,aAAa,MAAM,MAAM,CAAC;AAC7D,SAAO,QAAQ,OAAO,QAAQ,EAAE,aAAa,OAAO,OAAO,CAAC;AAC9D;AAOA,eAAsB,YACpB,OACA,QACA,cAAsB,GACtB,QACoC;AACpC,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,aAAa,MAAM,MAAM,CAAC;AAC7D,SAAO;AAAA,IACL;AAAA,IACA,OAAO,MAAM,UAA4C;AACvD,UAAI;AACF,cAAM,QAAQ,MAAM,OAAO,MAAM,KAAK;AACtC,eAAO,EAAE,QAAQ,aAAa,MAAM;AAAA,MACtC,SAAS,QAAQ;AACf,eAAO,EAAE,QAAQ,YAAY,OAAO;AAAA,MACtC;AAAA,IACF;AAAA,IACA,EAAE,aAAa,OAAO,QAAQ,aAAa,MAAM;AAAA,EACnD;AACF;",
|
|
5
|
+
"mappings": ";AAMA,OAAO,aAAa;AAOpB,eAAsB,KACpB,OACA,QACA,cAAsB,GACtB,QACc;AACd,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,aAAa,MAAM,MAAM,CAAC;AAC7D,SAAO,QAAQ,OAAO,QAAQ,EAAE,aAAa,OAAO,OAAO,CAAC;AAC9D;AAOA,eAAsB,YACpB,OACA,QACA,cAAsB,GACtB,QACoC;AACpC,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,aAAa,MAAM,MAAM,CAAC;AAC7D,SAAO;AAAA,IACL;AAAA,IACA,OAAO,MAAM,UAA4C;AACvD,UAAI;AACF,cAAM,QAAQ,MAAM,OAAO,MAAM,KAAK;AACtC,eAAO,EAAE,QAAQ,aAAa,MAAM;AAAA,MACtC,SAAS,QAAQ;AACf,eAAO,EAAE,QAAQ,YAAY,OAAO;AAAA,MACtC;AAAA,IACF;AAAA,IACA,EAAE,aAAa,OAAO,QAAQ,aAAa,MAAM;AAAA,EACnD;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,32 @@
|
|
|
1
|
+
// src/utils/content-extractor.ts
|
|
1
2
|
import { Readability } from "@mozilla/readability";
|
|
2
3
|
import { JSDOM, VirtualConsole } from "jsdom";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
// src/utils/logger.ts
|
|
6
|
+
import { Logger } from "mcp-use";
|
|
7
|
+
function getLogger(name) {
|
|
8
|
+
return Logger.get(name);
|
|
9
|
+
}
|
|
10
|
+
function mcpLog(level, message, loggerName) {
|
|
11
|
+
const logger = getLogger(loggerName ?? "research-powerpack");
|
|
12
|
+
switch (level) {
|
|
13
|
+
case "debug":
|
|
14
|
+
logger.debug(message);
|
|
15
|
+
break;
|
|
16
|
+
case "info":
|
|
17
|
+
logger.info(message);
|
|
18
|
+
break;
|
|
19
|
+
case "warning":
|
|
20
|
+
logger.warn(message);
|
|
21
|
+
break;
|
|
22
|
+
case "error":
|
|
23
|
+
logger.error(message);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/utils/content-extractor.ts
|
|
29
|
+
var MAX_READABILITY_BYTES = 15e5;
|
|
5
30
|
function extractReadableContent(html, url) {
|
|
6
31
|
if (!html || typeof html !== "string") {
|
|
7
32
|
return { title: "", content: html ?? "", extracted: false };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../src/utils/content-extractor.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Content Extractor \u2014 strips HTML chrome (cookie banners, nav, footer,\n * repeated hero blocks) before scraped pages reach the LLM extractor or the\n * raw fallback path. Both paths benefit equally \u2014 the cleaner the content,\n * the less LLM tokens are spent on noise and the less raw HTML the agent has\n * to reason around.\n *\n * Implementation: Mozilla Readability over jsdom. Falls back to the original\n * HTML if Readability cannot identify an article body \u2014 never throws.\n *\n * See: docs/code-review/context/02-current-tool-surface.md (E5) for the\n * baseline 12,704-char Merge blog probe; this module's acceptance bar is\n * <8,000 chars on the same input.\n */\n\nimport { Readability } from '@mozilla/readability';\nimport { JSDOM, VirtualConsole } from 'jsdom';\nimport { mcpLog } from './logger.js';\n\nexport interface ExtractedContent {\n /** Page title \u2014 if Readability could identify one. */\n readonly title: string;\n /** Main article content (HTML, ready for HTML\u2192Markdown conversion). */\n readonly content: string;\n /** Author byline if extractable. */\n readonly byline?: string;\n /** True if Readability ran successfully; false on fallback. */\n readonly extracted: boolean;\n}\n\n/** Maximum HTML length we attempt to feed jsdom. Larger pages are passed\n * through unchanged \u2014 Readability + jsdom are O(n) but allocate a real DOM\n * per call which can balloon RSS on a 5MB SPA bundle. */\nconst MAX_READABILITY_BYTES = 1_500_000 as const;\n\nexport function extractReadableContent(html: string, url?: string): ExtractedContent {\n if (!html || typeof html !== 'string') {\n return { title: '', content: html ?? '', extracted: false };\n }\n\n if (html.length > MAX_READABILITY_BYTES) {\n return { title: '', content: html, extracted: false };\n }\n\n // Quick heuristic \u2014 if there's no HTML structure, skip Readability entirely.\n if (!html.includes('<')) {\n return { title: '', content: html, extracted: false };\n }\n\n // jsdom emits noisy \"could not parse CSS\" / network errors on real pages.\n // Silence them so they do not pollute server-side stderr.\n const virtualConsole = new VirtualConsole();\n virtualConsole.on('error', () => {});\n virtualConsole.on('warn', () => {});\n virtualConsole.on('jsdomError', () => {});\n\n let dom: JSDOM;\n try {\n dom = new JSDOM(html, {\n url: url && /^https?:/i.test(url) ? url : 'https://example.com/',\n virtualConsole,\n });\n } catch (err) {\n mcpLog('warning', `JSDOM construction failed: ${err instanceof Error ? err.message : String(err)}`, 'content-extractor');\n return { title: '', content: html, extracted: false };\n }\n\n try {\n const reader = new Readability(dom.window.document, {\n // Keep classes that downstream cleanup may need; Turndown ignores them.\n keepClasses: false,\n // Strip <script>/<style> already handled by Readability defaults.\n });\n const article = reader.parse();\n if (!article || !article.content) {\n return { title: article?.title ?? '', content: html, extracted: false };\n }\n return {\n title: article.title ?? '',\n content: article.content,\n byline: article.byline ?? undefined,\n extracted: true,\n };\n } catch (err) {\n mcpLog('warning', `Readability.parse failed: ${err instanceof Error ? err.message : String(err)}`, 'content-extractor');\n return { title: '', content: html, extracted: false };\n } finally {\n // jsdom retains references via global refs \u2014 close the window to free RSS.\n try { dom.window.close(); } catch { /* ignore */ }\n }\n}\n"],
|
|
5
|
-
"mappings": "AAeA,SAAS,mBAAmB;AAC5B,SAAS,OAAO,sBAAsB;
|
|
3
|
+
"sources": ["../../../src/utils/content-extractor.ts", "../../../src/utils/logger.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Content Extractor \u2014 strips HTML chrome (cookie banners, nav, footer,\n * repeated hero blocks) before scraped pages reach the LLM extractor or the\n * raw fallback path. Both paths benefit equally \u2014 the cleaner the content,\n * the less LLM tokens are spent on noise and the less raw HTML the agent has\n * to reason around.\n *\n * Implementation: Mozilla Readability over jsdom. Falls back to the original\n * HTML if Readability cannot identify an article body \u2014 never throws.\n *\n * See: docs/code-review/context/02-current-tool-surface.md (E5) for the\n * baseline 12,704-char Merge blog probe; this module's acceptance bar is\n * <8,000 chars on the same input.\n */\n\nimport { Readability } from '@mozilla/readability';\nimport { JSDOM, VirtualConsole } from 'jsdom';\nimport { mcpLog } from './logger.js';\n\nexport interface ExtractedContent {\n /** Page title \u2014 if Readability could identify one. */\n readonly title: string;\n /** Main article content (HTML, ready for HTML\u2192Markdown conversion). */\n readonly content: string;\n /** Author byline if extractable. */\n readonly byline?: string;\n /** True if Readability ran successfully; false on fallback. */\n readonly extracted: boolean;\n}\n\n/** Maximum HTML length we attempt to feed jsdom. Larger pages are passed\n * through unchanged \u2014 Readability + jsdom are O(n) but allocate a real DOM\n * per call which can balloon RSS on a 5MB SPA bundle. */\nconst MAX_READABILITY_BYTES = 1_500_000 as const;\n\nexport function extractReadableContent(html: string, url?: string): ExtractedContent {\n if (!html || typeof html !== 'string') {\n return { title: '', content: html ?? '', extracted: false };\n }\n\n if (html.length > MAX_READABILITY_BYTES) {\n return { title: '', content: html, extracted: false };\n }\n\n // Quick heuristic \u2014 if there's no HTML structure, skip Readability entirely.\n if (!html.includes('<')) {\n return { title: '', content: html, extracted: false };\n }\n\n // jsdom emits noisy \"could not parse CSS\" / network errors on real pages.\n // Silence them so they do not pollute server-side stderr.\n const virtualConsole = new VirtualConsole();\n virtualConsole.on('error', () => {});\n virtualConsole.on('warn', () => {});\n virtualConsole.on('jsdomError', () => {});\n\n let dom: JSDOM;\n try {\n dom = new JSDOM(html, {\n url: url && /^https?:/i.test(url) ? url : 'https://example.com/',\n virtualConsole,\n });\n } catch (err) {\n mcpLog('warning', `JSDOM construction failed: ${err instanceof Error ? err.message : String(err)}`, 'content-extractor');\n return { title: '', content: html, extracted: false };\n }\n\n try {\n const reader = new Readability(dom.window.document, {\n // Keep classes that downstream cleanup may need; Turndown ignores them.\n keepClasses: false,\n // Strip <script>/<style> already handled by Readability defaults.\n });\n const article = reader.parse();\n if (!article || !article.content) {\n return { title: article?.title ?? '', content: html, extracted: false };\n }\n return {\n title: article.title ?? '',\n content: article.content,\n byline: article.byline ?? undefined,\n extracted: true,\n };\n } catch (err) {\n mcpLog('warning', `Readability.parse failed: ${err instanceof Error ? err.message : String(err)}`, 'content-extractor');\n return { title: '', content: html, extracted: false };\n } finally {\n // jsdom retains references via global refs \u2014 close the window to free RSS.\n try { dom.window.close(); } catch { /* ignore */ }\n }\n}\n", "/**\n * Server logging utility.\n *\n * This server is HTTP-only, so logging must never depend on a transport-bound\n * MCP server instance. All logs flow through mcp-use's Logger, which writes to\n * stderr in Node and console in the browser.\n */\n\nimport { Logger } from 'mcp-use';\n\nexport type LogLevel = 'debug' | 'info' | 'warning' | 'error';\n\nfunction getLogger(name: string) {\n return Logger.get(name);\n}\n\n/**\n * Structured log helper backed by mcp-use's Logger.\n *\n * @param level - Log level.\n * @param message - Message to emit.\n * @param loggerName - Tool/component name for context (falls back to \"research-powerpack\").\n */\nexport function mcpLog(level: LogLevel, message: string, loggerName?: string): void {\n const logger = getLogger(loggerName ?? 'research-powerpack');\n\n switch (level) {\n case 'debug':\n logger.debug(message);\n break;\n case 'info':\n logger.info(message);\n break;\n case 'warning':\n logger.warn(message);\n break;\n case 'error':\n logger.error(message);\n break;\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAeA,SAAS,mBAAmB;AAC5B,SAAS,OAAO,sBAAsB;;;ACRtC,SAAS,cAAc;AAIvB,SAAS,UAAU,MAAc;AAC/B,SAAO,OAAO,IAAI,IAAI;AACxB;AASO,SAAS,OAAO,OAAiB,SAAiB,YAA2B;AAClF,QAAM,SAAS,UAAU,cAAc,oBAAoB;AAE3D,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,MAAM,OAAO;AACpB;AAAA,IACF,KAAK;AACH,aAAO,KAAK,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,KAAK,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,MAAM,OAAO;AACpB;AAAA,EACJ;AACF;;;ADPA,IAAM,wBAAwB;AAEvB,SAAS,uBAAuB,MAAc,KAAgC;AACnF,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,WAAO,EAAE,OAAO,IAAI,SAAS,QAAQ,IAAI,WAAW,MAAM;AAAA,EAC5D;AAEA,MAAI,KAAK,SAAS,uBAAuB;AACvC,WAAO,EAAE,OAAO,IAAI,SAAS,MAAM,WAAW,MAAM;AAAA,EACtD;AAGA,MAAI,CAAC,KAAK,SAAS,GAAG,GAAG;AACvB,WAAO,EAAE,OAAO,IAAI,SAAS,MAAM,WAAW,MAAM;AAAA,EACtD;AAIA,QAAM,iBAAiB,IAAI,eAAe;AAC1C,iBAAe,GAAG,SAAS,MAAM;AAAA,EAAC,CAAC;AACnC,iBAAe,GAAG,QAAQ,MAAM;AAAA,EAAC,CAAC;AAClC,iBAAe,GAAG,cAAc,MAAM;AAAA,EAAC,CAAC;AAExC,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,MAAM,MAAM;AAAA,MACpB,KAAK,OAAO,YAAY,KAAK,GAAG,IAAI,MAAM;AAAA,MAC1C;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,WAAO,WAAW,8BAA8B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,IAAI,mBAAmB;AACvH,WAAO,EAAE,OAAO,IAAI,SAAS,MAAM,WAAW,MAAM;AAAA,EACtD;AAEA,MAAI;AACF,UAAM,SAAS,IAAI,YAAY,IAAI,OAAO,UAAU;AAAA;AAAA,MAElD,aAAa;AAAA;AAAA,IAEf,CAAC;AACD,UAAM,UAAU,OAAO,MAAM;AAC7B,QAAI,CAAC,WAAW,CAAC,QAAQ,SAAS;AAChC,aAAO,EAAE,OAAO,SAAS,SAAS,IAAI,SAAS,MAAM,WAAW,MAAM;AAAA,IACxE;AACA,WAAO;AAAA,MACL,OAAO,QAAQ,SAAS;AAAA,MACxB,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ,UAAU;AAAA,MAC1B,WAAW;AAAA,IACb;AAAA,EACF,SAAS,KAAK;AACZ,WAAO,WAAW,6BAA6B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,IAAI,mBAAmB;AACtH,WAAO,EAAE,OAAO,IAAI,SAAS,MAAM,WAAW,MAAM;AAAA,EACtD,UAAE;AAEA,QAAI;AAAE,UAAI,OAAO,MAAM;AAAA,IAAG,QAAQ;AAAA,IAAe;AAAA,EACnD;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/utils/content-quality.ts
|
|
2
|
+
var MIN_MARKDOWN_CHARS = 800;
|
|
3
|
+
var MIN_MARKDOWN_WORDS = 120;
|
|
4
|
+
var BLOCK_PHRASES = [
|
|
4
5
|
"access denied",
|
|
5
6
|
"are you a human",
|
|
6
7
|
"captcha",
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/content-quality.ts"],
|
|
4
4
|
"sourcesContent": ["export interface ContentQuality {\n readonly weak: boolean;\n readonly reason: string;\n readonly charCount: number;\n readonly wordCount: number;\n readonly blockPhrase?: string;\n}\n\nconst MIN_MARKDOWN_CHARS = 800 as const;\nconst MIN_MARKDOWN_WORDS = 120 as const;\n\nconst BLOCK_PHRASES = [\n 'access denied',\n 'are you a human',\n 'captcha',\n 'checking your browser',\n 'enable javascript',\n 'just a moment',\n 'login required',\n 'please enable cookies',\n 'please verify you are human',\n 'sign in to continue',\n 'temporarily unavailable',\n] as const;\n\nfunction countWords(content: string): number {\n const matches = content.trim().match(/\\S+/g);\n return matches ? matches.length : 0;\n}\n\nfunction findBlockPhrase(content: string): string | undefined {\n const lower = content.toLowerCase();\n return BLOCK_PHRASES.find((phrase) => lower.includes(phrase));\n}\n\nexport function assessMarkdownQuality(content: string): ContentQuality {\n const trimmed = content.trim();\n const charCount = trimmed.length;\n const wordCount = countWords(trimmed);\n const blockPhrase = findBlockPhrase(trimmed);\n\n if (blockPhrase) {\n return {\n weak: true,\n reason: `blocked_or_interstitial:${blockPhrase}`,\n charCount,\n wordCount,\n blockPhrase,\n };\n }\n\n if (charCount < MIN_MARKDOWN_CHARS) {\n return {\n weak: true,\n reason: `too_few_chars:${charCount}<${MIN_MARKDOWN_CHARS}`,\n charCount,\n wordCount,\n };\n }\n\n if (wordCount < MIN_MARKDOWN_WORDS) {\n return {\n weak: true,\n reason: `too_few_words:${wordCount}<${MIN_MARKDOWN_WORDS}`,\n charCount,\n wordCount,\n };\n }\n\n return {\n weak: false,\n reason: 'ok',\n charCount,\n wordCount,\n };\n}\n"],
|
|
5
|
-
"mappings": "AAQA,
|
|
5
|
+
"mappings": ";AAQA,IAAM,qBAAqB;AAC3B,IAAM,qBAAqB;AAE3B,IAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,WAAW,SAAyB;AAC3C,QAAM,UAAU,QAAQ,KAAK,EAAE,MAAM,MAAM;AAC3C,SAAO,UAAU,QAAQ,SAAS;AACpC;AAEA,SAAS,gBAAgB,SAAqC;AAC5D,QAAM,QAAQ,QAAQ,YAAY;AAClC,SAAO,cAAc,KAAK,CAAC,WAAW,MAAM,SAAS,MAAM,CAAC;AAC9D;AAEO,SAAS,sBAAsB,SAAiC;AACrE,QAAM,UAAU,QAAQ,KAAK;AAC7B,QAAM,YAAY,QAAQ;AAC1B,QAAM,YAAY,WAAW,OAAO;AACpC,QAAM,cAAc,gBAAgB,OAAO;AAE3C,MAAI,aAAa;AACf,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ,2BAA2B,WAAW;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,oBAAoB;AAClC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ,iBAAiB,SAAS,IAAI,kBAAkB;AAAA,MACxD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,oBAAoB;AAClC,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ,iBAAiB,SAAS,IAAI,kBAAkB;AAAA,MACxD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/src/utils/errors.js
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// src/utils/logger.ts
|
|
2
|
+
import { Logger } from "mcp-use";
|
|
3
|
+
function getLogger(name) {
|
|
4
|
+
return Logger.get(name);
|
|
5
|
+
}
|
|
6
|
+
function mcpLog(level, message, loggerName) {
|
|
7
|
+
const logger = getLogger(loggerName ?? "research-powerpack");
|
|
8
|
+
switch (level) {
|
|
9
|
+
case "debug":
|
|
10
|
+
logger.debug(message);
|
|
11
|
+
break;
|
|
12
|
+
case "info":
|
|
13
|
+
logger.info(message);
|
|
14
|
+
break;
|
|
15
|
+
case "warning":
|
|
16
|
+
logger.warn(message);
|
|
17
|
+
break;
|
|
18
|
+
case "error":
|
|
19
|
+
logger.error(message);
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/utils/errors.ts
|
|
25
|
+
var ErrorCode = {
|
|
3
26
|
// Retryable errors
|
|
4
27
|
RATE_LIMITED: "RATE_LIMITED",
|
|
5
28
|
TIMEOUT: "TIMEOUT",
|
|
@@ -16,7 +39,7 @@ const ErrorCode = {
|
|
|
16
39
|
PARSE_ERROR: "PARSE_ERROR",
|
|
17
40
|
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
18
41
|
};
|
|
19
|
-
|
|
42
|
+
var DEFAULT_RETRY_OPTIONS = {
|
|
20
43
|
maxRetries: 3,
|
|
21
44
|
baseDelayMs: 1e3,
|
|
22
45
|
maxDelayMs: 3e4,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../src/utils/errors.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Robust error handling utilities for MCP server\n * Ensures the server NEVER crashes and always returns structured responses\n */\n\nimport { mcpLog } from './logger.js';\n\n// ============================================================================\n// Error Codes (MCP-compliant)\n// ============================================================================\n\nexport const ErrorCode = {\n // Retryable errors\n RATE_LIMITED: 'RATE_LIMITED',\n TIMEOUT: 'TIMEOUT',\n NETWORK_ERROR: 'NETWORK_ERROR',\n SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',\n \n // Non-retryable errors\n AUTH_ERROR: 'AUTH_ERROR',\n INVALID_INPUT: 'INVALID_INPUT',\n NOT_FOUND: 'NOT_FOUND',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n UNSUPPORTED_BINARY_CONTENT: 'UNSUPPORTED_BINARY_CONTENT',\n\n // Internal errors\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n PARSE_ERROR: 'PARSE_ERROR',\n UNKNOWN_ERROR: 'UNKNOWN_ERROR',\n} as const;\n\ntype ErrorCodeType = typeof ErrorCode[keyof typeof ErrorCode];\n\n// ============================================================================\n// Structured Error Types\n// ============================================================================\n\nexport interface StructuredError {\n code: ErrorCodeType;\n message: string;\n retryable: boolean;\n statusCode?: number;\n cause?: string;\n}\n\ninterface RetryOptions {\n readonly maxRetries: number;\n readonly baseDelayMs: number;\n readonly maxDelayMs: number;\n readonly retryableStatuses: readonly number[];\n readonly onRetry?: (attempt: number, error: StructuredError, delayMs: number) => void;\n}\n\nconst DEFAULT_RETRY_OPTIONS: RetryOptions = {\n maxRetries: 3,\n baseDelayMs: 1000,\n maxDelayMs: 30000,\n retryableStatuses: [408, 429, 500, 502, 503, 504, 510],\n};\n\n// ============================================================================\n// Error Classification \u2014 Atomic Classifiers\n// ============================================================================\n\n/**\n * Classify DOMException (AbortError from AbortController timeouts)\n */\nfunction classifyDomException(error: DOMException): StructuredError {\n if (error.name === 'AbortError') {\n return { code: ErrorCode.TIMEOUT, message: 'Request timed out', retryable: true };\n }\n return { code: ErrorCode.UNKNOWN_ERROR, message: error.message, retryable: false };\n}\n\n/**\n * Classify by Node.js error codes (ECONNREFUSED, ENOTFOUND, etc.)\n * Returns null if no matching code is found.\n */\nfunction classifyByErrorCode(error: { code?: string; message?: string }): StructuredError | null {\n const errCode = error.code;\n if (!errCode) return null;\n\n const networkErrorMessages: Record<string, string> = {\n ECONNREFUSED: 'Connection refused \u2014 service may be down',\n ECONNRESET: 'Connection was reset \u2014 please retry',\n ECONNABORTED: 'Connection aborted \u2014 please retry',\n ENOTFOUND: 'Service not reachable \u2014 check your network',\n EPIPE: 'Connection lost \u2014 please retry',\n EAI_AGAIN: 'DNS lookup failed \u2014 check your network',\n };\n\n if (errCode === 'ECONNREFUSED' || errCode === 'ENOTFOUND' || errCode === 'ECONNRESET') {\n return { code: ErrorCode.NETWORK_ERROR, message: networkErrorMessages[errCode] || 'Network connection failed', retryable: true, cause: error.message };\n }\n\n if (errCode === 'ECONNABORTED' || errCode === 'ETIMEDOUT') {\n return { code: ErrorCode.TIMEOUT, message: networkErrorMessages[errCode] || 'Request timed out', retryable: true, cause: error.message };\n }\n\n return null;\n}\n\n/**\n * Classify by HTTP status code extracted from error objects (axios-style, fetch-style, etc.)\n * Returns null if no status code is found.\n */\nfunction classifyByStatusCode(error: { status?: number; statusCode?: number; response?: { status?: number }; message?: string }): StructuredError | null {\n const status = error.response?.status || error.status || error.statusCode;\n if (!status) return null;\n return classifyHttpError(status, error.message || String(error));\n}\n\n/**\n * Classify by error message patterns (timeout, rate-limit, auth, parse errors)\n * Returns null if no pattern matches.\n */\nfunction classifyByMessage(message: string): StructuredError | null {\n const lower = message.toLowerCase();\n\n // Timeout patterns\n if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('aborterror')) {\n return { code: ErrorCode.TIMEOUT, message: 'Request timed out', retryable: true, cause: message };\n }\n\n // Rate-limit patterns\n if (lower.includes('rate limit') || lower.includes('too many requests')) {\n return { code: ErrorCode.RATE_LIMITED, message: 'Rate limit exceeded', retryable: true, cause: message };\n }\n\n // API key errors\n if (message.includes('API_KEY') || message.includes('api_key') || message.includes('Invalid API')) {\n return { code: ErrorCode.AUTH_ERROR, message: 'API key missing or invalid', retryable: false, cause: message };\n }\n\n // Parse errors\n if (message.includes('JSON') || message.includes('parse') || message.includes('Unexpected token')) {\n return { code: ErrorCode.PARSE_ERROR, message: 'Failed to parse response', retryable: false, cause: message };\n }\n\n return null;\n}\n\n/**\n * Catch-all fallback classification when no other classifier matches.\n */\nfunction classifyFallback(message: string, cause?: unknown): StructuredError {\n return {\n code: ErrorCode.UNKNOWN_ERROR,\n message,\n retryable: false,\n cause: cause ? String(cause) : undefined,\n };\n}\n\n// ============================================================================\n// Main Error Classification Pipeline\n// ============================================================================\n\n/**\n * Classify any error into a structured format.\n * NEVER throws \u2014 always returns a valid StructuredError.\n */\nexport function classifyError(error: unknown): StructuredError {\n if (error == null) {\n return { code: ErrorCode.UNKNOWN_ERROR, message: 'An unknown error occurred', retryable: false };\n }\n\n if (error instanceof DOMException) return classifyDomException(error);\n\n if (!isErrorLike(error)) {\n return { code: ErrorCode.UNKNOWN_ERROR, message: String(error), retryable: false };\n }\n\n return classifyByErrorCode(error)\n ?? classifyByStatusCode(error)\n ?? classifyByMessage(error.message ?? String(error))\n ?? classifyFallback(error.message ?? String(error), error.cause);\n}\n\n/**\n * Type guard for error-like objects with common error properties\n */\nfunction isErrorLike(value: unknown): value is {\n message?: string;\n response?: { status?: number; data?: unknown };\n status?: number;\n statusCode?: number;\n code?: string;\n name?: string;\n cause?: unknown;\n} {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Classify HTTP status codes into structured errors.\n * Exhaustive switch with grouped default handling for unknown ranges.\n */\nfunction classifyHttpError(status: number, message: string): StructuredError {\n switch (status) {\n case 400:\n return { code: ErrorCode.INVALID_INPUT, message: 'Bad request', retryable: false, statusCode: status };\n case 401:\n return { code: ErrorCode.AUTH_ERROR, message: 'Invalid API key', retryable: false, statusCode: status };\n case 403:\n return { code: ErrorCode.QUOTA_EXCEEDED, message: 'Access forbidden or quota exceeded', retryable: false, statusCode: status };\n case 404:\n return { code: ErrorCode.NOT_FOUND, message: 'Resource not found', retryable: false, statusCode: status };\n case 408:\n return { code: ErrorCode.TIMEOUT, message: 'Request timeout', retryable: true, statusCode: status };\n case 429:\n return { code: ErrorCode.RATE_LIMITED, message: 'Rate limit exceeded', retryable: true, statusCode: status };\n case 500:\n return { code: ErrorCode.INTERNAL_ERROR, message: 'Server error', retryable: true, statusCode: status };\n case 502:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Bad gateway', retryable: true, statusCode: status };\n case 503:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Service unavailable', retryable: true, statusCode: status };\n case 504:\n return { code: ErrorCode.TIMEOUT, message: 'Gateway timeout', retryable: true, statusCode: status };\n case 510:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Request canceled', retryable: true, statusCode: status };\n default:\n if (status >= 500) {\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: `Server error: ${status}`, retryable: true, statusCode: status };\n }\n if (status >= 400) {\n return { code: ErrorCode.INVALID_INPUT, message: `Client error: ${status}`, retryable: false, statusCode: status };\n }\n return { code: ErrorCode.UNKNOWN_ERROR, message: `HTTP ${status}: ${message}`, retryable: false, statusCode: status };\n }\n}\n\n// ============================================================================\n// Retry Logic with Exponential Backoff\n// ============================================================================\n\n/**\n * Calculate delay with exponential backoff and jitter\n */\nfunction calculateBackoff(attempt: number, options: RetryOptions): number {\n const exponentialDelay = options.baseDelayMs * Math.pow(2, attempt);\n const jitter = Math.random() * 0.3 * exponentialDelay; // 0-30% jitter\n return Math.min(exponentialDelay + jitter, options.maxDelayMs);\n}\n\n/**\n * Sleep utility that respects abort signals\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n function onAbort() {\n clearTimeout(timeout);\n reject(new DOMException('Aborted', 'AbortError'));\n }\n\n const timeout = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n // Re-check: signal may have aborted between initial check and listener registration\n if (signal?.aborted) {\n onAbort();\n }\n });\n}\n\n/**\n * Wrap a fetch call with timeout via AbortController\n */\nexport function fetchWithTimeout(\n url: string,\n options: RequestInit & { timeoutMs?: number } = {}\n): Promise<Response> {\n const { timeoutMs = 30000, signal: externalSignal, ...fetchOptions } = options;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n let onExternalAbort: (() => void) | undefined;\n if (externalSignal) {\n onExternalAbort = () => controller.abort();\n externalSignal.addEventListener('abort', onExternalAbort, { once: true });\n if (externalSignal.aborted) {\n controller.abort();\n }\n }\n\n return fetch(url, { ...fetchOptions, signal: controller.signal }).finally(() => {\n clearTimeout(timeoutId);\n if (externalSignal && onExternalAbort) {\n externalSignal.removeEventListener('abort', onExternalAbort);\n }\n });\n}\n\n// ============================================================================\n// Stability Wrappers \u2014 Network resilience for LLM API calls\n// ============================================================================\n\n/**\n * Wrap a non-streaming API call with activity-based timeout detection.\n * If the call hasn't completed within `stallMs`, abort and retry.\n * This catches \"stuck\" connections where TCP stays open but no data flows.\n *\n * @param fn - Async function that accepts an AbortSignal\n * @param stallMs - Max milliseconds to wait for the call to complete before considering it stuck\n * @param maxAttempts - Max retry attempts for stalled requests\n * @param label - Label for log messages\n * @returns The result of the function\n */\nexport async function withStallProtection<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n stallMs: number,\n maxAttempts: number = 2,\n label: string = 'request',\n): Promise<T> {\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const controller = new AbortController();\n let stallTimer: ReturnType<typeof setTimeout> | undefined;\n\n const stallPromise = new Promise<never>((_, reject) => {\n stallTimer = setTimeout(() => {\n controller.abort();\n reject(Object.assign(new Error(`Service temporarily unavailable \u2014 no response received (attempt ${attempt + 1}/${maxAttempts})`), {\n code: 'ESTALLED',\n retryable: attempt < maxAttempts - 1,\n }));\n }, stallMs);\n });\n\n let fnPromise: Promise<T> | undefined;\n try {\n fnPromise = fn(controller.signal);\n const result = await Promise.race([fnPromise, stallPromise]);\n clearTimeout(stallTimer);\n return result;\n } catch (err) {\n // Suppress unhandled rejection from the losing promise\n // (e.g. fnPromise rejects after stallPromise wins the race)\n fnPromise?.catch(() => {});\n clearTimeout(stallTimer);\n const isStall = err instanceof Error && (err as NodeJS.ErrnoException).code === 'ESTALLED';\n if (isStall && attempt < maxAttempts - 1) {\n const backoff = calculateBackoff(attempt, DEFAULT_RETRY_OPTIONS);\n mcpLog('warning', `${label} stalled, retrying in ${backoff}ms (attempt ${attempt + 1})`, 'stability');\n await sleep(backoff);\n continue;\n }\n throw err;\n }\n }\n // Should never reach here, but TypeScript needs it\n throw new Error(`${label} failed after ${maxAttempts} stall-protection attempts`);\n}\n"],
|
|
5
|
-
"mappings": "
|
|
3
|
+
"sources": ["../../../src/utils/logger.ts", "../../../src/utils/errors.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Server logging utility.\n *\n * This server is HTTP-only, so logging must never depend on a transport-bound\n * MCP server instance. All logs flow through mcp-use's Logger, which writes to\n * stderr in Node and console in the browser.\n */\n\nimport { Logger } from 'mcp-use';\n\nexport type LogLevel = 'debug' | 'info' | 'warning' | 'error';\n\nfunction getLogger(name: string) {\n return Logger.get(name);\n}\n\n/**\n * Structured log helper backed by mcp-use's Logger.\n *\n * @param level - Log level.\n * @param message - Message to emit.\n * @param loggerName - Tool/component name for context (falls back to \"research-powerpack\").\n */\nexport function mcpLog(level: LogLevel, message: string, loggerName?: string): void {\n const logger = getLogger(loggerName ?? 'research-powerpack');\n\n switch (level) {\n case 'debug':\n logger.debug(message);\n break;\n case 'info':\n logger.info(message);\n break;\n case 'warning':\n logger.warn(message);\n break;\n case 'error':\n logger.error(message);\n break;\n }\n}\n", "/**\n * Robust error handling utilities for MCP server\n * Ensures the server NEVER crashes and always returns structured responses\n */\n\nimport { mcpLog } from './logger.js';\n\n// ============================================================================\n// Error Codes (MCP-compliant)\n// ============================================================================\n\nexport const ErrorCode = {\n // Retryable errors\n RATE_LIMITED: 'RATE_LIMITED',\n TIMEOUT: 'TIMEOUT',\n NETWORK_ERROR: 'NETWORK_ERROR',\n SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',\n \n // Non-retryable errors\n AUTH_ERROR: 'AUTH_ERROR',\n INVALID_INPUT: 'INVALID_INPUT',\n NOT_FOUND: 'NOT_FOUND',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n UNSUPPORTED_BINARY_CONTENT: 'UNSUPPORTED_BINARY_CONTENT',\n\n // Internal errors\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n PARSE_ERROR: 'PARSE_ERROR',\n UNKNOWN_ERROR: 'UNKNOWN_ERROR',\n} as const;\n\ntype ErrorCodeType = typeof ErrorCode[keyof typeof ErrorCode];\n\n// ============================================================================\n// Structured Error Types\n// ============================================================================\n\nexport interface StructuredError {\n code: ErrorCodeType;\n message: string;\n retryable: boolean;\n statusCode?: number;\n cause?: string;\n}\n\ninterface RetryOptions {\n readonly maxRetries: number;\n readonly baseDelayMs: number;\n readonly maxDelayMs: number;\n readonly retryableStatuses: readonly number[];\n readonly onRetry?: (attempt: number, error: StructuredError, delayMs: number) => void;\n}\n\nconst DEFAULT_RETRY_OPTIONS: RetryOptions = {\n maxRetries: 3,\n baseDelayMs: 1000,\n maxDelayMs: 30000,\n retryableStatuses: [408, 429, 500, 502, 503, 504, 510],\n};\n\n// ============================================================================\n// Error Classification \u2014 Atomic Classifiers\n// ============================================================================\n\n/**\n * Classify DOMException (AbortError from AbortController timeouts)\n */\nfunction classifyDomException(error: DOMException): StructuredError {\n if (error.name === 'AbortError') {\n return { code: ErrorCode.TIMEOUT, message: 'Request timed out', retryable: true };\n }\n return { code: ErrorCode.UNKNOWN_ERROR, message: error.message, retryable: false };\n}\n\n/**\n * Classify by Node.js error codes (ECONNREFUSED, ENOTFOUND, etc.)\n * Returns null if no matching code is found.\n */\nfunction classifyByErrorCode(error: { code?: string; message?: string }): StructuredError | null {\n const errCode = error.code;\n if (!errCode) return null;\n\n const networkErrorMessages: Record<string, string> = {\n ECONNREFUSED: 'Connection refused \u2014 service may be down',\n ECONNRESET: 'Connection was reset \u2014 please retry',\n ECONNABORTED: 'Connection aborted \u2014 please retry',\n ENOTFOUND: 'Service not reachable \u2014 check your network',\n EPIPE: 'Connection lost \u2014 please retry',\n EAI_AGAIN: 'DNS lookup failed \u2014 check your network',\n };\n\n if (errCode === 'ECONNREFUSED' || errCode === 'ENOTFOUND' || errCode === 'ECONNRESET') {\n return { code: ErrorCode.NETWORK_ERROR, message: networkErrorMessages[errCode] || 'Network connection failed', retryable: true, cause: error.message };\n }\n\n if (errCode === 'ECONNABORTED' || errCode === 'ETIMEDOUT') {\n return { code: ErrorCode.TIMEOUT, message: networkErrorMessages[errCode] || 'Request timed out', retryable: true, cause: error.message };\n }\n\n return null;\n}\n\n/**\n * Classify by HTTP status code extracted from error objects (axios-style, fetch-style, etc.)\n * Returns null if no status code is found.\n */\nfunction classifyByStatusCode(error: { status?: number; statusCode?: number; response?: { status?: number }; message?: string }): StructuredError | null {\n const status = error.response?.status || error.status || error.statusCode;\n if (!status) return null;\n return classifyHttpError(status, error.message || String(error));\n}\n\n/**\n * Classify by error message patterns (timeout, rate-limit, auth, parse errors)\n * Returns null if no pattern matches.\n */\nfunction classifyByMessage(message: string): StructuredError | null {\n const lower = message.toLowerCase();\n\n // Timeout patterns\n if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('aborterror')) {\n return { code: ErrorCode.TIMEOUT, message: 'Request timed out', retryable: true, cause: message };\n }\n\n // Rate-limit patterns\n if (lower.includes('rate limit') || lower.includes('too many requests')) {\n return { code: ErrorCode.RATE_LIMITED, message: 'Rate limit exceeded', retryable: true, cause: message };\n }\n\n // API key errors\n if (message.includes('API_KEY') || message.includes('api_key') || message.includes('Invalid API')) {\n return { code: ErrorCode.AUTH_ERROR, message: 'API key missing or invalid', retryable: false, cause: message };\n }\n\n // Parse errors\n if (message.includes('JSON') || message.includes('parse') || message.includes('Unexpected token')) {\n return { code: ErrorCode.PARSE_ERROR, message: 'Failed to parse response', retryable: false, cause: message };\n }\n\n return null;\n}\n\n/**\n * Catch-all fallback classification when no other classifier matches.\n */\nfunction classifyFallback(message: string, cause?: unknown): StructuredError {\n return {\n code: ErrorCode.UNKNOWN_ERROR,\n message,\n retryable: false,\n cause: cause ? String(cause) : undefined,\n };\n}\n\n// ============================================================================\n// Main Error Classification Pipeline\n// ============================================================================\n\n/**\n * Classify any error into a structured format.\n * NEVER throws \u2014 always returns a valid StructuredError.\n */\nexport function classifyError(error: unknown): StructuredError {\n if (error == null) {\n return { code: ErrorCode.UNKNOWN_ERROR, message: 'An unknown error occurred', retryable: false };\n }\n\n if (error instanceof DOMException) return classifyDomException(error);\n\n if (!isErrorLike(error)) {\n return { code: ErrorCode.UNKNOWN_ERROR, message: String(error), retryable: false };\n }\n\n return classifyByErrorCode(error)\n ?? classifyByStatusCode(error)\n ?? classifyByMessage(error.message ?? String(error))\n ?? classifyFallback(error.message ?? String(error), error.cause);\n}\n\n/**\n * Type guard for error-like objects with common error properties\n */\nfunction isErrorLike(value: unknown): value is {\n message?: string;\n response?: { status?: number; data?: unknown };\n status?: number;\n statusCode?: number;\n code?: string;\n name?: string;\n cause?: unknown;\n} {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Classify HTTP status codes into structured errors.\n * Exhaustive switch with grouped default handling for unknown ranges.\n */\nfunction classifyHttpError(status: number, message: string): StructuredError {\n switch (status) {\n case 400:\n return { code: ErrorCode.INVALID_INPUT, message: 'Bad request', retryable: false, statusCode: status };\n case 401:\n return { code: ErrorCode.AUTH_ERROR, message: 'Invalid API key', retryable: false, statusCode: status };\n case 403:\n return { code: ErrorCode.QUOTA_EXCEEDED, message: 'Access forbidden or quota exceeded', retryable: false, statusCode: status };\n case 404:\n return { code: ErrorCode.NOT_FOUND, message: 'Resource not found', retryable: false, statusCode: status };\n case 408:\n return { code: ErrorCode.TIMEOUT, message: 'Request timeout', retryable: true, statusCode: status };\n case 429:\n return { code: ErrorCode.RATE_LIMITED, message: 'Rate limit exceeded', retryable: true, statusCode: status };\n case 500:\n return { code: ErrorCode.INTERNAL_ERROR, message: 'Server error', retryable: true, statusCode: status };\n case 502:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Bad gateway', retryable: true, statusCode: status };\n case 503:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Service unavailable', retryable: true, statusCode: status };\n case 504:\n return { code: ErrorCode.TIMEOUT, message: 'Gateway timeout', retryable: true, statusCode: status };\n case 510:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Request canceled', retryable: true, statusCode: status };\n default:\n if (status >= 500) {\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: `Server error: ${status}`, retryable: true, statusCode: status };\n }\n if (status >= 400) {\n return { code: ErrorCode.INVALID_INPUT, message: `Client error: ${status}`, retryable: false, statusCode: status };\n }\n return { code: ErrorCode.UNKNOWN_ERROR, message: `HTTP ${status}: ${message}`, retryable: false, statusCode: status };\n }\n}\n\n// ============================================================================\n// Retry Logic with Exponential Backoff\n// ============================================================================\n\n/**\n * Calculate delay with exponential backoff and jitter\n */\nfunction calculateBackoff(attempt: number, options: RetryOptions): number {\n const exponentialDelay = options.baseDelayMs * Math.pow(2, attempt);\n const jitter = Math.random() * 0.3 * exponentialDelay; // 0-30% jitter\n return Math.min(exponentialDelay + jitter, options.maxDelayMs);\n}\n\n/**\n * Sleep utility that respects abort signals\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n function onAbort() {\n clearTimeout(timeout);\n reject(new DOMException('Aborted', 'AbortError'));\n }\n\n const timeout = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n // Re-check: signal may have aborted between initial check and listener registration\n if (signal?.aborted) {\n onAbort();\n }\n });\n}\n\n/**\n * Wrap a fetch call with timeout via AbortController\n */\nexport function fetchWithTimeout(\n url: string,\n options: RequestInit & { timeoutMs?: number } = {}\n): Promise<Response> {\n const { timeoutMs = 30000, signal: externalSignal, ...fetchOptions } = options;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n let onExternalAbort: (() => void) | undefined;\n if (externalSignal) {\n onExternalAbort = () => controller.abort();\n externalSignal.addEventListener('abort', onExternalAbort, { once: true });\n if (externalSignal.aborted) {\n controller.abort();\n }\n }\n\n return fetch(url, { ...fetchOptions, signal: controller.signal }).finally(() => {\n clearTimeout(timeoutId);\n if (externalSignal && onExternalAbort) {\n externalSignal.removeEventListener('abort', onExternalAbort);\n }\n });\n}\n\n// ============================================================================\n// Stability Wrappers \u2014 Network resilience for LLM API calls\n// ============================================================================\n\n/**\n * Wrap a non-streaming API call with activity-based timeout detection.\n * If the call hasn't completed within `stallMs`, abort and retry.\n * This catches \"stuck\" connections where TCP stays open but no data flows.\n *\n * @param fn - Async function that accepts an AbortSignal\n * @param stallMs - Max milliseconds to wait for the call to complete before considering it stuck\n * @param maxAttempts - Max retry attempts for stalled requests\n * @param label - Label for log messages\n * @returns The result of the function\n */\nexport async function withStallProtection<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n stallMs: number,\n maxAttempts: number = 2,\n label: string = 'request',\n): Promise<T> {\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const controller = new AbortController();\n let stallTimer: ReturnType<typeof setTimeout> | undefined;\n\n const stallPromise = new Promise<never>((_, reject) => {\n stallTimer = setTimeout(() => {\n controller.abort();\n reject(Object.assign(new Error(`Service temporarily unavailable \u2014 no response received (attempt ${attempt + 1}/${maxAttempts})`), {\n code: 'ESTALLED',\n retryable: attempt < maxAttempts - 1,\n }));\n }, stallMs);\n });\n\n let fnPromise: Promise<T> | undefined;\n try {\n fnPromise = fn(controller.signal);\n const result = await Promise.race([fnPromise, stallPromise]);\n clearTimeout(stallTimer);\n return result;\n } catch (err) {\n // Suppress unhandled rejection from the losing promise\n // (e.g. fnPromise rejects after stallPromise wins the race)\n fnPromise?.catch(() => {});\n clearTimeout(stallTimer);\n const isStall = err instanceof Error && (err as NodeJS.ErrnoException).code === 'ESTALLED';\n if (isStall && attempt < maxAttempts - 1) {\n const backoff = calculateBackoff(attempt, DEFAULT_RETRY_OPTIONS);\n mcpLog('warning', `${label} stalled, retrying in ${backoff}ms (attempt ${attempt + 1})`, 'stability');\n await sleep(backoff);\n continue;\n }\n throw err;\n }\n }\n // Should never reach here, but TypeScript needs it\n throw new Error(`${label} failed after ${maxAttempts} stall-protection attempts`);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/src/utils/logger.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/logger.ts"],
|
|
4
4
|
"sourcesContent": ["/**\n * Server logging utility.\n *\n * This server is HTTP-only, so logging must never depend on a transport-bound\n * MCP server instance. All logs flow through mcp-use's Logger, which writes to\n * stderr in Node and console in the browser.\n */\n\nimport { Logger } from 'mcp-use';\n\nexport type LogLevel = 'debug' | 'info' | 'warning' | 'error';\n\nfunction getLogger(name: string) {\n return Logger.get(name);\n}\n\n/**\n * Structured log helper backed by mcp-use's Logger.\n *\n * @param level - Log level.\n * @param message - Message to emit.\n * @param loggerName - Tool/component name for context (falls back to \"research-powerpack\").\n */\nexport function mcpLog(level: LogLevel, message: string, loggerName?: string): void {\n const logger = getLogger(loggerName ?? 'research-powerpack');\n\n switch (level) {\n case 'debug':\n logger.debug(message);\n break;\n case 'info':\n logger.info(message);\n break;\n case 'warning':\n logger.warn(message);\n break;\n case 'error':\n logger.error(message);\n break;\n }\n}\n"],
|
|
5
|
-
"mappings": "AAQA,SAAS,cAAc;AAIvB,SAAS,UAAU,MAAc;AAC/B,SAAO,OAAO,IAAI,IAAI;AACxB;AASO,SAAS,OAAO,OAAiB,SAAiB,YAA2B;AAClF,QAAM,SAAS,UAAU,cAAc,oBAAoB;AAE3D,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,MAAM,OAAO;AACpB;AAAA,IACF,KAAK;AACH,aAAO,KAAK,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,KAAK,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,MAAM,OAAO;AACpB;AAAA,EACJ;AACF;",
|
|
5
|
+
"mappings": ";AAQA,SAAS,cAAc;AAIvB,SAAS,UAAU,MAAc;AAC/B,SAAO,OAAO,IAAI,IAAI;AACxB;AASO,SAAS,OAAO,OAAiB,SAAiB,YAA2B;AAClF,QAAM,SAAS,UAAU,cAAc,oBAAoB;AAE3D,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,MAAM,OAAO;AACpB;AAAA,IACF,KAAK;AACH,aAAO,KAAK,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,KAAK,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,MAAM,OAAO;AACpB;AAAA,EACJ;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/markdown-formatter.ts"],
|
|
4
4
|
"sourcesContent": ["/**\n * Markdown formatting utilities\n */\n\nexport function removeMetaTags(content: string): string {\n if (!content || typeof content !== 'string') {\n return content;\n }\n\n const lines = content.split('\\n');\n const filteredLines = lines.filter(line => {\n const trimmed = line.trim();\n return !trimmed.startsWith('- Meta:') && !trimmed.startsWith('Meta:');\n });\n\n return filteredLines.join('\\n');\n}\n"],
|
|
5
|
-
"mappings": "AAIO,SAAS,eAAe,SAAyB;AACtD,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,gBAAgB,MAAM,OAAO,UAAQ;AACzC,UAAM,UAAU,KAAK,KAAK;AAC1B,WAAO,CAAC,QAAQ,WAAW,SAAS,KAAK,CAAC,QAAQ,WAAW,OAAO;AAAA,EACtE,CAAC;AAED,SAAO,cAAc,KAAK,IAAI;AAChC;",
|
|
5
|
+
"mappings": ";AAIO,SAAS,eAAe,SAAyB;AACtD,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,gBAAgB,MAAM,OAAO,UAAQ;AACzC,UAAM,UAAU,KAAK,KAAK;AAC1B,WAAO,CAAC,QAAQ,WAAW,SAAS,KAAK,CAAC,QAAQ,WAAW,OAAO;AAAA,EACtE,CAAC;AAED,SAAO,cAAc,KAAK,IAAI;AAChC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// src/utils/query-relax.ts
|
|
2
|
+
var QUOTED_PHRASE_RE = /"([^"]*)"/g;
|
|
3
|
+
var HAS_BOOLEAN_GROUPING = /\b(?:OR|AND)\b|[()]/;
|
|
4
|
+
var OPERATOR_CHAR_IN_PHRASE = /[():[\]]/;
|
|
5
|
+
var OPERATOR_CHAR_GLOBAL = /[():[\]]/g;
|
|
6
|
+
var PATH_LIKE_IN_PHRASE = /\/|~\/|^@|\.{3,}/;
|
|
7
|
+
var URI_SCHEME_IN_PHRASE = /^[a-z][a-z0-9+.-]*:/i;
|
|
8
|
+
var HAS_SITE_OPERATOR = /\bsite:\S+/i;
|
|
9
|
+
var SITE_OPERATOR_GLOBAL = /\bsite:\S+/gi;
|
|
9
10
|
function renderSeg(seg) {
|
|
10
11
|
return seg.type === "raw" ? seg.text : seg.quoted ? `"${seg.text}"` : seg.text;
|
|
11
12
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/query-relax.ts"],
|
|
4
4
|
"sourcesContent": ["/**\n * Query relaxation for search fan-out.\n *\n * Two-phase rewriter that addresses systematic Google query failures observed\n * in the Serper log:\n * - 3+ back-to-back quoted phrases get implicit-AND'd by Google \u2192 no page\n * contains all rare tokens \u2192 0 results.\n * - Quoted phrases with operator chars (parens, colons, brackets) \u2014 Google\n * strips them inside quotes, so the quotes only impose a pointless AND.\n * - Quoted paths/URLs (`/`, `~/`, leading `@`, 3+ dots) \u2014 same reason.\n * - Tiny `site:` corpus that returns 0 \u2014 drop site filter on retry.\n * - Verbatim long phrases that don't appear word-for-word \u2014 strip quotes on retry.\n *\n * Phase A (`normalizeQueryForDispatch`) is always-on, deterministic, lossless.\n * Phase B (`relaxQueryForRetry`) is aggressive; only invoke when Phase A's\n * dispatched form returned zero results.\n */\n\nconst QUOTED_PHRASE_RE = /\"([^\"]*)\"/g;\nconst HAS_BOOLEAN_GROUPING = /\\b(?:OR|AND)\\b|[()]/;\nconst OPERATOR_CHAR_IN_PHRASE = /[():[\\]]/;\nconst OPERATOR_CHAR_GLOBAL = /[():[\\]]/g;\nconst PATH_LIKE_IN_PHRASE = /\\/|~\\/|^@|\\.{3,}/;\nconst URI_SCHEME_IN_PHRASE = /^[a-z][a-z0-9+.-]*:/i;\nconst HAS_SITE_OPERATOR = /\\bsite:\\S+/i;\nconst SITE_OPERATOR_GLOBAL = /\\bsite:\\S+/gi;\n\nexport interface RewriteResult {\n rewritten: string;\n changed: boolean;\n rules: string[];\n}\n\ninterface PhraseSeg { type: 'phrase'; text: string; quoted: boolean }\ninterface RawSeg { type: 'raw'; text: string }\ntype Seg = PhraseSeg | RawSeg;\n\nfunction renderSeg(seg: Seg): string {\n return seg.type === 'raw' ? seg.text : seg.quoted ? `\"${seg.text}\"` : seg.text;\n}\n\nfunction tokenize(query: string): Seg[] {\n const segs: Seg[] = [];\n let last = 0;\n for (const m of query.matchAll(QUOTED_PHRASE_RE)) {\n const start = m.index ?? 0;\n const end = start + m[0].length;\n if (start > last) segs.push({ type: 'raw', text: query.slice(last, start) });\n segs.push({ type: 'phrase', text: m[1] ?? '', quoted: true });\n last = end;\n }\n if (last < query.length) segs.push({ type: 'raw', text: query.slice(last) });\n return segs;\n}\n\nfunction rebuild(segs: Seg[]): string {\n return segs\n .map(renderSeg)\n .join('')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction onlyWhitespaceBetween(segs: Seg[], fromIndex: number, toIndex: number): boolean {\n for (let i = fromIndex + 1; i < toIndex; i += 1) {\n const seg = segs[i];\n if (seg === undefined) continue;\n if (seg.type !== 'raw' || seg.text.trim() !== '') {\n return false;\n }\n }\n return true;\n}\n\nfunction buildAnchoredOrGroup(segs: Seg[], quotedIndices: number[]): string | null {\n const groupStart = quotedIndices[1];\n const groupEnd = quotedIndices[quotedIndices.length - 1];\n if (groupStart === undefined || groupEnd === undefined) {\n return null;\n }\n\n for (let i = 2; i < quotedIndices.length; i += 1) {\n const previous = quotedIndices[i - 1];\n const current = quotedIndices[i];\n if (\n previous === undefined\n || current === undefined\n || !onlyWhitespaceBetween(segs, previous, current)\n ) {\n return null;\n }\n }\n\n const groupIndices = new Set(quotedIndices.slice(1));\n const parts: string[] = [];\n for (let i = 0; i < segs.length; i += 1) {\n const seg = segs[i];\n if (seg === undefined) continue;\n\n if (seg.type === 'raw') {\n if (i > groupStart && i < groupEnd) {\n continue;\n }\n parts.push(seg.text);\n continue;\n }\n\n if (groupIndices.has(i)) {\n parts.push(i === groupStart ? ' (' : ' OR ');\n parts.push(renderSeg(seg));\n if (i === groupEnd) {\n parts.push(')');\n }\n continue;\n }\n\n parts.push(renderSeg(seg));\n }\n\n return parts.join('').replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Phase A \u2014 pre-dispatch normalizer. Runs on every query before it leaves the\n * server. Three rules apply in order; each only fires when the original query\n * was statistically going to mis-handle in Google anyway.\n *\n * Rule A1: phrase contains `(`, `)`, `:`, `[`, `]` \u2192 drop quotes; replace those\n * chars with space (Google strips them inside quotes anyway).\n * Rule A2: phrase contains a URI scheme, `/`, `~/`, leading `@`, or 3+ dots\n * \u2192 drop quotes only (Google tokenizes path separators in or out of quotes).\n * Rule A3: \u22653 still-quoted phrases AND no existing `OR`/`AND`/parens \u2192 keep\n * first phrase as anchor; group consecutive subsequent phrases with\n * ` OR `. Bare words and `site:`/`filetype:` operators are preserved\n * verbatim.\n */\nexport function normalizeQueryForDispatch(query: string): RewriteResult {\n const original = query.trim().replace(/\\s+/g, ' ');\n if (!original) {\n return { rewritten: original, changed: false, rules: [] };\n }\n const segs = tokenize(query);\n const rules: string[] = [];\n\n // A1 \u2014 operator chars inside quotes are pointless AND constraints.\n for (const s of segs) {\n if (\n s.type === 'phrase'\n && s.quoted\n && OPERATOR_CHAR_IN_PHRASE.test(s.text)\n && !URI_SCHEME_IN_PHRASE.test(s.text)\n ) {\n s.quoted = false;\n s.text = s.text.replace(OPERATOR_CHAR_GLOBAL, ' ');\n if (!rules.includes('A1')) rules.push('A1');\n }\n }\n\n // A2 \u2014 path/URL inside quotes; quoting doesn't help recall.\n for (const s of segs) {\n if (\n s.type === 'phrase'\n && s.quoted\n && (URI_SCHEME_IN_PHRASE.test(s.text) || PATH_LIKE_IN_PHRASE.test(s.text))\n ) {\n s.quoted = false;\n if (!rules.includes('A2')) rules.push('A2');\n }\n }\n\n // A3 \u2014 phrase-AND collapse. Trigger requires \u22653 phrases that survive A1+A2,\n // and no existing boolean grouping in the raw (non-quoted) part of the query.\n const stillQuotedIndices: number[] = [];\n for (let i = 0; i < segs.length; i += 1) {\n const seg = segs[i];\n if (seg?.type === 'phrase' && seg.quoted) {\n stillQuotedIndices.push(i);\n }\n }\n const rawJoined = segs\n .filter((s): s is RawSeg => s.type === 'raw')\n .map((s) => s.text)\n .join(' ');\n\n if (stillQuotedIndices.length >= 3 && !HAS_BOOLEAN_GROUPING.test(rawJoined)) {\n const grouped = buildAnchoredOrGroup(segs, stillQuotedIndices);\n if (grouped !== null) {\n rules.push('A3');\n return { rewritten: grouped, changed: grouped !== original, rules };\n }\n }\n\n const rewritten = rebuild(segs);\n return { rewritten, changed: rewritten !== original, rules };\n}\n\n/**\n * Phase B \u2014 on-empty retry. Only invoked for queries whose Phase-A dispatched\n * form returned zero results from Serper. Strips ALL remaining quotes and the\n * `site:` operator (if present). Caller should skip the retry when the\n * relaxed form equals the dispatched form.\n *\n * Rule B1: strip every `\"` (turns each phrase into a bag of words; Google\n * ranks by token co-occurrence instead of forcing verbatim match).\n * Rule B2: drop `site:operator` (broadens to open web; catches \"tiny corpus\"\n * and \"site path doesn't exist\" cases at once).\n */\nexport function relaxQueryForRetry(\n query: string,\n options: { dropSite?: boolean } = {},\n): RewriteResult {\n const original = query.trim().replace(/\\s+/g, ' ');\n if (!original) {\n return { rewritten: original, changed: false, rules: [] };\n }\n const dropSite = options.dropSite ?? true;\n const rules: string[] = [];\n let result = query;\n\n if (result.includes('\"')) {\n result = result.replace(/\"/g, '');\n rules.push('B1');\n }\n\n if (dropSite && HAS_SITE_OPERATOR.test(result)) {\n result = result.replace(SITE_OPERATOR_GLOBAL, ' ');\n rules.push('B2');\n }\n\n result = result.replace(/\\s+/g, ' ').trim();\n return { rewritten: result, changed: result !== original, rules };\n}\n"],
|
|
5
|
-
"mappings": "AAkBA,
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/response.ts"],
|
|
4
4
|
"sourcesContent": ["/**\n * MCP Response Formatters\n */\n\n/** Duration thresholds in milliseconds */\nconst SECONDS_MS = 1_000 as const;\nconst MINUTES_MS = 60_000 as const;\n\n// ============================================================================\n// Success Response Formatter\n// ============================================================================\n\nexport interface SuccessOptions {\n /** Title/header for the response */\n readonly title: string;\n /** Summary section (70% of content) */\n readonly summary: string;\n /** Optional data section (20% of content) */\n readonly data?: string;\n /** Optional next steps (10% of content) */\n readonly nextSteps?: string[];\n /** Optional metadata footer */\n readonly metadata?: Record<string, string | number>;\n}\n\n/**\n * Format a successful response using 70/20/10 pattern\n */\nexport function formatSuccess(opts: SuccessOptions): string {\n const parts: string[] = [];\n\n // Title\n parts.push(`\u2713 ${opts.title}`);\n parts.push('');\n\n // Summary (70%)\n parts.push(opts.summary);\n\n // Data section (20%)\n if (opts.data) {\n parts.push('');\n parts.push('---');\n parts.push(opts.data);\n }\n\n // Next steps (10%)\n if (opts.nextSteps?.length) {\n parts.push('');\n parts.push('---');\n parts.push('**Next Steps:**');\n opts.nextSteps.forEach(step => parts.push(`\u2192 ${step}`));\n }\n\n // Metadata footer\n if (opts.metadata && Object.keys(opts.metadata).length > 0) {\n parts.push('');\n parts.push('---');\n const metaStr = Object.entries(opts.metadata)\n .map(([k, v]) => `${k}: ${v}`)\n .join(' | ');\n parts.push(`*${metaStr}*`);\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Error Response Formatter\n// ============================================================================\n\nexport interface ErrorOptions {\n /** Error code (e.g., RATE_LIMITED, TIMEOUT) */\n readonly code: string;\n /** Human-readable error message */\n readonly message: string;\n /** Is this error retryable? */\n readonly retryable?: boolean;\n /** How to fix the error */\n readonly howToFix?: string[];\n /** Alternative actions */\n readonly alternatives?: string[];\n /** Tool name for context */\n readonly toolName?: string;\n}\n\n/**\n * Format an error response with recovery guidance\n * Designed to keep agents moving \u2014 every error includes actionable alternatives\n */\nexport function formatError(opts: ErrorOptions): string {\n const parts: string[] = [];\n\n // Error header\n const prefix = opts.toolName ? `[${opts.toolName}] ` : '';\n parts.push(`\u274C ${prefix}${opts.code}: ${opts.message}`);\n\n // Retryable hint\n if (opts.retryable) {\n parts.push('*Retryable.*');\n }\n\n // How to fix\n if (opts.howToFix?.length) {\n parts.push('');\n parts.push('**How to Fix:**');\n opts.howToFix.forEach((step, i) => parts.push(`${i + 1}. ${step}`));\n }\n\n // Alternatives\n if (opts.alternatives?.length) {\n parts.push('');\n parts.push('**Alternatives:**');\n opts.alternatives.forEach((alt, i) => parts.push(`${i + 1}. ${alt}`));\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Batch Header Formatter\n// ============================================================================\n\nexport interface BatchHeaderOptions {\n /** Batch operation title */\n readonly title: string;\n /** Total items attempted */\n readonly totalItems: number;\n /** Successfully processed count */\n readonly successful: number;\n /** Failed count */\n readonly failed: number;\n /** Optional tokens per item */\n readonly tokensPerItem?: number;\n /** Optional batch count */\n readonly batches?: number;\n /** Extra stats to include */\n readonly extras?: Record<string, string | number>;\n}\n\n/**\n * Format a batch operation header with stats\n */\nexport function formatBatchHeader(opts: BatchHeaderOptions): string {\n const parts: string[] = [];\n\n // Title with emoji based on success rate\n const successRate = opts.totalItems > 0 ? opts.successful / opts.totalItems : 0;\n const emoji = successRate === 1 ? '\u2713' : successRate >= 0.5 ? '\u26A0\uFE0F' : '\u274C';\n parts.push(`${emoji} ${opts.title}`);\n parts.push('');\n\n // Stats\n parts.push(`\u2022 Total: ${opts.totalItems}`);\n parts.push(`\u2022 Successful: ${opts.successful}`);\n if (opts.failed > 0) {\n parts.push(`\u2022 Failed: ${opts.failed}`);\n }\n if (opts.tokensPerItem) {\n parts.push(`\u2022 Tokens/item: ~${opts.tokensPerItem.toLocaleString()}`);\n }\n if (opts.batches) {\n parts.push(`\u2022 Batches: ${opts.batches}`);\n }\n\n // Extra stats\n if (opts.extras) {\n Object.entries(opts.extras).forEach(([key, val]) => {\n parts.push(`\u2022 ${key}: ${val}`);\n });\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Duration Formatter\n// ============================================================================\n\n/**\n * Format duration in human-readable form\n */\nexport function formatDuration(ms: number): string {\n if (ms < SECONDS_MS) return `${ms}ms`;\n if (ms < MINUTES_MS) return `${(ms / SECONDS_MS).toFixed(1)}s`;\n return `${(ms / MINUTES_MS).toFixed(1)}m`;\n}\n\n"],
|
|
5
|
-
"mappings": "AAKA,
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/src/utils/retry.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/utils/retry.ts
|
|
2
|
+
var JITTER_FACTOR = 0.3;
|
|
3
|
+
var EXPONENTIAL_BASE = 2;
|
|
4
|
+
var DEFAULT_BASE_DELAY_MS = 1e3;
|
|
5
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
5
6
|
function calculateBackoff(attempt, baseDelayMs = DEFAULT_BASE_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS) {
|
|
6
7
|
const exponentialDelay = baseDelayMs * Math.pow(EXPONENTIAL_BASE, attempt);
|
|
7
8
|
const jitter = JITTER_FACTOR * exponentialDelay * Math.random();
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/retry.ts"],
|
|
4
4
|
"sourcesContent": ["/**\n * Shared retry and backoff utilities\n */\n\n/** Jitter factor to prevent thundering herd */\nconst JITTER_FACTOR = 0.3 as const;\n\n/** Exponential base for backoff calculation */\nconst EXPONENTIAL_BASE = 2 as const;\n\n/** Default base delay for exponential backoff (ms) */\nconst DEFAULT_BASE_DELAY_MS = 1_000 as const;\n\n/** Default maximum backoff delay cap (ms) */\nconst DEFAULT_MAX_DELAY_MS = 30_000 as const;\n\n/**\n * Calculate exponential backoff delay with jitter.\n * Formula: min(base * 2^attempt + random_jitter, maxDelay)\n *\n * @param attempt - Zero-based retry attempt number\n * @param baseDelayMs - Base delay in milliseconds (default: 1000)\n * @param maxDelayMs - Maximum delay cap in milliseconds (default: 30000)\n * @returns Delay in milliseconds with jitter applied\n */\nexport function calculateBackoff(\n attempt: number,\n baseDelayMs: number = DEFAULT_BASE_DELAY_MS,\n maxDelayMs: number = DEFAULT_MAX_DELAY_MS,\n): number {\n const exponentialDelay = baseDelayMs * Math.pow(EXPONENTIAL_BASE, attempt);\n const jitter = JITTER_FACTOR * exponentialDelay * Math.random();\n return Math.min(exponentialDelay + jitter, maxDelayMs);\n}\n"],
|
|
5
|
-
"mappings": "AAKA,
|
|
5
|
+
"mappings": ";AAKA,IAAM,gBAAgB;AAGtB,IAAM,mBAAmB;AAGzB,IAAM,wBAAwB;AAG9B,IAAM,uBAAuB;AAWtB,SAAS,iBACd,SACA,cAAsB,uBACtB,aAAqB,sBACb;AACR,QAAM,mBAAmB,cAAc,KAAK,IAAI,kBAAkB,OAAO;AACzE,QAAM,SAAS,gBAAgB,mBAAmB,KAAK,OAAO;AAC9D,SAAO,KAAK,IAAI,mBAAmB,QAAQ,UAAU;AACvD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/utils/sanitize.ts
|
|
2
|
+
var CONTROL_CHARS = /[\x00-\x1f\x7f]/g;
|
|
3
|
+
var URLS = /https?:\/\/\S+/gi;
|
|
4
|
+
var MARKDOWN_LINKS = /\[([^\]]+)\]\([^)]+\)/g;
|
|
4
5
|
function sanitizeSuggestion(input) {
|
|
5
6
|
return input.replace(CONTROL_CHARS, " ").replace(MARKDOWN_LINKS, "$1").replace(URLS, "").replace(/\s+/g, " ").trim();
|
|
6
7
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/sanitize.ts"],
|
|
4
4
|
"sourcesContent": ["const CONTROL_CHARS = /[\\x00-\\x1f\\x7f]/g;\nconst URLS = /https?:\\/\\/\\S+/gi;\nconst MARKDOWN_LINKS = /\\[([^\\]]+)\\]\\([^)]+\\)/g;\n\nexport function sanitizeSuggestion(input: string): string {\n return input\n .replace(CONTROL_CHARS, ' ')\n .replace(MARKDOWN_LINKS, '$1')\n .replace(URLS, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n"],
|
|
5
|
-
"mappings": "AAAA,
|
|
5
|
+
"mappings": ";AAAA,IAAM,gBAAgB;AACtB,IAAM,OAAO;AACb,IAAM,iBAAiB;AAEhB,SAAS,mBAAmB,OAAuB;AACxD,SAAO,MACJ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,gBAAgB,IAAI,EAC5B,QAAQ,MAAM,EAAE,EAChB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|