mcp-researchpowerpack 3.6.9

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 (119) hide show
  1. package/README.md +635 -0
  2. package/dist/clients/reddit.d.ts +74 -0
  3. package/dist/clients/reddit.d.ts.map +1 -0
  4. package/dist/clients/reddit.js +305 -0
  5. package/dist/clients/reddit.js.map +1 -0
  6. package/dist/clients/research.d.ts +67 -0
  7. package/dist/clients/research.d.ts.map +1 -0
  8. package/dist/clients/research.js +252 -0
  9. package/dist/clients/research.js.map +1 -0
  10. package/dist/clients/scraper.d.ts +71 -0
  11. package/dist/clients/scraper.d.ts.map +1 -0
  12. package/dist/clients/scraper.js +321 -0
  13. package/dist/clients/scraper.js.map +1 -0
  14. package/dist/clients/search.d.ts +62 -0
  15. package/dist/clients/search.d.ts.map +1 -0
  16. package/dist/clients/search.js +219 -0
  17. package/dist/clients/search.js.map +1 -0
  18. package/dist/config/index.d.ts +62 -0
  19. package/dist/config/index.d.ts.map +1 -0
  20. package/dist/config/index.js +142 -0
  21. package/dist/config/index.js.map +1 -0
  22. package/dist/config/loader.d.ts +40 -0
  23. package/dist/config/loader.d.ts.map +1 -0
  24. package/dist/config/loader.js +305 -0
  25. package/dist/config/loader.js.map +1 -0
  26. package/dist/config/types.d.ts +81 -0
  27. package/dist/config/types.d.ts.map +1 -0
  28. package/dist/config/types.js +6 -0
  29. package/dist/config/types.js.map +1 -0
  30. package/dist/config/yaml/tools.yaml +130 -0
  31. package/dist/index.d.ts +7 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +271 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/schemas/deep-research.d.ts +64 -0
  36. package/dist/schemas/deep-research.d.ts.map +1 -0
  37. package/dist/schemas/deep-research.js +224 -0
  38. package/dist/schemas/deep-research.js.map +1 -0
  39. package/dist/schemas/scrape-links.d.ts +32 -0
  40. package/dist/schemas/scrape-links.d.ts.map +1 -0
  41. package/dist/schemas/scrape-links.js +34 -0
  42. package/dist/schemas/scrape-links.js.map +1 -0
  43. package/dist/schemas/web-search.d.ts +22 -0
  44. package/dist/schemas/web-search.d.ts.map +1 -0
  45. package/dist/schemas/web-search.js +21 -0
  46. package/dist/schemas/web-search.js.map +1 -0
  47. package/dist/services/file-attachment.d.ts +30 -0
  48. package/dist/services/file-attachment.d.ts.map +1 -0
  49. package/dist/services/file-attachment.js +199 -0
  50. package/dist/services/file-attachment.js.map +1 -0
  51. package/dist/services/llm-processor.d.ts +27 -0
  52. package/dist/services/llm-processor.d.ts.map +1 -0
  53. package/dist/services/llm-processor.js +179 -0
  54. package/dist/services/llm-processor.js.map +1 -0
  55. package/dist/services/markdown-cleaner.d.ts +8 -0
  56. package/dist/services/markdown-cleaner.d.ts.map +1 -0
  57. package/dist/services/markdown-cleaner.js +44 -0
  58. package/dist/services/markdown-cleaner.js.map +1 -0
  59. package/dist/tools/definitions.d.ts +16 -0
  60. package/dist/tools/definitions.d.ts.map +1 -0
  61. package/dist/tools/definitions.js +17 -0
  62. package/dist/tools/definitions.js.map +1 -0
  63. package/dist/tools/reddit.d.ts +14 -0
  64. package/dist/tools/reddit.d.ts.map +1 -0
  65. package/dist/tools/reddit.js +213 -0
  66. package/dist/tools/reddit.js.map +1 -0
  67. package/dist/tools/registry.d.ts +71 -0
  68. package/dist/tools/registry.d.ts.map +1 -0
  69. package/dist/tools/registry.js +242 -0
  70. package/dist/tools/registry.js.map +1 -0
  71. package/dist/tools/research.d.ts +14 -0
  72. package/dist/tools/research.d.ts.map +1 -0
  73. package/dist/tools/research.js +194 -0
  74. package/dist/tools/research.js.map +1 -0
  75. package/dist/tools/scrape.d.ts +14 -0
  76. package/dist/tools/scrape.d.ts.map +1 -0
  77. package/dist/tools/scrape.js +201 -0
  78. package/dist/tools/scrape.js.map +1 -0
  79. package/dist/tools/search.d.ts +10 -0
  80. package/dist/tools/search.d.ts.map +1 -0
  81. package/dist/tools/search.js +137 -0
  82. package/dist/tools/search.js.map +1 -0
  83. package/dist/tools/utils.d.ts +105 -0
  84. package/dist/tools/utils.d.ts.map +1 -0
  85. package/dist/tools/utils.js +159 -0
  86. package/dist/tools/utils.js.map +1 -0
  87. package/dist/utils/concurrency.d.ts +29 -0
  88. package/dist/utils/concurrency.d.ts.map +1 -0
  89. package/dist/utils/concurrency.js +73 -0
  90. package/dist/utils/concurrency.js.map +1 -0
  91. package/dist/utils/errors.d.ts +77 -0
  92. package/dist/utils/errors.d.ts.map +1 -0
  93. package/dist/utils/errors.js +335 -0
  94. package/dist/utils/errors.js.map +1 -0
  95. package/dist/utils/logger.d.ts +39 -0
  96. package/dist/utils/logger.d.ts.map +1 -0
  97. package/dist/utils/logger.js +57 -0
  98. package/dist/utils/logger.js.map +1 -0
  99. package/dist/utils/markdown-formatter.d.ts +5 -0
  100. package/dist/utils/markdown-formatter.d.ts.map +1 -0
  101. package/dist/utils/markdown-formatter.js +15 -0
  102. package/dist/utils/markdown-formatter.js.map +1 -0
  103. package/dist/utils/response.d.ts +88 -0
  104. package/dist/utils/response.d.ts.map +1 -0
  105. package/dist/utils/response.js +151 -0
  106. package/dist/utils/response.js.map +1 -0
  107. package/dist/utils/url-aggregator.d.ts +90 -0
  108. package/dist/utils/url-aggregator.d.ts.map +1 -0
  109. package/dist/utils/url-aggregator.js +502 -0
  110. package/dist/utils/url-aggregator.js.map +1 -0
  111. package/dist/version.d.ts +30 -0
  112. package/dist/version.d.ts.map +1 -0
  113. package/dist/version.js +60 -0
  114. package/dist/version.js.map +1 -0
  115. package/dist/worker.d.ts +17 -0
  116. package/dist/worker.d.ts.map +1 -0
  117. package/dist/worker.js +53 -0
  118. package/dist/worker.js.map +1 -0
  119. package/package.json +73 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reddit.d.ts","sourceRoot":"","sources":["../../src/clients/reddit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,UAAU,IAAI;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,GAAG,KAAK,CAAC,CAAC;IACzC,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,iBAAiB,CAAC;CACtC;AAED,UAAU,iBAAiB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,CAK/E;AAcD,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ;IAAU,OAAO,CAAC,YAAY;IAF1D,OAAO,CAAC,SAAS,CAA6D;gBAE1D,QAAQ,EAAE,MAAM,EAAU,YAAY,EAAE,MAAM;IAElE;;;;;OAKG;YACW,IAAI;IAqBlB;;OAEG;YACW,WAAW;IAuEzB,OAAO,CAAC,QAAQ;IAKhB;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,SAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA2GlE,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,eAAe;IA4BjB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,SAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,GAAG,KAAK,CAAC,CAAC;IAarF,aAAa,CACjB,IAAI,EAAE,MAAM,EAAE,EACd,mBAAmB,CAAC,EAAE,MAAM,EAC5B,aAAa,UAAO,EACpB,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,GACpF,OAAO,CAAC,eAAe,CAAC;CAqD5B"}
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Reddit OAuth API Client
3
+ * Fetches posts and comments sorted by score (most upvoted first)
4
+ * Implements robust error handling that NEVER crashes
5
+ */
6
+ import { REDDIT } from '../config/index.js';
7
+ import { USER_AGENT_VERSION } from '../version.js';
8
+ import { classifyError, fetchWithTimeout, sleep, ErrorCode, } from '../utils/errors.js';
9
+ import { pMap, pMapSettled } from '../utils/concurrency.js';
10
+ import { mcpLog } from '../utils/logger.js';
11
+ export function calculateCommentAllocation(postCount) {
12
+ const totalBudget = REDDIT.MAX_COMMENT_BUDGET;
13
+ const perPostBase = Math.floor(totalBudget / postCount);
14
+ const perPostCapped = Math.min(perPostBase, REDDIT.MAX_COMMENTS_PER_POST);
15
+ return { totalBudget, perPostBase, perPostCapped, redistributed: false };
16
+ }
17
+ // ============================================================================
18
+ // Module-Level Token Cache (shared across all RedditClient instances)
19
+ // ============================================================================
20
+ let cachedToken = null;
21
+ let cachedTokenExpiry = 0;
22
+ // Token cache logging only when DEBUG env is set
23
+ const DEBUG_TOKEN_CACHE = process.env.DEBUG_REDDIT === 'true';
24
+ // Pending auth promise for deduplicating concurrent auth calls
25
+ let pendingAuthPromise = null;
26
+ export class RedditClient {
27
+ clientId;
28
+ clientSecret;
29
+ // Instance-level references (now point to module cache)
30
+ // User agent uses centralized version from package.json - auto-synced!
31
+ userAgent = `script:${USER_AGENT_VERSION} (by /u/research-powerpack)`;
32
+ constructor(clientId, clientSecret) {
33
+ this.clientId = clientId;
34
+ this.clientSecret = clientSecret;
35
+ }
36
+ /**
37
+ * Authenticate with Reddit API with retry logic
38
+ * Uses module-level token cache and promise deduplication to prevent
39
+ * concurrent auth calls from firing multiple token requests
40
+ * Returns null on failure instead of throwing
41
+ */
42
+ async auth() {
43
+ // Return module-cached token if still valid (with 60s buffer)
44
+ if (cachedToken && Date.now() < cachedTokenExpiry - 60000) {
45
+ if (DEBUG_TOKEN_CACHE)
46
+ console.error('[RedditClient] Token cache HIT');
47
+ return cachedToken;
48
+ }
49
+ // Deduplicate concurrent auth calls - if one is already in flight, await it
50
+ if (pendingAuthPromise) {
51
+ if (DEBUG_TOKEN_CACHE)
52
+ console.error('[RedditClient] Auth already in flight, awaiting...');
53
+ return pendingAuthPromise;
54
+ }
55
+ pendingAuthPromise = this.performAuth();
56
+ try {
57
+ return await pendingAuthPromise;
58
+ }
59
+ finally {
60
+ pendingAuthPromise = null;
61
+ }
62
+ }
63
+ /**
64
+ * Internal auth implementation - called only once per cache miss
65
+ */
66
+ async performAuth() {
67
+ if (DEBUG_TOKEN_CACHE)
68
+ console.error('[RedditClient] Token cache MISS - authenticating');
69
+ const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
70
+ for (let attempt = 0; attempt < 3; attempt++) {
71
+ try {
72
+ const res = await fetchWithTimeout('https://www.reddit.com/api/v1/access_token', {
73
+ method: 'POST',
74
+ headers: {
75
+ 'Authorization': `Basic ${credentials}`,
76
+ 'Content-Type': 'application/x-www-form-urlencoded',
77
+ 'User-Agent': this.userAgent,
78
+ },
79
+ body: 'grant_type=client_credentials',
80
+ timeoutMs: 15000,
81
+ });
82
+ if (!res.ok) {
83
+ const text = await res.text().catch(() => '');
84
+ mcpLog('error', `Auth failed (${res.status}): ${text}`, 'reddit');
85
+ // 401/403 are not retryable - invalidate cache
86
+ if (res.status === 401 || res.status === 403) {
87
+ cachedToken = null;
88
+ cachedTokenExpiry = 0;
89
+ return null;
90
+ }
91
+ // Retry on server errors
92
+ if (res.status >= 500 && attempt < 2) {
93
+ await sleep(REDDIT.RETRY_DELAYS[attempt] || 2000);
94
+ continue;
95
+ }
96
+ return null;
97
+ }
98
+ const data = await res.json();
99
+ if (!data.access_token) {
100
+ mcpLog('error', 'Auth response missing access_token', 'reddit');
101
+ return null;
102
+ }
103
+ // Update module-level cache (shared across all instances)
104
+ cachedToken = data.access_token;
105
+ cachedTokenExpiry = Date.now() + (data.expires_in || 3600) * 1000;
106
+ return cachedToken;
107
+ }
108
+ catch (error) {
109
+ const err = classifyError(error);
110
+ mcpLog('error', `Auth error (attempt ${attempt + 1}): ${err.message}`, 'reddit');
111
+ // Invalidate cache on auth errors
112
+ if (err.code === ErrorCode.AUTH_ERROR) {
113
+ cachedToken = null;
114
+ cachedTokenExpiry = 0;
115
+ }
116
+ if (attempt < 2 && err.retryable) {
117
+ await sleep(REDDIT.RETRY_DELAYS[attempt] || 2000);
118
+ continue;
119
+ }
120
+ return null;
121
+ }
122
+ }
123
+ return null;
124
+ }
125
+ parseUrl(url) {
126
+ const m = url.match(/reddit\.com\/r\/([^\/]+)\/comments\/([a-z0-9]+)/i);
127
+ return m ? { sub: m[1], id: m[2] } : null;
128
+ }
129
+ /**
130
+ * Get a single Reddit post with comments
131
+ * Returns PostResult or throws Error (for use with Promise.allSettled)
132
+ */
133
+ async getPost(url, maxComments = 100) {
134
+ const parsed = this.parseUrl(url);
135
+ if (!parsed) {
136
+ throw new Error(`Invalid Reddit URL format: ${url}`);
137
+ }
138
+ // Auth - returns null on failure
139
+ const token = await this.auth();
140
+ if (!token) {
141
+ throw new Error('Reddit authentication failed - check credentials');
142
+ }
143
+ const limit = Math.min(maxComments, 500);
144
+ let lastError = null;
145
+ for (let attempt = 0; attempt < REDDIT.RETRY_COUNT; attempt++) {
146
+ try {
147
+ const apiUrl = `https://oauth.reddit.com/r/${parsed.sub}/comments/${parsed.id}?sort=top&limit=${limit}&depth=10&raw_json=1`;
148
+ const res = await fetchWithTimeout(apiUrl, {
149
+ headers: {
150
+ 'Authorization': `Bearer ${token}`,
151
+ 'User-Agent': this.userAgent,
152
+ },
153
+ timeoutMs: 30000,
154
+ });
155
+ // Rate limited - always retry with backoff
156
+ if (res.status === 429) {
157
+ const delay = REDDIT.RETRY_DELAYS[attempt] || 32000;
158
+ mcpLog('warning', `Rate limited. Retry ${attempt + 1}/${REDDIT.RETRY_COUNT} after ${delay}ms`, 'reddit');
159
+ await sleep(delay);
160
+ continue;
161
+ }
162
+ // 404 - Post doesn't exist
163
+ if (res.status === 404) {
164
+ throw new Error(`Post not found: ${url}`);
165
+ }
166
+ // Other errors
167
+ if (!res.ok) {
168
+ lastError = classifyError({ status: res.status });
169
+ if (lastError.retryable && attempt < REDDIT.RETRY_COUNT - 1) {
170
+ const delay = REDDIT.RETRY_DELAYS[attempt] || 2000;
171
+ mcpLog('warning', `API error ${res.status}. Retry ${attempt + 1}/${REDDIT.RETRY_COUNT}`, 'reddit');
172
+ await sleep(delay);
173
+ continue;
174
+ }
175
+ throw new Error(`Reddit API error: ${res.status}`);
176
+ }
177
+ // Parse response safely
178
+ let data;
179
+ try {
180
+ data = await res.json();
181
+ }
182
+ catch (parseError) {
183
+ throw new Error('Failed to parse Reddit API response');
184
+ }
185
+ const [postListing, commentListing] = data;
186
+ const p = postListing?.data?.children?.[0]?.data;
187
+ if (!p) {
188
+ throw new Error(`Post data not found in response: ${url}`);
189
+ }
190
+ const post = {
191
+ title: p.title || 'Untitled',
192
+ author: p.author || '[deleted]',
193
+ subreddit: p.subreddit || parsed.sub,
194
+ body: this.formatBody(p),
195
+ score: p.score || 0,
196
+ commentCount: p.num_comments || 0,
197
+ url: `https://reddit.com${p.permalink || ''}`,
198
+ created: new Date((p.created_utc || 0) * 1000),
199
+ flair: p.link_flair_text || undefined,
200
+ isNsfw: p.over_18 || false,
201
+ isPinned: p.stickied || false,
202
+ };
203
+ const comments = this.extractComments(commentListing?.data?.children || [], maxComments, post.author);
204
+ return { post, comments, allocatedComments: maxComments, actualComments: post.commentCount };
205
+ }
206
+ catch (error) {
207
+ lastError = classifyError(error);
208
+ // Don't retry non-retryable errors
209
+ if (!lastError.retryable) {
210
+ throw error instanceof Error ? error : new Error(lastError.message);
211
+ }
212
+ if (attempt < REDDIT.RETRY_COUNT - 1) {
213
+ const delay = REDDIT.RETRY_DELAYS[attempt] || 2000;
214
+ mcpLog('warning', `${lastError.code}: ${lastError.message}. Retry ${attempt + 1}/${REDDIT.RETRY_COUNT}`, 'reddit');
215
+ await sleep(delay);
216
+ }
217
+ }
218
+ }
219
+ // All retries exhausted
220
+ throw new Error(lastError?.message || 'Failed to fetch Reddit post after retries');
221
+ }
222
+ formatBody(p) {
223
+ if (p.selftext?.trim())
224
+ return p.selftext;
225
+ if (p.is_self)
226
+ return '';
227
+ if (p.url)
228
+ return `**Link:** ${p.url}`;
229
+ return '';
230
+ }
231
+ extractComments(children, maxComments, opAuthor) {
232
+ const result = [];
233
+ const extract = (items, depth = 0) => {
234
+ const sorted = [...items].sort((a, b) => (b.data?.score || 0) - (a.data?.score || 0));
235
+ for (const c of sorted) {
236
+ if (result.length >= maxComments)
237
+ return;
238
+ if (c.kind !== 't1' || !c.data?.author || c.data.author === '[deleted]')
239
+ continue;
240
+ result.push({
241
+ author: c.data.author,
242
+ body: c.data.body || '',
243
+ score: c.data.score || 0,
244
+ depth,
245
+ isOP: c.data.author === opAuthor,
246
+ });
247
+ if (c.data.replies?.data?.children && result.length < maxComments) {
248
+ extract(c.data.replies.data.children, depth + 1);
249
+ }
250
+ }
251
+ };
252
+ extract(children);
253
+ return result;
254
+ }
255
+ async getPosts(urls, maxComments = 100) {
256
+ if (urls.length <= REDDIT.BATCH_SIZE) {
257
+ // Limit to 5 concurrent Reddit API calls
258
+ const results = await pMap(urls, u => this.getPost(u, maxComments).catch(e => e), 5);
259
+ return new Map(urls.map((u, i) => [u, results[i]]));
260
+ }
261
+ return (await this.batchGetPosts(urls, maxComments)).results;
262
+ }
263
+ async batchGetPosts(urls, maxCommentsOverride, fetchComments = true, onBatchComplete) {
264
+ const totalBatches = Math.ceil(urls.length / REDDIT.BATCH_SIZE);
265
+ const allResults = new Map();
266
+ let rateLimitHits = 0;
267
+ const allocation = calculateCommentAllocation(urls.length);
268
+ const commentsPerPost = fetchComments ? (maxCommentsOverride || allocation.perPostCapped) : 0;
269
+ mcpLog('info', `Fetching ${urls.length} posts in ${totalBatches} batch(es), ${commentsPerPost} comments/post`, 'reddit');
270
+ for (let batchNum = 0; batchNum < totalBatches; batchNum++) {
271
+ const startIdx = batchNum * REDDIT.BATCH_SIZE;
272
+ const batchUrls = urls.slice(startIdx, startIdx + REDDIT.BATCH_SIZE);
273
+ mcpLog('info', `Batch ${batchNum + 1}/${totalBatches} (${batchUrls.length} posts)`, 'reddit');
274
+ // Limit to 5 concurrent Reddit API calls within each batch
275
+ const batchResults = await pMapSettled(batchUrls, url => this.getPost(url, commentsPerPost), 5);
276
+ for (let i = 0; i < batchResults.length; i++) {
277
+ const result = batchResults[i];
278
+ const url = batchUrls[i] || '';
279
+ if (result.status === 'fulfilled') {
280
+ allResults.set(url, result.value);
281
+ }
282
+ else {
283
+ const errorMsg = result.reason?.message || String(result.reason);
284
+ if (errorMsg.includes('429') || errorMsg.includes('rate'))
285
+ rateLimitHits++;
286
+ allResults.set(url, new Error(errorMsg));
287
+ }
288
+ }
289
+ // Safe callback invocation
290
+ try {
291
+ onBatchComplete?.(batchNum + 1, totalBatches, allResults.size);
292
+ }
293
+ catch (callbackError) {
294
+ mcpLog('error', `onBatchComplete callback error: ${callbackError}`, 'reddit');
295
+ }
296
+ mcpLog('info', `Batch ${batchNum + 1} complete (${allResults.size}/${urls.length})`, 'reddit');
297
+ // Small delay between batches
298
+ if (batchNum < totalBatches - 1) {
299
+ await sleep(500);
300
+ }
301
+ }
302
+ return { results: allResults, batchesProcessed: totalBatches, totalPosts: urls.length, rateLimitHits, commentAllocation: allocation };
303
+ }
304
+ }
305
+ //# sourceMappingURL=reddit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reddit.js","sourceRoot":"","sources":["../../src/clients/reddit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Deep Research Client
3
+ * Handles research API requests with web search capabilities
4
+ * Implements robust retry logic and NEVER crashes the server
5
+ */
6
+ import { type StructuredError } from '../utils/errors.js';
7
+ interface ResearchParams {
8
+ question: string;
9
+ systemPrompt?: string;
10
+ reasoningEffort?: 'low' | 'medium' | 'high';
11
+ maxSearchResults?: number;
12
+ maxTokens?: number;
13
+ temperature?: number;
14
+ responseFormat?: {
15
+ type: 'json_object' | 'text';
16
+ };
17
+ }
18
+ export interface ResearchResponse {
19
+ id: string;
20
+ model: string;
21
+ created: number;
22
+ content: string;
23
+ finishReason?: string;
24
+ usage?: {
25
+ promptTokens: number;
26
+ completionTokens: number;
27
+ totalTokens: number;
28
+ sourcesUsed?: number;
29
+ };
30
+ annotations?: Array<{
31
+ type: 'url_citation';
32
+ url: string;
33
+ title: string;
34
+ startIndex: number;
35
+ endIndex: number;
36
+ }>;
37
+ error?: StructuredError;
38
+ }
39
+ export declare class ResearchClient {
40
+ private client;
41
+ constructor();
42
+ /**
43
+ * Check if an error is retryable for research requests
44
+ */
45
+ private isRetryableError;
46
+ /**
47
+ * Calculate backoff for research retries
48
+ */
49
+ private calculateBackoff;
50
+ /**
51
+ * Build request payload based on model type
52
+ * Gemini models use tools with google_search, others use search_parameters
53
+ */
54
+ private buildRequestPayload;
55
+ /**
56
+ * Execute a single research request with a specific model
57
+ */
58
+ private executeResearch;
59
+ /**
60
+ * Perform research with retry logic and fallback to secondary model
61
+ * Returns a ResearchResponse - may contain error field on failure
62
+ * NEVER throws - always returns a valid response object
63
+ */
64
+ research(params: ResearchParams): Promise<ResearchResponse>;
65
+ }
66
+ export {};
67
+ //# sourceMappingURL=research.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"research.d.ts","sourceRoot":"","sources":["../../src/clients/research.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAG5B,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE;QAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAAA;KAAE,CAAC;CACnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE;QACN,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,IAAI,EAAE,cAAc,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AA4BD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;;IAevB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA6BxB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAMxB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAqD3B;;OAEG;YACW,eAAe;IA+F7B;;;;OAIG;IACG,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAkElE"}
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Deep Research Client
3
+ * Handles research API requests with web search capabilities
4
+ * Implements robust retry logic and NEVER crashes the server
5
+ */
6
+ import OpenAI from 'openai';
7
+ import { RESEARCH } from '../config/index.js';
8
+ import { classifyError, sleep, ErrorCode, } from '../utils/errors.js';
9
+ import { mcpLog } from '../utils/logger.js';
10
+ // Research-specific retry configuration
11
+ // Research requests can be long-running, so we use longer delays
12
+ const RESEARCH_RETRY_CONFIG = {
13
+ maxRetries: 3, // 3 retries for robustness on transient failures
14
+ baseDelayMs: 5000, // Longer base delay
15
+ maxDelayMs: 60000,
16
+ };
17
+ // Retryable status codes for research API
18
+ const RETRYABLE_RESEARCH_CODES = new Set([429, 500, 502, 503, 504]);
19
+ // Models that use Gemini-style google_search tool instead of search_parameters
20
+ const GEMINI_STYLE_MODELS = new Set([
21
+ 'google/gemini-2.5-flash',
22
+ 'google/gemini-2.5-pro',
23
+ 'google/gemini-2.0-flash',
24
+ 'google/gemini-pro',
25
+ ]);
26
+ /**
27
+ * Check if a model uses Gemini-style google_search tool
28
+ */
29
+ function isGeminiStyleModel(model) {
30
+ return GEMINI_STYLE_MODELS.has(model) || model.startsWith('google/gemini');
31
+ }
32
+ export class ResearchClient {
33
+ client;
34
+ constructor() {
35
+ if (!RESEARCH.API_KEY) {
36
+ throw new Error('OPENROUTER_API_KEY is required for research');
37
+ }
38
+ this.client = new OpenAI({
39
+ baseURL: RESEARCH.BASE_URL,
40
+ apiKey: RESEARCH.API_KEY,
41
+ timeout: RESEARCH.TIMEOUT_MS,
42
+ maxRetries: 0, // We handle retries ourselves
43
+ });
44
+ }
45
+ /**
46
+ * Check if an error is retryable for research requests
47
+ */
48
+ isRetryableError(error) {
49
+ if (!error)
50
+ return false;
51
+ const err = error;
52
+ // Check HTTP status codes
53
+ if (err.status && RETRYABLE_RESEARCH_CODES.has(err.status)) {
54
+ return true;
55
+ }
56
+ // Check message patterns
57
+ const message = (err.message || '').toLowerCase();
58
+ if (message.includes('rate limit') ||
59
+ message.includes('timeout') ||
60
+ message.includes('timed out') ||
61
+ message.includes('service unavailable') ||
62
+ message.includes('connection')) {
63
+ return true;
64
+ }
65
+ return false;
66
+ }
67
+ /**
68
+ * Calculate backoff for research retries
69
+ */
70
+ calculateBackoff(attempt) {
71
+ const exponentialDelay = RESEARCH_RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt);
72
+ const jitter = Math.random() * 0.3 * exponentialDelay;
73
+ return Math.min(exponentialDelay + jitter, RESEARCH_RETRY_CONFIG.maxDelayMs);
74
+ }
75
+ /**
76
+ * Build request payload based on model type
77
+ * Gemini models use tools with google_search, others use search_parameters
78
+ */
79
+ buildRequestPayload(model, messages, options) {
80
+ const { temperature, reasoningEffort, maxTokens, maxSearchResults, responseFormat } = options;
81
+ if (isGeminiStyleModel(model)) {
82
+ // Gemini uses tools with google_search
83
+ const payload = {
84
+ model,
85
+ messages,
86
+ temperature,
87
+ max_tokens: maxTokens,
88
+ tools: [
89
+ {
90
+ type: 'google_search',
91
+ googleSearch: {},
92
+ },
93
+ ],
94
+ };
95
+ if (responseFormat) {
96
+ payload.response_format = responseFormat;
97
+ }
98
+ return payload;
99
+ }
100
+ // Default: use search_parameters (for Grok, Perplexity, etc.)
101
+ const payload = {
102
+ model,
103
+ messages,
104
+ temperature,
105
+ reasoning_effort: reasoningEffort,
106
+ max_completion_tokens: maxTokens,
107
+ search_parameters: {
108
+ mode: 'on',
109
+ max_search_results: Math.min(maxSearchResults, 30),
110
+ return_citations: true,
111
+ sources: [{ type: 'web' }],
112
+ },
113
+ };
114
+ if (responseFormat) {
115
+ payload.response_format = responseFormat;
116
+ }
117
+ return payload;
118
+ }
119
+ /**
120
+ * Execute a single research request with a specific model
121
+ */
122
+ async executeResearch(model, messages, options) {
123
+ const requestPayload = this.buildRequestPayload(model, messages, options);
124
+ let lastError;
125
+ // Retry loop for this model
126
+ for (let attempt = 0; attempt <= RESEARCH_RETRY_CONFIG.maxRetries; attempt++) {
127
+ try {
128
+ if (attempt > 0) {
129
+ mcpLog('warning', `Retry attempt ${attempt}/${RESEARCH_RETRY_CONFIG.maxRetries} for ${model}`, 'research');
130
+ }
131
+ const response = await this.client.chat.completions.create(requestPayload);
132
+ const choice = response.choices?.[0];
133
+ const message = choice?.message;
134
+ // Validate response
135
+ if (!message?.content && !choice) {
136
+ lastError = {
137
+ code: ErrorCode.INTERNAL_ERROR,
138
+ message: 'Research API returned empty response',
139
+ retryable: true,
140
+ };
141
+ if (attempt < RESEARCH_RETRY_CONFIG.maxRetries) {
142
+ const delayMs = this.calculateBackoff(attempt);
143
+ mcpLog('warning', `Empty response, retrying in ${delayMs}ms...`, 'research');
144
+ await sleep(delayMs);
145
+ continue;
146
+ }
147
+ }
148
+ return {
149
+ id: response.id || '',
150
+ model: response.model || model,
151
+ created: response.created || Date.now(),
152
+ content: message?.content || '',
153
+ finishReason: choice?.finish_reason,
154
+ usage: response.usage ? {
155
+ promptTokens: response.usage.prompt_tokens,
156
+ completionTokens: response.usage.completion_tokens,
157
+ totalTokens: response.usage.total_tokens,
158
+ sourcesUsed: response.usage.num_sources_used,
159
+ } : undefined,
160
+ annotations: message?.annotations?.map((a) => ({
161
+ type: 'url_citation',
162
+ url: a.url_citation?.url || '',
163
+ title: a.url_citation?.title || '',
164
+ startIndex: a.url_citation?.start_index || 0,
165
+ endIndex: a.url_citation?.end_index || 0,
166
+ })),
167
+ };
168
+ }
169
+ catch (error) {
170
+ lastError = classifyError(error);
171
+ const err = error;
172
+ mcpLog('error', `Error with ${model} (attempt ${attempt + 1}): ${lastError.message} (status: ${err.status})`, 'research');
173
+ // Check if we should retry
174
+ if (this.isRetryableError(error) && attempt < RESEARCH_RETRY_CONFIG.maxRetries) {
175
+ const delayMs = this.calculateBackoff(attempt);
176
+ mcpLog('warning', `Retrying in ${delayMs}ms...`, 'research');
177
+ await sleep(delayMs);
178
+ continue;
179
+ }
180
+ // Non-retryable or max retries reached
181
+ break;
182
+ }
183
+ }
184
+ // Return error response
185
+ return {
186
+ id: '',
187
+ model,
188
+ created: Date.now(),
189
+ content: '',
190
+ error: lastError || {
191
+ code: ErrorCode.UNKNOWN_ERROR,
192
+ message: 'Unknown research error',
193
+ retryable: false,
194
+ },
195
+ };
196
+ }
197
+ /**
198
+ * Perform research with retry logic and fallback to secondary model
199
+ * Returns a ResearchResponse - may contain error field on failure
200
+ * NEVER throws - always returns a valid response object
201
+ */
202
+ async research(params) {
203
+ const { question, systemPrompt, reasoningEffort = RESEARCH.REASONING_EFFORT, maxSearchResults = RESEARCH.MAX_URLS, maxTokens = 32000, temperature = 0.3, responseFormat, } = params;
204
+ // Validate input
205
+ if (!question?.trim()) {
206
+ return {
207
+ id: '',
208
+ model: RESEARCH.MODEL,
209
+ created: Date.now(),
210
+ content: '',
211
+ error: {
212
+ code: ErrorCode.INVALID_INPUT,
213
+ message: 'Research question cannot be empty',
214
+ retryable: false,
215
+ },
216
+ };
217
+ }
218
+ const messages = [];
219
+ if (systemPrompt) {
220
+ messages.push({ role: 'system', content: systemPrompt });
221
+ }
222
+ messages.push({ role: 'user', content: question });
223
+ const options = { temperature, reasoningEffort, maxTokens, maxSearchResults, responseFormat };
224
+ // Try primary model first
225
+ mcpLog('info', `Trying primary model: ${RESEARCH.MODEL}`, 'research');
226
+ const primaryResult = await this.executeResearch(RESEARCH.MODEL, messages, options);
227
+ if (!primaryResult.error) {
228
+ return primaryResult;
229
+ }
230
+ // Primary failed - try fallback model if different
231
+ if (RESEARCH.FALLBACK_MODEL && RESEARCH.FALLBACK_MODEL !== RESEARCH.MODEL) {
232
+ mcpLog('warning', `Primary model failed, trying fallback: ${RESEARCH.FALLBACK_MODEL}`, 'research');
233
+ const fallbackResult = await this.executeResearch(RESEARCH.FALLBACK_MODEL, messages, options);
234
+ if (!fallbackResult.error) {
235
+ return fallbackResult;
236
+ }
237
+ // Both failed - return the fallback error (more recent)
238
+ mcpLog('error', `Both models failed. Primary: ${primaryResult.error?.message}, Fallback: ${fallbackResult.error?.message}`, 'research');
239
+ return {
240
+ ...fallbackResult,
241
+ content: `Research failed with both models. Primary (${RESEARCH.MODEL}): ${primaryResult.error?.message}. Fallback (${RESEARCH.FALLBACK_MODEL}): ${fallbackResult.error?.message}`,
242
+ };
243
+ }
244
+ // No fallback or same model - return primary error
245
+ mcpLog('error', `All attempts failed: ${primaryResult.error?.message}`, 'research');
246
+ return {
247
+ ...primaryResult,
248
+ content: `Research failed: ${primaryResult.error?.message}`,
249
+ };
250
+ }
251
+ }
252
+ //# sourceMappingURL=research.js.map