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/mcp-use.json
CHANGED
package/dist/src/clients/jina.js
CHANGED
|
@@ -1,17 +1,203 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 = {
|
|
26
|
+
// Retryable errors
|
|
27
|
+
RATE_LIMITED: "RATE_LIMITED",
|
|
28
|
+
TIMEOUT: "TIMEOUT",
|
|
29
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
30
|
+
SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
|
|
31
|
+
// Non-retryable errors
|
|
32
|
+
AUTH_ERROR: "AUTH_ERROR",
|
|
33
|
+
INVALID_INPUT: "INVALID_INPUT",
|
|
34
|
+
NOT_FOUND: "NOT_FOUND",
|
|
35
|
+
QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
|
|
36
|
+
UNSUPPORTED_BINARY_CONTENT: "UNSUPPORTED_BINARY_CONTENT",
|
|
37
|
+
// Internal errors
|
|
38
|
+
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
39
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
40
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
41
|
+
};
|
|
42
|
+
function classifyDomException(error) {
|
|
43
|
+
if (error.name === "AbortError") {
|
|
44
|
+
return { code: ErrorCode.TIMEOUT, message: "Request timed out", retryable: true };
|
|
45
|
+
}
|
|
46
|
+
return { code: ErrorCode.UNKNOWN_ERROR, message: error.message, retryable: false };
|
|
47
|
+
}
|
|
48
|
+
function classifyByErrorCode(error) {
|
|
49
|
+
const errCode = error.code;
|
|
50
|
+
if (!errCode) return null;
|
|
51
|
+
const networkErrorMessages = {
|
|
52
|
+
ECONNREFUSED: "Connection refused \u2014 service may be down",
|
|
53
|
+
ECONNRESET: "Connection was reset \u2014 please retry",
|
|
54
|
+
ECONNABORTED: "Connection aborted \u2014 please retry",
|
|
55
|
+
ENOTFOUND: "Service not reachable \u2014 check your network",
|
|
56
|
+
EPIPE: "Connection lost \u2014 please retry",
|
|
57
|
+
EAI_AGAIN: "DNS lookup failed \u2014 check your network"
|
|
58
|
+
};
|
|
59
|
+
if (errCode === "ECONNREFUSED" || errCode === "ENOTFOUND" || errCode === "ECONNRESET") {
|
|
60
|
+
return { code: ErrorCode.NETWORK_ERROR, message: networkErrorMessages[errCode] || "Network connection failed", retryable: true, cause: error.message };
|
|
61
|
+
}
|
|
62
|
+
if (errCode === "ECONNABORTED" || errCode === "ETIMEDOUT") {
|
|
63
|
+
return { code: ErrorCode.TIMEOUT, message: networkErrorMessages[errCode] || "Request timed out", retryable: true, cause: error.message };
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
function classifyByStatusCode(error) {
|
|
68
|
+
const status = error.response?.status || error.status || error.statusCode;
|
|
69
|
+
if (!status) return null;
|
|
70
|
+
return classifyHttpError(status, error.message || String(error));
|
|
71
|
+
}
|
|
72
|
+
function classifyByMessage(message) {
|
|
73
|
+
const lower = message.toLowerCase();
|
|
74
|
+
if (lower.includes("timeout") || lower.includes("timed out") || lower.includes("aborterror")) {
|
|
75
|
+
return { code: ErrorCode.TIMEOUT, message: "Request timed out", retryable: true, cause: message };
|
|
76
|
+
}
|
|
77
|
+
if (lower.includes("rate limit") || lower.includes("too many requests")) {
|
|
78
|
+
return { code: ErrorCode.RATE_LIMITED, message: "Rate limit exceeded", retryable: true, cause: message };
|
|
79
|
+
}
|
|
80
|
+
if (message.includes("API_KEY") || message.includes("api_key") || message.includes("Invalid API")) {
|
|
81
|
+
return { code: ErrorCode.AUTH_ERROR, message: "API key missing or invalid", retryable: false, cause: message };
|
|
82
|
+
}
|
|
83
|
+
if (message.includes("JSON") || message.includes("parse") || message.includes("Unexpected token")) {
|
|
84
|
+
return { code: ErrorCode.PARSE_ERROR, message: "Failed to parse response", retryable: false, cause: message };
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function classifyFallback(message, cause) {
|
|
89
|
+
return {
|
|
90
|
+
code: ErrorCode.UNKNOWN_ERROR,
|
|
91
|
+
message,
|
|
92
|
+
retryable: false,
|
|
93
|
+
cause: cause ? String(cause) : void 0
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function classifyError(error) {
|
|
97
|
+
if (error == null) {
|
|
98
|
+
return { code: ErrorCode.UNKNOWN_ERROR, message: "An unknown error occurred", retryable: false };
|
|
99
|
+
}
|
|
100
|
+
if (error instanceof DOMException) return classifyDomException(error);
|
|
101
|
+
if (!isErrorLike(error)) {
|
|
102
|
+
return { code: ErrorCode.UNKNOWN_ERROR, message: String(error), retryable: false };
|
|
103
|
+
}
|
|
104
|
+
return classifyByErrorCode(error) ?? classifyByStatusCode(error) ?? classifyByMessage(error.message ?? String(error)) ?? classifyFallback(error.message ?? String(error), error.cause);
|
|
105
|
+
}
|
|
106
|
+
function isErrorLike(value) {
|
|
107
|
+
return typeof value === "object" && value !== null;
|
|
108
|
+
}
|
|
109
|
+
function classifyHttpError(status, message) {
|
|
110
|
+
switch (status) {
|
|
111
|
+
case 400:
|
|
112
|
+
return { code: ErrorCode.INVALID_INPUT, message: "Bad request", retryable: false, statusCode: status };
|
|
113
|
+
case 401:
|
|
114
|
+
return { code: ErrorCode.AUTH_ERROR, message: "Invalid API key", retryable: false, statusCode: status };
|
|
115
|
+
case 403:
|
|
116
|
+
return { code: ErrorCode.QUOTA_EXCEEDED, message: "Access forbidden or quota exceeded", retryable: false, statusCode: status };
|
|
117
|
+
case 404:
|
|
118
|
+
return { code: ErrorCode.NOT_FOUND, message: "Resource not found", retryable: false, statusCode: status };
|
|
119
|
+
case 408:
|
|
120
|
+
return { code: ErrorCode.TIMEOUT, message: "Request timeout", retryable: true, statusCode: status };
|
|
121
|
+
case 429:
|
|
122
|
+
return { code: ErrorCode.RATE_LIMITED, message: "Rate limit exceeded", retryable: true, statusCode: status };
|
|
123
|
+
case 500:
|
|
124
|
+
return { code: ErrorCode.INTERNAL_ERROR, message: "Server error", retryable: true, statusCode: status };
|
|
125
|
+
case 502:
|
|
126
|
+
return { code: ErrorCode.SERVICE_UNAVAILABLE, message: "Bad gateway", retryable: true, statusCode: status };
|
|
127
|
+
case 503:
|
|
128
|
+
return { code: ErrorCode.SERVICE_UNAVAILABLE, message: "Service unavailable", retryable: true, statusCode: status };
|
|
129
|
+
case 504:
|
|
130
|
+
return { code: ErrorCode.TIMEOUT, message: "Gateway timeout", retryable: true, statusCode: status };
|
|
131
|
+
case 510:
|
|
132
|
+
return { code: ErrorCode.SERVICE_UNAVAILABLE, message: "Request canceled", retryable: true, statusCode: status };
|
|
133
|
+
default:
|
|
134
|
+
if (status >= 500) {
|
|
135
|
+
return { code: ErrorCode.SERVICE_UNAVAILABLE, message: `Server error: ${status}`, retryable: true, statusCode: status };
|
|
136
|
+
}
|
|
137
|
+
if (status >= 400) {
|
|
138
|
+
return { code: ErrorCode.INVALID_INPUT, message: `Client error: ${status}`, retryable: false, statusCode: status };
|
|
139
|
+
}
|
|
140
|
+
return { code: ErrorCode.UNKNOWN_ERROR, message: `HTTP ${status}: ${message}`, retryable: false, statusCode: status };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function sleep(ms, signal) {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
if (signal?.aborted) {
|
|
146
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
function onAbort() {
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
152
|
+
}
|
|
153
|
+
const timeout = setTimeout(() => {
|
|
154
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
155
|
+
resolve();
|
|
156
|
+
}, ms);
|
|
157
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
158
|
+
if (signal?.aborted) {
|
|
159
|
+
onAbort();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function fetchWithTimeout(url, options = {}) {
|
|
164
|
+
const { timeoutMs = 3e4, signal: externalSignal, ...fetchOptions } = options;
|
|
165
|
+
const controller = new AbortController();
|
|
166
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
167
|
+
let onExternalAbort;
|
|
168
|
+
if (externalSignal) {
|
|
169
|
+
onExternalAbort = () => controller.abort();
|
|
170
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
171
|
+
if (externalSignal.aborted) {
|
|
172
|
+
controller.abort();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return fetch(url, { ...fetchOptions, signal: controller.signal }).finally(() => {
|
|
176
|
+
clearTimeout(timeoutId);
|
|
177
|
+
if (externalSignal && onExternalAbort) {
|
|
178
|
+
externalSignal.removeEventListener("abort", onExternalAbort);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/utils/retry.ts
|
|
184
|
+
var JITTER_FACTOR = 0.3;
|
|
185
|
+
var EXPONENTIAL_BASE = 2;
|
|
186
|
+
var DEFAULT_BASE_DELAY_MS = 1e3;
|
|
187
|
+
var DEFAULT_MAX_DELAY_MS = 3e4;
|
|
188
|
+
function calculateBackoff(attempt, baseDelayMs = DEFAULT_BASE_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS) {
|
|
189
|
+
const exponentialDelay = baseDelayMs * Math.pow(EXPONENTIAL_BASE, attempt);
|
|
190
|
+
const jitter = JITTER_FACTOR * exponentialDelay * Math.random();
|
|
191
|
+
return Math.min(exponentialDelay + jitter, maxDelayMs);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/clients/jina.ts
|
|
195
|
+
var JINA_READER_BASE = "https://r.jina.ai/";
|
|
196
|
+
var JINA_SEARCH_BASE = "https://s.jina.ai/";
|
|
197
|
+
var DEFAULT_TIMEOUT_SECONDS = 15;
|
|
198
|
+
var DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_SECONDS * 1e3;
|
|
199
|
+
var MAX_RETRIES = 2;
|
|
200
|
+
var SEARCH_RESULTS_PER_QUERY = 10;
|
|
15
201
|
function buildJinaSearchUrl(query) {
|
|
16
202
|
const params = new URLSearchParams({ q: query });
|
|
17
203
|
return `${JINA_SEARCH_BASE}?${params.toString()}`;
|
|
@@ -21,7 +207,7 @@ function buildScrapeDoProxyUrl(token, parameters = "render=false") {
|
|
|
21
207
|
if (!trimmed) return "";
|
|
22
208
|
return `http://${encodeURIComponent(trimmed)}:${parameters}@proxy.scrape.do:8080`;
|
|
23
209
|
}
|
|
24
|
-
|
|
210
|
+
var JinaClient = class {
|
|
25
211
|
apiKey;
|
|
26
212
|
constructor(apiKey) {
|
|
27
213
|
const fromEnv = process.env.JINA_API_KEY?.trim();
|
|
@@ -222,7 +408,7 @@ class JinaClient {
|
|
|
222
408
|
};
|
|
223
409
|
}
|
|
224
410
|
}
|
|
225
|
-
}
|
|
411
|
+
};
|
|
226
412
|
function parseReaderContent(raw) {
|
|
227
413
|
try {
|
|
228
414
|
const parsed = JSON.parse(raw);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../src/clients/jina.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Jina Reader Client\n *\n * Converts any URL (including PDFs, DOCX, PPTX, HTML) into clean markdown via\n * the public Jina Reader endpoint. Used by raw/smart scrape tools for full\n * markdown capture before optional LLM extraction.\n *\n * NEVER throws \u2014 every failure surfaces as a classified `StructuredError`\n * in the returned response, matching the shape of `ScraperClient.scrape`.\n *\n * Auth: optional `JINA_API_KEY` raises the rate limit from 20 RPM to 200+ RPM.\n * Without a key the endpoint still works; we just retry more aggressively on\n * 429 responses.\n */\n\nimport {\n classifyError,\n fetchWithTimeout,\n sleep,\n ErrorCode,\n type StructuredError,\n} from '../utils/errors.js';\nimport { calculateBackoff } from '../utils/retry.js';\nimport { mcpLog } from '../utils/logger.js';\nimport type { MultipleSearchResponse, QuerySearchResult } from './search.js';\n\n// \u2500\u2500 Constants \u2500\u2500\n\nconst JINA_READER_BASE = 'https://r.jina.ai/' as const;\nconst JINA_SEARCH_BASE = 'https://s.jina.ai/' as const;\nconst DEFAULT_TIMEOUT_SECONDS = 15 as const;\nconst DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_SECONDS * 1000;\nconst MAX_RETRIES = 2 as const;\nconst SEARCH_RESULTS_PER_QUERY = 10 as const;\n\nexport function buildJinaSearchUrl(query: string): string {\n const params = new URLSearchParams({ q: query });\n return `${JINA_SEARCH_BASE}?${params.toString()}`;\n}\n\nexport function buildScrapeDoProxyUrl(token: string, parameters = 'render=false'): string {\n const trimmed = token.trim();\n if (!trimmed) return '';\n return `http://${encodeURIComponent(trimmed)}:${parameters}@proxy.scrape.do:8080`;\n}\n\n// \u2500\u2500 Interfaces \u2500\u2500\n\nexport interface JinaConvertRequest {\n readonly url: string;\n readonly timeoutSeconds?: number;\n readonly proxyUrl?: string;\n readonly noCache?: boolean;\n readonly allowProxyRetry?: boolean;\n}\n\nexport interface JinaConvertResponse {\n readonly content: string;\n readonly statusCode: number;\n /** Always 0 \u2014 Jina is a separate service from Scrape.do's credit pool. */\n readonly credits: 0;\n readonly usageTokens?: number;\n readonly error?: StructuredError;\n}\n\n// \u2500\u2500 Client \u2500\u2500\n\nexport class JinaClient {\n private readonly apiKey: string | undefined;\n\n constructor(apiKey?: string) {\n const fromEnv = process.env.JINA_API_KEY?.trim();\n this.apiKey = apiKey?.trim() || fromEnv || undefined;\n }\n\n /**\n * Convert a URL to markdown via Jina Reader.\n * NEVER throws \u2014 always returns a JinaConvertResponse (possibly with error).\n */\n async convert(request: JinaConvertRequest): Promise<JinaConvertResponse> {\n const {\n url,\n timeoutSeconds = DEFAULT_TIMEOUT_SECONDS,\n proxyUrl,\n noCache = false,\n allowProxyRetry = false,\n } = request;\n\n try {\n new URL(url);\n } catch {\n return {\n content: `Invalid URL: ${url}`,\n statusCode: 400,\n credits: 0,\n error: { code: ErrorCode.INVALID_INPUT, message: `Invalid URL: ${url}`, retryable: false },\n };\n }\n\n const first = await this.convertOnce({\n url,\n timeoutSeconds,\n proxyUrl,\n noCache,\n });\n\n if (!first.error || !allowProxyRetry || proxyUrl || isTerminalReaderError(first.error)) {\n return first;\n }\n\n mcpLog('warning', `Jina Reader failed for ${url}; retrying with Jina proxy`, 'jina');\n return this.convertOnce({\n url,\n timeoutSeconds,\n proxyUrl: 'auto',\n noCache: true,\n });\n }\n\n async searchMultiple(queries: string[]): Promise<MultipleSearchResponse> {\n const startTime = Date.now();\n if (queries.length === 0) {\n return {\n searches: [],\n totalQueries: 0,\n executionTime: 0,\n error: { code: ErrorCode.INVALID_INPUT, message: 'No queries provided', retryable: false },\n };\n }\n if (!this.apiKey) {\n return {\n searches: [],\n totalQueries: queries.length,\n executionTime: Date.now() - startTime,\n error: { code: ErrorCode.AUTH_ERROR, message: 'Jina Search requires JINA_API_KEY', retryable: false },\n };\n }\n\n const searches = await Promise.all(queries.map((query) => this.searchOne(query)));\n const firstError = searches.find((search) => search.error)?.error;\n const allFailed = searches.every((search) => search.error);\n return {\n searches,\n totalQueries: queries.length,\n executionTime: Date.now() - startTime,\n ...(allFailed && firstError ? { error: firstError } : {}),\n };\n }\n\n private async convertOnce(request: Required<Pick<JinaConvertRequest, 'url' | 'timeoutSeconds' | 'noCache'>> & Pick<JinaConvertRequest, 'proxyUrl'>): Promise<JinaConvertResponse> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n };\n if (this.apiKey) headers['Authorization'] = `Bearer ${this.apiKey}`;\n if (request.proxyUrl && request.proxyUrl !== 'auto') {\n headers['X-Proxy-Url'] = request.proxyUrl;\n }\n\n const body: Record<string, unknown> = {\n url: request.url,\n respondWith: 'markdown',\n timeout: request.timeoutSeconds,\n base: 'final',\n removeOverlay: true,\n };\n if (request.proxyUrl === 'auto') body['proxy'] = 'auto';\n if (request.noCache) body['noCache'] = true;\n\n return this.fetchReader(body, headers, request.timeoutSeconds);\n }\n\n private async fetchReader(\n body: Record<string, unknown>,\n headers: Record<string, string>,\n timeoutSeconds: number,\n ): Promise<JinaConvertResponse> {\n let lastError: StructuredError | undefined;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetchWithTimeout(JINA_READER_BASE, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n timeoutMs: (timeoutSeconds + 5) * 1000,\n });\n\n const raw = await response.text().catch((readError: unknown) =>\n `Failed to read Jina response: ${readError instanceof Error ? readError.message : String(readError)}`,\n );\n const usageHeader = response.headers.get('x-usage-tokens');\n const usageTokens = usageHeader ? Number(usageHeader) : undefined;\n const parsed = parseReaderContent(raw);\n\n if (response.ok) {\n if (!parsed.content.trim()) {\n return emptyReaderResponse(response.status, usageTokens);\n }\n return {\n content: parsed.content,\n statusCode: response.status,\n credits: 0,\n usageTokens: Number.isFinite(usageTokens) ? usageTokens : undefined,\n };\n }\n\n const terminal = terminalReaderResponse(response.status, parsed.content || raw);\n if (terminal) return terminal;\n\n lastError = classifyError({ status: response.status, message: raw.slice(0, 200) });\n if (lastError.retryable && attempt < MAX_RETRIES) {\n const delayMs = calculateBackoff(attempt);\n mcpLog(\n 'warning',\n `Jina ${response.status} on attempt ${attempt + 1}/${MAX_RETRIES + 1}. Retrying in ${delayMs}ms`,\n 'jina',\n );\n await sleep(delayMs);\n continue;\n }\n return {\n content: `Jina Reader error (${response.status}): ${raw.slice(0, 200)}`,\n statusCode: response.status,\n credits: 0,\n error: lastError,\n };\n } catch (error) {\n lastError = classifyError(error);\n if (lastError.retryable && attempt < MAX_RETRIES) {\n const delayMs = calculateBackoff(attempt);\n mcpLog(\n 'warning',\n `Jina ${lastError.code}: ${lastError.message}. Retry ${attempt + 1}/${MAX_RETRIES + 1} in ${delayMs}ms`,\n 'jina',\n );\n await sleep(delayMs);\n continue;\n }\n return {\n content: `Jina Reader failed: ${lastError.message}`,\n statusCode: lastError.statusCode ?? 500,\n credits: 0,\n error: lastError,\n };\n }\n }\n\n return {\n content: `Jina Reader failed after ${MAX_RETRIES + 1} attempts: ${lastError?.message ?? 'Unknown error'}`,\n statusCode: lastError?.statusCode ?? 500,\n credits: 0,\n error: lastError ?? { code: ErrorCode.UNKNOWN_ERROR, message: 'All retries exhausted', retryable: false },\n };\n }\n\n private async searchOne(query: string): Promise<QuerySearchResult> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n Authorization: `Bearer ${this.apiKey ?? ''}`,\n };\n\n try {\n const response = await fetchWithTimeout(buildJinaSearchUrl(query), {\n method: 'GET',\n headers,\n timeoutMs: DEFAULT_TIMEOUT_MS,\n });\n const raw = await response.text().catch((readError: unknown) =>\n `Failed to read Jina Search response: ${readError instanceof Error ? readError.message : String(readError)}`,\n );\n\n if (!response.ok) {\n return {\n query,\n results: [],\n totalResults: 0,\n related: [],\n error: classifyError({ status: response.status, message: raw.slice(0, 200) }),\n };\n }\n\n const results = parseSearchResults(raw);\n return { query, results, totalResults: results.length, related: [] };\n } catch (error) {\n return {\n query,\n results: [],\n totalResults: 0,\n related: [],\n error: classifyError(error),\n };\n }\n }\n}\n\nfunction parseReaderContent(raw: string): { content: string } {\n try {\n const parsed: unknown = JSON.parse(raw);\n const data = readRecord(parsed, 'data');\n const content = readString(data, 'content');\n if (content) return { content };\n } catch {\n // Jina may return text/plain on some degraded paths; use it as markdown.\n }\n return { content: raw };\n}\n\nfunction emptyReaderResponse(statusCode: number, usageTokens: number | undefined): JinaConvertResponse {\n return {\n content: 'Jina returned an empty body',\n statusCode,\n credits: 0,\n usageTokens: Number.isFinite(usageTokens) ? usageTokens : undefined,\n error: {\n code: ErrorCode.UNSUPPORTED_BINARY_CONTENT,\n message: 'Jina Reader returned empty content for this URL',\n retryable: false,\n },\n };\n}\n\nfunction terminalReaderResponse(statusCode: number, content: string): JinaConvertResponse | null {\n if (statusCode === 401 || statusCode === 403) {\n return {\n content: `Jina auth/quota error (${statusCode}): ${content.slice(0, 200)}`,\n statusCode,\n credits: 0,\n error: {\n code: statusCode === 401 ? ErrorCode.AUTH_ERROR : ErrorCode.QUOTA_EXCEEDED,\n message: statusCode === 401\n ? 'Jina Reader auth failed \u2014 check JINA_API_KEY'\n : 'Jina Reader quota exceeded',\n retryable: false,\n statusCode,\n },\n };\n }\n\n if (statusCode === 404) {\n return {\n content: 'Jina could not fetch the target URL (404)',\n statusCode: 404,\n credits: 0,\n error: {\n code: ErrorCode.NOT_FOUND,\n message: 'Target URL not reachable by Jina Reader',\n retryable: false,\n statusCode: 404,\n },\n };\n }\n\n if (statusCode >= 400 && statusCode < 500 && statusCode !== 429) {\n return {\n content: `Jina Reader error (${statusCode}): ${content.slice(0, 200)}`,\n statusCode,\n credits: 0,\n error: {\n code: ErrorCode.INVALID_INPUT,\n message: `Jina Reader returned ${statusCode}`,\n retryable: false,\n statusCode,\n },\n };\n }\n\n return null;\n}\n\nexport function isTerminalReaderError(error: StructuredError): boolean {\n return !error.retryable\n && (\n error.code === ErrorCode.AUTH_ERROR\n || error.code === ErrorCode.QUOTA_EXCEEDED\n || error.code === ErrorCode.NOT_FOUND\n || error.code === ErrorCode.INVALID_INPUT\n );\n}\n\nfunction parseSearchResults(raw: string): QuerySearchResult['results'] {\n let data: unknown;\n try {\n const parsed: unknown = JSON.parse(raw);\n data = readUnknown(parsed, 'data');\n } catch {\n data = parseMarkdownSearchResults(raw);\n }\n\n const items = Array.isArray(data) ? data : [];\n return items\n .map((item, index) => normalizeSearchItem(item, index))\n .filter((item): item is QuerySearchResult['results'][number] => item !== null)\n .slice(0, SEARCH_RESULTS_PER_QUERY);\n}\n\nfunction normalizeSearchItem(item: unknown, index: number): QuerySearchResult['results'][number] | null {\n const link = readString(item, 'url') ?? readString(item, 'link');\n if (!link) return null;\n return {\n title: readString(item, 'title') || link,\n link,\n snippet: (\n readString(item, 'snippet')\n || readString(item, 'description')\n || readString(item, 'content')\n || ''\n ).slice(0, 500),\n date: readString(item, 'date') ?? readString(item, 'publishedTime'),\n position: index + 1,\n };\n}\n\nfunction parseMarkdownSearchResults(raw: string): Array<Record<string, string>> {\n const items: Array<Record<string, string>> = [];\n const markdownLink = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g;\n let match: RegExpExecArray | null;\n while ((match = markdownLink.exec(raw)) !== null && items.length < SEARCH_RESULTS_PER_QUERY) {\n const title = match[1];\n const url = match[2];\n if (title && url) items.push({ title, url });\n }\n return items;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readUnknown(value: unknown, key: string): unknown {\n return isRecord(value) ? value[key] : undefined;\n}\n\nfunction readRecord(value: unknown, key: string): Record<string, unknown> | undefined {\n const child = readUnknown(value, key);\n return isRecord(child) ? child : undefined;\n}\n\nfunction readString(value: unknown, key: string): string | undefined {\n const child = readUnknown(value, key);\n return typeof child === 'string' ? child : undefined;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
3
|
+
"sources": ["../../../src/utils/logger.ts", "../../../src/utils/errors.ts", "../../../src/utils/retry.ts", "../../../src/clients/jina.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", "/**\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", "/**\n * Jina Reader Client\n *\n * Converts any URL (including PDFs, DOCX, PPTX, HTML) into clean markdown via\n * the public Jina Reader endpoint. Used by raw/smart scrape tools for full\n * markdown capture before optional LLM extraction.\n *\n * NEVER throws \u2014 every failure surfaces as a classified `StructuredError`\n * in the returned response, matching the shape of `ScraperClient.scrape`.\n *\n * Auth: optional `JINA_API_KEY` raises the rate limit from 20 RPM to 200+ RPM.\n * Without a key the endpoint still works; we just retry more aggressively on\n * 429 responses.\n */\n\nimport {\n classifyError,\n fetchWithTimeout,\n sleep,\n ErrorCode,\n type StructuredError,\n} from '../utils/errors.js';\nimport { calculateBackoff } from '../utils/retry.js';\nimport { mcpLog } from '../utils/logger.js';\nimport type { MultipleSearchResponse, QuerySearchResult } from './search.js';\n\n// \u2500\u2500 Constants \u2500\u2500\n\nconst JINA_READER_BASE = 'https://r.jina.ai/' as const;\nconst JINA_SEARCH_BASE = 'https://s.jina.ai/' as const;\nconst DEFAULT_TIMEOUT_SECONDS = 15 as const;\nconst DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_SECONDS * 1000;\nconst MAX_RETRIES = 2 as const;\nconst SEARCH_RESULTS_PER_QUERY = 10 as const;\n\nexport function buildJinaSearchUrl(query: string): string {\n const params = new URLSearchParams({ q: query });\n return `${JINA_SEARCH_BASE}?${params.toString()}`;\n}\n\nexport function buildScrapeDoProxyUrl(token: string, parameters = 'render=false'): string {\n const trimmed = token.trim();\n if (!trimmed) return '';\n return `http://${encodeURIComponent(trimmed)}:${parameters}@proxy.scrape.do:8080`;\n}\n\n// \u2500\u2500 Interfaces \u2500\u2500\n\nexport interface JinaConvertRequest {\n readonly url: string;\n readonly timeoutSeconds?: number;\n readonly proxyUrl?: string;\n readonly noCache?: boolean;\n readonly allowProxyRetry?: boolean;\n}\n\nexport interface JinaConvertResponse {\n readonly content: string;\n readonly statusCode: number;\n /** Always 0 \u2014 Jina is a separate service from Scrape.do's credit pool. */\n readonly credits: 0;\n readonly usageTokens?: number;\n readonly error?: StructuredError;\n}\n\n// \u2500\u2500 Client \u2500\u2500\n\nexport class JinaClient {\n private readonly apiKey: string | undefined;\n\n constructor(apiKey?: string) {\n const fromEnv = process.env.JINA_API_KEY?.trim();\n this.apiKey = apiKey?.trim() || fromEnv || undefined;\n }\n\n /**\n * Convert a URL to markdown via Jina Reader.\n * NEVER throws \u2014 always returns a JinaConvertResponse (possibly with error).\n */\n async convert(request: JinaConvertRequest): Promise<JinaConvertResponse> {\n const {\n url,\n timeoutSeconds = DEFAULT_TIMEOUT_SECONDS,\n proxyUrl,\n noCache = false,\n allowProxyRetry = false,\n } = request;\n\n try {\n new URL(url);\n } catch {\n return {\n content: `Invalid URL: ${url}`,\n statusCode: 400,\n credits: 0,\n error: { code: ErrorCode.INVALID_INPUT, message: `Invalid URL: ${url}`, retryable: false },\n };\n }\n\n const first = await this.convertOnce({\n url,\n timeoutSeconds,\n proxyUrl,\n noCache,\n });\n\n if (!first.error || !allowProxyRetry || proxyUrl || isTerminalReaderError(first.error)) {\n return first;\n }\n\n mcpLog('warning', `Jina Reader failed for ${url}; retrying with Jina proxy`, 'jina');\n return this.convertOnce({\n url,\n timeoutSeconds,\n proxyUrl: 'auto',\n noCache: true,\n });\n }\n\n async searchMultiple(queries: string[]): Promise<MultipleSearchResponse> {\n const startTime = Date.now();\n if (queries.length === 0) {\n return {\n searches: [],\n totalQueries: 0,\n executionTime: 0,\n error: { code: ErrorCode.INVALID_INPUT, message: 'No queries provided', retryable: false },\n };\n }\n if (!this.apiKey) {\n return {\n searches: [],\n totalQueries: queries.length,\n executionTime: Date.now() - startTime,\n error: { code: ErrorCode.AUTH_ERROR, message: 'Jina Search requires JINA_API_KEY', retryable: false },\n };\n }\n\n const searches = await Promise.all(queries.map((query) => this.searchOne(query)));\n const firstError = searches.find((search) => search.error)?.error;\n const allFailed = searches.every((search) => search.error);\n return {\n searches,\n totalQueries: queries.length,\n executionTime: Date.now() - startTime,\n ...(allFailed && firstError ? { error: firstError } : {}),\n };\n }\n\n private async convertOnce(request: Required<Pick<JinaConvertRequest, 'url' | 'timeoutSeconds' | 'noCache'>> & Pick<JinaConvertRequest, 'proxyUrl'>): Promise<JinaConvertResponse> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n };\n if (this.apiKey) headers['Authorization'] = `Bearer ${this.apiKey}`;\n if (request.proxyUrl && request.proxyUrl !== 'auto') {\n headers['X-Proxy-Url'] = request.proxyUrl;\n }\n\n const body: Record<string, unknown> = {\n url: request.url,\n respondWith: 'markdown',\n timeout: request.timeoutSeconds,\n base: 'final',\n removeOverlay: true,\n };\n if (request.proxyUrl === 'auto') body['proxy'] = 'auto';\n if (request.noCache) body['noCache'] = true;\n\n return this.fetchReader(body, headers, request.timeoutSeconds);\n }\n\n private async fetchReader(\n body: Record<string, unknown>,\n headers: Record<string, string>,\n timeoutSeconds: number,\n ): Promise<JinaConvertResponse> {\n let lastError: StructuredError | undefined;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetchWithTimeout(JINA_READER_BASE, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n timeoutMs: (timeoutSeconds + 5) * 1000,\n });\n\n const raw = await response.text().catch((readError: unknown) =>\n `Failed to read Jina response: ${readError instanceof Error ? readError.message : String(readError)}`,\n );\n const usageHeader = response.headers.get('x-usage-tokens');\n const usageTokens = usageHeader ? Number(usageHeader) : undefined;\n const parsed = parseReaderContent(raw);\n\n if (response.ok) {\n if (!parsed.content.trim()) {\n return emptyReaderResponse(response.status, usageTokens);\n }\n return {\n content: parsed.content,\n statusCode: response.status,\n credits: 0,\n usageTokens: Number.isFinite(usageTokens) ? usageTokens : undefined,\n };\n }\n\n const terminal = terminalReaderResponse(response.status, parsed.content || raw);\n if (terminal) return terminal;\n\n lastError = classifyError({ status: response.status, message: raw.slice(0, 200) });\n if (lastError.retryable && attempt < MAX_RETRIES) {\n const delayMs = calculateBackoff(attempt);\n mcpLog(\n 'warning',\n `Jina ${response.status} on attempt ${attempt + 1}/${MAX_RETRIES + 1}. Retrying in ${delayMs}ms`,\n 'jina',\n );\n await sleep(delayMs);\n continue;\n }\n return {\n content: `Jina Reader error (${response.status}): ${raw.slice(0, 200)}`,\n statusCode: response.status,\n credits: 0,\n error: lastError,\n };\n } catch (error) {\n lastError = classifyError(error);\n if (lastError.retryable && attempt < MAX_RETRIES) {\n const delayMs = calculateBackoff(attempt);\n mcpLog(\n 'warning',\n `Jina ${lastError.code}: ${lastError.message}. Retry ${attempt + 1}/${MAX_RETRIES + 1} in ${delayMs}ms`,\n 'jina',\n );\n await sleep(delayMs);\n continue;\n }\n return {\n content: `Jina Reader failed: ${lastError.message}`,\n statusCode: lastError.statusCode ?? 500,\n credits: 0,\n error: lastError,\n };\n }\n }\n\n return {\n content: `Jina Reader failed after ${MAX_RETRIES + 1} attempts: ${lastError?.message ?? 'Unknown error'}`,\n statusCode: lastError?.statusCode ?? 500,\n credits: 0,\n error: lastError ?? { code: ErrorCode.UNKNOWN_ERROR, message: 'All retries exhausted', retryable: false },\n };\n }\n\n private async searchOne(query: string): Promise<QuerySearchResult> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n Authorization: `Bearer ${this.apiKey ?? ''}`,\n };\n\n try {\n const response = await fetchWithTimeout(buildJinaSearchUrl(query), {\n method: 'GET',\n headers,\n timeoutMs: DEFAULT_TIMEOUT_MS,\n });\n const raw = await response.text().catch((readError: unknown) =>\n `Failed to read Jina Search response: ${readError instanceof Error ? readError.message : String(readError)}`,\n );\n\n if (!response.ok) {\n return {\n query,\n results: [],\n totalResults: 0,\n related: [],\n error: classifyError({ status: response.status, message: raw.slice(0, 200) }),\n };\n }\n\n const results = parseSearchResults(raw);\n return { query, results, totalResults: results.length, related: [] };\n } catch (error) {\n return {\n query,\n results: [],\n totalResults: 0,\n related: [],\n error: classifyError(error),\n };\n }\n }\n}\n\nfunction parseReaderContent(raw: string): { content: string } {\n try {\n const parsed: unknown = JSON.parse(raw);\n const data = readRecord(parsed, 'data');\n const content = readString(data, 'content');\n if (content) return { content };\n } catch {\n // Jina may return text/plain on some degraded paths; use it as markdown.\n }\n return { content: raw };\n}\n\nfunction emptyReaderResponse(statusCode: number, usageTokens: number | undefined): JinaConvertResponse {\n return {\n content: 'Jina returned an empty body',\n statusCode,\n credits: 0,\n usageTokens: Number.isFinite(usageTokens) ? usageTokens : undefined,\n error: {\n code: ErrorCode.UNSUPPORTED_BINARY_CONTENT,\n message: 'Jina Reader returned empty content for this URL',\n retryable: false,\n },\n };\n}\n\nfunction terminalReaderResponse(statusCode: number, content: string): JinaConvertResponse | null {\n if (statusCode === 401 || statusCode === 403) {\n return {\n content: `Jina auth/quota error (${statusCode}): ${content.slice(0, 200)}`,\n statusCode,\n credits: 0,\n error: {\n code: statusCode === 401 ? ErrorCode.AUTH_ERROR : ErrorCode.QUOTA_EXCEEDED,\n message: statusCode === 401\n ? 'Jina Reader auth failed \u2014 check JINA_API_KEY'\n : 'Jina Reader quota exceeded',\n retryable: false,\n statusCode,\n },\n };\n }\n\n if (statusCode === 404) {\n return {\n content: 'Jina could not fetch the target URL (404)',\n statusCode: 404,\n credits: 0,\n error: {\n code: ErrorCode.NOT_FOUND,\n message: 'Target URL not reachable by Jina Reader',\n retryable: false,\n statusCode: 404,\n },\n };\n }\n\n if (statusCode >= 400 && statusCode < 500 && statusCode !== 429) {\n return {\n content: `Jina Reader error (${statusCode}): ${content.slice(0, 200)}`,\n statusCode,\n credits: 0,\n error: {\n code: ErrorCode.INVALID_INPUT,\n message: `Jina Reader returned ${statusCode}`,\n retryable: false,\n statusCode,\n },\n };\n }\n\n return null;\n}\n\nexport function isTerminalReaderError(error: StructuredError): boolean {\n return !error.retryable\n && (\n error.code === ErrorCode.AUTH_ERROR\n || error.code === ErrorCode.QUOTA_EXCEEDED\n || error.code === ErrorCode.NOT_FOUND\n || error.code === ErrorCode.INVALID_INPUT\n );\n}\n\nfunction parseSearchResults(raw: string): QuerySearchResult['results'] {\n let data: unknown;\n try {\n const parsed: unknown = JSON.parse(raw);\n data = readUnknown(parsed, 'data');\n } catch {\n data = parseMarkdownSearchResults(raw);\n }\n\n const items = Array.isArray(data) ? data : [];\n return items\n .map((item, index) => normalizeSearchItem(item, index))\n .filter((item): item is QuerySearchResult['results'][number] => item !== null)\n .slice(0, SEARCH_RESULTS_PER_QUERY);\n}\n\nfunction normalizeSearchItem(item: unknown, index: number): QuerySearchResult['results'][number] | null {\n const link = readString(item, 'url') ?? readString(item, 'link');\n if (!link) return null;\n return {\n title: readString(item, 'title') || link,\n link,\n snippet: (\n readString(item, 'snippet')\n || readString(item, 'description')\n || readString(item, 'content')\n || ''\n ).slice(0, 500),\n date: readString(item, 'date') ?? readString(item, 'publishedTime'),\n position: index + 1,\n };\n}\n\nfunction parseMarkdownSearchResults(raw: string): Array<Record<string, string>> {\n const items: Array<Record<string, string>> = [];\n const markdownLink = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g;\n let match: RegExpExecArray | null;\n while ((match = markdownLink.exec(raw)) !== null && items.length < SEARCH_RESULTS_PER_QUERY) {\n const title = match[1];\n const url = match[2];\n if (title && url) items.push({ title, url });\n }\n return items;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readUnknown(value: unknown, key: string): unknown {\n return isRecord(value) ? value[key] : undefined;\n}\n\nfunction readRecord(value: unknown, key: string): Record<string, unknown> | undefined {\n const child = readUnknown(value, key);\n return isRecord(child) ? child : undefined;\n}\n\nfunction readString(value: unknown, key: string): string | undefined {\n const child = readUnknown(value, key);\n return typeof child === 'string' ? child : undefined;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|