mcp-researchpowerpack 3.9.4 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +116 -208
  2. package/dist/index.js +280 -337
  3. package/dist/index.js.map +7 -1
  4. package/dist/mcp-use.json +7 -0
  5. package/dist/src/clients/reddit.js +278 -0
  6. package/dist/src/clients/reddit.js.map +7 -0
  7. package/dist/src/clients/scraper.js +326 -0
  8. package/dist/src/clients/scraper.js.map +7 -0
  9. package/dist/src/clients/search.js +217 -0
  10. package/dist/src/clients/search.js.map +7 -0
  11. package/dist/src/config/index.js +138 -0
  12. package/dist/src/config/index.js.map +7 -0
  13. package/dist/src/prompts/deep-research.js +41 -0
  14. package/dist/src/prompts/deep-research.js.map +7 -0
  15. package/dist/src/prompts/reddit-sentiment.js +47 -0
  16. package/dist/src/prompts/reddit-sentiment.js.map +7 -0
  17. package/dist/src/schemas/reddit.js +21 -0
  18. package/dist/src/schemas/reddit.js.map +7 -0
  19. package/dist/src/schemas/scrape-links.js +26 -0
  20. package/dist/src/schemas/scrape-links.js.map +7 -0
  21. package/dist/src/schemas/start-research.js +17 -0
  22. package/dist/src/schemas/start-research.js.map +7 -0
  23. package/dist/src/schemas/web-search.js +53 -0
  24. package/dist/src/schemas/web-search.js.map +7 -0
  25. package/dist/src/services/llm-processor.js +675 -0
  26. package/dist/src/services/llm-processor.js.map +7 -0
  27. package/dist/src/services/markdown-cleaner.js +62 -0
  28. package/dist/src/services/markdown-cleaner.js.map +7 -0
  29. package/dist/src/services/workflow-state.js +116 -0
  30. package/dist/src/services/workflow-state.js.map +7 -0
  31. package/dist/src/tools/mcp-helpers.js +50 -0
  32. package/dist/src/tools/mcp-helpers.js.map +7 -0
  33. package/dist/src/tools/reddit.js +277 -0
  34. package/dist/src/tools/reddit.js.map +7 -0
  35. package/dist/src/tools/registry.js +18 -0
  36. package/dist/src/tools/registry.js.map +7 -0
  37. package/dist/src/tools/scrape.js +334 -0
  38. package/dist/src/tools/scrape.js.map +7 -0
  39. package/dist/src/tools/search.js +423 -0
  40. package/dist/src/tools/search.js.map +7 -0
  41. package/dist/src/tools/start-research.js +199 -0
  42. package/dist/src/tools/start-research.js.map +7 -0
  43. package/dist/src/tools/utils.js +29 -0
  44. package/dist/src/tools/utils.js.map +7 -0
  45. package/dist/src/utils/bootstrap-guard.js +27 -0
  46. package/dist/src/utils/bootstrap-guard.js.map +7 -0
  47. package/dist/src/utils/concurrency.js +62 -0
  48. package/dist/src/utils/concurrency.js.map +7 -0
  49. package/dist/src/utils/content-extractor.js +61 -0
  50. package/dist/src/utils/content-extractor.js.map +7 -0
  51. package/dist/src/utils/errors.js +211 -0
  52. package/dist/src/utils/errors.js.map +7 -0
  53. package/dist/src/utils/logger.js +25 -0
  54. package/dist/src/utils/logger.js.map +7 -0
  55. package/dist/src/utils/markdown-formatter.js +15 -0
  56. package/dist/src/utils/markdown-formatter.js.map +7 -0
  57. package/dist/src/utils/reddit-keyword-guard.js +29 -0
  58. package/dist/src/utils/reddit-keyword-guard.js.map +7 -0
  59. package/dist/src/utils/response.js +81 -0
  60. package/dist/src/utils/response.js.map +7 -0
  61. package/dist/src/utils/retry.js +13 -0
  62. package/dist/src/utils/retry.js.map +7 -0
  63. package/dist/src/utils/sanitize.js +10 -0
  64. package/dist/src/utils/sanitize.js.map +7 -0
  65. package/dist/src/utils/source-type.js +41 -0
  66. package/dist/src/utils/source-type.js.map +7 -0
  67. package/dist/src/utils/url-aggregator.js +227 -0
  68. package/dist/src/utils/url-aggregator.js.map +7 -0
  69. package/dist/src/utils/workflow-key.js +14 -0
  70. package/dist/src/utils/workflow-key.js.map +7 -0
  71. package/dist/src/version.js +32 -0
  72. package/dist/src/version.js.map +7 -0
  73. package/package.json +33 -28
  74. package/dist/clients/reddit.d.ts +0 -69
  75. package/dist/clients/reddit.d.ts.map +0 -1
  76. package/dist/clients/reddit.js +0 -369
  77. package/dist/clients/reddit.js.map +0 -1
  78. package/dist/clients/research.d.ts +0 -67
  79. package/dist/clients/research.d.ts.map +0 -1
  80. package/dist/clients/research.js +0 -290
  81. package/dist/clients/research.js.map +0 -1
  82. package/dist/clients/scraper.d.ts +0 -72
  83. package/dist/clients/scraper.d.ts.map +0 -1
  84. package/dist/clients/scraper.js +0 -338
  85. package/dist/clients/scraper.js.map +0 -1
  86. package/dist/clients/search.d.ts +0 -57
  87. package/dist/clients/search.d.ts.map +0 -1
  88. package/dist/clients/search.js +0 -223
  89. package/dist/clients/search.js.map +0 -1
  90. package/dist/config/index.d.ts +0 -78
  91. package/dist/config/index.d.ts.map +0 -1
  92. package/dist/config/index.js +0 -201
  93. package/dist/config/index.js.map +0 -1
  94. package/dist/config/loader.d.ts +0 -40
  95. package/dist/config/loader.d.ts.map +0 -1
  96. package/dist/config/loader.js +0 -322
  97. package/dist/config/loader.js.map +0 -1
  98. package/dist/config/types.d.ts +0 -81
  99. package/dist/config/types.d.ts.map +0 -1
  100. package/dist/config/types.js +0 -6
  101. package/dist/config/types.js.map +0 -1
  102. package/dist/config/yaml/tools.yaml +0 -146
  103. package/dist/index.d.ts +0 -7
  104. package/dist/index.d.ts.map +0 -1
  105. package/dist/schemas/deep-research.d.ts +0 -64
  106. package/dist/schemas/deep-research.d.ts.map +0 -1
  107. package/dist/schemas/deep-research.js +0 -224
  108. package/dist/schemas/deep-research.js.map +0 -1
  109. package/dist/schemas/scrape-links.d.ts +0 -32
  110. package/dist/schemas/scrape-links.d.ts.map +0 -1
  111. package/dist/schemas/scrape-links.js +0 -34
  112. package/dist/schemas/scrape-links.js.map +0 -1
  113. package/dist/schemas/web-search.d.ts +0 -22
  114. package/dist/schemas/web-search.d.ts.map +0 -1
  115. package/dist/schemas/web-search.js +0 -21
  116. package/dist/schemas/web-search.js.map +0 -1
  117. package/dist/services/file-attachment.d.ts +0 -30
  118. package/dist/services/file-attachment.d.ts.map +0 -1
  119. package/dist/services/file-attachment.js +0 -207
  120. package/dist/services/file-attachment.js.map +0 -1
  121. package/dist/services/llm-processor.d.ts +0 -29
  122. package/dist/services/llm-processor.d.ts.map +0 -1
  123. package/dist/services/llm-processor.js +0 -244
  124. package/dist/services/llm-processor.js.map +0 -1
  125. package/dist/services/markdown-cleaner.d.ts +0 -8
  126. package/dist/services/markdown-cleaner.d.ts.map +0 -1
  127. package/dist/services/markdown-cleaner.js +0 -74
  128. package/dist/services/markdown-cleaner.js.map +0 -1
  129. package/dist/tools/definitions.d.ts +0 -16
  130. package/dist/tools/definitions.d.ts.map +0 -1
  131. package/dist/tools/definitions.js +0 -17
  132. package/dist/tools/definitions.js.map +0 -1
  133. package/dist/tools/reddit.d.ts +0 -14
  134. package/dist/tools/reddit.d.ts.map +0 -1
  135. package/dist/tools/reddit.js +0 -265
  136. package/dist/tools/reddit.js.map +0 -1
  137. package/dist/tools/registry.d.ts +0 -71
  138. package/dist/tools/registry.d.ts.map +0 -1
  139. package/dist/tools/registry.js +0 -252
  140. package/dist/tools/registry.js.map +0 -1
  141. package/dist/tools/research.d.ts +0 -14
  142. package/dist/tools/research.d.ts.map +0 -1
  143. package/dist/tools/research.js +0 -196
  144. package/dist/tools/research.js.map +0 -1
  145. package/dist/tools/scrape.d.ts +0 -14
  146. package/dist/tools/scrape.d.ts.map +0 -1
  147. package/dist/tools/scrape.js +0 -234
  148. package/dist/tools/scrape.js.map +0 -1
  149. package/dist/tools/search.d.ts +0 -10
  150. package/dist/tools/search.d.ts.map +0 -1
  151. package/dist/tools/search.js +0 -158
  152. package/dist/tools/search.js.map +0 -1
  153. package/dist/tools/utils.d.ts +0 -105
  154. package/dist/tools/utils.d.ts.map +0 -1
  155. package/dist/tools/utils.js +0 -159
  156. package/dist/tools/utils.js.map +0 -1
  157. package/dist/utils/concurrency.d.ts +0 -28
  158. package/dist/utils/concurrency.d.ts.map +0 -1
  159. package/dist/utils/concurrency.js +0 -92
  160. package/dist/utils/concurrency.js.map +0 -1
  161. package/dist/utils/errors.d.ts +0 -95
  162. package/dist/utils/errors.d.ts.map +0 -1
  163. package/dist/utils/errors.js +0 -390
  164. package/dist/utils/errors.js.map +0 -1
  165. package/dist/utils/logger.d.ts +0 -39
  166. package/dist/utils/logger.d.ts.map +0 -1
  167. package/dist/utils/logger.js +0 -57
  168. package/dist/utils/logger.js.map +0 -1
  169. package/dist/utils/markdown-formatter.d.ts +0 -5
  170. package/dist/utils/markdown-formatter.d.ts.map +0 -1
  171. package/dist/utils/markdown-formatter.js +0 -15
  172. package/dist/utils/markdown-formatter.js.map +0 -1
  173. package/dist/utils/response.d.ts +0 -93
  174. package/dist/utils/response.d.ts.map +0 -1
  175. package/dist/utils/response.js +0 -170
  176. package/dist/utils/response.js.map +0 -1
  177. package/dist/utils/retry.d.ts +0 -43
  178. package/dist/utils/retry.d.ts.map +0 -1
  179. package/dist/utils/retry.js +0 -57
  180. package/dist/utils/retry.js.map +0 -1
  181. package/dist/utils/url-aggregator.d.ts +0 -90
  182. package/dist/utils/url-aggregator.d.ts.map +0 -1
  183. package/dist/utils/url-aggregator.js +0 -538
  184. package/dist/utils/url-aggregator.js.map +0 -1
  185. package/dist/version.d.ts +0 -29
  186. package/dist/version.d.ts.map +0 -1
  187. package/dist/version.js +0 -55
  188. package/dist/version.js.map +0 -1
