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.
Files changed (70) hide show
  1. package/dist/index.js +4662 -21
  2. package/dist/index.js.map +4 -4
  3. package/dist/mcp-use.json +2 -2
  4. package/dist/src/clients/jina.js +202 -16
  5. package/dist/src/clients/jina.js.map +3 -3
  6. package/dist/src/clients/kernel.js +254 -7
  7. package/dist/src/clients/kernel.js.map +4 -4
  8. package/dist/src/clients/reddit.js +326 -23
  9. package/dist/src/clients/reddit.js.map +4 -4
  10. package/dist/src/clients/scraper.js +345 -22
  11. package/dist/src/clients/scraper.js.map +4 -4
  12. package/dist/src/clients/search.js +316 -20
  13. package/dist/src/clients/search.js.map +4 -4
  14. package/dist/src/config/index.js +39 -10
  15. package/dist/src/config/index.js.map +3 -3
  16. package/dist/src/effect/errors.js +130 -5
  17. package/dist/src/effect/errors.js.map +3 -3
  18. package/dist/src/effect/runtime.js +1893 -4
  19. package/dist/src/effect/runtime.js.map +4 -4
  20. package/dist/src/effect/services.js +2124 -22
  21. package/dist/src/effect/services.js.map +4 -4
  22. package/dist/src/schemas/scrape-links.js +6 -5
  23. package/dist/src/schemas/scrape-links.js.map +1 -1
  24. package/dist/src/schemas/start-research.js +2 -1
  25. package/dist/src/schemas/start-research.js.map +1 -1
  26. package/dist/src/schemas/web-search.js +9 -8
  27. package/dist/src/schemas/web-search.js.map +1 -1
  28. package/dist/src/services/llm-processor.js +406 -25
  29. package/dist/src/services/llm-processor.js.map +4 -4
  30. package/dist/src/services/markdown-cleaner.js +6 -5
  31. package/dist/src/services/markdown-cleaner.js.map +1 -1
  32. package/dist/src/tools/mcp-helpers.js +2 -1
  33. package/dist/src/tools/mcp-helpers.js.map +1 -1
  34. package/dist/src/tools/registry.js +4629 -3
  35. package/dist/src/tools/registry.js.map +4 -4
  36. package/dist/src/tools/scrape.js +2610 -80
  37. package/dist/src/tools/scrape.js.map +4 -4
  38. package/dist/src/tools/search.js +2388 -59
  39. package/dist/src/tools/search.js.map +4 -4
  40. package/dist/src/tools/start-research.js +2030 -23
  41. package/dist/src/tools/start-research.js.map +4 -4
  42. package/dist/src/tools/utils.js +98 -7
  43. package/dist/src/tools/utils.js.map +3 -3
  44. package/dist/src/utils/concurrency.js +1 -0
  45. package/dist/src/utils/concurrency.js.map +1 -1
  46. package/dist/src/utils/content-extractor.js +27 -2
  47. package/dist/src/utils/content-extractor.js.map +3 -3
  48. package/dist/src/utils/content-quality.js +4 -3
  49. package/dist/src/utils/content-quality.js.map +1 -1
  50. package/dist/src/utils/errors.js +26 -3
  51. package/dist/src/utils/errors.js.map +3 -3
  52. package/dist/src/utils/logger.js +1 -0
  53. package/dist/src/utils/logger.js.map +1 -1
  54. package/dist/src/utils/markdown-formatter.js +1 -0
  55. package/dist/src/utils/markdown-formatter.js.map +1 -1
  56. package/dist/src/utils/query-relax.js +9 -8
  57. package/dist/src/utils/query-relax.js.map +1 -1
  58. package/dist/src/utils/response.js +3 -2
  59. package/dist/src/utils/response.js.map +1 -1
  60. package/dist/src/utils/retry.js +5 -4
  61. package/dist/src/utils/retry.js.map +1 -1
  62. package/dist/src/utils/sanitize.js +4 -3
  63. package/dist/src/utils/sanitize.js.map +1 -1
  64. package/dist/src/utils/source-type.js +4 -3
  65. package/dist/src/utils/source-type.js.map +1 -1
  66. package/dist/src/utils/url-aggregator.js +112 -11
  67. package/dist/src/utils/url-aggregator.js.map +3 -3
  68. package/dist/src/version.js +7 -6
  69. package/dist/src/version.js.map +1 -1
  70. package/package.json +3 -3
@@ -1,24 +1,327 @@
1
+ // src/clients/reddit.ts
2
+ import { Logger as Logger3 } from "mcp-use";
3
+
4
+ // src/config/index.ts
1
5
  import { Logger } from "mcp-use";
2
- import { REDDIT, CONCURRENCY } from "../config/index.js";
3
- import { USER_AGENT_VERSION } from "../version.js";
4
- import { calculateBackoff } from "../utils/retry.js";
5
- import {
6
- classifyError,
7
- fetchWithTimeout,
8
- sleep,
9
- ErrorCode
10
- } from "../utils/errors.js";
11
- import { pMapSettled } from "../utils/concurrency.js";
12
- import { mcpLog } from "../utils/logger.js";
13
- const REDDIT_TOKEN_URL = "https://www.reddit.com/api/v1/access_token";
14
- const REDDIT_API_BASE = "https://oauth.reddit.com";
15
- const TOKEN_EXPIRY_MS = 55e3;
16
- const FETCH_LIMIT = 500;
17
- let cachedToken = null;
18
- let cachedTokenExpiry = 0;
19
- const DEBUG_TOKEN_CACHE = process.env.DEBUG_REDDIT === "true";
20
- const clientLogger = Logger.get("reddit-client");
21
- let pendingAuthPromise = null;
6
+
7
+ // src/version.ts
8
+ import { createRequire } from "module";
9
+ import { fileURLToPath } from "url";
10
+ import { dirname, join } from "path";
11
+ var DEFAULT_PACKAGE_INFO = {
12
+ version: "3.9.5",
13
+ name: "mcp-researchpowerpack-http",
14
+ description: "Research Powerpack MCP Server"
15
+ };
16
+ var packageJson = { ...DEFAULT_PACKAGE_INFO };
17
+ try {
18
+ if (typeof import.meta.url === "string" && import.meta.url.startsWith("file:")) {
19
+ const _require = createRequire(import.meta.url);
20
+ const _dirname = dirname(fileURLToPath(import.meta.url));
21
+ try {
22
+ packageJson = _require(join(_dirname, "..", "package.json"));
23
+ } catch {
24
+ packageJson = _require(join(_dirname, "..", "..", "package.json"));
25
+ }
26
+ }
27
+ } catch {
28
+ }
29
+ var VERSION = packageJson.version;
30
+ var PACKAGE_NAME = packageJson.name;
31
+ var PACKAGE_DESCRIPTION = packageJson.description;
32
+ var USER_AGENT_VERSION = `${PACKAGE_NAME}/${VERSION}`;
33
+
34
+ // src/config/index.ts
35
+ function safeParseInt(value, defaultVal, min, max) {
36
+ const logger = Logger.get("config");
37
+ if (!value) {
38
+ return defaultVal;
39
+ }
40
+ const parsed = parseInt(value, 10);
41
+ if (isNaN(parsed)) {
42
+ logger.warn(`Invalid number "${value}", using default ${defaultVal}`);
43
+ return defaultVal;
44
+ }
45
+ if (parsed < min) {
46
+ logger.warn(`Value ${parsed} below minimum ${min}, clamping to ${min}`);
47
+ return min;
48
+ }
49
+ if (parsed > max) {
50
+ logger.warn(`Value ${parsed} above maximum ${max}, clamping to ${max}`);
51
+ return max;
52
+ }
53
+ return parsed;
54
+ }
55
+ var CONCURRENCY = {
56
+ SEARCH: safeParseInt(process.env.CONCURRENCY_SEARCH, 50, 1, 200),
57
+ SCRAPER: safeParseInt(process.env.CONCURRENCY_SCRAPER, 50, 1, 200),
58
+ JINA_READER: safeParseInt(process.env.CONCURRENCY_JINA_READER, 50, 1, 200),
59
+ REDDIT: safeParseInt(process.env.CONCURRENCY_REDDIT, 50, 1, 200),
60
+ LLM_EXTRACTION: safeParseInt(process.env.LLM_CONCURRENCY, 50, 1, 200),
61
+ KERNEL: safeParseInt(process.env.CONCURRENCY_KERNEL, 3, 1, 20)
62
+ };
63
+ var REDDIT = {
64
+ BATCH_SIZE: 10,
65
+ MAX_WORDS_PER_POST: 5e4,
66
+ MAX_WORDS_TOTAL: 5e5,
67
+ MIN_POSTS: 1,
68
+ MAX_POSTS: 50,
69
+ RETRY_COUNT: 5,
70
+ RETRY_DELAYS: [2e3, 4e3, 8e3, 16e3, 32e3]
71
+ };
72
+ var cachedLlmExtraction = null;
73
+ function getLlmExtraction() {
74
+ if (cachedLlmExtraction) return cachedLlmExtraction;
75
+ const apiKey = process.env.LLM_API_KEY?.trim() || "";
76
+ const baseUrl = process.env.LLM_BASE_URL?.trim();
77
+ const model = process.env.LLM_MODEL?.trim();
78
+ const fallbackModel = process.env.LLM_FALLBACK_MODEL?.trim() || "";
79
+ if (apiKey && !baseUrl) {
80
+ throw new Error(
81
+ "LLM_BASE_URL is required when LLM_API_KEY is set. Set LLM_BASE_URL to your OpenAI-compatible endpoint."
82
+ );
83
+ }
84
+ if (apiKey && !model) {
85
+ throw new Error(
86
+ "LLM_MODEL is required when LLM_API_KEY is set."
87
+ );
88
+ }
89
+ cachedLlmExtraction = {
90
+ API_KEY: apiKey,
91
+ BASE_URL: baseUrl || "",
92
+ MODEL: model || "",
93
+ FALLBACK_MODEL: fallbackModel
94
+ };
95
+ return cachedLlmExtraction;
96
+ }
97
+ var LLM_EXTRACTION = new Proxy({}, {
98
+ get(_target, prop) {
99
+ return getLlmExtraction()[prop];
100
+ }
101
+ });
102
+
103
+ // src/utils/retry.ts
104
+ var JITTER_FACTOR = 0.3;
105
+ var EXPONENTIAL_BASE = 2;
106
+ var DEFAULT_BASE_DELAY_MS = 1e3;
107
+ var DEFAULT_MAX_DELAY_MS = 3e4;
108
+ function calculateBackoff(attempt, baseDelayMs = DEFAULT_BASE_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS) {
109
+ const exponentialDelay = baseDelayMs * Math.pow(EXPONENTIAL_BASE, attempt);
110
+ const jitter = JITTER_FACTOR * exponentialDelay * Math.random();
111
+ return Math.min(exponentialDelay + jitter, maxDelayMs);
112
+ }
113
+
114
+ // src/utils/logger.ts
115
+ import { Logger as Logger2 } from "mcp-use";
116
+ function getLogger(name) {
117
+ return Logger2.get(name);
118
+ }
119
+ function mcpLog(level, message, loggerName) {
120
+ const logger = getLogger(loggerName ?? "research-powerpack");
121
+ switch (level) {
122
+ case "debug":
123
+ logger.debug(message);
124
+ break;
125
+ case "info":
126
+ logger.info(message);
127
+ break;
128
+ case "warning":
129
+ logger.warn(message);
130
+ break;
131
+ case "error":
132
+ logger.error(message);
133
+ break;
134
+ }
135
+ }
136
+
137
+ // src/utils/errors.ts
138
+ var ErrorCode = {
139
+ // Retryable errors
140
+ RATE_LIMITED: "RATE_LIMITED",
141
+ TIMEOUT: "TIMEOUT",
142
+ NETWORK_ERROR: "NETWORK_ERROR",
143
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
144
+ // Non-retryable errors
145
+ AUTH_ERROR: "AUTH_ERROR",
146
+ INVALID_INPUT: "INVALID_INPUT",
147
+ NOT_FOUND: "NOT_FOUND",
148
+ QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
149
+ UNSUPPORTED_BINARY_CONTENT: "UNSUPPORTED_BINARY_CONTENT",
150
+ // Internal errors
151
+ INTERNAL_ERROR: "INTERNAL_ERROR",
152
+ PARSE_ERROR: "PARSE_ERROR",
153
+ UNKNOWN_ERROR: "UNKNOWN_ERROR"
154
+ };
155
+ function classifyDomException(error) {
156
+ if (error.name === "AbortError") {
157
+ return { code: ErrorCode.TIMEOUT, message: "Request timed out", retryable: true };
158
+ }
159
+ return { code: ErrorCode.UNKNOWN_ERROR, message: error.message, retryable: false };
160
+ }
161
+ function classifyByErrorCode(error) {
162
+ const errCode = error.code;
163
+ if (!errCode) return null;
164
+ const networkErrorMessages = {
165
+ ECONNREFUSED: "Connection refused \u2014 service may be down",
166
+ ECONNRESET: "Connection was reset \u2014 please retry",
167
+ ECONNABORTED: "Connection aborted \u2014 please retry",
168
+ ENOTFOUND: "Service not reachable \u2014 check your network",
169
+ EPIPE: "Connection lost \u2014 please retry",
170
+ EAI_AGAIN: "DNS lookup failed \u2014 check your network"
171
+ };
172
+ if (errCode === "ECONNREFUSED" || errCode === "ENOTFOUND" || errCode === "ECONNRESET") {
173
+ return { code: ErrorCode.NETWORK_ERROR, message: networkErrorMessages[errCode] || "Network connection failed", retryable: true, cause: error.message };
174
+ }
175
+ if (errCode === "ECONNABORTED" || errCode === "ETIMEDOUT") {
176
+ return { code: ErrorCode.TIMEOUT, message: networkErrorMessages[errCode] || "Request timed out", retryable: true, cause: error.message };
177
+ }
178
+ return null;
179
+ }
180
+ function classifyByStatusCode(error) {
181
+ const status = error.response?.status || error.status || error.statusCode;
182
+ if (!status) return null;
183
+ return classifyHttpError(status, error.message || String(error));
184
+ }
185
+ function classifyByMessage(message) {
186
+ const lower = message.toLowerCase();
187
+ if (lower.includes("timeout") || lower.includes("timed out") || lower.includes("aborterror")) {
188
+ return { code: ErrorCode.TIMEOUT, message: "Request timed out", retryable: true, cause: message };
189
+ }
190
+ if (lower.includes("rate limit") || lower.includes("too many requests")) {
191
+ return { code: ErrorCode.RATE_LIMITED, message: "Rate limit exceeded", retryable: true, cause: message };
192
+ }
193
+ if (message.includes("API_KEY") || message.includes("api_key") || message.includes("Invalid API")) {
194
+ return { code: ErrorCode.AUTH_ERROR, message: "API key missing or invalid", retryable: false, cause: message };
195
+ }
196
+ if (message.includes("JSON") || message.includes("parse") || message.includes("Unexpected token")) {
197
+ return { code: ErrorCode.PARSE_ERROR, message: "Failed to parse response", retryable: false, cause: message };
198
+ }
199
+ return null;
200
+ }
201
+ function classifyFallback(message, cause) {
202
+ return {
203
+ code: ErrorCode.UNKNOWN_ERROR,
204
+ message,
205
+ retryable: false,
206
+ cause: cause ? String(cause) : void 0
207
+ };
208
+ }
209
+ function classifyError(error) {
210
+ if (error == null) {
211
+ return { code: ErrorCode.UNKNOWN_ERROR, message: "An unknown error occurred", retryable: false };
212
+ }
213
+ if (error instanceof DOMException) return classifyDomException(error);
214
+ if (!isErrorLike(error)) {
215
+ return { code: ErrorCode.UNKNOWN_ERROR, message: String(error), retryable: false };
216
+ }
217
+ return classifyByErrorCode(error) ?? classifyByStatusCode(error) ?? classifyByMessage(error.message ?? String(error)) ?? classifyFallback(error.message ?? String(error), error.cause);
218
+ }
219
+ function isErrorLike(value) {
220
+ return typeof value === "object" && value !== null;
221
+ }
222
+ function classifyHttpError(status, message) {
223
+ switch (status) {
224
+ case 400:
225
+ return { code: ErrorCode.INVALID_INPUT, message: "Bad request", retryable: false, statusCode: status };
226
+ case 401:
227
+ return { code: ErrorCode.AUTH_ERROR, message: "Invalid API key", retryable: false, statusCode: status };
228
+ case 403:
229
+ return { code: ErrorCode.QUOTA_EXCEEDED, message: "Access forbidden or quota exceeded", retryable: false, statusCode: status };
230
+ case 404:
231
+ return { code: ErrorCode.NOT_FOUND, message: "Resource not found", retryable: false, statusCode: status };
232
+ case 408:
233
+ return { code: ErrorCode.TIMEOUT, message: "Request timeout", retryable: true, statusCode: status };
234
+ case 429:
235
+ return { code: ErrorCode.RATE_LIMITED, message: "Rate limit exceeded", retryable: true, statusCode: status };
236
+ case 500:
237
+ return { code: ErrorCode.INTERNAL_ERROR, message: "Server error", retryable: true, statusCode: status };
238
+ case 502:
239
+ return { code: ErrorCode.SERVICE_UNAVAILABLE, message: "Bad gateway", retryable: true, statusCode: status };
240
+ case 503:
241
+ return { code: ErrorCode.SERVICE_UNAVAILABLE, message: "Service unavailable", retryable: true, statusCode: status };
242
+ case 504:
243
+ return { code: ErrorCode.TIMEOUT, message: "Gateway timeout", retryable: true, statusCode: status };
244
+ case 510:
245
+ return { code: ErrorCode.SERVICE_UNAVAILABLE, message: "Request canceled", retryable: true, statusCode: status };
246
+ default:
247
+ if (status >= 500) {
248
+ return { code: ErrorCode.SERVICE_UNAVAILABLE, message: `Server error: ${status}`, retryable: true, statusCode: status };
249
+ }
250
+ if (status >= 400) {
251
+ return { code: ErrorCode.INVALID_INPUT, message: `Client error: ${status}`, retryable: false, statusCode: status };
252
+ }
253
+ return { code: ErrorCode.UNKNOWN_ERROR, message: `HTTP ${status}: ${message}`, retryable: false, statusCode: status };
254
+ }
255
+ }
256
+ function sleep(ms, signal) {
257
+ return new Promise((resolve, reject) => {
258
+ if (signal?.aborted) {
259
+ reject(new DOMException("Aborted", "AbortError"));
260
+ return;
261
+ }
262
+ function onAbort() {
263
+ clearTimeout(timeout);
264
+ reject(new DOMException("Aborted", "AbortError"));
265
+ }
266
+ const timeout = setTimeout(() => {
267
+ if (signal) signal.removeEventListener("abort", onAbort);
268
+ resolve();
269
+ }, ms);
270
+ signal?.addEventListener("abort", onAbort, { once: true });
271
+ if (signal?.aborted) {
272
+ onAbort();
273
+ }
274
+ });
275
+ }
276
+ function fetchWithTimeout(url, options = {}) {
277
+ const { timeoutMs = 3e4, signal: externalSignal, ...fetchOptions } = options;
278
+ const controller = new AbortController();
279
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
280
+ let onExternalAbort;
281
+ if (externalSignal) {
282
+ onExternalAbort = () => controller.abort();
283
+ externalSignal.addEventListener("abort", onExternalAbort, { once: true });
284
+ if (externalSignal.aborted) {
285
+ controller.abort();
286
+ }
287
+ }
288
+ return fetch(url, { ...fetchOptions, signal: controller.signal }).finally(() => {
289
+ clearTimeout(timeoutId);
290
+ if (externalSignal && onExternalAbort) {
291
+ externalSignal.removeEventListener("abort", onExternalAbort);
292
+ }
293
+ });
294
+ }
295
+
296
+ // src/utils/concurrency.ts
297
+ import pMapLib from "p-map";
298
+ async function pMapSettled(items, mapper, concurrency = 6, signal) {
299
+ if (items.length === 0) return [];
300
+ const limit = Math.max(1, Math.min(concurrency, items.length));
301
+ return pMapLib(
302
+ items,
303
+ async (item, index) => {
304
+ try {
305
+ const value = await mapper(item, index);
306
+ return { status: "fulfilled", value };
307
+ } catch (reason) {
308
+ return { status: "rejected", reason };
309
+ }
310
+ },
311
+ { concurrency: limit, signal, stopOnError: false }
312
+ );
313
+ }
314
+
315
+ // src/clients/reddit.ts
316
+ var REDDIT_TOKEN_URL = "https://www.reddit.com/api/v1/access_token";
317
+ var REDDIT_API_BASE = "https://oauth.reddit.com";
318
+ var TOKEN_EXPIRY_MS = 55e3;
319
+ var FETCH_LIMIT = 500;
320
+ var cachedToken = null;
321
+ var cachedTokenExpiry = 0;
322
+ var DEBUG_TOKEN_CACHE = process.env.DEBUG_REDDIT === "true";
323
+ var clientLogger = Logger3.get("reddit-client");
324
+ var pendingAuthPromise = null;
22
325
  async function fetchRedditJson(sub, id, token, userAgent) {
23
326
  const limit = Math.min(FETCH_LIMIT, 500);
24
327
  const apiUrl = `${REDDIT_API_BASE}/r/${sub}/comments/${id}?sort=top&limit=${limit}&depth=10&raw_json=1`;
@@ -73,7 +376,7 @@ function formatBody(p) {
73
376
  if (p.url) return `**Link:** ${p.url}`;
74
377
  return "";
75
378
  }
76
- const MAX_COMMENT_DEPTH = 15;
379
+ var MAX_COMMENT_DEPTH = 15;
77
380
  function parseCommentTree(commentListing, opAuthor) {
78
381
  const result = [];
79
382
  const extract = (items, depth = 0) => {
@@ -118,7 +421,7 @@ async function processBatch(client, batchUrls) {
118
421
  }
119
422
  return { results, rateLimitHits };
120
423
  }
121
- class RedditClient {
424
+ var RedditClient = class {
122
425
  constructor(clientId, clientSecret) {
123
426
  this.clientId = clientId;
124
427
  this.clientSecret = clientSecret;
@@ -271,7 +574,7 @@ class RedditClient {
271
574
  }
272
575
  return { results: allResults, batchesProcessed: totalBatches, totalPosts: urls.length, rateLimitHits };
273
576
  }
274
- }
577
+ };
275
578
  export {
276
579
  RedditClient
277
580
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/clients/reddit.ts"],
4
- "sourcesContent": ["/**\n * Reddit OAuth API Client\n * Fetches posts and comments sorted by score (most upvoted first)\n * Implements robust error handling that NEVER crashes\n */\n\nimport { Logger } from 'mcp-use';\n\nimport { REDDIT, CONCURRENCY } from '../config/index.js';\nimport { USER_AGENT_VERSION } from '../version.js';\nimport { calculateBackoff } from '../utils/retry.js';\nimport {\n classifyError,\n fetchWithTimeout,\n sleep,\n ErrorCode,\n type StructuredError,\n} from '../utils/errors.js';\nimport { pMap, pMapSettled } from '../utils/concurrency.js';\nimport { mcpLog } from '../utils/logger.js';\n\n// \u2500\u2500 Constants \u2500\u2500\n\nconst REDDIT_TOKEN_URL = 'https://www.reddit.com/api/v1/access_token' as const;\nconst REDDIT_API_BASE = 'https://oauth.reddit.com' as const;\nconst TOKEN_EXPIRY_MS = 55_000 as const; // 55 second expiry (conservative)\n\n// \u2500\u2500 Data Interfaces \u2500\u2500\n\ninterface Post {\n readonly title: string;\n readonly author: string;\n readonly subreddit: string;\n readonly body: string;\n readonly score: number;\n readonly commentCount: number;\n readonly url: string;\n readonly created: Date;\n readonly flair?: string;\n readonly isNsfw: boolean;\n readonly isPinned: boolean;\n}\n\nexport interface Comment {\n readonly author: string;\n readonly body: string;\n readonly score: number;\n readonly depth: number;\n readonly isOP: boolean;\n}\n\nexport interface PostResult {\n readonly post: Post;\n readonly comments: Comment[];\n readonly actualComments: number;\n}\n\nexport interface BatchPostResult {\n readonly results: Map<string, PostResult | Error>;\n readonly batchesProcessed: number;\n readonly totalPosts: number;\n readonly rateLimitHits: number;\n}\n\n/** Reddit API \"Listing\" wrapper */\ninterface RedditListing<T> {\n readonly kind: string;\n readonly data: {\n readonly children: ReadonlyArray<{ readonly kind: string; readonly data: T }>;\n readonly after?: string;\n readonly before?: string;\n };\n}\n\n/** Reddit post data from API */\ninterface RedditPostData {\n readonly title: string;\n readonly selftext: string;\n readonly selftext_html?: string;\n readonly author: string;\n readonly subreddit: string;\n readonly score: number;\n readonly upvote_ratio: number;\n readonly num_comments: number;\n readonly created_utc: number;\n readonly url: string;\n readonly permalink: string;\n readonly is_self: boolean;\n readonly over_18: boolean;\n readonly stickied: boolean;\n readonly link_flair_text?: string;\n readonly [key: string]: unknown;\n}\n\n/** Reddit comment data from API */\ninterface RedditCommentData {\n readonly body?: string;\n readonly author?: string;\n readonly score?: number;\n readonly created_utc?: number;\n readonly replies?: RedditListing<RedditCommentData> | string;\n readonly [key: string]: unknown;\n}\n\ntype RedditPostResponse = [RedditListing<RedditPostData>, RedditListing<RedditCommentData>];\n\n/** Max comments to fetch per post from Reddit API */\n/** Reddit API caps at 500 comments per request */\nconst FETCH_LIMIT = 500;\n\n// ============================================================================\n// Module-Level Token Cache (shared across all RedditClient instances)\n// ============================================================================\nlet cachedToken: string | null = null;\nlet cachedTokenExpiry = 0;\n\n// Token cache logging only when DEBUG env is set\nconst DEBUG_TOKEN_CACHE = process.env.DEBUG_REDDIT === 'true';\nconst clientLogger = Logger.get('reddit-client');\n\n// Pending auth promise for deduplicating concurrent auth calls\nlet pendingAuthPromise: Promise<string | null> | null = null;\n\n// \u2500\u2500 Decomposed Helpers \u2500\u2500\n\n/**\n * Fetch a Reddit post's JSON from the API\n */\nasync function fetchRedditJson(\n sub: string,\n id: string,\n token: string,\n userAgent: string,\n): Promise<RedditPostResponse> {\n const limit = Math.min(FETCH_LIMIT, 500);\n const apiUrl = `${REDDIT_API_BASE}/r/${sub}/comments/${id}?sort=top&limit=${limit}&depth=10&raw_json=1`;\n\n const res = await fetchWithTimeout(apiUrl, {\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'User-Agent': userAgent,\n },\n timeoutMs: 30000,\n });\n\n if (res.status === 429) {\n const err = new Error('Rate limited by Reddit API');\n (err as Error & { status: number }).status = 429;\n throw err;\n }\n\n if (res.status === 404) {\n throw new Error(`Post not found: /r/${sub}/comments/${id}`);\n }\n\n if (!res.ok) {\n const err = new Error(`Reddit API error: ${res.status}`);\n (err as Error & { status: number }).status = res.status;\n throw err;\n }\n\n try {\n return await res.json() as RedditPostResponse;\n } catch {\n throw new Error('Failed to parse Reddit API response');\n }\n}\n\n/**\n * Extract structured post data from a Reddit listing\n */\nfunction parsePostData(\n postListing: RedditListing<RedditPostData>,\n sub: string,\n): Post {\n const p = postListing?.data?.children?.[0]?.data;\n if (!p) {\n throw new Error(`Post data not found in response for /r/${sub}`);\n }\n\n return {\n title: p.title || 'Untitled',\n author: p.author || '[deleted]',\n subreddit: p.subreddit || sub,\n body: formatBody(p),\n score: p.score || 0,\n commentCount: p.num_comments || 0,\n url: `https://reddit.com${p.permalink || ''}`,\n created: new Date((p.created_utc || 0) * 1000),\n flair: p.link_flair_text || undefined,\n isNsfw: p.over_18 || false,\n isPinned: p.stickied || false,\n };\n}\n\nfunction formatBody(p: RedditPostData): string {\n if (p.selftext?.trim()) return p.selftext;\n if (p.is_self) return '';\n if (p.url) return `**Link:** ${p.url}`;\n return '';\n}\n\n/** Safety cap on comment tree recursion depth */\nconst MAX_COMMENT_DEPTH = 15 as const;\n\n/**\n * Extract and sort comments from a Reddit comment listing\n */\nfunction parseCommentTree(\n commentListing: RedditListing<RedditCommentData>,\n opAuthor: string,\n): Comment[] {\n const result: Comment[] = [];\n\n const extract = (items: ReadonlyArray<{ readonly kind: string; readonly data: RedditCommentData }>, depth = 0): void => {\n if (depth > MAX_COMMENT_DEPTH) return;\n const sorted = [...items].sort((a, b) => (b.data?.score || 0) - (a.data?.score || 0));\n\n for (const c of sorted) {\n if (c.kind !== 't1' || !c.data?.author || c.data.author === '[deleted]') continue;\n\n result.push({\n author: c.data.author,\n body: c.data.body || '',\n score: c.data.score || 0,\n depth,\n isOP: c.data.author === opAuthor,\n });\n\n if (typeof c.data.replies === 'object' && c.data.replies?.data?.children) {\n extract(c.data.replies.data.children, depth + 1);\n }\n }\n };\n\n extract(commentListing?.data?.children || []);\n return result;\n}\n\n// \u2500\u2500 Batch Helpers \u2500\u2500\n\n/**\n * Process a single batch of Reddit URLs, returning results keyed by URL\n */\nasync function processBatch(\n client: RedditClient,\n batchUrls: string[],\n): Promise<{ results: Map<string, PostResult | Error>; rateLimitHits: number }> {\n const results = new Map<string, PostResult | Error>();\n let rateLimitHits = 0;\n\n const batchResults = await pMapSettled(\n batchUrls,\n url => client.getPost(url),\n CONCURRENCY.REDDIT,\n );\n\n for (let i = 0; i < batchResults.length; i++) {\n const result = batchResults[i];\n if (!result) continue;\n const url = batchUrls[i] ?? '';\n\n if (result.status === 'fulfilled') {\n results.set(url, result.value);\n } else {\n const errorMsg = result.reason?.message || String(result.reason);\n if (errorMsg.includes('429') || errorMsg.includes('rate')) rateLimitHits++;\n results.set(url, new Error(errorMsg));\n }\n }\n\n return { results, rateLimitHits };\n}\n\n// \u2500\u2500 RedditClient Class \u2500\u2500\n\nexport class RedditClient {\n private userAgent = `script:${USER_AGENT_VERSION} (by /u/research-powerpack)`;\n\n constructor(private clientId: string, private clientSecret: string) {}\n\n /**\n * Authenticate with Reddit API with retry logic\n * Uses module-level token cache and promise deduplication to prevent\n * concurrent auth calls from firing multiple token requests\n * Returns null on failure instead of throwing\n */\n private async auth(): Promise<string | null> {\n if (cachedToken && Date.now() < cachedTokenExpiry - TOKEN_EXPIRY_MS) {\n if (DEBUG_TOKEN_CACHE) clientLogger.debug('Token cache HIT');\n return cachedToken;\n }\n\n if (pendingAuthPromise) {\n if (DEBUG_TOKEN_CACHE) clientLogger.debug('Auth already in flight, awaiting...');\n return pendingAuthPromise;\n }\n\n pendingAuthPromise = this.performAuth();\n try {\n return await pendingAuthPromise;\n } finally {\n pendingAuthPromise = null;\n }\n }\n\n private async performAuth(): Promise<string | null> {\n if (DEBUG_TOKEN_CACHE) clientLogger.debug('Token cache MISS - authenticating');\n\n const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');\n\n for (let attempt = 0; attempt < 3; attempt++) {\n try {\n const res = await fetchWithTimeout(REDDIT_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Authorization': `Basic ${credentials}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': this.userAgent,\n },\n body: 'grant_type=client_credentials',\n timeoutMs: 15000,\n });\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n mcpLog('error', `Auth failed (${res.status}): ${text}`, 'reddit');\n\n if (res.status === 401 || res.status === 403) {\n cachedToken = null;\n cachedTokenExpiry = 0;\n return null;\n }\n\n if (res.status >= 500 && attempt < 2) {\n await sleep(calculateBackoff(attempt));\n continue;\n }\n\n return null;\n }\n\n const data = await res.json() as { access_token?: string; expires_in?: number };\n if (!data.access_token) {\n mcpLog('error', 'Auth response missing access_token', 'reddit');\n return null;\n }\n\n cachedToken = data.access_token;\n cachedTokenExpiry = Date.now() + (data.expires_in || 3600) * 1000;\n return cachedToken;\n\n } catch (error) {\n const err = classifyError(error);\n mcpLog('error', `Auth error (attempt ${attempt + 1}): ${err.message}`, 'reddit');\n\n if (err.code === ErrorCode.AUTH_ERROR) {\n cachedToken = null;\n cachedTokenExpiry = 0;\n }\n\n if (attempt < 2 && err.retryable) {\n await sleep(calculateBackoff(attempt));\n continue;\n }\n\n return null;\n }\n }\n\n return null;\n }\n\n private parseUrl(url: string): { sub: string; id: string } | null {\n const m = url.match(/reddit\\.com\\/r\\/([^\\/]+)\\/comments\\/([a-z0-9]+)/i);\n return m ? { sub: m[1]!, id: m[2]! } : null;\n }\n\n /**\n * Get a single Reddit post with comments\n * Returns PostResult or throws Error (for use with Promise.allSettled)\n */\n async getPost(url: string): Promise<PostResult> {\n const parsed = this.parseUrl(url);\n if (!parsed) {\n throw new Error(`Invalid Reddit URL format: ${url}`);\n }\n\n const token = await this.auth();\n if (!token) {\n throw new Error('Reddit authentication failed - check credentials');\n }\n\n let lastError: StructuredError | null = null;\n\n for (let attempt = 0; attempt < REDDIT.RETRY_COUNT; attempt++) {\n try {\n const data = await fetchRedditJson(parsed.sub, parsed.id, token, this.userAgent);\n const [postListing, commentListing] = data;\n\n const post = parsePostData(postListing, parsed.sub);\n const comments = parseCommentTree(commentListing, post.author);\n\n return { post, comments, actualComments: post.commentCount };\n\n } catch (error) {\n lastError = classifyError(error);\n\n // Rate limited \u2014 always retry with backoff\n const status = (error as Error & { status?: number }).status;\n if (status === 429) {\n const delay = REDDIT.RETRY_DELAYS[attempt] || 32000;\n mcpLog('warning', `Rate limited. Retry ${attempt + 1}/${REDDIT.RETRY_COUNT} after ${delay}ms`, 'reddit');\n await sleep(delay);\n continue;\n }\n\n if (!lastError.retryable) {\n throw error instanceof Error ? error : new Error(lastError.message);\n }\n\n if (attempt < REDDIT.RETRY_COUNT - 1) {\n const delay = REDDIT.RETRY_DELAYS[attempt] || 2000;\n mcpLog('warning', `${lastError.code}: ${lastError.message}. Retry ${attempt + 1}/${REDDIT.RETRY_COUNT}`, 'reddit');\n await sleep(delay);\n }\n }\n }\n\n throw new Error(lastError?.message || 'Failed to fetch Reddit post after retries');\n }\n\n async batchGetPosts(\n urls: string[],\n fetchComments = true,\n onBatchComplete?: (batchNum: number, totalBatches: number, processed: number) => void,\n ): Promise<BatchPostResult> {\n const allResults = new Map<string, PostResult | Error>();\n let rateLimitHits = 0;\n\n const totalBatches = Math.ceil(urls.length / REDDIT.BATCH_SIZE);\n mcpLog('info', `Fetching ${urls.length} posts in ${totalBatches} batch(es), up to ${FETCH_LIMIT} comments/post`, 'reddit');\n\n for (let batchNum = 0; batchNum < totalBatches; batchNum++) {\n const startIdx = batchNum * REDDIT.BATCH_SIZE;\n const batchUrls = urls.slice(startIdx, startIdx + REDDIT.BATCH_SIZE);\n\n mcpLog('info', `Batch ${batchNum + 1}/${totalBatches} (${batchUrls.length} posts)`, 'reddit');\n\n const batchResult = await processBatch(this, batchUrls);\n for (const [url, result] of batchResult.results) {\n allResults.set(url, result);\n }\n rateLimitHits += batchResult.rateLimitHits;\n\n try {\n onBatchComplete?.(batchNum + 1, totalBatches, allResults.size);\n } catch (callbackError) {\n mcpLog('error', `onBatchComplete callback error: ${callbackError}`, 'reddit');\n }\n\n mcpLog('info', `Batch ${batchNum + 1} complete (${allResults.size}/${urls.length})`, 'reddit');\n\n if (batchNum < totalBatches - 1) {\n await sleep(500);\n }\n }\n\n return { results: allResults, batchesProcessed: totalBatches, totalPosts: urls.length, rateLimitHits };\n }\n}\n"],
5
- "mappings": "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",
6
- "names": []
3
+ "sources": ["../../../src/clients/reddit.ts", "../../../src/config/index.ts", "../../../src/version.ts", "../../../src/utils/retry.ts", "../../../src/utils/logger.ts", "../../../src/utils/errors.ts", "../../../src/utils/concurrency.ts"],
4
+ "sourcesContent": ["/**\n * Reddit OAuth API Client\n * Fetches posts and comments sorted by score (most upvoted first)\n * Implements robust error handling that NEVER crashes\n */\n\nimport { Logger } from 'mcp-use';\n\nimport { REDDIT, CONCURRENCY } from '../config/index.js';\nimport { USER_AGENT_VERSION } from '../version.js';\nimport { calculateBackoff } from '../utils/retry.js';\nimport {\n classifyError,\n fetchWithTimeout,\n sleep,\n ErrorCode,\n type StructuredError,\n} from '../utils/errors.js';\nimport { pMap, pMapSettled } from '../utils/concurrency.js';\nimport { mcpLog } from '../utils/logger.js';\n\n// \u2500\u2500 Constants \u2500\u2500\n\nconst REDDIT_TOKEN_URL = 'https://www.reddit.com/api/v1/access_token' as const;\nconst REDDIT_API_BASE = 'https://oauth.reddit.com' as const;\nconst TOKEN_EXPIRY_MS = 55_000 as const; // 55 second expiry (conservative)\n\n// \u2500\u2500 Data Interfaces \u2500\u2500\n\ninterface Post {\n readonly title: string;\n readonly author: string;\n readonly subreddit: string;\n readonly body: string;\n readonly score: number;\n readonly commentCount: number;\n readonly url: string;\n readonly created: Date;\n readonly flair?: string;\n readonly isNsfw: boolean;\n readonly isPinned: boolean;\n}\n\nexport interface Comment {\n readonly author: string;\n readonly body: string;\n readonly score: number;\n readonly depth: number;\n readonly isOP: boolean;\n}\n\nexport interface PostResult {\n readonly post: Post;\n readonly comments: Comment[];\n readonly actualComments: number;\n}\n\nexport interface BatchPostResult {\n readonly results: Map<string, PostResult | Error>;\n readonly batchesProcessed: number;\n readonly totalPosts: number;\n readonly rateLimitHits: number;\n}\n\n/** Reddit API \"Listing\" wrapper */\ninterface RedditListing<T> {\n readonly kind: string;\n readonly data: {\n readonly children: ReadonlyArray<{ readonly kind: string; readonly data: T }>;\n readonly after?: string;\n readonly before?: string;\n };\n}\n\n/** Reddit post data from API */\ninterface RedditPostData {\n readonly title: string;\n readonly selftext: string;\n readonly selftext_html?: string;\n readonly author: string;\n readonly subreddit: string;\n readonly score: number;\n readonly upvote_ratio: number;\n readonly num_comments: number;\n readonly created_utc: number;\n readonly url: string;\n readonly permalink: string;\n readonly is_self: boolean;\n readonly over_18: boolean;\n readonly stickied: boolean;\n readonly link_flair_text?: string;\n readonly [key: string]: unknown;\n}\n\n/** Reddit comment data from API */\ninterface RedditCommentData {\n readonly body?: string;\n readonly author?: string;\n readonly score?: number;\n readonly created_utc?: number;\n readonly replies?: RedditListing<RedditCommentData> | string;\n readonly [key: string]: unknown;\n}\n\ntype RedditPostResponse = [RedditListing<RedditPostData>, RedditListing<RedditCommentData>];\n\n/** Max comments to fetch per post from Reddit API */\n/** Reddit API caps at 500 comments per request */\nconst FETCH_LIMIT = 500;\n\n// ============================================================================\n// Module-Level Token Cache (shared across all RedditClient instances)\n// ============================================================================\nlet cachedToken: string | null = null;\nlet cachedTokenExpiry = 0;\n\n// Token cache logging only when DEBUG env is set\nconst DEBUG_TOKEN_CACHE = process.env.DEBUG_REDDIT === 'true';\nconst clientLogger = Logger.get('reddit-client');\n\n// Pending auth promise for deduplicating concurrent auth calls\nlet pendingAuthPromise: Promise<string | null> | null = null;\n\n// \u2500\u2500 Decomposed Helpers \u2500\u2500\n\n/**\n * Fetch a Reddit post's JSON from the API\n */\nasync function fetchRedditJson(\n sub: string,\n id: string,\n token: string,\n userAgent: string,\n): Promise<RedditPostResponse> {\n const limit = Math.min(FETCH_LIMIT, 500);\n const apiUrl = `${REDDIT_API_BASE}/r/${sub}/comments/${id}?sort=top&limit=${limit}&depth=10&raw_json=1`;\n\n const res = await fetchWithTimeout(apiUrl, {\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'User-Agent': userAgent,\n },\n timeoutMs: 30000,\n });\n\n if (res.status === 429) {\n const err = new Error('Rate limited by Reddit API');\n (err as Error & { status: number }).status = 429;\n throw err;\n }\n\n if (res.status === 404) {\n throw new Error(`Post not found: /r/${sub}/comments/${id}`);\n }\n\n if (!res.ok) {\n const err = new Error(`Reddit API error: ${res.status}`);\n (err as Error & { status: number }).status = res.status;\n throw err;\n }\n\n try {\n return await res.json() as RedditPostResponse;\n } catch {\n throw new Error('Failed to parse Reddit API response');\n }\n}\n\n/**\n * Extract structured post data from a Reddit listing\n */\nfunction parsePostData(\n postListing: RedditListing<RedditPostData>,\n sub: string,\n): Post {\n const p = postListing?.data?.children?.[0]?.data;\n if (!p) {\n throw new Error(`Post data not found in response for /r/${sub}`);\n }\n\n return {\n title: p.title || 'Untitled',\n author: p.author || '[deleted]',\n subreddit: p.subreddit || sub,\n body: formatBody(p),\n score: p.score || 0,\n commentCount: p.num_comments || 0,\n url: `https://reddit.com${p.permalink || ''}`,\n created: new Date((p.created_utc || 0) * 1000),\n flair: p.link_flair_text || undefined,\n isNsfw: p.over_18 || false,\n isPinned: p.stickied || false,\n };\n}\n\nfunction formatBody(p: RedditPostData): string {\n if (p.selftext?.trim()) return p.selftext;\n if (p.is_self) return '';\n if (p.url) return `**Link:** ${p.url}`;\n return '';\n}\n\n/** Safety cap on comment tree recursion depth */\nconst MAX_COMMENT_DEPTH = 15 as const;\n\n/**\n * Extract and sort comments from a Reddit comment listing\n */\nfunction parseCommentTree(\n commentListing: RedditListing<RedditCommentData>,\n opAuthor: string,\n): Comment[] {\n const result: Comment[] = [];\n\n const extract = (items: ReadonlyArray<{ readonly kind: string; readonly data: RedditCommentData }>, depth = 0): void => {\n if (depth > MAX_COMMENT_DEPTH) return;\n const sorted = [...items].sort((a, b) => (b.data?.score || 0) - (a.data?.score || 0));\n\n for (const c of sorted) {\n if (c.kind !== 't1' || !c.data?.author || c.data.author === '[deleted]') continue;\n\n result.push({\n author: c.data.author,\n body: c.data.body || '',\n score: c.data.score || 0,\n depth,\n isOP: c.data.author === opAuthor,\n });\n\n if (typeof c.data.replies === 'object' && c.data.replies?.data?.children) {\n extract(c.data.replies.data.children, depth + 1);\n }\n }\n };\n\n extract(commentListing?.data?.children || []);\n return result;\n}\n\n// \u2500\u2500 Batch Helpers \u2500\u2500\n\n/**\n * Process a single batch of Reddit URLs, returning results keyed by URL\n */\nasync function processBatch(\n client: RedditClient,\n batchUrls: string[],\n): Promise<{ results: Map<string, PostResult | Error>; rateLimitHits: number }> {\n const results = new Map<string, PostResult | Error>();\n let rateLimitHits = 0;\n\n const batchResults = await pMapSettled(\n batchUrls,\n url => client.getPost(url),\n CONCURRENCY.REDDIT,\n );\n\n for (let i = 0; i < batchResults.length; i++) {\n const result = batchResults[i];\n if (!result) continue;\n const url = batchUrls[i] ?? '';\n\n if (result.status === 'fulfilled') {\n results.set(url, result.value);\n } else {\n const errorMsg = result.reason?.message || String(result.reason);\n if (errorMsg.includes('429') || errorMsg.includes('rate')) rateLimitHits++;\n results.set(url, new Error(errorMsg));\n }\n }\n\n return { results, rateLimitHits };\n}\n\n// \u2500\u2500 RedditClient Class \u2500\u2500\n\nexport class RedditClient {\n private userAgent = `script:${USER_AGENT_VERSION} (by /u/research-powerpack)`;\n\n constructor(private clientId: string, private clientSecret: string) {}\n\n /**\n * Authenticate with Reddit API with retry logic\n * Uses module-level token cache and promise deduplication to prevent\n * concurrent auth calls from firing multiple token requests\n * Returns null on failure instead of throwing\n */\n private async auth(): Promise<string | null> {\n if (cachedToken && Date.now() < cachedTokenExpiry - TOKEN_EXPIRY_MS) {\n if (DEBUG_TOKEN_CACHE) clientLogger.debug('Token cache HIT');\n return cachedToken;\n }\n\n if (pendingAuthPromise) {\n if (DEBUG_TOKEN_CACHE) clientLogger.debug('Auth already in flight, awaiting...');\n return pendingAuthPromise;\n }\n\n pendingAuthPromise = this.performAuth();\n try {\n return await pendingAuthPromise;\n } finally {\n pendingAuthPromise = null;\n }\n }\n\n private async performAuth(): Promise<string | null> {\n if (DEBUG_TOKEN_CACHE) clientLogger.debug('Token cache MISS - authenticating');\n\n const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');\n\n for (let attempt = 0; attempt < 3; attempt++) {\n try {\n const res = await fetchWithTimeout(REDDIT_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Authorization': `Basic ${credentials}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': this.userAgent,\n },\n body: 'grant_type=client_credentials',\n timeoutMs: 15000,\n });\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n mcpLog('error', `Auth failed (${res.status}): ${text}`, 'reddit');\n\n if (res.status === 401 || res.status === 403) {\n cachedToken = null;\n cachedTokenExpiry = 0;\n return null;\n }\n\n if (res.status >= 500 && attempt < 2) {\n await sleep(calculateBackoff(attempt));\n continue;\n }\n\n return null;\n }\n\n const data = await res.json() as { access_token?: string; expires_in?: number };\n if (!data.access_token) {\n mcpLog('error', 'Auth response missing access_token', 'reddit');\n return null;\n }\n\n cachedToken = data.access_token;\n cachedTokenExpiry = Date.now() + (data.expires_in || 3600) * 1000;\n return cachedToken;\n\n } catch (error) {\n const err = classifyError(error);\n mcpLog('error', `Auth error (attempt ${attempt + 1}): ${err.message}`, 'reddit');\n\n if (err.code === ErrorCode.AUTH_ERROR) {\n cachedToken = null;\n cachedTokenExpiry = 0;\n }\n\n if (attempt < 2 && err.retryable) {\n await sleep(calculateBackoff(attempt));\n continue;\n }\n\n return null;\n }\n }\n\n return null;\n }\n\n private parseUrl(url: string): { sub: string; id: string } | null {\n const m = url.match(/reddit\\.com\\/r\\/([^\\/]+)\\/comments\\/([a-z0-9]+)/i);\n return m ? { sub: m[1]!, id: m[2]! } : null;\n }\n\n /**\n * Get a single Reddit post with comments\n * Returns PostResult or throws Error (for use with Promise.allSettled)\n */\n async getPost(url: string): Promise<PostResult> {\n const parsed = this.parseUrl(url);\n if (!parsed) {\n throw new Error(`Invalid Reddit URL format: ${url}`);\n }\n\n const token = await this.auth();\n if (!token) {\n throw new Error('Reddit authentication failed - check credentials');\n }\n\n let lastError: StructuredError | null = null;\n\n for (let attempt = 0; attempt < REDDIT.RETRY_COUNT; attempt++) {\n try {\n const data = await fetchRedditJson(parsed.sub, parsed.id, token, this.userAgent);\n const [postListing, commentListing] = data;\n\n const post = parsePostData(postListing, parsed.sub);\n const comments = parseCommentTree(commentListing, post.author);\n\n return { post, comments, actualComments: post.commentCount };\n\n } catch (error) {\n lastError = classifyError(error);\n\n // Rate limited \u2014 always retry with backoff\n const status = (error as Error & { status?: number }).status;\n if (status === 429) {\n const delay = REDDIT.RETRY_DELAYS[attempt] || 32000;\n mcpLog('warning', `Rate limited. Retry ${attempt + 1}/${REDDIT.RETRY_COUNT} after ${delay}ms`, 'reddit');\n await sleep(delay);\n continue;\n }\n\n if (!lastError.retryable) {\n throw error instanceof Error ? error : new Error(lastError.message);\n }\n\n if (attempt < REDDIT.RETRY_COUNT - 1) {\n const delay = REDDIT.RETRY_DELAYS[attempt] || 2000;\n mcpLog('warning', `${lastError.code}: ${lastError.message}. Retry ${attempt + 1}/${REDDIT.RETRY_COUNT}`, 'reddit');\n await sleep(delay);\n }\n }\n }\n\n throw new Error(lastError?.message || 'Failed to fetch Reddit post after retries');\n }\n\n async batchGetPosts(\n urls: string[],\n fetchComments = true,\n onBatchComplete?: (batchNum: number, totalBatches: number, processed: number) => void,\n ): Promise<BatchPostResult> {\n const allResults = new Map<string, PostResult | Error>();\n let rateLimitHits = 0;\n\n const totalBatches = Math.ceil(urls.length / REDDIT.BATCH_SIZE);\n mcpLog('info', `Fetching ${urls.length} posts in ${totalBatches} batch(es), up to ${FETCH_LIMIT} comments/post`, 'reddit');\n\n for (let batchNum = 0; batchNum < totalBatches; batchNum++) {\n const startIdx = batchNum * REDDIT.BATCH_SIZE;\n const batchUrls = urls.slice(startIdx, startIdx + REDDIT.BATCH_SIZE);\n\n mcpLog('info', `Batch ${batchNum + 1}/${totalBatches} (${batchUrls.length} posts)`, 'reddit');\n\n const batchResult = await processBatch(this, batchUrls);\n for (const [url, result] of batchResult.results) {\n allResults.set(url, result);\n }\n rateLimitHits += batchResult.rateLimitHits;\n\n try {\n onBatchComplete?.(batchNum + 1, totalBatches, allResults.size);\n } catch (callbackError) {\n mcpLog('error', `onBatchComplete callback error: ${callbackError}`, 'reddit');\n }\n\n mcpLog('info', `Batch ${batchNum + 1} complete (${allResults.size}/${urls.length})`, 'reddit');\n\n if (batchNum < totalBatches - 1) {\n await sleep(500);\n }\n }\n\n return { results: allResults, batchesProcessed: totalBatches, totalPosts: urls.length, rateLimitHits };\n }\n}\n", "/**\n * Consolidated configuration\n * All environment variables, constants, and LLM config in one place\n */\n\nimport { Logger } from 'mcp-use';\n\nimport { VERSION, PACKAGE_NAME, PACKAGE_DESCRIPTION } from '../version.js';\n\n// ============================================================================\n// Safe Integer Parsing Helper\n// ============================================================================\n\n/**\n * Safely parse an integer from environment variable with bounds checking\n */\nfunction safeParseInt(\n value: string | undefined,\n defaultVal: number,\n min: number,\n max: number\n): number {\n const logger = Logger.get('config');\n\n if (!value) {\n return defaultVal;\n }\n\n const parsed = parseInt(value, 10);\n\n if (isNaN(parsed)) {\n logger.warn(`Invalid number \"${value}\", using default ${defaultVal}`);\n return defaultVal;\n }\n\n if (parsed < min) {\n logger.warn(`Value ${parsed} below minimum ${min}, clamping to ${min}`);\n return min;\n }\n\n if (parsed > max) {\n logger.warn(`Value ${parsed} above maximum ${max}, clamping to ${max}`);\n return max;\n }\n\n return parsed;\n}\n\n\n// ============================================================================\n// Environment Parsing\n// ============================================================================\n\ninterface EnvConfig {\n SCRAPER_API_KEY: string;\n SEARCH_API_KEY: string | undefined;\n REDDIT_CLIENT_ID: string | undefined;\n REDDIT_CLIENT_SECRET: string | undefined;\n JINA_API_KEY: string | undefined;\n KERNEL_API_KEY: string | undefined;\n KERNEL_PROJECT: string | undefined;\n}\n\nlet cachedEnv: EnvConfig | null = null;\n\nexport function parseEnv(): EnvConfig {\n if (cachedEnv) return cachedEnv;\n cachedEnv = {\n SCRAPER_API_KEY: process.env.SCRAPEDO_API_KEY || '',\n SEARCH_API_KEY: process.env.SERPER_API_KEY || undefined,\n REDDIT_CLIENT_ID: process.env.REDDIT_CLIENT_ID || undefined,\n REDDIT_CLIENT_SECRET: process.env.REDDIT_CLIENT_SECRET || undefined,\n JINA_API_KEY: process.env.JINA_API_KEY || undefined,\n KERNEL_API_KEY: process.env.KERNEL_API_KEY || undefined,\n KERNEL_PROJECT: process.env.KERNEL_PROJECT || undefined,\n };\n return cachedEnv;\n}\n\n// ============================================================================\n// MCP Server Configuration\n// ============================================================================\n\nexport const SERVER = {\n NAME: PACKAGE_NAME,\n VERSION: VERSION,\n DESCRIPTION: PACKAGE_DESCRIPTION,\n} as const;\n\n// ============================================================================\n// Capability Detection (which features are available based on ENV)\n// ============================================================================\n\nexport interface Capabilities {\n reddit: boolean; // REDDIT_CLIENT_ID + REDDIT_CLIENT_SECRET\n search: boolean; // SERPER_API_KEY or JINA_API_KEY\n serperSearch: boolean; // SERPER_API_KEY\n jina: boolean; // JINA_API_KEY (Reader auth + Search)\n scraping: boolean; // SCRAPEDO_API_KEY\n kernel: boolean; // KERNEL_API_KEY\n llmExtraction: boolean; // LLM_API_KEY + LLM_BASE_URL + LLM_MODEL\n}\n\nexport function getCapabilities(): Capabilities {\n const env = parseEnv();\n return {\n reddit: !!(env.REDDIT_CLIENT_ID && env.REDDIT_CLIENT_SECRET),\n search: !!(env.SEARCH_API_KEY || env.JINA_API_KEY),\n serperSearch: !!env.SEARCH_API_KEY,\n jina: !!env.JINA_API_KEY,\n scraping: !!env.SCRAPER_API_KEY,\n kernel: !!env.KERNEL_API_KEY,\n llmExtraction: getLLMConfigStatus().configured,\n };\n}\n\nexport function getMissingEnvMessage(capability: keyof Capabilities): string {\n const messages: Record<keyof Capabilities, string> = {\n reddit: '\u274C **Reddit scraping unavailable.** Set `REDDIT_CLIENT_ID` and `REDDIT_CLIENT_SECRET` to enable threaded Reddit post fetching in `raw-scrape-links` and `smart-scrape-links`.\\n\\n\uD83D\uDC49 Create a Reddit app at: https://www.reddit.com/prefs/apps (select \"script\" type)',\n search: '\u274C **Search unavailable.** Set `SERPER_API_KEY` or `JINA_API_KEY` to enable `raw-web-search` and `smart-web-search`.\\n\\n\uD83D\uDC49 Serper provides Google SERPs; Jina Search is used as a fallback/search-only provider.',\n serperSearch: '\u274C **Serper search unavailable.** Set `SERPER_API_KEY` to enable Google-backed primary search.',\n jina: '\u274C **Jina unavailable.** Set `JINA_API_KEY` to enable Jina Search and authenticated Jina Reader requests.',\n scraping: '\u26A0\uFE0F **Scrape.do proxy fallback unavailable.** Set `SCRAPEDO_API_KEY` to enable Jina Reader retries through Scrape.do proxy mode.\\n\\n\uD83D\uDC49 Sign up at: https://scrape.do (1,000 free credits)',\n kernel: '\u274C **Kernel browser rendering unavailable.** Set `KERNEL_API_KEY` to enable optional browser-render fallback for raw/smart scraping.',\n llmExtraction: '\u26A0\uFE0F **AI extraction disabled.** Set `LLM_API_KEY`, `LLM_BASE_URL`, and `LLM_MODEL` to enable `smart-web-search` and `smart-scrape-links`.\\n\\nUse the raw tools when you need markdown without LLM processing.',\n };\n return messages[capability];\n}\n\n// ============================================================================\n// Concurrency Limits\n// ============================================================================\n\nexport const CONCURRENCY = {\n SEARCH: safeParseInt(process.env.CONCURRENCY_SEARCH, 50, 1, 200),\n SCRAPER: safeParseInt(process.env.CONCURRENCY_SCRAPER, 50, 1, 200),\n JINA_READER: safeParseInt(process.env.CONCURRENCY_JINA_READER, 50, 1, 200),\n REDDIT: safeParseInt(process.env.CONCURRENCY_REDDIT, 50, 1, 200),\n LLM_EXTRACTION: safeParseInt(process.env.LLM_CONCURRENCY, 50, 1, 200),\n KERNEL: safeParseInt(process.env.CONCURRENCY_KERNEL, 3, 1, 20),\n} as const;\n\nexport const SCRAPER = {\n BATCH_SIZE: 30,\n EXTRACTION_PREFIX: 'Extract from document only \u2014 never hallucinate or add external knowledge.',\n EXTRACTION_SUFFIX: 'First line = content, not preamble. No confirmation messages.',\n} as const;\n\n// ============================================================================\n// Reddit Configuration\n// ============================================================================\n\nexport const REDDIT = {\n BATCH_SIZE: 10,\n MAX_WORDS_PER_POST: 50_000,\n MAX_WORDS_TOTAL: 500_000,\n MIN_POSTS: 1,\n MAX_POSTS: 50,\n RETRY_COUNT: 5,\n RETRY_DELAYS: [2000, 4000, 8000, 16000, 32000] as const,\n} as const;\n\n// ============================================================================\n// CTR Weights for URL Ranking (inspired from CTR research)\n// ============================================================================\n\nexport const CTR_WEIGHTS: Record<number, number> = {\n 1: 100.00,\n 2: 60.00,\n 3: 48.89,\n 4: 33.33,\n 5: 28.89,\n 6: 26.44,\n 7: 24.44,\n 8: 17.78,\n 9: 13.33,\n 10: 12.56,\n} as const;\n\n// ============================================================================\n// LLM Configuration\n//\n// Required vars (all must be set together when LLM is enabled):\n// LLM_API_KEY \u2014 API key for the OpenAI-compatible endpoint\n// LLM_BASE_URL \u2014 endpoint base URL (e.g. https://server.up.railway.app/v1)\n// LLM_MODEL \u2014 primary model (e.g. gpt-5.4-mini)\n//\n// Optional:\n// LLM_FALLBACK_MODEL \u2014 model to use after primary exhausts all retries (e.g. gpt-5.4)\n// LLM_CONCURRENCY \u2014 parallel LLM calls (default: 50)\n//\n// Reasoning effort is always 'low' \u2014 not configurable.\n// ============================================================================\n\ninterface LlmExtractionConfig {\n readonly MODEL: string;\n readonly FALLBACK_MODEL: string;\n readonly BASE_URL: string;\n readonly API_KEY: string;\n}\n\nexport type LLMRequiredEnvVar = 'LLM_API_KEY' | 'LLM_BASE_URL' | 'LLM_MODEL';\n\nexport interface LLMConfigStatus {\n readonly configured: boolean;\n readonly apiKeyPresent: boolean;\n readonly baseUrlPresent: boolean;\n readonly modelPresent: boolean;\n readonly missingVars: readonly LLMRequiredEnvVar[];\n readonly error: string | null;\n}\n\nlet cachedLlmConfigStatus: LLMConfigStatus | null = null;\n\nexport function getLLMConfigStatus(): LLMConfigStatus {\n if (cachedLlmConfigStatus) return cachedLlmConfigStatus;\n\n const apiKeyPresent = !!process.env.LLM_API_KEY?.trim();\n const baseUrlPresent = !!process.env.LLM_BASE_URL?.trim();\n const modelPresent = !!process.env.LLM_MODEL?.trim();\n const missingVars: LLMRequiredEnvVar[] = [];\n\n if (!apiKeyPresent) missingVars.push('LLM_API_KEY');\n if (!baseUrlPresent) missingVars.push('LLM_BASE_URL');\n if (!modelPresent) missingVars.push('LLM_MODEL');\n\n const configured = missingVars.length === 0;\n cachedLlmConfigStatus = {\n configured,\n apiKeyPresent,\n baseUrlPresent,\n modelPresent,\n missingVars,\n error: configured\n ? null\n : `LLM disabled: missing ${missingVars.join(', ')}`,\n };\n return cachedLlmConfigStatus;\n}\n\n/**\n * Test-only \u2014 drop every env-derived cache so a test can mutate process.env\n * and re-read fresh values. Covers the LLM config caches AND the parseEnv()\n * cache (which holds SCRAPEDO_API_KEY, SERPER_API_KEY, REDDIT_CLIENT_*,\n * JINA_API_KEY, KERNEL_API_KEY, KERNEL_PROJECT). Tests that scrub non-LLM env vars must also see a clean\n * env on the next parseEnv() call, otherwise getCapabilities() returns\n * stale flags.\n */\nexport function _resetLLMConfigStatusForTests(): void {\n cachedLlmConfigStatus = null;\n cachedLlmExtraction = null;\n cachedEnv = null;\n}\n\nlet cachedLlmExtraction: LlmExtractionConfig | null = null;\n\nfunction getLlmExtraction(): LlmExtractionConfig {\n if (cachedLlmExtraction) return cachedLlmExtraction;\n\n const apiKey = process.env.LLM_API_KEY?.trim() || '';\n const baseUrl = process.env.LLM_BASE_URL?.trim();\n const model = process.env.LLM_MODEL?.trim();\n const fallbackModel = process.env.LLM_FALLBACK_MODEL?.trim() || '';\n\n if (apiKey && !baseUrl) {\n throw new Error(\n 'LLM_BASE_URL is required when LLM_API_KEY is set. ' +\n 'Set LLM_BASE_URL to your OpenAI-compatible endpoint.',\n );\n }\n if (apiKey && !model) {\n throw new Error(\n 'LLM_MODEL is required when LLM_API_KEY is set.',\n );\n }\n\n cachedLlmExtraction = {\n API_KEY: apiKey,\n BASE_URL: baseUrl || '',\n MODEL: model || '',\n FALLBACK_MODEL: fallbackModel,\n };\n return cachedLlmExtraction;\n}\n\nexport const LLM_EXTRACTION: LlmExtractionConfig = new Proxy({} as LlmExtractionConfig, {\n get(_target, prop: string) {\n return getLlmExtraction()[prop as keyof LlmExtractionConfig];\n },\n});\n", "/**\n * Version Module - Single Source of Truth\n * \n * This module reads package metadata from package.json at runtime and keeps a\n * fallback copy for environments where package.json cannot be resolved.\n * \n * Usage:\n * import { VERSION, PACKAGE_NAME } from './version.js';\n */\n\nimport { createRequire } from 'module';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\n\n// Defaults used if package.json cannot be loaded at runtime.\nconst DEFAULT_PACKAGE_INFO = {\n version: '3.9.5',\n name: 'mcp-researchpowerpack-http',\n description: 'Research Powerpack MCP Server',\n} as const;\n\nlet packageJson: { version: string; name: string; description: string } = { ...DEFAULT_PACKAGE_INFO };\n\ntry {\n if (typeof import.meta.url === 'string' && import.meta.url.startsWith('file:')) {\n const _require = createRequire(import.meta.url);\n const _dirname = dirname(fileURLToPath(import.meta.url));\n try {\n packageJson = _require(join(_dirname, '..', 'package.json'));\n } catch {\n packageJson = _require(join(_dirname, '..', '..', 'package.json'));\n }\n }\n} catch {\n // Keep hardcoded defaults when package.json is unavailable\n}\n\n/**\n * Package version from package.json\n * This is the single source of truth for versioning\n */\nexport const VERSION: string = packageJson.version;\n\n/**\n * Package name from package.json\n */\nexport const PACKAGE_NAME: string = packageJson.name;\n\n/**\n * Package description from package.json\n */\nexport const PACKAGE_DESCRIPTION: string = packageJson.description;\n\n/**\n * Formatted version string for user agents and logging\n * Example: \"mcp-researchpowerpack-http/3.2.0\"\n */\nexport const USER_AGENT_VERSION: string = `${PACKAGE_NAME}/${VERSION}`;\n\n// VERSION_INFO removed - unused, individual exports sufficient\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 * 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 * 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": "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",
6
+ "names": ["Logger", "Logger", "Logger"]
7
7
  }