@@ -0,0 +1,211 @@
1
+ import { mcpLog } from "./logger.js";
2
+ const ErrorCode = {
3
+ // Retryable errors
4
+ RATE_LIMITED: "RATE_LIMITED",
5
+ TIMEOUT: "TIMEOUT",
6
+ NETWORK_ERROR: "NETWORK_ERROR",
7
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
8
+ // Non-retryable errors
9
+ AUTH_ERROR: "AUTH_ERROR",
10
+ INVALID_INPUT: "INVALID_INPUT",
11
+ NOT_FOUND: "NOT_FOUND",
12
+ QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
13
+ // Internal errors
14
+ INTERNAL_ERROR: "INTERNAL_ERROR",
15
+ PARSE_ERROR: "PARSE_ERROR",
16
+ UNKNOWN_ERROR: "UNKNOWN_ERROR"
17
+ };
18
+ const DEFAULT_RETRY_OPTIONS = {
19
+ maxRetries: 3,
20
+ baseDelayMs: 1e3,
21
+ maxDelayMs: 3e4,
22
+ retryableStatuses: [408, 429, 500, 502, 503, 504, 510]
23
+ };
24
+ function classifyDomException(error) {
25
+ if (error.name === "AbortError") {
26
+ return { code: ErrorCode.TIMEOUT, message: "Request timed out", retryable: true };
27
+ }
28
+ return { code: ErrorCode.UNKNOWN_ERROR, message: error.message, retryable: false };
29
+ }
30
+ function classifyByErrorCode(error) {
31
+ const errCode = error.code;
32
+ if (!errCode) return null;
33
+ const networkErrorMessages = {
34
+ ECONNREFUSED: "Connection refused \u2014 service may be down",
35
+ ECONNRESET: "Connection was reset \u2014 please retry",
36
+ ECONNABORTED: "Connection aborted \u2014 please retry",
37
+ ENOTFOUND: "Service not reachable \u2014 check your network",
38
+ EPIPE: "Connection lost \u2014 please retry",
39
+ EAI_AGAIN: "DNS lookup failed \u2014 check your network"
40
+ };
41
+ if (errCode === "ECONNREFUSED" || errCode === "ENOTFOUND" || errCode === "ECONNRESET") {
42
+ return { code: ErrorCode.NETWORK_ERROR, message: networkErrorMessages[errCode] || "Network connection failed", retryable: true, cause: error.message };
43
+ }
44
+ if (errCode === "ECONNABORTED" || errCode === "ETIMEDOUT") {
45
+ return { code: ErrorCode.TIMEOUT, message: networkErrorMessages[errCode] || "Request timed out", retryable: true, cause: error.message };
46
+ }
47
+ return null;
48
+ }
49
+ function classifyByStatusCode(error) {
50
+ const status = error.response?.status || error.status || error.statusCode;
51
+ if (!status) return null;
52
+ return classifyHttpError(status, error.message || String(error));
53
+ }
54
+ function classifyByMessage(message) {
55
+ const lower = message.toLowerCase();
56
+ if (lower.includes("timeout") || lower.includes("timed out") || lower.includes("aborterror")) {
57
+ return { code: ErrorCode.TIMEOUT, message: "Request timed out", retryable: true, cause: message };
58
+ }
59
+ if (lower.includes("rate limit") || lower.includes("too many requests")) {
60
+ return { code: ErrorCode.RATE_LIMITED, message: "Rate limit exceeded", retryable: true, cause: message };
61
+ }
62
+ if (message.includes("API_KEY") || message.includes("api_key") || message.includes("Invalid API")) {
63
+ return { code: ErrorCode.AUTH_ERROR, message: "API key missing or invalid", retryable: false, cause: message };
64
+ }
65
+ if (message.includes("JSON") || message.includes("parse") || message.includes("Unexpected token")) {
66
+ return { code: ErrorCode.PARSE_ERROR, message: "Failed to parse response", retryable: false, cause: message };
67
+ }
68
+ return null;
69
+ }
70
+ function classifyFallback(message, cause) {
71
+ return {
72
+ code: ErrorCode.UNKNOWN_ERROR,
73
+ message,
74
+ retryable: false,
75
+ cause: cause ? String(cause) : void 0
76
+ };
77
+ }
78
+ function classifyError(error) {
79
+ if (error == null) {
80
+ return { code: ErrorCode.UNKNOWN_ERROR, message: "An unknown error occurred", retryable: false };
81
+ }
82
+ if (error instanceof DOMException) return classifyDomException(error);
83
+ if (!isErrorLike(error)) {
84
+ return { code: ErrorCode.UNKNOWN_ERROR, message: String(error), retryable: false };
85
+ }
86
+ return classifyByErrorCode(error) ?? classifyByStatusCode(error) ?? classifyByMessage(error.message ?? String(error)) ?? classifyFallback(error.message ?? String(error), error.cause);
87
+ }
88
+ function isErrorLike(value) {
89
+ return typeof value === "object" && value !== null;
90
+ }
91
+ function classifyHttpError(status, message) {
92
+ switch (status) {
93
+ case 400:
94
+ return { code: ErrorCode.INVALID_INPUT, message: "Bad request", retryable: false, statusCode: status };
95
+ case 401:
96
+ return { code: ErrorCode.AUTH_ERROR, message: "Invalid API key", retryable: false, statusCode: status };
97
+ case 403:
98
+ return { code: ErrorCode.QUOTA_EXCEEDED, message: "Access forbidden or quota exceeded", retryable: false, statusCode: status };
99
+ case 404:
100
+ return { code: ErrorCode.NOT_FOUND, message: "Resource not found", retryable: false, statusCode: status };
101
+ case 408:
102
+ return { code: ErrorCode.TIMEOUT, message: "Request timeout", retryable: true, statusCode: status };
103
+ case 429:
104
+ return { code: ErrorCode.RATE_LIMITED, message: "Rate limit exceeded", retryable: true, statusCode: status };
105
+ case 500:
106
+ return { code: ErrorCode.INTERNAL_ERROR, message: "Server error", retryable: true, statusCode: status };
107
+ case 502:
108
+ return { code: ErrorCode.SERVICE_UNAVAILABLE, message: "Bad gateway", retryable: true, statusCode: status };
109
+ case 503:
110
+ return { code: ErrorCode.SERVICE_UNAVAILABLE, message: "Service unavailable", retryable: true, statusCode: status };
111
+ case 504:
112
+ return { code: ErrorCode.TIMEOUT, message: "Gateway timeout", retryable: true, statusCode: status };
113
+ case 510:
114
+ return { code: ErrorCode.SERVICE_UNAVAILABLE, message: "Request canceled", retryable: true, statusCode: status };
115
+ default:
116
+ if (status >= 500) {
117
+ return { code: ErrorCode.SERVICE_UNAVAILABLE, message: `Server error: ${status}`, retryable: true, statusCode: status };
118
+ }
119
+ if (status >= 400) {
120
+ return { code: ErrorCode.INVALID_INPUT, message: `Client error: ${status}`, retryable: false, statusCode: status };
121
+ }
122
+ return { code: ErrorCode.UNKNOWN_ERROR, message: `HTTP ${status}: ${message}`, retryable: false, statusCode: status };
123
+ }
124
+ }
125
+ function calculateBackoff(attempt, options) {
126
+ const exponentialDelay = options.baseDelayMs * Math.pow(2, attempt);
127
+ const jitter = Math.random() * 0.3 * exponentialDelay;
128
+ return Math.min(exponentialDelay + jitter, options.maxDelayMs);
129
+ }
130
+ function sleep(ms, signal) {
131
+ return new Promise((resolve, reject) => {
132
+ if (signal?.aborted) {
133
+ reject(new DOMException("Aborted", "AbortError"));
134
+ return;
135
+ }
136
+ function onAbort() {
137
+ clearTimeout(timeout);
138
+ reject(new DOMException("Aborted", "AbortError"));
139
+ }
140
+ const timeout = setTimeout(() => {
141
+ if (signal) signal.removeEventListener("abort", onAbort);
142
+ resolve();
143
+ }, ms);
144
+ signal?.addEventListener("abort", onAbort, { once: true });
145
+ if (signal?.aborted) {
146
+ onAbort();
147
+ }
148
+ });
149
+ }
150
+ function fetchWithTimeout(url, options = {}) {
151
+ const { timeoutMs = 3e4, signal: externalSignal, ...fetchOptions } = options;
152
+ const controller = new AbortController();
153
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
154
+ let onExternalAbort;
155
+ if (externalSignal) {
156
+ onExternalAbort = () => controller.abort();
157
+ externalSignal.addEventListener("abort", onExternalAbort, { once: true });
158
+ if (externalSignal.aborted) {
159
+ controller.abort();
160
+ }
161
+ }
162
+ return fetch(url, { ...fetchOptions, signal: controller.signal }).finally(() => {
163
+ clearTimeout(timeoutId);
164
+ if (externalSignal && onExternalAbort) {
165
+ externalSignal.removeEventListener("abort", onExternalAbort);
166
+ }
167
+ });
168
+ }
169
+ async function withStallProtection(fn, stallMs, maxAttempts = 2, label = "request") {
170
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
171
+ const controller = new AbortController();
172
+ let stallTimer;
173
+ const stallPromise = new Promise((_, reject) => {
174
+ stallTimer = setTimeout(() => {
175
+ controller.abort();
176
+ reject(Object.assign(new Error(`Service temporarily unavailable \u2014 no response received (attempt ${attempt + 1}/${maxAttempts})`), {
177
+ code: "ESTALLED",
178
+ retryable: attempt < maxAttempts - 1
179
+ }));
180
+ }, stallMs);
181
+ });
182
+ let fnPromise;
183
+ try {
184
+ fnPromise = fn(controller.signal);
185
+ const result = await Promise.race([fnPromise, stallPromise]);
186
+ clearTimeout(stallTimer);
187
+ return result;
188
+ } catch (err) {
189
+ fnPromise?.catch(() => {
190
+ });
191
+ clearTimeout(stallTimer);
192
+ const isStall = err instanceof Error && err.code === "ESTALLED";
193
+ if (isStall && attempt < maxAttempts - 1) {
194
+ const backoff = calculateBackoff(attempt, DEFAULT_RETRY_OPTIONS);
195
+ mcpLog("warning", `${label} stalled, retrying in ${backoff}ms (attempt ${attempt + 1})`, "stability");
196
+ await sleep(backoff);
197
+ continue;
198
+ }
199
+ throw err;
200
+ }
201
+ }
202
+ throw new Error(`${label} failed after ${maxAttempts} stall-protection attempts`);
203
+ }
204
+ export {
205
+ ErrorCode,
206
+ classifyError,
207
+ fetchWithTimeout,
208
+ sleep,
209
+ withStallProtection
210
+ };
211
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/errors.ts"],
4
+ "sourcesContent": ["/**\n * Robust error handling utilities for MCP server\n * Ensures the server NEVER crashes and always returns structured responses\n */\n\nimport { mcpLog } from './logger.js';\n\n// ============================================================================\n// Error Codes (MCP-compliant)\n// ============================================================================\n\nexport const ErrorCode = {\n // Retryable errors\n RATE_LIMITED: 'RATE_LIMITED',\n TIMEOUT: 'TIMEOUT',\n NETWORK_ERROR: 'NETWORK_ERROR',\n SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',\n \n // Non-retryable errors\n AUTH_ERROR: 'AUTH_ERROR',\n INVALID_INPUT: 'INVALID_INPUT',\n NOT_FOUND: 'NOT_FOUND',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n \n // Internal errors\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n PARSE_ERROR: 'PARSE_ERROR',\n UNKNOWN_ERROR: 'UNKNOWN_ERROR',\n} as const;\n\ntype ErrorCodeType = typeof ErrorCode[keyof typeof ErrorCode];\n\n// ============================================================================\n// Structured Error Types\n// ============================================================================\n\nexport interface StructuredError {\n code: ErrorCodeType;\n message: string;\n retryable: boolean;\n statusCode?: number;\n cause?: string;\n}\n\ninterface RetryOptions {\n readonly maxRetries: number;\n readonly baseDelayMs: number;\n readonly maxDelayMs: number;\n readonly retryableStatuses: readonly number[];\n readonly onRetry?: (attempt: number, error: StructuredError, delayMs: number) => void;\n}\n\nconst DEFAULT_RETRY_OPTIONS: RetryOptions = {\n maxRetries: 3,\n baseDelayMs: 1000,\n maxDelayMs: 30000,\n retryableStatuses: [408, 429, 500, 502, 503, 504, 510],\n};\n\n// ============================================================================\n// Error Classification \u2014 Atomic Classifiers\n// ============================================================================\n\n/**\n * Classify DOMException (AbortError from AbortController timeouts)\n */\nfunction classifyDomException(error: DOMException): StructuredError {\n if (error.name === 'AbortError') {\n return { code: ErrorCode.TIMEOUT, message: 'Request timed out', retryable: true };\n }\n return { code: ErrorCode.UNKNOWN_ERROR, message: error.message, retryable: false };\n}\n\n/**\n * Classify by Node.js error codes (ECONNREFUSED, ENOTFOUND, etc.)\n * Returns null if no matching code is found.\n */\nfunction classifyByErrorCode(error: { code?: string; message?: string }): StructuredError | null {\n const errCode = error.code;\n if (!errCode) return null;\n\n const networkErrorMessages: Record<string, string> = {\n ECONNREFUSED: 'Connection refused \u2014 service may be down',\n ECONNRESET: 'Connection was reset \u2014 please retry',\n ECONNABORTED: 'Connection aborted \u2014 please retry',\n ENOTFOUND: 'Service not reachable \u2014 check your network',\n EPIPE: 'Connection lost \u2014 please retry',\n EAI_AGAIN: 'DNS lookup failed \u2014 check your network',\n };\n\n if (errCode === 'ECONNREFUSED' || errCode === 'ENOTFOUND' || errCode === 'ECONNRESET') {\n return { code: ErrorCode.NETWORK_ERROR, message: networkErrorMessages[errCode] || 'Network connection failed', retryable: true, cause: error.message };\n }\n\n if (errCode === 'ECONNABORTED' || errCode === 'ETIMEDOUT') {\n return { code: ErrorCode.TIMEOUT, message: networkErrorMessages[errCode] || 'Request timed out', retryable: true, cause: error.message };\n }\n\n return null;\n}\n\n/**\n * Classify by HTTP status code extracted from error objects (axios-style, fetch-style, etc.)\n * Returns null if no status code is found.\n */\nfunction classifyByStatusCode(error: { status?: number; statusCode?: number; response?: { status?: number }; message?: string }): StructuredError | null {\n const status = error.response?.status || error.status || error.statusCode;\n if (!status) return null;\n return classifyHttpError(status, error.message || String(error));\n}\n\n/**\n * Classify by error message patterns (timeout, rate-limit, auth, parse errors)\n * Returns null if no pattern matches.\n */\nfunction classifyByMessage(message: string): StructuredError | null {\n const lower = message.toLowerCase();\n\n // Timeout patterns\n if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('aborterror')) {\n return { code: ErrorCode.TIMEOUT, message: 'Request timed out', retryable: true, cause: message };\n }\n\n // Rate-limit patterns\n if (lower.includes('rate limit') || lower.includes('too many requests')) {\n return { code: ErrorCode.RATE_LIMITED, message: 'Rate limit exceeded', retryable: true, cause: message };\n }\n\n // API key errors\n if (message.includes('API_KEY') || message.includes('api_key') || message.includes('Invalid API')) {\n return { code: ErrorCode.AUTH_ERROR, message: 'API key missing or invalid', retryable: false, cause: message };\n }\n\n // Parse errors\n if (message.includes('JSON') || message.includes('parse') || message.includes('Unexpected token')) {\n return { code: ErrorCode.PARSE_ERROR, message: 'Failed to parse response', retryable: false, cause: message };\n }\n\n return null;\n}\n\n/**\n * Catch-all fallback classification when no other classifier matches.\n */\nfunction classifyFallback(message: string, cause?: unknown): StructuredError {\n return {\n code: ErrorCode.UNKNOWN_ERROR,\n message,\n retryable: false,\n cause: cause ? String(cause) : undefined,\n };\n}\n\n// ============================================================================\n// Main Error Classification Pipeline\n// ============================================================================\n\n/**\n * Classify any error into a structured format.\n * NEVER throws \u2014 always returns a valid StructuredError.\n */\nexport function classifyError(error: unknown): StructuredError {\n if (error == null) {\n return { code: ErrorCode.UNKNOWN_ERROR, message: 'An unknown error occurred', retryable: false };\n }\n\n if (error instanceof DOMException) return classifyDomException(error);\n\n if (!isErrorLike(error)) {\n return { code: ErrorCode.UNKNOWN_ERROR, message: String(error), retryable: false };\n }\n\n return classifyByErrorCode(error)\n ?? classifyByStatusCode(error)\n ?? classifyByMessage(error.message ?? String(error))\n ?? classifyFallback(error.message ?? String(error), error.cause);\n}\n\n/**\n * Type guard for error-like objects with common error properties\n */\nfunction isErrorLike(value: unknown): value is {\n message?: string;\n response?: { status?: number; data?: unknown };\n status?: number;\n statusCode?: number;\n code?: string;\n name?: string;\n cause?: unknown;\n} {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Classify HTTP status codes into structured errors.\n * Exhaustive switch with grouped default handling for unknown ranges.\n */\nfunction classifyHttpError(status: number, message: string): StructuredError {\n switch (status) {\n case 400:\n return { code: ErrorCode.INVALID_INPUT, message: 'Bad request', retryable: false, statusCode: status };\n case 401:\n return { code: ErrorCode.AUTH_ERROR, message: 'Invalid API key', retryable: false, statusCode: status };\n case 403:\n return { code: ErrorCode.QUOTA_EXCEEDED, message: 'Access forbidden or quota exceeded', retryable: false, statusCode: status };\n case 404:\n return { code: ErrorCode.NOT_FOUND, message: 'Resource not found', retryable: false, statusCode: status };\n case 408:\n return { code: ErrorCode.TIMEOUT, message: 'Request timeout', retryable: true, statusCode: status };\n case 429:\n return { code: ErrorCode.RATE_LIMITED, message: 'Rate limit exceeded', retryable: true, statusCode: status };\n case 500:\n return { code: ErrorCode.INTERNAL_ERROR, message: 'Server error', retryable: true, statusCode: status };\n case 502:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Bad gateway', retryable: true, statusCode: status };\n case 503:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Service unavailable', retryable: true, statusCode: status };\n case 504:\n return { code: ErrorCode.TIMEOUT, message: 'Gateway timeout', retryable: true, statusCode: status };\n case 510:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Request canceled', retryable: true, statusCode: status };\n default:\n if (status >= 500) {\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: `Server error: ${status}`, retryable: true, statusCode: status };\n }\n if (status >= 400) {\n return { code: ErrorCode.INVALID_INPUT, message: `Client error: ${status}`, retryable: false, statusCode: status };\n }\n return { code: ErrorCode.UNKNOWN_ERROR, message: `HTTP ${status}: ${message}`, retryable: false, statusCode: status };\n }\n}\n\n// ============================================================================\n// Retry Logic with Exponential Backoff\n// ============================================================================\n\n/**\n * Calculate delay with exponential backoff and jitter\n */\nfunction calculateBackoff(attempt: number, options: RetryOptions): number {\n const exponentialDelay = options.baseDelayMs * Math.pow(2, attempt);\n const jitter = Math.random() * 0.3 * exponentialDelay; // 0-30% jitter\n return Math.min(exponentialDelay + jitter, options.maxDelayMs);\n}\n\n/**\n * Sleep utility that respects abort signals\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n function onAbort() {\n clearTimeout(timeout);\n reject(new DOMException('Aborted', 'AbortError'));\n }\n\n const timeout = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n // Re-check: signal may have aborted between initial check and listener registration\n if (signal?.aborted) {\n onAbort();\n }\n });\n}\n\n/**\n * Wrap a fetch call with timeout via AbortController\n */\nexport function fetchWithTimeout(\n url: string,\n options: RequestInit & { timeoutMs?: number } = {}\n): Promise<Response> {\n const { timeoutMs = 30000, signal: externalSignal, ...fetchOptions } = options;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n let onExternalAbort: (() => void) | undefined;\n if (externalSignal) {\n onExternalAbort = () => controller.abort();\n externalSignal.addEventListener('abort', onExternalAbort, { once: true });\n if (externalSignal.aborted) {\n controller.abort();\n }\n }\n\n return fetch(url, { ...fetchOptions, signal: controller.signal }).finally(() => {\n clearTimeout(timeoutId);\n if (externalSignal && onExternalAbort) {\n externalSignal.removeEventListener('abort', onExternalAbort);\n }\n });\n}\n\n// ============================================================================\n// Stability Wrappers \u2014 Network resilience for LLM API calls\n// ============================================================================\n\n/**\n * Wrap a non-streaming API call with activity-based timeout detection.\n * If the call hasn't completed within `stallMs`, abort and retry.\n * This catches \"stuck\" connections where TCP stays open but no data flows.\n *\n * @param fn - Async function that accepts an AbortSignal\n * @param stallMs - Max milliseconds to wait for the call to complete before considering it stuck\n * @param maxAttempts - Max retry attempts for stalled requests\n * @param label - Label for log messages\n * @returns The result of the function\n */\nexport async function withStallProtection<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n stallMs: number,\n maxAttempts: number = 2,\n label: string = 'request',\n): Promise<T> {\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const controller = new AbortController();\n let stallTimer: ReturnType<typeof setTimeout> | undefined;\n\n const stallPromise = new Promise<never>((_, reject) => {\n stallTimer = setTimeout(() => {\n controller.abort();\n reject(Object.assign(new Error(`Service temporarily unavailable \u2014 no response received (attempt ${attempt + 1}/${maxAttempts})`), {\n code: 'ESTALLED',\n retryable: attempt < maxAttempts - 1,\n }));\n }, stallMs);\n });\n\n let fnPromise: Promise<T> | undefined;\n try {\n fnPromise = fn(controller.signal);\n const result = await Promise.race([fnPromise, stallPromise]);\n clearTimeout(stallTimer);\n return result;\n } catch (err) {\n // Suppress unhandled rejection from the losing promise\n // (e.g. fnPromise rejects after stallPromise wins the race)\n fnPromise?.catch(() => {});\n clearTimeout(stallTimer);\n const isStall = err instanceof Error && (err as NodeJS.ErrnoException).code === 'ESTALLED';\n if (isStall && attempt < maxAttempts - 1) {\n const backoff = calculateBackoff(attempt, DEFAULT_RETRY_OPTIONS);\n mcpLog('warning', `${label} stalled, retrying in ${backoff}ms (attempt ${attempt + 1})`, 'stability');\n await sleep(backoff);\n continue;\n }\n throw err;\n }\n }\n // Should never reach here, but TypeScript needs it\n throw new Error(`${label} failed after ${maxAttempts} stall-protection attempts`);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,25 @@
1
+ import { Logger } from "mcp-use";
2
+ function getLogger(name) {
3
+ return Logger.get(name);
4
+ }
5
+ function mcpLog(level, message, loggerName) {
6
+ const logger = getLogger(loggerName ?? "research-powerpack");
7
+ switch (level) {
8
+ case "debug":
9
+ logger.debug(message);
10
+ break;
11
+ case "info":
12
+ logger.info(message);
13
+ break;
14
+ case "warning":
15
+ logger.warn(message);
16
+ break;
17
+ case "error":
18
+ logger.error(message);
19
+ break;
20
+ }
21
+ }
22
+ export {
23
+ mcpLog
24
+ };
25
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/logger.ts"],
4
+ "sourcesContent": ["/**\n * Server logging utility.\n *\n * This server is HTTP-only, so logging must never depend on a transport-bound\n * MCP server instance. All logs flow through mcp-use's Logger, which writes to\n * stderr in Node and console in the browser.\n */\n\nimport { Logger } from 'mcp-use';\n\nexport type LogLevel = 'debug' | 'info' | 'warning' | 'error';\n\nfunction getLogger(name: string) {\n return Logger.get(name);\n}\n\n/**\n * Structured log helper backed by mcp-use's Logger.\n *\n * @param level - Log level.\n * @param message - Message to emit.\n * @param loggerName - Tool/component name for context (falls back to \"research-powerpack\").\n */\nexport function mcpLog(level: LogLevel, message: string, loggerName?: string): void {\n const logger = getLogger(loggerName ?? 'research-powerpack');\n\n switch (level) {\n case 'debug':\n logger.debug(message);\n break;\n case 'info':\n logger.info(message);\n break;\n case 'warning':\n logger.warn(message);\n break;\n case 'error':\n logger.error(message);\n break;\n }\n}\n"],
5
+ "mappings": "AAQA,SAAS,cAAc;AAIvB,SAAS,UAAU,MAAc;AAC/B,SAAO,OAAO,IAAI,IAAI;AACxB;AASO,SAAS,OAAO,OAAiB,SAAiB,YAA2B;AAClF,QAAM,SAAS,UAAU,cAAc,oBAAoB;AAE3D,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,MAAM,OAAO;AACpB;AAAA,IACF,KAAK;AACH,aAAO,KAAK,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,KAAK,OAAO;AACnB;AAAA,IACF,KAAK;AACH,aAAO,MAAM,OAAO;AACpB;AAAA,EACJ;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,15 @@
1
+ function removeMetaTags(content) {
2
+ if (!content || typeof content !== "string") {
3
+ return content;
4
+ }
5
+ const lines = content.split("\n");
6
+ const filteredLines = lines.filter((line) => {
7
+ const trimmed = line.trim();
8
+ return !trimmed.startsWith("- Meta:") && !trimmed.startsWith("Meta:");
9
+ });
10
+ return filteredLines.join("\n");
11
+ }
12
+ export {
13
+ removeMetaTags
14
+ };
15
+ //# sourceMappingURL=markdown-formatter.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/markdown-formatter.ts"],
4
+ "sourcesContent": ["/**\n * Markdown formatting utilities\n */\n\nexport function removeMetaTags(content: string): string {\n if (!content || typeof content !== 'string') {\n return content;\n }\n\n const lines = content.split('\\n');\n const filteredLines = lines.filter(line => {\n const trimmed = line.trim();\n return !trimmed.startsWith('- Meta:') && !trimmed.startsWith('Meta:');\n });\n\n return filteredLines.join('\\n');\n}\n"],
5
+ "mappings": "AAIO,SAAS,eAAe,SAAyB;AACtD,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,gBAAgB,MAAM,OAAO,UAAQ;AACzC,UAAM,UAAU,KAAK,KAAK;AAC1B,WAAO,CAAC,QAAQ,WAAW,SAAS,KAAK,CAAC,QAAQ,WAAW,OAAO;AAAA,EACtE,CAAC;AAED,SAAO,cAAc,KAAK,IAAI;AAChC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,29 @@
1
+ import { error } from "mcp-use/server";
2
+ import {
3
+ getWorkflowStateStore
4
+ } from "../services/workflow-state.js";
5
+ import { buildWorkflowKey } from "./workflow-key.js";
6
+ const REDDIT_KEYWORD_PATTERN = /\breddit\b/i;
7
+ async function redditKeywordGuard(ctx, queries, store = getWorkflowStateStore()) {
8
+ const matchedQuery = queries.find((query) => REDDIT_KEYWORD_PATTERN.test(query));
9
+ if (!matchedQuery) {
10
+ return null;
11
+ }
12
+ const workflowKey = buildWorkflowKey(ctx);
13
+ const state = await store.get(workflowKey);
14
+ if (state.redditWarningShown) {
15
+ return null;
16
+ }
17
+ await store.patch(workflowKey, { redditWarningShown: true });
18
+ return error([
19
+ "\u274C Putting `reddit` in a `web-search` query without setting the scope flag wastes a turn \u2014 the result is biased toward subreddit homepages and namesake hits.",
20
+ "",
21
+ `Blocked query: ${matchedQuery}`,
22
+ "",
23
+ 'For Reddit discovery, call `web-search` with `scope: "reddit"` \u2014 the server appends `site:reddit.com` and filters results to post permalinks. For non-Reddit web search, drop the word "reddit" from the query.'
24
+ ].join("\n"));
25
+ }
26
+ export {
27
+ redditKeywordGuard
28
+ };
29
+ //# sourceMappingURL=reddit-keyword-guard.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/reddit-keyword-guard.ts"],
4
+ "sourcesContent": ["import { error, type TypedCallToolResult } from 'mcp-use/server';\n\nimport {\n getWorkflowStateStore,\n type WorkflowStateStore,\n} from '../services/workflow-state.js';\nimport { buildWorkflowKey, type WorkflowKeyContext } from './workflow-key.js';\n\nconst REDDIT_KEYWORD_PATTERN = /\\breddit\\b/i;\n\nexport async function redditKeywordGuard(\n ctx: WorkflowKeyContext,\n queries: string[],\n store: WorkflowStateStore = getWorkflowStateStore(),\n): Promise<TypedCallToolResult<never> | null> {\n const matchedQuery = queries.find((query) => REDDIT_KEYWORD_PATTERN.test(query));\n if (!matchedQuery) {\n return null;\n }\n\n const workflowKey = buildWorkflowKey(ctx);\n const state = await store.get(workflowKey);\n if (state.redditWarningShown) {\n return null;\n }\n\n await store.patch(workflowKey, { redditWarningShown: true });\n\n return error([\n '\u274C Putting `reddit` in a `web-search` query without setting the scope flag wastes a turn \u2014 the result is biased toward subreddit homepages and namesake hits.',\n '',\n `Blocked query: ${matchedQuery}`,\n '',\n 'For Reddit discovery, call `web-search` with `scope: \"reddit\"` \u2014 the server appends `site:reddit.com` and filters results to post permalinks. For non-Reddit web search, drop the word \"reddit\" from the query.',\n ].join('\\n'));\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAuC;AAEhD;AAAA,EACE;AAAA,OAEK;AACP,SAAS,wBAAiD;AAE1D,MAAM,yBAAyB;AAE/B,eAAsB,mBACpB,KACA,SACA,QAA4B,sBAAsB,GACN;AAC5C,QAAM,eAAe,QAAQ,KAAK,CAAC,UAAU,uBAAuB,KAAK,KAAK,CAAC;AAC/E,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,iBAAiB,GAAG;AACxC,QAAM,QAAQ,MAAM,MAAM,IAAI,WAAW;AACzC,MAAI,MAAM,oBAAoB;AAC5B,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,MAAM,aAAa,EAAE,oBAAoB,KAAK,CAAC;AAE3D,SAAO,MAAM;AAAA,IACX;AAAA,IACA;AAAA,IACA,kBAAkB,YAAY;AAAA,IAC9B;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI,CAAC;AACd;",
6
+ "names": []
7
+ }
@@ -0,0 +1,81 @@
1
+ const SECONDS_MS = 1e3;
2
+ const MINUTES_MS = 6e4;
3
+ function formatSuccess(opts) {
4
+ const parts = [];
5
+ parts.push(`\u2713 ${opts.title}`);
6
+ parts.push("");
7
+ parts.push(opts.summary);
8
+ if (opts.data) {
9
+ parts.push("");
10
+ parts.push("---");
11
+ parts.push(opts.data);
12
+ }
13
+ if (opts.nextSteps?.length) {
14
+ parts.push("");
15
+ parts.push("---");
16
+ parts.push("**Next Steps:**");
17
+ opts.nextSteps.forEach((step) => parts.push(`\u2192 ${step}`));
18
+ }
19
+ if (opts.metadata && Object.keys(opts.metadata).length > 0) {
20
+ parts.push("");
21
+ parts.push("---");
22
+ const metaStr = Object.entries(opts.metadata).map(([k, v]) => `${k}: ${v}`).join(" | ");
23
+ parts.push(`*${metaStr}*`);
24
+ }
25
+ return parts.join("\n");
26
+ }
27
+ function formatError(opts) {
28
+ const parts = [];
29
+ const prefix = opts.toolName ? `[${opts.toolName}] ` : "";
30
+ parts.push(`\u274C ${prefix}${opts.code}: ${opts.message}`);
31
+ if (opts.retryable) {
32
+ parts.push("*Retryable.*");
33
+ }
34
+ if (opts.howToFix?.length) {
35
+ parts.push("");
36
+ parts.push("**How to Fix:**");
37
+ opts.howToFix.forEach((step, i) => parts.push(`${i + 1}. ${step}`));
38
+ }
39
+ if (opts.alternatives?.length) {
40
+ parts.push("");
41
+ parts.push("**Alternatives:**");
42
+ opts.alternatives.forEach((alt, i) => parts.push(`${i + 1}. ${alt}`));
43
+ }
44
+ return parts.join("\n");
45
+ }
46
+ function formatBatchHeader(opts) {
47
+ const parts = [];
48
+ const successRate = opts.totalItems > 0 ? opts.successful / opts.totalItems : 0;
49
+ const emoji = successRate === 1 ? "\u2713" : successRate >= 0.5 ? "\u26A0\uFE0F" : "\u274C";
50
+ parts.push(`${emoji} ${opts.title}`);
51
+ parts.push("");
52
+ parts.push(`\u2022 Total: ${opts.totalItems}`);
53
+ parts.push(`\u2022 Successful: ${opts.successful}`);
54
+ if (opts.failed > 0) {
55
+ parts.push(`\u2022 Failed: ${opts.failed}`);
56
+ }
57
+ if (opts.tokensPerItem) {
58
+ parts.push(`\u2022 Tokens/item: ~${opts.tokensPerItem.toLocaleString()}`);
59
+ }
60
+ if (opts.batches) {
61
+ parts.push(`\u2022 Batches: ${opts.batches}`);
62
+ }
63
+ if (opts.extras) {
64
+ Object.entries(opts.extras).forEach(([key, val]) => {
65
+ parts.push(`\u2022 ${key}: ${val}`);
66
+ });
67
+ }
68
+ return parts.join("\n");
69
+ }
70
+ function formatDuration(ms) {
71
+ if (ms < SECONDS_MS) return `${ms}ms`;
72
+ if (ms < MINUTES_MS) return `${(ms / SECONDS_MS).toFixed(1)}s`;
73
+ return `${(ms / MINUTES_MS).toFixed(1)}m`;
74
+ }
75
+ export {
76
+ formatBatchHeader,
77
+ formatDuration,
78
+ formatError,
79
+ formatSuccess
80
+ };
81
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/response.ts"],
4
+ "sourcesContent": ["/**\n * MCP Response Formatters\n */\n\n/** Duration thresholds in milliseconds */\nconst SECONDS_MS = 1_000 as const;\nconst MINUTES_MS = 60_000 as const;\n\n// ============================================================================\n// Success Response Formatter\n// ============================================================================\n\nexport interface SuccessOptions {\n /** Title/header for the response */\n readonly title: string;\n /** Summary section (70% of content) */\n readonly summary: string;\n /** Optional data section (20% of content) */\n readonly data?: string;\n /** Optional next steps (10% of content) */\n readonly nextSteps?: string[];\n /** Optional metadata footer */\n readonly metadata?: Record<string, string | number>;\n}\n\n/**\n * Format a successful response using 70/20/10 pattern\n */\nexport function formatSuccess(opts: SuccessOptions): string {\n const parts: string[] = [];\n\n // Title\n parts.push(`\u2713 ${opts.title}`);\n parts.push('');\n\n // Summary (70%)\n parts.push(opts.summary);\n\n // Data section (20%)\n if (opts.data) {\n parts.push('');\n parts.push('---');\n parts.push(opts.data);\n }\n\n // Next steps (10%)\n if (opts.nextSteps?.length) {\n parts.push('');\n parts.push('---');\n parts.push('**Next Steps:**');\n opts.nextSteps.forEach(step => parts.push(`\u2192 ${step}`));\n }\n\n // Metadata footer\n if (opts.metadata && Object.keys(opts.metadata).length > 0) {\n parts.push('');\n parts.push('---');\n const metaStr = Object.entries(opts.metadata)\n .map(([k, v]) => `${k}: ${v}`)\n .join(' | ');\n parts.push(`*${metaStr}*`);\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Error Response Formatter\n// ============================================================================\n\nexport interface ErrorOptions {\n /** Error code (e.g., RATE_LIMITED, TIMEOUT) */\n readonly code: string;\n /** Human-readable error message */\n readonly message: string;\n /** Is this error retryable? */\n readonly retryable?: boolean;\n /** How to fix the error */\n readonly howToFix?: string[];\n /** Alternative actions */\n readonly alternatives?: string[];\n /** Tool name for context */\n readonly toolName?: string;\n}\n\n/**\n * Format an error response with recovery guidance\n * Designed to keep agents moving \u2014 every error includes actionable alternatives\n */\nexport function formatError(opts: ErrorOptions): string {\n const parts: string[] = [];\n\n // Error header\n const prefix = opts.toolName ? `[${opts.toolName}] ` : '';\n parts.push(`\u274C ${prefix}${opts.code}: ${opts.message}`);\n\n // Retryable hint\n if (opts.retryable) {\n parts.push('*Retryable.*');\n }\n\n // How to fix\n if (opts.howToFix?.length) {\n parts.push('');\n parts.push('**How to Fix:**');\n opts.howToFix.forEach((step, i) => parts.push(`${i + 1}. ${step}`));\n }\n\n // Alternatives\n if (opts.alternatives?.length) {\n parts.push('');\n parts.push('**Alternatives:**');\n opts.alternatives.forEach((alt, i) => parts.push(`${i + 1}. ${alt}`));\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Batch Header Formatter\n// ============================================================================\n\nexport interface BatchHeaderOptions {\n /** Batch operation title */\n readonly title: string;\n /** Total items attempted */\n readonly totalItems: number;\n /** Successfully processed count */\n readonly successful: number;\n /** Failed count */\n readonly failed: number;\n /** Optional tokens per item */\n readonly tokensPerItem?: number;\n /** Optional batch count */\n readonly batches?: number;\n /** Extra stats to include */\n readonly extras?: Record<string, string | number>;\n}\n\n/**\n * Format a batch operation header with stats\n */\nexport function formatBatchHeader(opts: BatchHeaderOptions): string {\n const parts: string[] = [];\n\n // Title with emoji based on success rate\n const successRate = opts.totalItems > 0 ? opts.successful / opts.totalItems : 0;\n const emoji = successRate === 1 ? '\u2713' : successRate >= 0.5 ? '\u26A0\uFE0F' : '\u274C';\n parts.push(`${emoji} ${opts.title}`);\n parts.push('');\n\n // Stats\n parts.push(`\u2022 Total: ${opts.totalItems}`);\n parts.push(`\u2022 Successful: ${opts.successful}`);\n if (opts.failed > 0) {\n parts.push(`\u2022 Failed: ${opts.failed}`);\n }\n if (opts.tokensPerItem) {\n parts.push(`\u2022 Tokens/item: ~${opts.tokensPerItem.toLocaleString()}`);\n }\n if (opts.batches) {\n parts.push(`\u2022 Batches: ${opts.batches}`);\n }\n\n // Extra stats\n if (opts.extras) {\n Object.entries(opts.extras).forEach(([key, val]) => {\n parts.push(`\u2022 ${key}: ${val}`);\n });\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Duration Formatter\n// ============================================================================\n\n/**\n * Format duration in human-readable form\n */\nexport function formatDuration(ms: number): string {\n if (ms < SECONDS_MS) return `${ms}ms`;\n if (ms < MINUTES_MS) return `${(ms / SECONDS_MS).toFixed(1)}s`;\n return `${(ms / MINUTES_MS).toFixed(1)}m`;\n}\n\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,13 @@
1
+ const JITTER_FACTOR = 0.3;
2
+ const EXPONENTIAL_BASE = 2;
3
+ const DEFAULT_BASE_DELAY_MS = 1e3;
4
+ const DEFAULT_MAX_DELAY_MS = 3e4;
5
+ function calculateBackoff(attempt, baseDelayMs = DEFAULT_BASE_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS) {
6
+ const exponentialDelay = baseDelayMs * Math.pow(EXPONENTIAL_BASE, attempt);
7
+ const jitter = JITTER_FACTOR * exponentialDelay * Math.random();
8
+ return Math.min(exponentialDelay + jitter, maxDelayMs);
9
+ }
10
+ export {
11
+ calculateBackoff
12
+ };
13
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/retry.ts"],
4
+ "sourcesContent": ["/**\n * Shared retry and backoff utilities\n */\n\n/** Jitter factor to prevent thundering herd */\nconst JITTER_FACTOR = 0.3 as const;\n\n/** Exponential base for backoff calculation */\nconst EXPONENTIAL_BASE = 2 as const;\n\n/** Default base delay for exponential backoff (ms) */\nconst DEFAULT_BASE_DELAY_MS = 1_000 as const;\n\n/** Default maximum backoff delay cap (ms) */\nconst DEFAULT_MAX_DELAY_MS = 30_000 as const;\n\n/**\n * Calculate exponential backoff delay with jitter.\n * Formula: min(base * 2^attempt + random_jitter, maxDelay)\n *\n * @param attempt - Zero-based retry attempt number\n * @param baseDelayMs - Base delay in milliseconds (default: 1000)\n * @param maxDelayMs - Maximum delay cap in milliseconds (default: 30000)\n * @returns Delay in milliseconds with jitter applied\n */\nexport function calculateBackoff(\n attempt: number,\n baseDelayMs: number = DEFAULT_BASE_DELAY_MS,\n maxDelayMs: number = DEFAULT_MAX_DELAY_MS,\n): number {\n const exponentialDelay = baseDelayMs * Math.pow(EXPONENTIAL_BASE, attempt);\n const jitter = JITTER_FACTOR * exponentialDelay * Math.random();\n return Math.min(exponentialDelay + jitter, maxDelayMs);\n}\n"],
5
+ "mappings": "AAKA,MAAM,gBAAgB;AAGtB,MAAM,mBAAmB;AAGzB,MAAM,wBAAwB;AAG9B,MAAM,uBAAuB;AAWtB,SAAS,iBACd,SACA,cAAsB,uBACtB,aAAqB,sBACb;AACR,QAAM,mBAAmB,cAAc,KAAK,IAAI,kBAAkB,OAAO;AACzE,QAAM,SAAS,gBAAgB,mBAAmB,KAAK,OAAO;AAC9D,SAAO,KAAK,IAAI,mBAAmB,QAAQ,UAAU;AACvD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,10 @@
1
+ const CONTROL_CHARS = /[\x00-\x1f\x7f]/g;
2
+ const URLS = /https?:\/\/\S+/gi;
3
+ const MARKDOWN_LINKS = /\[([^\]]+)\]\([^)]+\)/g;
4
+ function sanitizeSuggestion(input) {
5
+ return input.replace(CONTROL_CHARS, " ").replace(MARKDOWN_LINKS, "$1").replace(URLS, "").replace(/\s+/g, " ").trim();
6
+ }
7
+ export {
8
+ sanitizeSuggestion
9
+ };
10
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/sanitize.ts"],
4
+ "sourcesContent": ["const CONTROL_CHARS = /[\\x00-\\x1f\\x7f]/g;\nconst URLS = /https?:\\/\\/\\S+/gi;\nconst MARKDOWN_LINKS = /\\[([^\\]]+)\\]\\([^)]+\\)/g;\n\nexport function sanitizeSuggestion(input: string): string {\n return input\n .replace(CONTROL_CHARS, ' ')\n .replace(MARKDOWN_LINKS, '$1')\n .replace(URLS, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n"],
5
+ "mappings": "AAAA,MAAM,gBAAgB;AACtB,MAAM,OAAO;AACb,MAAM,iBAAiB;AAEhB,SAAS,mBAAmB,OAAuB;AACxD,SAAO,MACJ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,gBAAgB,IAAI,EAC5B,QAAQ,MAAM,EAAE,EAChB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;",
6
+ "names": []
7
+ }
@@ -0,0 +1,41 @@
1
+ const RULES = [
2
+ // Reddit post permalinks (subreddit homepages are filtered out upstream).
3
+ [/(?:^|\.)reddit\.com\//i, "reddit"],
4
+ [/(?:^|\.)github\.com\//i, "github"],
5
+ [/(?:^|\.)gitlab\.com\//i, "github"],
6
+ // CVE-prefixed paths are unambiguous regardless of host.
7
+ [/\/CVE-\d{4}-\d+/i, "cve"],
8
+ [/(?:^|\.)nvd\.nist\.gov\//i, "cve"],
9
+ [/(?:^|\.)stackoverflow\.com\//i, "qa"],
10
+ [/(?:^|\.)stackexchange\.com\//i, "qa"],
11
+ [/(?:^|\.)arxiv\.org\//i, "paper"],
12
+ [/(?:^|\.)medium\.com\//i, "blog"],
13
+ [/(?:^|\.)dev\.to\//i, "blog"],
14
+ [/(?:^|\.)substack\.com\//i, "blog"],
15
+ // Docs subdomains and /docs/ paths.
16
+ [/^(?:[a-z0-9-]+\.)*docs\./i, "docs"],
17
+ [/\/docs\//i, "docs"],
18
+ [/(?:^|\.)readthedocs\.io\//i, "docs"],
19
+ // Video.
20
+ [/(?:^|\.)youtube\.com\/watch/i, "video"],
21
+ [/(?:^|\.)youtu\.be\//i, "video"],
22
+ // News / engineering blogs (last so it doesn't capture vendor docs).
23
+ [/(?:^|\.)(?:news|blog|engineering)\.[a-z0-9-]+\.[a-z]{2,}\//i, "news"]
24
+ ];
25
+ function classifySourceByUrl(url) {
26
+ let candidate;
27
+ try {
28
+ const u = new URL(url);
29
+ candidate = `${u.hostname}${u.pathname}`;
30
+ } catch {
31
+ candidate = url;
32
+ }
33
+ for (const [re, type] of RULES) {
34
+ if (re.test(candidate)) return type;
35
+ }
36
+ return "web";
37
+ }
38
+ export {
39
+ classifySourceByUrl
40
+ };
41
+ //# sourceMappingURL=source-type.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/source-type.ts"],
4
+ "sourcesContent": ["/**\n * Hostname/path-heuristic source-type tagging. Works without the LLM\n * classifier so degraded-mode web-search responses still carry a\n * `source_type` field per result. When the LLM classifier IS available,\n * its tag wins (the classifier sees title + snippet as well, not just URL).\n *\n * See: mcp-revisions/output-shaping/06-source-type-tagging-without-llm.md.\n */\n\nexport type SourceType =\n | 'reddit'\n | 'github'\n | 'docs'\n | 'blog'\n | 'paper'\n | 'qa'\n | 'cve'\n | 'news'\n | 'video'\n | 'web';\n\nconst RULES: Array<[RegExp, SourceType]> = [\n // Reddit post permalinks (subreddit homepages are filtered out upstream).\n [/(?:^|\\.)reddit\\.com\\//i, 'reddit'],\n [/(?:^|\\.)github\\.com\\//i, 'github'],\n [/(?:^|\\.)gitlab\\.com\\//i, 'github'],\n // CVE-prefixed paths are unambiguous regardless of host.\n [/\\/CVE-\\d{4}-\\d+/i, 'cve'],\n [/(?:^|\\.)nvd\\.nist\\.gov\\//i, 'cve'],\n [/(?:^|\\.)stackoverflow\\.com\\//i, 'qa'],\n [/(?:^|\\.)stackexchange\\.com\\//i, 'qa'],\n [/(?:^|\\.)arxiv\\.org\\//i, 'paper'],\n [/(?:^|\\.)medium\\.com\\//i, 'blog'],\n [/(?:^|\\.)dev\\.to\\//i, 'blog'],\n [/(?:^|\\.)substack\\.com\\//i, 'blog'],\n // Docs subdomains and /docs/ paths.\n [/^(?:[a-z0-9-]+\\.)*docs\\./i, 'docs'],\n [/\\/docs\\//i, 'docs'],\n [/(?:^|\\.)readthedocs\\.io\\//i, 'docs'],\n // Video.\n [/(?:^|\\.)youtube\\.com\\/watch/i, 'video'],\n [/(?:^|\\.)youtu\\.be\\//i, 'video'],\n // News / engineering blogs (last so it doesn't capture vendor docs).\n [/(?:^|\\.)(?:news|blog|engineering)\\.[a-z0-9-]+\\.[a-z]{2,}\\//i, 'news'],\n];\n\nexport function classifySourceByUrl(url: string): SourceType {\n let candidate: string;\n try {\n const u = new URL(url);\n // Match against `host + pathname` so rules can use either or both.\n candidate = `${u.hostname}${u.pathname}`;\n } catch {\n candidate = url;\n }\n for (const [re, type] of RULES) {\n if (re.test(candidate)) return type;\n }\n return 'web';\n}\n"],
5
+ "mappings": "AAqBA,MAAM,QAAqC;AAAA;AAAA,EAEzC,CAAC,0BAA0B,QAAQ;AAAA,EACnC,CAAC,0BAA0B,QAAQ;AAAA,EACnC,CAAC,0BAA0B,QAAQ;AAAA;AAAA,EAEnC,CAAC,oBAAoB,KAAK;AAAA,EAC1B,CAAC,6BAA6B,KAAK;AAAA,EACnC,CAAC,iCAAiC,IAAI;AAAA,EACtC,CAAC,iCAAiC,IAAI;AAAA,EACtC,CAAC,yBAAyB,OAAO;AAAA,EACjC,CAAC,0BAA0B,MAAM;AAAA,EACjC,CAAC,sBAAsB,MAAM;AAAA,EAC7B,CAAC,4BAA4B,MAAM;AAAA;AAAA,EAEnC,CAAC,6BAA6B,MAAM;AAAA,EACpC,CAAC,aAAa,MAAM;AAAA,EACpB,CAAC,8BAA8B,MAAM;AAAA;AAAA,EAErC,CAAC,gCAAgC,OAAO;AAAA,EACxC,CAAC,wBAAwB,OAAO;AAAA;AAAA,EAEhC,CAAC,+DAA+D,MAAM;AACxE;AAEO,SAAS,oBAAoB,KAAyB;AAC3D,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,GAAG;AAErB,gBAAY,GAAG,EAAE,QAAQ,GAAG,EAAE,QAAQ;AAAA,EACxC,QAAQ;AACN,gBAAY;AAAA,EACd;AACA,aAAW,CAAC,IAAI,IAAI,KAAK,OAAO;AAC9B,QAAI,GAAG,KAAK,SAAS,EAAG,QAAO;AAAA,EACjC;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }