mcp-researchpowerpack 7.0.8 → 7.0.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.
- package/dist/mcp-use.json +2 -2
- package/dist/src/schemas/scrape-links.js +2 -1
- package/dist/src/schemas/scrape-links.js.map +2 -2
- package/dist/src/schemas/web-search.js +4 -1
- package/dist/src/schemas/web-search.js.map +2 -2
- package/dist/src/tools/scrape.js +7 -1
- package/dist/src/tools/scrape.js.map +2 -2
- package/dist/src/tools/search.js +4 -1
- package/dist/src/tools/search.js.map +2 -2
- package/package.json +1 -1
package/dist/mcp-use.json
CHANGED
|
@@ -5,7 +5,8 @@ const urlSchema = z.string().url({ message: "scrape: Invalid URL format" }).refi
|
|
|
5
5
|
).describe("A fully-qualified HTTP or HTTPS URL to scrape.");
|
|
6
6
|
const urlsSchema = z.array(urlSchema).min(1, { message: "scrape: At least 1 URL required" }).max(50, { message: "scrape: At most 50 URLs allowed per call" }).describe("URLs to fetch in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments). Non-Reddit URLs use Jina Reader first, then Jina Reader through Scrape.do proxy when configured, then optional Kernel browser rendering for web pages.");
|
|
7
7
|
const rawScrapeLinksParamsSchema = z.object({
|
|
8
|
-
urls: urlsSchema
|
|
8
|
+
urls: urlsSchema,
|
|
9
|
+
extract: z.never().optional()
|
|
9
10
|
}).strict();
|
|
10
11
|
const smartScrapeLinksParamsSchema = z.object({
|
|
11
12
|
urls: z.array(urlSchema).min(1, { message: "scrape: At least 1 URL required" }).max(50, { message: "scrape: At most 50 URLs allowed per call" }).describe("URLs to fetch and extract in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments). Non-Reddit URLs use Jina Reader first, then Jina Reader through Scrape.do proxy when configured, then optional Kernel browser rendering for web pages. Mix reddit + non-reddit URLs freely; branches run concurrently. Prefer contextually grouped batches \u2014 call this tool multiple times in parallel when URL sets are unrelated."),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/schemas/scrape-links.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod';\n\nconst urlSchema = z\n .string()\n .url({ message: 'scrape: Invalid URL format' })\n .refine(\n url => url.startsWith('http://') || url.startsWith('https://'),\n { message: 'scrape: URL must use http:// or https://' }\n )\n .describe('A fully-qualified HTTP or HTTPS URL to scrape.');\n\nconst urlsSchema = z\n .array(urlSchema)\n .min(1, { message: 'scrape: At least 1 URL required' })\n .max(50, { message: 'scrape: At most 50 URLs allowed per call' })\n .describe('URLs to fetch in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments). Non-Reddit URLs use Jina Reader first, then Jina Reader through Scrape.do proxy when configured, then optional Kernel browser rendering for web pages.');\n\nexport const rawScrapeLinksParamsSchema = z.object({\n urls: urlsSchema,\n}).strict();\n\nexport const smartScrapeLinksParamsSchema = z.object({\n urls: z\n .array(urlSchema)\n .min(1, { message: 'scrape: At least 1 URL required' })\n .max(50, { message: 'scrape: At most 50 URLs allowed per call' })\n .describe('URLs to fetch and extract in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments). Non-Reddit URLs use Jina Reader first, then Jina Reader through Scrape.do proxy when configured, then optional Kernel browser rendering for web pages. Mix reddit + non-reddit URLs freely; branches run concurrently. Prefer contextually grouped batches \u2014 call this tool multiple times in parallel when URL sets are unrelated.'),\n extract: z\n .string()\n .min(1, { message: 'smart-scrape-links: extract cannot be empty' })\n .describe(\n 'Required semantic extraction instruction. Describe the SHAPE of what you want, separated by `|`. The extractor classifies each page (docs / github-thread / reddit / marketing / cve / paper / announcement / qa / blog / changelog / release-notes) and adjusts emphasis per type: preserves numbers/versions/stacktraces verbatim from docs and CVE pages, quotes Reddit/HN with attribution plus sentiment distribution, flags what the page did NOT answer in a \"Not found\" section, and surfaces referenced-but-unscraped URLs in a \"Follow-up signals\" section. Good examples: \"root cause | affected versions | fix | workarounds | timeline\"; \"pricing tiers | rate limits | enterprise contact | free-tier quotas\"; \"maintainer decisions | accepted fix commits | stacktraces | resolved version\".',\n ),\n}).strict();\n\nexport type RawScrapeLinksParams = z.infer<typeof rawScrapeLinksParamsSchema>;\nexport type SmartScrapeLinksParams = z.infer<typeof smartScrapeLinksParamsSchema>;\n\n// Internal alias retained for shared tests/helpers. Public tools register the\n// raw/smart schemas above, not this alias.\nexport const scrapeLinksParamsSchema = smartScrapeLinksParamsSchema;\nexport type ScrapeLinksParams = SmartScrapeLinksParams;\n\n// Scrape tools are markdown-only at the MCP boundary.\nexport type ScrapeLinksOutput = Record<string, never>;\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAElB,MAAM,YAAY,EACf,OAAO,EACP,IAAI,EAAE,SAAS,6BAA6B,CAAC,EAC7C;AAAA,EACC,SAAO,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU;AAAA,EAC7D,EAAE,SAAS,2CAA2C;AACxD,EACC,SAAS,gDAAgD;AAE5D,MAAM,aAAa,EAChB,MAAM,SAAS,EACf,IAAI,GAAG,EAAE,SAAS,kCAAkC,CAAC,EACrD,IAAI,IAAI,EAAE,SAAS,2CAA2C,CAAC,EAC/D,SAAS,mUAAmU;AAExU,MAAM,6BAA6B,EAAE,OAAO;AAAA,EACjD,MAAM;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod';\n\nconst urlSchema = z\n .string()\n .url({ message: 'scrape: Invalid URL format' })\n .refine(\n url => url.startsWith('http://') || url.startsWith('https://'),\n { message: 'scrape: URL must use http:// or https://' }\n )\n .describe('A fully-qualified HTTP or HTTPS URL to scrape.');\n\nconst urlsSchema = z\n .array(urlSchema)\n .min(1, { message: 'scrape: At least 1 URL required' })\n .max(50, { message: 'scrape: At most 50 URLs allowed per call' })\n .describe('URLs to fetch in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments). Non-Reddit URLs use Jina Reader first, then Jina Reader through Scrape.do proxy when configured, then optional Kernel browser rendering for web pages.');\n\nexport const rawScrapeLinksParamsSchema = z.object({\n urls: urlsSchema,\n extract: z.never().optional(),\n}).strict();\n\nexport const smartScrapeLinksParamsSchema = z.object({\n urls: z\n .array(urlSchema)\n .min(1, { message: 'scrape: At least 1 URL required' })\n .max(50, { message: 'scrape: At most 50 URLs allowed per call' })\n .describe('URLs to fetch and extract in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments). Non-Reddit URLs use Jina Reader first, then Jina Reader through Scrape.do proxy when configured, then optional Kernel browser rendering for web pages. Mix reddit + non-reddit URLs freely; branches run concurrently. Prefer contextually grouped batches \u2014 call this tool multiple times in parallel when URL sets are unrelated.'),\n extract: z\n .string()\n .min(1, { message: 'smart-scrape-links: extract cannot be empty' })\n .describe(\n 'Required semantic extraction instruction. Describe the SHAPE of what you want, separated by `|`. The extractor classifies each page (docs / github-thread / reddit / marketing / cve / paper / announcement / qa / blog / changelog / release-notes) and adjusts emphasis per type: preserves numbers/versions/stacktraces verbatim from docs and CVE pages, quotes Reddit/HN with attribution plus sentiment distribution, flags what the page did NOT answer in a \"Not found\" section, and surfaces referenced-but-unscraped URLs in a \"Follow-up signals\" section. Good examples: \"root cause | affected versions | fix | workarounds | timeline\"; \"pricing tiers | rate limits | enterprise contact | free-tier quotas\"; \"maintainer decisions | accepted fix commits | stacktraces | resolved version\".',\n ),\n}).strict();\n\nexport type RawScrapeLinksParams = z.infer<typeof rawScrapeLinksParamsSchema>;\nexport type SmartScrapeLinksParams = z.infer<typeof smartScrapeLinksParamsSchema>;\n\n// Internal alias retained for shared tests/helpers. Public tools register the\n// raw/smart schemas above, not this alias.\nexport const scrapeLinksParamsSchema = smartScrapeLinksParamsSchema;\nexport type ScrapeLinksParams = SmartScrapeLinksParams;\n\n// Scrape tools are markdown-only at the MCP boundary.\nexport type ScrapeLinksOutput = Record<string, never>;\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAElB,MAAM,YAAY,EACf,OAAO,EACP,IAAI,EAAE,SAAS,6BAA6B,CAAC,EAC7C;AAAA,EACC,SAAO,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU;AAAA,EAC7D,EAAE,SAAS,2CAA2C;AACxD,EACC,SAAS,gDAAgD;AAE5D,MAAM,aAAa,EAChB,MAAM,SAAS,EACf,IAAI,GAAG,EAAE,SAAS,kCAAkC,CAAC,EACrD,IAAI,IAAI,EAAE,SAAS,2CAA2C,CAAC,EAC/D,SAAS,mUAAmU;AAExU,MAAM,6BAA6B,EAAE,OAAO;AAAA,EACjD,MAAM;AAAA,EACN,SAAS,EAAE,MAAM,EAAE,SAAS;AAC9B,CAAC,EAAE,OAAO;AAEH,MAAM,+BAA+B,EAAE,OAAO;AAAA,EACnD,MAAM,EACH,MAAM,SAAS,EACf,IAAI,GAAG,EAAE,SAAS,kCAAkC,CAAC,EACrD,IAAI,IAAI,EAAE,SAAS,2CAA2C,CAAC,EAC/D,SAAS,igBAA4f;AAAA,EACxgB,SAAS,EACN,OAAO,EACP,IAAI,GAAG,EAAE,SAAS,8CAA8C,CAAC,EACjE;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EAAE,OAAO;AAOH,MAAM,0BAA0B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -20,7 +20,10 @@ const keywordsSchema = z.array(keywordSchema).min(1, { message: "search: At leas
|
|
|
20
20
|
`Search keywords to run in parallel. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Think of keywords as retrieval probes, not topic labels. Pack distinct facets in one call: official docs, implementation, failures, comparisons, sentiment, changelog, CVE, pricing, or other source classes.`
|
|
21
21
|
);
|
|
22
22
|
const rawWebSearchParamsSchema = z.object({
|
|
23
|
-
keywords: keywordsSchema
|
|
23
|
+
keywords: keywordsSchema,
|
|
24
|
+
extract: z.never().optional(),
|
|
25
|
+
scope: z.never().optional(),
|
|
26
|
+
verbose: z.never().optional()
|
|
24
27
|
}).strict();
|
|
25
28
|
const smartWebSearchParamsSchema = z.object({
|
|
26
29
|
keywords: keywordsSchema,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/schemas/web-search.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod';\n\nexport const QUERY_REWRITE_PAIR_EXAMPLES = [\n 'Bad: `<feature> support` \u2192 Better: `site:<official-docs-domain> \"<feature>\" \"<platform-or-version>\"`',\n 'Bad: `<product> pricing` \u2192 Better: `site:<vendor-domain> \"<product>\" pricing \"enterprise\" OR \"free tier\"`',\n 'Bad: `<library> bug fix` \u2192 Better: `\"<exact error text>\" \"<library-or-package>\" \"<version>\" site:github.com`',\n 'Bad: `<tool> reviews` \u2192 Better: `site:reddit.com/r/<community>/comments \"<tool>\" \"migration\" OR \"regression\"`',\n] as const;\n\nexport const QUERY_REWRITE_PAIR_GUIDANCE = [\n 'Write Google retrieval probes, not topic labels.',\n 'For each broad idea, rewrite it into a query that names the evidence source class, discriminating anchor terms, and one useful operator when possible.',\n 'Use rewrite-pair thinking before searching:',\n ...QUERY_REWRITE_PAIR_EXAMPLES,\n 'Do not repeat the same noun phrase with adjectives changed; fan out by source type and evidence need.',\n] as const;\n\nexport const QUERY_REWRITE_PAIR_GUIDANCE_TEXT = QUERY_REWRITE_PAIR_GUIDANCE.join(' ');\n\nconst keywordSchema = z\n .string()\n .min(1, { message: 'search: Keyword cannot be empty' })\n .describe(\n `A single search keyword/query. Each item runs as a separate parallel search. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT}`,\n );\n\nconst keywordsSchema = z\n .array(keywordSchema)\n .min(1, { message: 'search: At least 1 keyword required' })\n .max(50, { message: 'search: At most 50 keywords allowed per call' })\n .describe(\n `Search keywords to run in parallel. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Think of keywords as retrieval probes, not topic labels. Pack distinct facets in one call: official docs, implementation, failures, comparisons, sentiment, changelog, CVE, pricing, or other source classes.`,\n );\n\nexport const rawWebSearchParamsSchema = z.object({\n keywords: keywordsSchema,\n}).strict();\n\nexport const smartWebSearchParamsSchema = z.object({\n keywords: keywordsSchema,\n extract: z\n .string()\n .min(1, { message: 'smart-web-search: extract cannot be empty' })\n .describe(\n 'Semantic instruction for the relevance classifier \u2014 what \"relevant\" means for THIS goal. This is the post-sort target, so name the evidence you need and the source-of-truth expectation: e.g. official docs/release notes for specs, issue/PR/error text for bugs, Reddit/HN/blogs for lived experience, vendor pricing pages for pricing, CVE databases for security. Drives tiering (HIGHLY_RELEVANT / MAYBE_RELEVANT / OTHER), synthesis, gap analysis, and refine-query suggestions. Be specific: \"OAuth 2.1 support in TypeScript MCP frameworks \u2014 runnable code, not marketing\", not \"MCP OAuth\".',\n ),\n scope: z\n .enum(['web', 'reddit', 'both'])\n .default('web')\n .describe(\n 'Search scope. \"web\" (default) = open web, no augmentation. \"reddit\" = server appends `site:reddit.com` to every keyword and filters results to post permalinks (`/r/.+/comments/[a-z0-9]+/`); subreddit homepages are dropped. \"both\" = runs every keyword twice (open web + reddit-scoped), merges the result set, and tags each row with its source. Use \"reddit\" for sentiment/migration/lived-experience research; use \"both\" when opinion-heavy AND official sources also matter.',\n ),\n verbose: z\n .boolean()\n .default(false)\n .describe(\n 'Include per-row scoring/coverage metadata, the trailing Signals block, and CONSENSUS labels even when they carry little signal. Default false.',\n ),\n}).strict();\n\nexport type RawWebSearchParams = z.infer<typeof rawWebSearchParamsSchema>;\nexport type SmartWebSearchParams = z.infer<typeof smartWebSearchParamsSchema>;\n\n// Internal alias retained for shared tests/helpers. Public tools register the\n// raw/smart schemas above, not this alias.\nexport const webSearchParamsSchema = smartWebSearchParamsSchema;\nexport type WebSearchParams = SmartWebSearchParams;\n\n// Search tools are markdown-only at the MCP boundary.\nexport type WebSearchOutput = Record<string, never>;\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AAAA,EACH;AACF;AAEO,MAAM,mCAAmC,4BAA4B,KAAK,GAAG;AAEpF,MAAM,gBAAgB,EACnB,OAAO,EACP,IAAI,GAAG,EAAE,SAAS,kCAAkC,CAAC,EACrD;AAAA,EACC,gFAAgF,gCAAgC;AAClH;AAEF,MAAM,iBAAiB,EACpB,MAAM,aAAa,EACnB,IAAI,GAAG,EAAE,SAAS,sCAAsC,CAAC,EACzD,IAAI,IAAI,EAAE,SAAS,+CAA+C,CAAC,EACnE;AAAA,EACC,gKAAgK,gCAAgC;AAClM;AAEK,MAAM,2BAA2B,EAAE,OAAO;AAAA,EAC/C,UAAU;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod';\n\nexport const QUERY_REWRITE_PAIR_EXAMPLES = [\n 'Bad: `<feature> support` \u2192 Better: `site:<official-docs-domain> \"<feature>\" \"<platform-or-version>\"`',\n 'Bad: `<product> pricing` \u2192 Better: `site:<vendor-domain> \"<product>\" pricing \"enterprise\" OR \"free tier\"`',\n 'Bad: `<library> bug fix` \u2192 Better: `\"<exact error text>\" \"<library-or-package>\" \"<version>\" site:github.com`',\n 'Bad: `<tool> reviews` \u2192 Better: `site:reddit.com/r/<community>/comments \"<tool>\" \"migration\" OR \"regression\"`',\n] as const;\n\nexport const QUERY_REWRITE_PAIR_GUIDANCE = [\n 'Write Google retrieval probes, not topic labels.',\n 'For each broad idea, rewrite it into a query that names the evidence source class, discriminating anchor terms, and one useful operator when possible.',\n 'Use rewrite-pair thinking before searching:',\n ...QUERY_REWRITE_PAIR_EXAMPLES,\n 'Do not repeat the same noun phrase with adjectives changed; fan out by source type and evidence need.',\n] as const;\n\nexport const QUERY_REWRITE_PAIR_GUIDANCE_TEXT = QUERY_REWRITE_PAIR_GUIDANCE.join(' ');\n\nconst keywordSchema = z\n .string()\n .min(1, { message: 'search: Keyword cannot be empty' })\n .describe(\n `A single search keyword/query. Each item runs as a separate parallel search. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT}`,\n );\n\nconst keywordsSchema = z\n .array(keywordSchema)\n .min(1, { message: 'search: At least 1 keyword required' })\n .max(50, { message: 'search: At most 50 keywords allowed per call' })\n .describe(\n `Search keywords to run in parallel. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Think of keywords as retrieval probes, not topic labels. Pack distinct facets in one call: official docs, implementation, failures, comparisons, sentiment, changelog, CVE, pricing, or other source classes.`,\n );\n\nexport const rawWebSearchParamsSchema = z.object({\n keywords: keywordsSchema,\n extract: z.never().optional(),\n scope: z.never().optional(),\n verbose: z.never().optional(),\n}).strict();\n\nexport const smartWebSearchParamsSchema = z.object({\n keywords: keywordsSchema,\n extract: z\n .string()\n .min(1, { message: 'smart-web-search: extract cannot be empty' })\n .describe(\n 'Semantic instruction for the relevance classifier \u2014 what \"relevant\" means for THIS goal. This is the post-sort target, so name the evidence you need and the source-of-truth expectation: e.g. official docs/release notes for specs, issue/PR/error text for bugs, Reddit/HN/blogs for lived experience, vendor pricing pages for pricing, CVE databases for security. Drives tiering (HIGHLY_RELEVANT / MAYBE_RELEVANT / OTHER), synthesis, gap analysis, and refine-query suggestions. Be specific: \"OAuth 2.1 support in TypeScript MCP frameworks \u2014 runnable code, not marketing\", not \"MCP OAuth\".',\n ),\n scope: z\n .enum(['web', 'reddit', 'both'])\n .default('web')\n .describe(\n 'Search scope. \"web\" (default) = open web, no augmentation. \"reddit\" = server appends `site:reddit.com` to every keyword and filters results to post permalinks (`/r/.+/comments/[a-z0-9]+/`); subreddit homepages are dropped. \"both\" = runs every keyword twice (open web + reddit-scoped), merges the result set, and tags each row with its source. Use \"reddit\" for sentiment/migration/lived-experience research; use \"both\" when opinion-heavy AND official sources also matter.',\n ),\n verbose: z\n .boolean()\n .default(false)\n .describe(\n 'Include per-row scoring/coverage metadata, the trailing Signals block, and CONSENSUS labels even when they carry little signal. Default false.',\n ),\n}).strict();\n\nexport type RawWebSearchParams = z.infer<typeof rawWebSearchParamsSchema>;\nexport type SmartWebSearchParams = z.infer<typeof smartWebSearchParamsSchema>;\n\n// Internal alias retained for shared tests/helpers. Public tools register the\n// raw/smart schemas above, not this alias.\nexport const webSearchParamsSchema = smartWebSearchParamsSchema;\nexport type WebSearchParams = SmartWebSearchParams;\n\n// Search tools are markdown-only at the MCP boundary.\nexport type WebSearchOutput = Record<string, never>;\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AAAA,EACH;AACF;AAEO,MAAM,mCAAmC,4BAA4B,KAAK,GAAG;AAEpF,MAAM,gBAAgB,EACnB,OAAO,EACP,IAAI,GAAG,EAAE,SAAS,kCAAkC,CAAC,EACrD;AAAA,EACC,gFAAgF,gCAAgC;AAClH;AAEF,MAAM,iBAAiB,EACpB,MAAM,aAAa,EACnB,IAAI,GAAG,EAAE,SAAS,sCAAsC,CAAC,EACzD,IAAI,IAAI,EAAE,SAAS,+CAA+C,CAAC,EACnE;AAAA,EACC,gKAAgK,gCAAgC;AAClM;AAEK,MAAM,2BAA2B,EAAE,OAAO;AAAA,EAC/C,UAAU;AAAA,EACV,SAAS,EAAE,MAAM,EAAE,SAAS;AAAA,EAC5B,OAAO,EAAE,MAAM,EAAE,SAAS;AAAA,EAC1B,SAAS,EAAE,MAAM,EAAE,SAAS;AAC9B,CAAC,EAAE,OAAO;AAEH,MAAM,6BAA6B,EAAE,OAAO;AAAA,EACjD,UAAU;AAAA,EACV,SAAS,EACN,OAAO,EACP,IAAI,GAAG,EAAE,SAAS,4CAA4C,CAAC,EAC/D;AAAA,IACC;AAAA,EACF;AAAA,EACF,OAAO,EACJ,KAAK,CAAC,OAAO,UAAU,MAAM,CAAC,EAC9B,QAAQ,KAAK,EACb;AAAA,IACC;AAAA,EACF;AAAA,EACF,SAAS,EACN,QAAQ,EACR,QAAQ,KAAK,EACb;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EAAE,OAAO;AAOH,MAAM,wBAAwB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/src/tools/scrape.js
CHANGED
|
@@ -46,7 +46,10 @@ import {
|
|
|
46
46
|
} from "./mcp-helpers.js";
|
|
47
47
|
const markdownCleaner = new MarkdownCleaner();
|
|
48
48
|
function formatInputValidationError(toolName, issues) {
|
|
49
|
-
const details = issues.map((issue) =>
|
|
49
|
+
const details = issues.map((issue) => {
|
|
50
|
+
const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "<root>";
|
|
51
|
+
return `- ${path}: ${issue.message}`;
|
|
52
|
+
}).join("\n");
|
|
50
53
|
return `Invalid ${toolName} input.
|
|
51
54
|
|
|
52
55
|
${details}`;
|
|
@@ -772,6 +775,9 @@ async function handleScrapeLinksMode(params, reporter = NOOP_REPORTER) {
|
|
|
772
775
|
if (metrics.successful === 0 && metrics.failed > 0) {
|
|
773
776
|
return toolFailure(content);
|
|
774
777
|
}
|
|
778
|
+
if (params.smart && llmAttempted > 0 && llmErrors === llmAttempted) {
|
|
779
|
+
return toolFailure(content);
|
|
780
|
+
}
|
|
775
781
|
return toolSuccess(content);
|
|
776
782
|
}
|
|
777
783
|
function handleRawScrapeLinks(params, reporter = NOOP_REPORTER) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/tools/scrape.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Scrape Links Tool Handler\n *\n * Scrapes many URLs in parallel. Reddit permalinks (reddit.com/r/.../comments/...)\n * are auto-detected and routed through the Reddit API; all other URLs go through\n * Jina Reader first, then Scrape.do proxy-mode retry, then optional Kernel\n * browser rendering. Smart mode feeds successful items to the LLM extractor.\n *\n * NEVER throws \u2014 every error is returned as a tool-level failure response.\n */\n\nimport type { MCPServer } from 'mcp-use/server';\nimport { Effect, Either } from 'effect';\n\nimport {\n SCRAPER,\n CONCURRENCY,\n getCapabilities,\n getMissingEnvMessage,\n parseEnv,\n} from '../config/index.js';\nimport {\n rawScrapeLinksParamsSchema,\n smartScrapeLinksParamsSchema,\n type RawScrapeLinksParams,\n type SmartScrapeLinksParams,\n} from '../schemas/scrape-links.js';\nimport { type PostResult } from '../clients/reddit.js';\nimport { buildScrapeDoProxyUrl, isTerminalReaderError, type JinaConvertResponse } from '../clients/jina.js';\nimport { MarkdownCleaner } from '../services/markdown-cleaner.js';\nimport { createLLMProcessor } from '../services/llm-processor.js';\nimport { removeMetaTags } from '../utils/markdown-formatter.js';\nimport { extractReadableContent } from '../utils/content-extractor.js';\nimport { classifyError, ErrorCode } from '../utils/errors.js';\nimport { isDocumentUrl } from '../utils/source-type.js';\nimport { assessMarkdownQuality } from '../utils/content-quality.js';\nimport { runExternalEffect } from '../effect/runtime.js';\nimport {\n JinaService,\n JinaServiceLive,\n KernelService,\n KernelServiceLive,\n LlmService,\n LlmServiceLive,\n RedditService,\n RedditServiceLive,\n} from '../effect/services.js';\nimport { ProviderTimeoutError } from '../effect/errors.js';\nimport {\n mcpLog,\n formatSuccess,\n formatError,\n formatBatchHeader,\n formatDuration,\n} from './utils.js';\nimport {\n createToolReporter,\n NOOP_REPORTER,\n toolFailure,\n toolSuccess,\n toToolResponse,\n type ToolExecutionResult,\n type ToolReporter,\n} from './mcp-helpers.js';\n\nconst markdownCleaner = new MarkdownCleaner();\n\ntype ScrapeToolOutput = Record<string, never>;\n\ninterface ScrapeHandlerParams {\n urls: string[];\n extract?: string;\n smart: boolean;\n toolName: 'raw-scrape-links' | 'smart-scrape-links';\n}\n\nfunction formatInputValidationError(toolName: string, issues: readonly { message: string }[]): string {\n const details = issues.map((issue) => `- ${issue.message}`).join('\\n');\n return `Invalid ${toolName} input.\\n\\n${details}`;\n}\n\nfunction enhanceExtractionInstruction(instruction: string | undefined): string {\n const base = instruction || 'Extract the main content and key information from this page.';\n return `${SCRAPER.EXTRACTION_PREFIX}\\n\\n${base}\\n\\n${SCRAPER.EXTRACTION_SUFFIX}`;\n}\n\n// --- Types ---\n\ninterface ProcessedResult {\n url: string;\n content: string;\n index: number; // original position in params.urls[]\n /**\n * Cleaned markdown captured before LLM extraction. Preserved so the handler\n * can fall back to it when the LLM emits the terse \"Page did not load: X\"\n * escape line and would otherwise nuke the scraped body.\n */\n rawContent?: string;\n}\n\ninterface ScrapeMetrics {\n successful: number;\n failed: number;\n totalCredits: number;\n}\n\ninterface FailedContent {\n content: string;\n index: number;\n}\n\ninterface ScrapePhaseResult {\n successItems: ProcessedResult[];\n failedContents: FailedContent[];\n metrics: ScrapeMetrics;\n}\n\ninterface BranchInput {\n url: string;\n origIndex: number;\n}\n\ninterface KernelFallbackInput extends BranchInput {\n proxyError?: string;\n jinaError?: string;\n}\n\nfunction cleanFetchedContent(rawContent: string, url: string): string {\n try {\n const readable = extractReadableContent(rawContent, url);\n const sourceForCleaner = readable.extracted ? readable.content : rawContent;\n return markdownCleaner.processContent(sourceForCleaner);\n } catch {\n return rawContent;\n }\n}\n\nfunction effectErrorMessage(error: unknown): string {\n if (typeof error === 'object' && error !== null) {\n if ('error' in error) {\n const structured = (error as { error?: { message?: unknown } }).error;\n if (typeof structured?.message === 'string') return structured.message;\n }\n if ('message' in error && typeof (error as { message?: unknown }).message === 'string') {\n return (error as { message: string }).message;\n }\n if ('_tag' in error && typeof (error as { _tag?: unknown })._tag === 'string') {\n return (error as { _tag: string })._tag;\n }\n }\n return String(error);\n}\n\n// --- Reddit URL detection ---\n\nconst REDDIT_HOST = /(?:^|\\.)reddit\\.com$/i;\nconst REDDIT_POST_PERMALINK = /\\/r\\/[^/]+\\/comments\\/[a-z0-9]+/i;\n\nfunction isRedditUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname);\n } catch {\n return false;\n }\n}\n\nfunction isRedditPostPermalink(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname) && REDDIT_POST_PERMALINK.test(u.pathname);\n } catch {\n return false;\n }\n}\n\n// --- Error helper ---\n\nfunction createScrapeErrorResponse(\n code: string,\n message: string,\n startTime: number,\n toolName: ScrapeHandlerParams['toolName'] = 'raw-scrape-links',\n retryable = false,\n alternatives?: string[],\n): ToolExecutionResult<ScrapeToolOutput> {\n return toolFailure(\n `${formatError({\n code,\n message,\n retryable,\n toolName,\n howToFix: code === 'NO_URLS' ? ['Provide at least one valid URL'] : undefined,\n alternatives,\n })}\\n\\nExecution time: ${formatDuration(Date.now() - startTime)}`,\n );\n}\n\n// --- URL partitioning ---\n\ninterface PartitionedUrls {\n webInputs: BranchInput[];\n redditInputs: BranchInput[];\n documentInputs: BranchInput[];\n invalidEntries: { url: string; origIndex: number }[];\n}\n\nfunction partitionUrls(urls: string[]): PartitionedUrls {\n const webInputs: BranchInput[] = [];\n const redditInputs: BranchInput[] = [];\n const documentInputs: BranchInput[] = [];\n const invalidEntries: { url: string; origIndex: number }[] = [];\n\n for (let i = 0; i < urls.length; i++) {\n const url = urls[i]!;\n try {\n new URL(url);\n } catch {\n invalidEntries.push({ url, origIndex: i });\n continue;\n }\n // Document URLs (.pdf/.docx/.pptx/.xlsx) go straight to Jina Reader \u2014\n // bypassing Scrape.do because it cannot decode binary bodies. Ordered\n // before the Reddit check so a hypothetical PDF on a reddit-adjacent host\n // still takes the document path.\n if (isDocumentUrl(url)) {\n documentInputs.push({ url, origIndex: i });\n } else if (isRedditUrl(url)) {\n redditInputs.push({ url, origIndex: i });\n } else {\n webInputs.push({ url, origIndex: i });\n }\n }\n\n return { webInputs, redditInputs, documentInputs, invalidEntries };\n}\n\n// --- Jina-first branch ---\n\n/**\n * Format a Jina-failure line. When Scrape.do proxy mode was attempted, surface\n * both direct and proxy layers so callers can see exactly where fallback ended.\n *\n * Exported for unit testing.\n */\nexport function formatJinaFailure(url: string, jinaError: string, proxyError?: string): string {\n if (proxyError) {\n return `## ${url}\\n\\n\u274C Jina Reader failed. Direct: ${jinaError}. Scrape.do proxy: ${proxyError}.`;\n }\n return `## ${url}\\n\\n\u274C Document conversion failed: ${jinaError}`;\n}\n\nfunction jinaResultError(result: JinaConvertResponse): string | null {\n if (result.error || result.statusCode < 200 || result.statusCode >= 300) {\n return result.error?.message || `HTTP ${result.statusCode}`;\n }\n const quality = assessMarkdownQuality(result.content);\n if (quality.weak) {\n return `Weak Jina markdown (${quality.reason})`;\n }\n return null;\n}\n\nfunction canTryKernel(input: BranchInput, directError?: JinaConvertResponse['error']): boolean {\n if (isDocumentUrl(input.url)) return false;\n if (!directError) return true;\n if (directError.code === ErrorCode.NOT_FOUND || directError.code === ErrorCode.INVALID_INPUT) {\n return false;\n }\n return true;\n}\n\ninterface JinaFirstInput extends BranchInput {\n directError?: string;\n directStructuredError?: JinaConvertResponse['error'];\n proxyError?: string;\n}\n\nfunction formatJinaFirstFailure(input: JinaFirstInput): string {\n if (input.proxyError) {\n return formatJinaFailure(input.url, input.directError ?? 'Unknown direct failure', input.proxyError);\n }\n return formatJinaFailure(input.url, input.directError ?? 'Unknown direct failure');\n}\n\nasync function fetchJinaFirstBranch(\n inputs: BranchInput[],\n kernelEnabled: boolean,\n scrapeDoProxyUrl: string | undefined,\n): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n mcpLog(\n 'info',\n `[concurrency] jina direct branch: converting ${inputs.length} URL(s) with limit=${CONCURRENCY.JINA_READER}`,\n 'scrape',\n );\n\n const directResults = await runExternalEffect(\n Effect.gen(function* () {\n const jina = yield* JinaService;\n return yield* Effect.forEach(\n inputs,\n (input) =>\n jina.convert({ url: input.url, timeoutSeconds: 15, allowProxyRetry: false }).pipe(\n Effect.timeoutFail({\n duration: '20 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'jina',\n operation: 'convert:direct',\n durationMs: 20_000,\n }),\n }),\n Effect.either,\n ),\n { concurrency: CONCURRENCY.JINA_READER },\n );\n }),\n JinaServiceLive,\n );\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n const proxyInputs: JinaFirstInput[] = [];\n const kernelInputs: KernelFallbackInput[] = [];\n let successful = 0;\n let failed = 0;\n\n const enqueueFailure = (input: JinaFirstInput): void => {\n if (kernelEnabled && canTryKernel(input, input.directStructuredError)) {\n kernelInputs.push({\n url: input.url,\n origIndex: input.origIndex,\n proxyError: input.proxyError ? `Scrape.do proxy: ${input.proxyError}` : undefined,\n jinaError: input.directError,\n });\n return;\n }\n failed++;\n failedContents.push({ index: input.origIndex, content: formatJinaFirstFailure(input) });\n };\n\n for (let i = 0; i < directResults.length; i++) {\n const settled = directResults[i];\n const input = inputs[i]!;\n if (!settled) {\n proxyInputs.push({ ...input, directError: 'No result returned' });\n continue;\n }\n if (Either.isLeft(settled)) {\n const reason = effectErrorMessage(settled.left);\n proxyInputs.push({ ...input, directError: reason });\n continue;\n }\n\n const directError = jinaResultError(settled.right);\n if (!directError) {\n successful++;\n successItems.push({ url: input.url, content: settled.right.content, index: input.origIndex, rawContent: settled.right.content });\n continue;\n }\n\n const structuredError = settled.right.error;\n if (structuredError && isTerminalReaderError(structuredError)) {\n enqueueFailure({ ...input, directError, directStructuredError: structuredError });\n continue;\n }\n\n proxyInputs.push({ ...input, directError, directStructuredError: structuredError });\n }\n\n if (proxyInputs.length > 0) {\n if (!scrapeDoProxyUrl) {\n for (const input of proxyInputs) {\n enqueueFailure({ ...input, proxyError: 'SCRAPEDO_API_KEY is not configured' });\n }\n } else {\n mcpLog(\n 'info',\n `[concurrency] jina scrape.do proxy branch: retrying ${proxyInputs.length} URL(s) with limit=${CONCURRENCY.JINA_READER}`,\n 'scrape',\n );\n const proxyResults = await runExternalEffect(\n Effect.gen(function* () {\n const jina = yield* JinaService;\n return yield* Effect.forEach(\n proxyInputs,\n (input) =>\n jina.convert({\n url: input.url,\n timeoutSeconds: 15,\n proxyUrl: scrapeDoProxyUrl,\n noCache: true,\n allowProxyRetry: false,\n }).pipe(\n Effect.timeoutFail({\n duration: '20 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'jina',\n operation: 'convert:proxy',\n durationMs: 20_000,\n }),\n }),\n Effect.either,\n ),\n { concurrency: CONCURRENCY.JINA_READER },\n );\n }),\n JinaServiceLive,\n );\n\n for (let i = 0; i < proxyResults.length; i++) {\n const settled = proxyResults[i];\n const input = proxyInputs[i]!;\n if (!settled) {\n enqueueFailure({ ...input, proxyError: 'No result returned' });\n continue;\n }\n if (Either.isLeft(settled)) {\n enqueueFailure({ ...input, proxyError: effectErrorMessage(settled.left) });\n continue;\n }\n\n const proxyError = jinaResultError(settled.right);\n if (!proxyError) {\n successful++;\n successItems.push({ url: input.url, content: settled.right.content, index: input.origIndex, rawContent: settled.right.content });\n continue;\n }\n\n enqueueFailure({ ...input, proxyError });\n }\n }\n }\n\n if (kernelInputs.length > 0 && kernelEnabled) {\n const kernelPhase = await fetchKernelBranch(kernelInputs);\n successItems.push(...kernelPhase.successItems);\n failedContents.push(...kernelPhase.failedContents);\n successful += kernelPhase.metrics.successful;\n failed += kernelPhase.metrics.failed;\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\nfunction formatKernelFailure(\n url: string,\n kernelError: string,\n proxyError?: string,\n jinaError?: string,\n): string {\n const layers = [\n jinaError ? `Jina Reader: ${jinaError}` : undefined,\n proxyError ? proxyError : undefined,\n `Kernel: ${kernelError}`,\n ].filter((line): line is string => Boolean(line));\n return `## ${url}\\n\\n\u274C All scrape providers failed. ${layers.join('. ')}.`;\n}\n\nasync function fetchKernelBranch(\n inputs: KernelFallbackInput[],\n): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n mcpLog(\n 'info',\n `[concurrency] kernel branch: rendering ${inputs.length} URL(s) with limit=${CONCURRENCY.KERNEL}`,\n 'scrape',\n );\n\n const results = await runExternalEffect(\n Effect.gen(function* () {\n const kernel = yield* KernelService;\n return yield* Effect.forEach(\n inputs,\n (input) =>\n kernel.render({ url: input.url, timeoutSeconds: 15 }).pipe(\n Effect.timeoutFail({\n duration: '25 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'kernel',\n operation: 'render',\n durationMs: 25_000,\n }),\n }),\n Effect.either,\n ),\n { concurrency: CONCURRENCY.KERNEL },\n );\n }),\n KernelServiceLive,\n );\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n let successful = 0;\n let failed = 0;\n\n for (let i = 0; i < results.length; i++) {\n const settled = results[i];\n const input = inputs[i]!;\n if (!settled) {\n failed++;\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(input.url, 'No result returned', input.proxyError, input.jinaError),\n });\n continue;\n }\n if (Either.isLeft(settled)) {\n failed++;\n const reason = effectErrorMessage(settled.left);\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(input.url, reason, input.proxyError, input.jinaError),\n });\n continue;\n }\n\n const result = settled.right;\n if (result.error || result.statusCode < 200 || result.statusCode >= 300) {\n failed++;\n const errorMsg = result.error?.message || `HTTP ${result.statusCode}`;\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(input.url, errorMsg, input.proxyError, input.jinaError),\n });\n continue;\n }\n\n const content = cleanFetchedContent(result.content, result.finalUrl ?? input.url);\n const quality = assessMarkdownQuality(content);\n if (quality.weak) {\n failed++;\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(\n input.url,\n `Weak Kernel markdown (${quality.reason})`,\n input.proxyError,\n input.jinaError,\n ),\n });\n continue;\n }\n\n successful++;\n const finalUrl = result.finalUrl ?? input.url;\n successItems.push({ url: finalUrl, content, index: input.origIndex, rawContent: content });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Reddit branch ---\n\nfunction formatRedditPostAsMarkdown(result: PostResult): string {\n const { post, comments } = result;\n const lines: string[] = [];\n lines.push(`# ${post.title}`);\n lines.push('');\n lines.push(`**r/${post.subreddit}** \u2022 u/${post.author} \u2022 \u2B06\uFE0F ${post.score} \u2022 \uD83D\uDCAC ${post.commentCount} comments`);\n lines.push(`\uD83D\uDD17 ${post.url}`);\n lines.push('');\n if (post.body) {\n lines.push('## Post content');\n lines.push('');\n lines.push(post.body);\n lines.push('');\n }\n if (comments.length > 0) {\n lines.push(`## Top comments (${comments.length} total)`);\n lines.push('');\n for (const c of comments) {\n const indent = ' '.repeat(c.depth);\n const op = c.isOP ? ' **[OP]**' : '';\n const score = c.score >= 0 ? `+${c.score}` : `${c.score}`;\n lines.push(`${indent}- **u/${c.author}**${op} _(${score})_`);\n for (const line of c.body.split('\\n')) {\n lines.push(`${indent} ${line}`);\n }\n lines.push('');\n }\n }\n return lines.join('\\n');\n}\n\nasync function fetchRedditBranch(inputs: BranchInput[]): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n const env = parseEnv();\n if (!env.REDDIT_CLIENT_ID || !env.REDDIT_CLIENT_SECRET) {\n const failedContents = inputs.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Reddit URL detected, but Reddit API is not configured. Set \\`REDDIT_CLIENT_ID\\` and \\`REDDIT_CLIENT_SECRET\\` in the server env to enable threaded Reddit scraping.`,\n }),\n );\n return {\n successItems: [],\n failedContents,\n metrics: { successful: 0, failed: inputs.length, totalCredits: 0 },\n };\n }\n\n // Warn for non-permalink Reddit URLs (subreddit homepages, /new, /top, /hot,\n // user profiles). The Reddit API path we call requires /r/.../comments/... \u2014\n // reject upfront so the caller sees a helpful message instead of a 404.\n const [postInputs, nonPermalinks] = inputs.reduce<[BranchInput[], BranchInput[]]>(\n ([posts, rest], input) => {\n if (isRedditPostPermalink(input.url)) posts.push(input);\n else rest.push(input);\n return [posts, rest];\n },\n [[], []],\n );\n\n const nonPermalinkFailed = nonPermalinks.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Only Reddit post permalinks (/r/<sub>/comments/<id>/...) are supported. Use raw-web-search with explicit Reddit permalink probes or smart-web-search with scope:\"reddit\" to discover post permalinks first.`,\n }),\n );\n\n if (postInputs.length === 0) {\n return {\n successItems: [],\n failedContents: nonPermalinkFailed,\n metrics: { successful: 0, failed: nonPermalinks.length, totalCredits: 0 },\n };\n }\n\n mcpLog('info', `[concurrency] reddit branch: fetching ${postInputs.length} post(s) with limit=${CONCURRENCY.REDDIT}`, 'scrape');\n const urls = postInputs.map((i) => i.url);\n const batchResult = await runExternalEffect(\n Effect.gen(function* () {\n const reddit = yield* RedditService;\n return yield* reddit.batchGetPosts(urls, true).pipe(\n Effect.timeoutFail({\n duration: '60 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'reddit',\n operation: 'batchGetPosts',\n durationMs: 60_000,\n }),\n }),\n );\n }),\n RedditServiceLive(env.REDDIT_CLIENT_ID, env.REDDIT_CLIENT_SECRET),\n );\n const urlToIndex = new Map(postInputs.map((i) => [i.url, i.origIndex]));\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [...nonPermalinkFailed];\n let successful = 0;\n let failed = nonPermalinks.length;\n\n for (const [url, result] of batchResult.results) {\n const origIndex = urlToIndex.get(url) ?? -1;\n if (result instanceof Error) {\n failed++;\n failedContents.push({ index: origIndex, content: `## ${url}\\n\\n\u274C Reddit fetch failed: ${result.message}` });\n continue;\n }\n successful++;\n const md = formatRedditPostAsMarkdown(result);\n successItems.push({ url, content: md, index: origIndex, rawContent: md });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Terse-LLM-escape detection + raw fallback merger ---\n\n/**\n * The LLM extraction prompt tells the model to emit a single terse line when\n * a page \"clearly failed to load\" (login walls, JS-render-empty, paywalls,\n * etc.). In practice the LLM over-triggers this on partially-rendered pages,\n * causing smart scrape to return a one-line verdict and discard the cleaned\n * markdown. This detector + merger keep the verdict but re-attach a capped\n * slice of the raw markdown so the caller always has something to work with.\n */\nconst TERSE_LLM_FAILURE_RE =\n /^\\s*##\\s*Matches\\s*\\n+\\s*_Page did not load:\\s*([a-z0-9_-]+)_\\s*\\.?\\s*$/i;\n\n/** Cap on the raw-markdown slice appended under \"## Raw content ...\" */\nexport const RAW_FALLBACK_CHAR_CAP = 4000;\n\n/**\n * If `llmOutput` is exactly the terse \"## Matches\\n_Page did not load: X_\"\n * line, return the reason token (e.g. \"login-wall\"). Otherwise null.\n */\nexport function detectTerseFailure(llmOutput: string): string | null {\n const m = llmOutput.trim().match(TERSE_LLM_FAILURE_RE);\n return m ? m[1]! : null;\n}\n\n/**\n * When the LLM emitted the terse escape line, append a capped slice of the\n * raw cleaned markdown under a `## Raw content (...)` section so the caller\n * still has the actual scraped body to inspect. No-op otherwise.\n */\nexport function mergeLlmWithRawFallback(\n llmOutput: string,\n rawContent: string | undefined,\n): string {\n const reason = detectTerseFailure(llmOutput);\n if (!reason) return llmOutput;\n const trimmed = rawContent?.trim();\n if (!trimmed) return llmOutput;\n const snippet =\n trimmed.length > RAW_FALLBACK_CHAR_CAP\n ? trimmed.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n\u2026[raw truncated]'\n : trimmed;\n return `${llmOutput.trim()}\\n\\n## Raw content (LLM flagged page as ${reason})\\n\\n${snippet}`;\n}\n\n// --- LLM extraction (shared by both branches) ---\n\nasync function processItemsWithLlm(\n successItems: ProcessedResult[],\n enhancedInstruction: string | undefined,\n llmProcessor: ReturnType<typeof createLLMProcessor>,\n reporter: ToolReporter,\n): Promise<{ items: ProcessedResult[]; llmErrors: number; llmAttempted: number }> {\n let llmErrors = 0;\n\n // Raw-mode bypass: caller omitted `extract` \u2192 return cleaned markdown as-is.\n if (!enhancedInstruction) {\n if (successItems.length > 0) {\n mcpLog('info', 'Raw mode: extract omitted \u2014 returning cleaned scraped content without LLM pass', 'scrape');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n if (!llmProcessor || successItems.length === 0) {\n if (!llmProcessor && successItems.length > 0) {\n mcpLog('warning', 'LLM unavailable (LLM_API_KEY not set). Returning raw scraped content.', 'scrape');\n void reporter.log('warning', 'llm_extractor_unreachable: planner not configured; raw scraped content returned');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n mcpLog('info', `[concurrency] llm extraction: fanning out ${successItems.length} item(s) with limit=${CONCURRENCY.LLM_EXTRACTION}`, 'scrape');\n\n const llmResults = await runExternalEffect(\n Effect.gen(function* () {\n const llm = yield* LlmService;\n return yield* Effect.forEach(\n successItems,\n (item) =>\n llm.extractContent(\n item.content,\n { enabled: true, extract: enhancedInstruction, url: item.url },\n ).pipe(\n Effect.timeoutFail({\n duration: '155 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'llm',\n operation: 'extractContent',\n durationMs: 155_000,\n }),\n }),\n Effect.either,\n Effect.map((result) => ({ item, result })),\n ),\n { concurrency: CONCURRENCY.LLM_EXTRACTION },\n );\n }),\n LlmServiceLive,\n );\n\n const processedItems = llmResults.map(({ item, result }) => {\n mcpLog('debug', `LLM extracting ${item.url}...`, 'scrape');\n\n if (Either.isLeft(result)) {\n llmErrors++;\n const errorMessage = effectErrorMessage(result.left);\n mcpLog('warning', `LLM extraction failed for ${item.url}: ${errorMessage}`, 'scrape');\n void reporter.log('warning', `llm_extractor_unreachable: ${item.url} \u2014 ${errorMessage}`);\n const raw = item.rawContent?.trim();\n const rawSnippet = raw\n ? `\\n\\n## Raw content (unextracted)\\n\\n${raw.length > RAW_FALLBACK_CHAR_CAP ? raw.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n...[raw truncated]' : raw}`\n : '';\n return {\n ...item,\n content: `\u274C LLM extraction failed: ${errorMessage}${rawSnippet}`,\n };\n }\n\n const llmResult = result.right;\n\n if (llmResult.processed) {\n const merged = mergeLlmWithRawFallback(llmResult.content, item.rawContent);\n if (merged !== llmResult.content) {\n mcpLog('warning', `LLM emitted terse escape line for ${item.url} \u2014 preserved raw fallback`, 'scrape');\n void reporter.log('warning', `llm_terse_escape: ${item.url} \u2014 preserving raw fallback`);\n }\n return { ...item, content: merged };\n }\n\n llmErrors++;\n mcpLog('warning', `LLM extraction failed for ${item.url}: ${llmResult.error || 'unknown reason'}`, 'scrape');\n void reporter.log('warning', `llm_extractor_unreachable: ${item.url} \u2014 ${llmResult.error || 'unknown reason'}`);\n const raw = item.rawContent?.trim();\n const rawSnippet = raw\n ? `\\n\\n## Raw content (unextracted)\\n\\n${raw.length > RAW_FALLBACK_CHAR_CAP ? raw.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n...[raw truncated]' : raw}`\n : '';\n return {\n ...item,\n content: `\u274C LLM extraction failed: ${llmResult.error || 'unknown reason'}${rawSnippet}`,\n };\n });\n\n return { items: processedItems, llmErrors, llmAttempted: successItems.length };\n}\n\n// --- Output assembly ---\n\ninterface ContentEntry {\n content: string;\n index: number;\n}\n\nexport function assembleContentEntries(successItems: ProcessedResult[], failedContents: FailedContent[]): string[] {\n const successEntries: ContentEntry[] = successItems.map((item) => {\n let content = item.content;\n try {\n content = removeMetaTags(content);\n } catch {\n // Use content as-is\n }\n return { index: item.index, content: `## ${item.url}\\n\\n${content}` };\n });\n\n return [...failedContents, ...successEntries]\n .sort((a, b) => a.index - b.index)\n .map((entry) => entry.content);\n}\n\nfunction buildScrapeResponse(\n params: ScrapeHandlerParams,\n contents: string[],\n metrics: ScrapeMetrics,\n llmErrors: number,\n executionTime: number,\n llmAccounting: { llmAttempted: number; llmSucceeded: boolean },\n): string {\n const llmExtras: Record<string, string | number> = {};\n if (llmAccounting.llmAttempted > 0) {\n const ok = llmAccounting.llmAttempted - llmErrors;\n llmExtras['LLM extraction'] = `${ok}/${llmAccounting.llmAttempted} succeeded`;\n if (!llmAccounting.llmSucceeded) {\n llmExtras['LLM credit'] = '0 charged (no extraction produced)';\n }\n } else if (llmErrors > 0) {\n llmExtras['LLM extraction failures'] = llmErrors;\n }\n\n const batchHeader = formatBatchHeader({\n title: `Scraped Content (${params.urls.length} URLs)`,\n totalItems: params.urls.length,\n successful: metrics.successful,\n failed: metrics.failed,\n extras: {\n 'Credits used': metrics.totalCredits,\n ...llmExtras,\n },\n });\n\n const formattedContent = formatSuccess({\n title: 'Scraping Complete',\n summary: batchHeader,\n data: contents.join('\\n\\n---\\n\\n'),\n metadata: {\n 'Execution time': formatDuration(executionTime),\n },\n });\n\n return formattedContent;\n}\n\n// --- Handler ---\n\nasync function handleScrapeLinksMode(\n params: ScrapeHandlerParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeToolOutput>> {\n const startTime = Date.now();\n\n if (!params.urls || params.urls.length === 0) {\n return createScrapeErrorResponse('NO_URLS', 'No URLs provided', startTime, params.toolName);\n }\n\n if (params.smart && !createLLMProcessor()) {\n return toolFailure(getMissingEnvMessage('llmExtraction'));\n }\n\n const { webInputs, redditInputs, documentInputs, invalidEntries } = partitionUrls(params.urls);\n const validCount = webInputs.length + redditInputs.length + documentInputs.length;\n\n await reporter.log(\n 'info',\n `Partitioned ${params.urls.length} URL(s): ${webInputs.length} web, ${redditInputs.length} reddit, ${documentInputs.length} document, ${invalidEntries.length} invalid`,\n );\n\n if (validCount === 0) {\n return createScrapeErrorResponse(\n 'INVALID_URLS',\n `All ${params.urls.length} URLs are invalid`,\n startTime,\n params.toolName,\n false,\n [\n 'raw-web-search(keywords=[...]) \u2014 search for valid URLs first, then scrape the results',\n ],\n );\n }\n\n mcpLog(\n 'info',\n `Starting ${params.toolName}: ${webInputs.length} web + ${redditInputs.length} reddit + ${documentInputs.length} document URL(s)`,\n 'scrape',\n );\n await reporter.progress(15, 100, 'Preparing scrape clients');\n\n let kernelEnabled: boolean;\n let scrapeDoProxyUrl: string | undefined;\n try {\n const env = parseEnv();\n kernelEnabled = getCapabilities().kernel;\n scrapeDoProxyUrl = env.SCRAPER_API_KEY ? buildScrapeDoProxyUrl(env.SCRAPER_API_KEY) : undefined;\n } catch (error) {\n const err = classifyError(error);\n return createScrapeErrorResponse(\n 'CLIENT_INIT_FAILED',\n `Failed to initialize scrape providers: ${err.message}`,\n startTime,\n params.toolName,\n false,\n [\n 'raw-web-search(keywords=[\"topic key findings\", \"topic summary\"]) \u2014 search instead of scraping',\n ],\n );\n }\n\n const enhancedInstruction = params.smart ? enhanceExtractionInstruction(params.extract) : undefined;\n\n await reporter.progress(35, 100, 'Fetching page content');\n\n const jinaInputs = [...webInputs, ...documentInputs];\n const [jinaPhase, redditPhase] = await Promise.all([\n fetchJinaFirstBranch(jinaInputs, kernelEnabled, scrapeDoProxyUrl),\n fetchRedditBranch(redditInputs),\n ]);\n\n const successItems = [\n ...jinaPhase.successItems,\n ...redditPhase.successItems,\n ];\n const invalidFailed = invalidEntries.map(\n ({ url, origIndex }) => ({ index: origIndex, content: `## ${url}\\n\\n\u274C Invalid URL format` }),\n );\n const failedContents = [\n ...invalidFailed,\n ...jinaPhase.failedContents,\n ...redditPhase.failedContents,\n ];\n const metrics: ScrapeMetrics = {\n successful:\n jinaPhase.metrics.successful\n + redditPhase.metrics.successful\n ,\n failed:\n invalidEntries.length\n + jinaPhase.metrics.failed\n + redditPhase.metrics.failed,\n totalCredits: 0,\n };\n\n await reporter.log('info', `Fetched ${metrics.successful} page(s), ${metrics.failed} failed`);\n\n if (successItems.length > 0) {\n await reporter.progress(80, 100, 'Running LLM extraction over fetched pages');\n }\n\n const { items: processedItems, llmErrors, llmAttempted } = await processItemsWithLlm(\n successItems,\n enhancedInstruction,\n createLLMProcessor(),\n reporter,\n );\n\n const contents = assembleContentEntries(processedItems, failedContents);\n const executionTime = Date.now() - startTime;\n\n mcpLog(\n 'info',\n `Completed: ${metrics.successful} successful, ${metrics.failed} failed, ${metrics.totalCredits} credits used`,\n 'scrape',\n );\n\n const llmSucceeded = llmAttempted > 0 && llmErrors < llmAttempted;\n const content = buildScrapeResponse(\n params,\n contents,\n metrics,\n llmErrors,\n executionTime,\n { llmAttempted, llmSucceeded },\n );\n\n if (metrics.successful === 0 && metrics.failed > 0) {\n return toolFailure(content);\n }\n\n return toolSuccess(content);\n}\n\nexport function handleRawScrapeLinks(\n params: RawScrapeLinksParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeToolOutput>> {\n return handleScrapeLinksMode({ ...params, smart: false, toolName: 'raw-scrape-links' }, reporter);\n}\n\nexport function handleSmartScrapeLinks(\n params: SmartScrapeLinksParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeToolOutput>> {\n return handleScrapeLinksMode({ ...params, smart: true, toolName: 'smart-scrape-links' }, reporter);\n}\n\nexport function registerScrapeLinksTools(server: MCPServer): void {\n server.tool(\n {\n name: 'raw-scrape-links',\n title: 'Raw Scrape Links',\n description:\n 'Fetch URLs in parallel and return full markdown directly. Input is only `urls` (1\u201350). Reddit post permalinks route through the Reddit API with threaded comments. Non-Reddit URLs use Jina Reader first, then Jina Reader with Scrape.do proxy mode when SCRAPEDO_API_KEY is configured, then optional Kernel browser rendering for web pages. Use this for full source capture, Reddit comment harvesting, and raw evidence before synthesis.',\n schema: rawScrapeLinksParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = rawScrapeLinksParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('raw-scrape-links', parsed.error.issues)));\n }\n\n const reporter = createToolReporter(ctx, 'raw-scrape-links');\n const result = await handleRawScrapeLinks(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Scrape failed' : 'Scrape complete');\n return toToolResponse(result);\n },\n );\n\n server.tool(\n {\n name: 'smart-scrape-links',\n title: 'Smart Scrape Links',\n description:\n 'Fetch URLs in parallel, then always run per-URL LLM extraction. Input is `urls` (1\u201350) plus required `extract`. Reddit post permalinks route through the Reddit API with threaded comments. Non-Reddit URLs use Jina Reader first, then Jina Reader with Scrape.do proxy mode when SCRAPEDO_API_KEY is configured, then optional Kernel browser rendering for web pages. Each extracted page returns markdown sections such as `## Source`, `## Matches`, `## Not found`, and `## Follow-up signals`.',\n schema: smartScrapeLinksParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = smartScrapeLinksParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('smart-scrape-links', parsed.error.issues)));\n }\n\n const reporter = createToolReporter(ctx, 'smart-scrape-links');\n const result = await handleSmartScrapeLinks(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Scrape failed' : 'Scrape complete');\n return toToolResponse(result);\n },\n );\n}\n"],
|
|
5
|
-
"mappings": "AAYA,SAAS,QAAQ,cAAc;AAE/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAEP,SAAS,uBAAuB,6BAAuD;AACvF,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,sBAAsB;AAC/B,SAAS,8BAA8B;AACvC,SAAS,eAAe,iBAAiB;AACzC,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAEP,MAAM,kBAAkB,IAAI,gBAAgB;AAW5C,SAAS,2BAA2B,UAAkB,QAAgD;AACpG,QAAM,UAAU,OAAO,IAAI,CAAC,UAAU,KAAK,MAAM,OAAO,EAAE,EAAE,KAAK,IAAI;AACrE,SAAO,WAAW,QAAQ;AAAA;AAAA,EAAc,OAAO;AACjD;AAEA,SAAS,6BAA6B,aAAyC;AAC7E,QAAM,OAAO,eAAe;AAC5B,SAAO,GAAG,QAAQ,iBAAiB;AAAA;AAAA,EAAO,IAAI;AAAA;AAAA,EAAO,QAAQ,iBAAiB;AAChF;AA2CA,SAAS,oBAAoB,YAAoB,KAAqB;AACpE,MAAI;AACF,UAAM,WAAW,uBAAuB,YAAY,GAAG;AACvD,UAAM,mBAAmB,SAAS,YAAY,SAAS,UAAU;AACjE,WAAO,gBAAgB,eAAe,gBAAgB;AAAA,EACxD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,mBAAmB,OAAwB;AAClD,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,QAAI,WAAW,OAAO;AACpB,YAAM,aAAc,MAA4C;AAChE,UAAI,OAAO,YAAY,YAAY,SAAU,QAAO,WAAW;AAAA,IACjE;AACA,QAAI,aAAa,SAAS,OAAQ,MAAgC,YAAY,UAAU;AACtF,aAAQ,MAA8B;AAAA,IACxC;AACA,QAAI,UAAU,SAAS,OAAQ,MAA6B,SAAS,UAAU;AAC7E,aAAQ,MAA2B;AAAA,IACrC;AAAA,EACF;AACA,SAAO,OAAO,KAAK;AACrB;AAIA,MAAM,cAAc;AACpB,MAAM,wBAAwB;AAE9B,SAAS,YAAY,KAAsB;AACzC,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,WAAO,YAAY,KAAK,EAAE,QAAQ;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,sBAAsB,KAAsB;AACnD,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,WAAO,YAAY,KAAK,EAAE,QAAQ,KAAK,sBAAsB,KAAK,EAAE,QAAQ;AAAA,EAC9E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,SAAS,0BACP,MACA,SACA,WACA,WAA4C,oBAC5C,YAAY,OACZ,cACuC;AACvC,SAAO;AAAA,IACL,GAAG,YAAY;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,SAAS,YAAY,CAAC,gCAAgC,IAAI;AAAA,MACpE;AAAA,IACF,CAAC,CAAC;AAAA;AAAA,kBAAuB,eAAe,KAAK,IAAI,IAAI,SAAS,CAAC;AAAA,EACjE;AACF;AAWA,SAAS,cAAc,MAAiC;AACtD,QAAM,YAA2B,CAAC;AAClC,QAAM,eAA8B,CAAC;AACrC,QAAM,iBAAgC,CAAC;AACvC,QAAM,iBAAuD,CAAC;AAE9D,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI;AACF,UAAI,IAAI,GAAG;AAAA,IACb,QAAQ;AACN,qBAAe,KAAK,EAAE,KAAK,WAAW,EAAE,CAAC;AACzC;AAAA,IACF;AAKA,QAAI,cAAc,GAAG,GAAG;AACtB,qBAAe,KAAK,EAAE,KAAK,WAAW,EAAE,CAAC;AAAA,IAC3C,WAAW,YAAY,GAAG,GAAG;AAC3B,mBAAa,KAAK,EAAE,KAAK,WAAW,EAAE,CAAC;AAAA,IACzC,OAAO;AACL,gBAAU,KAAK,EAAE,KAAK,WAAW,EAAE,CAAC;AAAA,IACtC;AAAA,EACF;AAEA,SAAO,EAAE,WAAW,cAAc,gBAAgB,eAAe;AACnE;AAUO,SAAS,kBAAkB,KAAa,WAAmB,YAA6B;AAC7F,MAAI,YAAY;AACd,WAAO,MAAM,GAAG;AAAA;AAAA,qCAAqC,SAAS,sBAAsB,UAAU;AAAA,EAChG;AACA,SAAO,MAAM,GAAG;AAAA;AAAA,qCAAqC,SAAS;AAChE;AAEA,SAAS,gBAAgB,QAA4C;AACnE,MAAI,OAAO,SAAS,OAAO,aAAa,OAAO,OAAO,cAAc,KAAK;AACvE,WAAO,OAAO,OAAO,WAAW,QAAQ,OAAO,UAAU;AAAA,EAC3D;AACA,QAAM,UAAU,sBAAsB,OAAO,OAAO;AACpD,MAAI,QAAQ,MAAM;AAChB,WAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC9C;AACA,SAAO;AACT;AAEA,SAAS,aAAa,OAAoB,aAAqD;AAC7F,MAAI,cAAc,MAAM,GAAG,EAAG,QAAO;AACrC,MAAI,CAAC,YAAa,QAAO;AACzB,MAAI,YAAY,SAAS,UAAU,aAAa,YAAY,SAAS,UAAU,eAAe;AAC5F,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAQA,SAAS,uBAAuB,OAA+B;AAC7D,MAAI,MAAM,YAAY;AACpB,WAAO,kBAAkB,MAAM,KAAK,MAAM,eAAe,0BAA0B,MAAM,UAAU;AAAA,EACrG;AACA,SAAO,kBAAkB,MAAM,KAAK,MAAM,eAAe,wBAAwB;AACnF;AAEA,eAAe,qBACb,QACA,eACA,kBAC4B;AAC5B,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,EAAE,cAAc,CAAC,GAAG,gBAAgB,CAAC,GAAG,SAAS,EAAE,YAAY,GAAG,QAAQ,GAAG,cAAc,EAAE,EAAE;AAAA,EACxG;AAEA;AAAA,IACE;AAAA,IACA,gDAAgD,OAAO,MAAM,sBAAsB,YAAY,WAAW;AAAA,IAC1G;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,OAAO,IAAI,aAAa;AACtB,YAAM,OAAO,OAAO;AACpB,aAAO,OAAO,OAAO;AAAA,QACnB;AAAA,QACA,CAAC,UACC,KAAK,QAAQ,EAAE,KAAK,MAAM,KAAK,gBAAgB,IAAI,iBAAiB,MAAM,CAAC,EAAE;AAAA,UAC3E,OAAO,YAAY;AAAA,YACjB,UAAU;AAAA,YACV,WAAW,MAAM,IAAI,qBAAqB;AAAA,cACxC,UAAU;AAAA,cACV,WAAW;AAAA,cACX,YAAY;AAAA,YACd,CAAC;AAAA,UACH,CAAC;AAAA,UACD,OAAO;AAAA,QACT;AAAA,QACF,EAAE,aAAa,YAAY,YAAY;AAAA,MACzC;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF;AAEA,QAAM,eAAkC,CAAC;AACzC,QAAM,iBAAkC,CAAC;AACzC,QAAM,cAAgC,CAAC;AACvC,QAAM,eAAsC,CAAC;AAC7C,MAAI,aAAa;AACjB,MAAI,SAAS;AAEb,QAAM,iBAAiB,CAAC,UAAgC;AACtD,QAAI,iBAAiB,aAAa,OAAO,MAAM,qBAAqB,GAAG;AACrE,mBAAa,KAAK;AAAA,QAChB,KAAK,MAAM;AAAA,QACX,WAAW,MAAM;AAAA,QACjB,YAAY,MAAM,aAAa,oBAAoB,MAAM,UAAU,KAAK;AAAA,QACxE,WAAW,MAAM;AAAA,MACnB,CAAC;AACD;AAAA,IACF;AACA;AACA,mBAAe,KAAK,EAAE,OAAO,MAAM,WAAW,SAAS,uBAAuB,KAAK,EAAE,CAAC;AAAA,EACxF;AAEA,WAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,UAAM,UAAU,cAAc,CAAC;AAC/B,UAAM,QAAQ,OAAO,CAAC;AACtB,QAAI,CAAC,SAAS;AACZ,kBAAY,KAAK,EAAE,GAAG,OAAO,aAAa,qBAAqB,CAAC;AAChE;AAAA,IACF;AACA,QAAI,OAAO,OAAO,OAAO,GAAG;AAC1B,YAAM,SAAS,mBAAmB,QAAQ,IAAI;AAC9C,kBAAY,KAAK,EAAE,GAAG,OAAO,aAAa,OAAO,CAAC;AAClD;AAAA,IACF;AAEA,UAAM,cAAc,gBAAgB,QAAQ,KAAK;AACjD,QAAI,CAAC,aAAa;AAChB;AACA,mBAAa,KAAK,EAAE,KAAK,MAAM,KAAK,SAAS,QAAQ,MAAM,SAAS,OAAO,MAAM,WAAW,YAAY,QAAQ,MAAM,QAAQ,CAAC;AAC/H;AAAA,IACF;AAEA,UAAM,kBAAkB,QAAQ,MAAM;AACtC,QAAI,mBAAmB,sBAAsB,eAAe,GAAG;AAC7D,qBAAe,EAAE,GAAG,OAAO,aAAa,uBAAuB,gBAAgB,CAAC;AAChF;AAAA,IACF;AAEA,gBAAY,KAAK,EAAE,GAAG,OAAO,aAAa,uBAAuB,gBAAgB,CAAC;AAAA,EACpF;AAEA,MAAI,YAAY,SAAS,GAAG;AAC1B,QAAI,CAAC,kBAAkB;AACrB,iBAAW,SAAS,aAAa;AAC/B,uBAAe,EAAE,GAAG,OAAO,YAAY,qCAAqC,CAAC;AAAA,MAC/E;AAAA,IACF,OAAO;AACL;AAAA,QACE;AAAA,QACA,uDAAuD,YAAY,MAAM,sBAAsB,YAAY,WAAW;AAAA,QACtH;AAAA,MACF;AACA,YAAM,eAAe,MAAM;AAAA,QACzB,OAAO,IAAI,aAAa;AACtB,gBAAM,OAAO,OAAO;AACpB,iBAAO,OAAO,OAAO;AAAA,YACnB;AAAA,YACA,CAAC,UACC,KAAK,QAAQ;AAAA,cACX,KAAK,MAAM;AAAA,cACX,gBAAgB;AAAA,cAChB,UAAU;AAAA,cACV,SAAS;AAAA,cACT,iBAAiB;AAAA,YACnB,CAAC,EAAE;AAAA,cACD,OAAO,YAAY;AAAA,gBACjB,UAAU;AAAA,gBACV,WAAW,MAAM,IAAI,qBAAqB;AAAA,kBACxC,UAAU;AAAA,kBACV,WAAW;AAAA,kBACX,YAAY;AAAA,gBACd,CAAC;AAAA,cACH,CAAC;AAAA,cACD,OAAO;AAAA,YACT;AAAA,YACF,EAAE,aAAa,YAAY,YAAY;AAAA,UACzC;AAAA,QACF,CAAC;AAAA,QACD;AAAA,MACF;AAEA,eAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,cAAM,UAAU,aAAa,CAAC;AAC9B,cAAM,QAAQ,YAAY,CAAC;AAC3B,YAAI,CAAC,SAAS;AACZ,yBAAe,EAAE,GAAG,OAAO,YAAY,qBAAqB,CAAC;AAC7D;AAAA,QACF;AACA,YAAI,OAAO,OAAO,OAAO,GAAG;AAC1B,yBAAe,EAAE,GAAG,OAAO,YAAY,mBAAmB,QAAQ,IAAI,EAAE,CAAC;AACzE;AAAA,QACF;AAEA,cAAM,aAAa,gBAAgB,QAAQ,KAAK;AAChD,YAAI,CAAC,YAAY;AACf;AACA,uBAAa,KAAK,EAAE,KAAK,MAAM,KAAK,SAAS,QAAQ,MAAM,SAAS,OAAO,MAAM,WAAW,YAAY,QAAQ,MAAM,QAAQ,CAAC;AAC/H;AAAA,QACF;AAEA,uBAAe,EAAE,GAAG,OAAO,WAAW,CAAC;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,SAAS,KAAK,eAAe;AAC5C,UAAM,cAAc,MAAM,kBAAkB,YAAY;AACxD,iBAAa,KAAK,GAAG,YAAY,YAAY;AAC7C,mBAAe,KAAK,GAAG,YAAY,cAAc;AACjD,kBAAc,YAAY,QAAQ;AAClC,cAAU,YAAY,QAAQ;AAAA,EAChC;AAEA,SAAO,EAAE,cAAc,gBAAgB,SAAS,EAAE,YAAY,QAAQ,cAAc,EAAE,EAAE;AAC1F;AAEA,SAAS,oBACP,KACA,aACA,YACA,WACQ;AACR,QAAM,SAAS;AAAA,IACb,YAAY,gBAAgB,SAAS,KAAK;AAAA,IAC1C,aAAa,aAAa;AAAA,IAC1B,WAAW,WAAW;AAAA,EACxB,EAAE,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC;AAChD,SAAO,MAAM,GAAG;AAAA;AAAA,sCAAsC,OAAO,KAAK,IAAI,CAAC;AACzE;AAEA,eAAe,kBACb,QAC4B;AAC5B,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,EAAE,cAAc,CAAC,GAAG,gBAAgB,CAAC,GAAG,SAAS,EAAE,YAAY,GAAG,QAAQ,GAAG,cAAc,EAAE,EAAE;AAAA,EACxG;AAEA;AAAA,IACE;AAAA,IACA,0CAA0C,OAAO,MAAM,sBAAsB,YAAY,MAAM;AAAA,IAC/F;AAAA,EACF;AAEA,QAAM,UAAU,MAAM;AAAA,IACpB,OAAO,IAAI,aAAa;AACtB,YAAM,SAAS,OAAO;AACtB,aAAO,OAAO,OAAO;AAAA,QACnB;AAAA,QACA,CAAC,UACC,OAAO,OAAO,EAAE,KAAK,MAAM,KAAK,gBAAgB,GAAG,CAAC,EAAE;AAAA,UACpD,OAAO,YAAY;AAAA,YACjB,UAAU;AAAA,YACV,WAAW,MAAM,IAAI,qBAAqB;AAAA,cACxC,UAAU;AAAA,cACV,WAAW;AAAA,cACX,YAAY;AAAA,YACd,CAAC;AAAA,UACH,CAAC;AAAA,UACD,OAAO;AAAA,QACT;AAAA,QACF,EAAE,aAAa,YAAY,OAAO;AAAA,MACpC;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF;AAEA,QAAM,eAAkC,CAAC;AACzC,QAAM,iBAAkC,CAAC;AACzC,MAAI,aAAa;AACjB,MAAI,SAAS;AAEb,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,UAAU,QAAQ,CAAC;AACzB,UAAM,QAAQ,OAAO,CAAC;AACtB,QAAI,CAAC,SAAS;AACZ;AACA,qBAAe,KAAK;AAAA,QAClB,OAAO,MAAM;AAAA,QACb,SAAS,oBAAoB,MAAM,KAAK,sBAAsB,MAAM,YAAY,MAAM,SAAS;AAAA,MACjG,CAAC;AACD;AAAA,IACF;AACA,QAAI,OAAO,OAAO,OAAO,GAAG;AAC1B;AACA,YAAM,SAAS,mBAAmB,QAAQ,IAAI;AAC9C,qBAAe,KAAK;AAAA,QAClB,OAAO,MAAM;AAAA,QACb,SAAS,oBAAoB,MAAM,KAAK,QAAQ,MAAM,YAAY,MAAM,SAAS;AAAA,MACnF,CAAC;AACD;AAAA,IACF;AAEA,UAAM,SAAS,QAAQ;AACvB,QAAI,OAAO,SAAS,OAAO,aAAa,OAAO,OAAO,cAAc,KAAK;AACvE;AACA,YAAM,WAAW,OAAO,OAAO,WAAW,QAAQ,OAAO,UAAU;AACnE,qBAAe,KAAK;AAAA,QAClB,OAAO,MAAM;AAAA,QACb,SAAS,oBAAoB,MAAM,KAAK,UAAU,MAAM,YAAY,MAAM,SAAS;AAAA,MACrF,CAAC;AACD;AAAA,IACF;AAEA,UAAM,UAAU,oBAAoB,OAAO,SAAS,OAAO,YAAY,MAAM,GAAG;AAChF,UAAM,UAAU,sBAAsB,OAAO;AAC7C,QAAI,QAAQ,MAAM;AAChB;AACA,qBAAe,KAAK;AAAA,QAClB,OAAO,MAAM;AAAA,QACb,SAAS;AAAA,UACP,MAAM;AAAA,UACN,yBAAyB,QAAQ,MAAM;AAAA,UACvC,MAAM;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAEA;AACA,UAAM,WAAW,OAAO,YAAY,MAAM;AAC1C,iBAAa,KAAK,EAAE,KAAK,UAAU,SAAS,OAAO,MAAM,WAAW,YAAY,QAAQ,CAAC;AAAA,EAC3F;AAEA,SAAO,EAAE,cAAc,gBAAgB,SAAS,EAAE,YAAY,QAAQ,cAAc,EAAE,EAAE;AAC1F;AAIA,SAAS,2BAA2B,QAA4B;AAC9D,QAAM,EAAE,MAAM,SAAS,IAAI;AAC3B,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,KAAK,KAAK,KAAK,EAAE;AAC5B,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,OAAO,KAAK,SAAS,eAAU,KAAK,MAAM,wBAAS,KAAK,KAAK,qBAAS,KAAK,YAAY,WAAW;AAC7G,QAAM,KAAK,aAAM,KAAK,GAAG,EAAE;AAC3B,QAAM,KAAK,EAAE;AACb,MAAI,KAAK,MAAM;AACb,UAAM,KAAK,iBAAiB;AAC5B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,KAAK,EAAE;AAAA,EACf;AACA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,KAAK,oBAAoB,SAAS,MAAM,SAAS;AACvD,UAAM,KAAK,EAAE;AACb,eAAW,KAAK,UAAU;AACxB,YAAM,SAAS,KAAK,OAAO,EAAE,KAAK;AAClC,YAAM,KAAK,EAAE,OAAO,cAAc;AAClC,YAAM,QAAQ,EAAE,SAAS,IAAI,IAAI,EAAE,KAAK,KAAK,GAAG,EAAE,KAAK;AACvD,YAAM,KAAK,GAAG,MAAM,SAAS,EAAE,MAAM,KAAK,EAAE,MAAM,KAAK,IAAI;AAC3D,iBAAW,QAAQ,EAAE,KAAK,MAAM,IAAI,GAAG;AACrC,cAAM,KAAK,GAAG,MAAM,KAAK,IAAI,EAAE;AAAA,MACjC;AACA,YAAM,KAAK,EAAE;AAAA,IACf;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAe,kBAAkB,QAAmD;AAClF,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,EAAE,cAAc,CAAC,GAAG,gBAAgB,CAAC,GAAG,SAAS,EAAE,YAAY,GAAG,QAAQ,GAAG,cAAc,EAAE,EAAE;AAAA,EACxG;AAEA,QAAM,MAAM,SAAS;AACrB,MAAI,CAAC,IAAI,oBAAoB,CAAC,IAAI,sBAAsB;AACtD,UAAMA,kBAAiB,OAAO;AAAA,MAC5B,CAAC,OAAO;AAAA,QACN,OAAO,EAAE;AAAA,QACT,SAAS,MAAM,EAAE,GAAG;AAAA;AAAA;AAAA,MACtB;AAAA,IACF;AACA,WAAO;AAAA,MACL,cAAc,CAAC;AAAA,MACf,gBAAAA;AAAA,MACA,SAAS,EAAE,YAAY,GAAG,QAAQ,OAAO,QAAQ,cAAc,EAAE;AAAA,IACnE;AAAA,EACF;AAKA,QAAM,CAAC,YAAY,aAAa,IAAI,OAAO;AAAA,IACzC,CAAC,CAAC,OAAO,IAAI,GAAG,UAAU;AACxB,UAAI,sBAAsB,MAAM,GAAG,EAAG,OAAM,KAAK,KAAK;AAAA,UACjD,MAAK,KAAK,KAAK;AACpB,aAAO,CAAC,OAAO,IAAI;AAAA,IACrB;AAAA,IACA,CAAC,CAAC,GAAG,CAAC,CAAC;AAAA,EACT;AAEA,QAAM,qBAAqB,cAAc;AAAA,IACvC,CAAC,OAAO;AAAA,MACN,OAAO,EAAE;AAAA,MACT,SAAS,MAAM,EAAE,GAAG;AAAA;AAAA;AAAA,IACtB;AAAA,EACF;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO;AAAA,MACL,cAAc,CAAC;AAAA,MACf,gBAAgB;AAAA,MAChB,SAAS,EAAE,YAAY,GAAG,QAAQ,cAAc,QAAQ,cAAc,EAAE;AAAA,IAC1E;AAAA,EACF;AAEA,SAAO,QAAQ,yCAAyC,WAAW,MAAM,uBAAuB,YAAY,MAAM,IAAI,QAAQ;AAC9H,QAAM,OAAO,WAAW,IAAI,CAAC,MAAM,EAAE,GAAG;AACxC,QAAM,cAAc,MAAM;AAAA,IACxB,OAAO,IAAI,aAAa;AACtB,YAAM,SAAS,OAAO;AACtB,aAAO,OAAO,OAAO,cAAc,MAAM,IAAI,EAAE;AAAA,QAC7C,OAAO,YAAY;AAAA,UACjB,UAAU;AAAA,UACV,WAAW,MAAM,IAAI,qBAAqB;AAAA,YACxC,UAAU;AAAA,YACV,WAAW;AAAA,YACX,YAAY;AAAA,UACd,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,IACD,kBAAkB,IAAI,kBAAkB,IAAI,oBAAoB;AAAA,EAClE;AACA,QAAM,aAAa,IAAI,IAAI,WAAW,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;AAEtE,QAAM,eAAkC,CAAC;AACzC,QAAM,iBAAkC,CAAC,GAAG,kBAAkB;AAC9D,MAAI,aAAa;AACjB,MAAI,SAAS,cAAc;AAE3B,aAAW,CAAC,KAAK,MAAM,KAAK,YAAY,SAAS;AAC/C,UAAM,YAAY,WAAW,IAAI,GAAG,KAAK;AACzC,QAAI,kBAAkB,OAAO;AAC3B;AACA,qBAAe,KAAK,EAAE,OAAO,WAAW,SAAS,MAAM,GAAG;AAAA;AAAA,8BAA8B,OAAO,OAAO,GAAG,CAAC;AAC1G;AAAA,IACF;AACA;AACA,UAAM,KAAK,2BAA2B,MAAM;AAC5C,iBAAa,KAAK,EAAE,KAAK,SAAS,IAAI,OAAO,WAAW,YAAY,GAAG,CAAC;AAAA,EAC1E;AAEA,SAAO,EAAE,cAAc,gBAAgB,SAAS,EAAE,YAAY,QAAQ,cAAc,EAAE,EAAE;AAC1F;AAYA,MAAM,uBACJ;AAGK,MAAM,wBAAwB;AAM9B,SAAS,mBAAmB,WAAkC;AACnE,QAAM,IAAI,UAAU,KAAK,EAAE,MAAM,oBAAoB;AACrD,SAAO,IAAI,EAAE,CAAC,IAAK;AACrB;AAOO,SAAS,wBACd,WACA,YACQ;AACR,QAAM,SAAS,mBAAmB,SAAS;AAC3C,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,YAAY,KAAK;AACjC,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,UACJ,QAAQ,SAAS,wBACb,QAAQ,MAAM,GAAG,qBAAqB,IAAI,8BAC1C;AACN,SAAO,GAAG,UAAU,KAAK,CAAC;AAAA;AAAA,sCAA2C,MAAM;AAAA;AAAA,EAAQ,OAAO;AAC5F;AAIA,eAAe,oBACb,cACA,qBACA,cACA,UACgF;AAChF,MAAI,YAAY;AAGhB,MAAI,CAAC,qBAAqB;AACxB,QAAI,aAAa,SAAS,GAAG;AAC3B,aAAO,QAAQ,uFAAkF,QAAQ;AAAA,IAC3G;AACA,WAAO,EAAE,OAAO,cAAc,WAAW,cAAc,EAAE;AAAA,EAC3D;AAEA,MAAI,CAAC,gBAAgB,aAAa,WAAW,GAAG;AAC9C,QAAI,CAAC,gBAAgB,aAAa,SAAS,GAAG;AAC5C,aAAO,WAAW,yEAAyE,QAAQ;AACnG,WAAK,SAAS,IAAI,WAAW,iFAAiF;AAAA,IAChH;AACA,WAAO,EAAE,OAAO,cAAc,WAAW,cAAc,EAAE;AAAA,EAC3D;AAEA,SAAO,QAAQ,6CAA6C,aAAa,MAAM,uBAAuB,YAAY,cAAc,IAAI,QAAQ;AAE5I,QAAM,aAAa,MAAM;AAAA,IACvB,OAAO,IAAI,aAAa;AACtB,YAAM,MAAM,OAAO;AACnB,aAAO,OAAO,OAAO;AAAA,QACnB;AAAA,QACA,CAAC,SACC,IAAI;AAAA,UACF,KAAK;AAAA,UACL,EAAE,SAAS,MAAM,SAAS,qBAAqB,KAAK,KAAK,IAAI;AAAA,QAC/D,EAAE;AAAA,UACA,OAAO,YAAY;AAAA,YACjB,UAAU;AAAA,YACV,WAAW,MAAM,IAAI,qBAAqB;AAAA,cACxC,UAAU;AAAA,cACV,WAAW;AAAA,cACX,YAAY;AAAA,YACd,CAAC;AAAA,UACH,CAAC;AAAA,UACD,OAAO;AAAA,UACP,OAAO,IAAI,CAAC,YAAY,EAAE,MAAM,OAAO,EAAE;AAAA,QAC3C;AAAA,QACF,EAAE,aAAa,YAAY,eAAe;AAAA,MAC5C;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF;AAEA,QAAM,iBAAiB,WAAW,IAAI,CAAC,EAAE,MAAM,OAAO,MAAM;AACxD,WAAO,SAAS,kBAAkB,KAAK,GAAG,OAAO,QAAQ;AAEzD,QAAI,OAAO,OAAO,MAAM,GAAG;AACzB;AACA,YAAM,eAAe,mBAAmB,OAAO,IAAI;AACnD,aAAO,WAAW,6BAA6B,KAAK,GAAG,KAAK,YAAY,IAAI,QAAQ;AACpF,WAAK,SAAS,IAAI,WAAW,8BAA8B,KAAK,GAAG,WAAM,YAAY,EAAE;AACvF,YAAMC,OAAM,KAAK,YAAY,KAAK;AAClC,YAAMC,cAAaD,OACf;AAAA;AAAA;AAAA;AAAA,EAAuCA,KAAI,SAAS,wBAAwBA,KAAI,MAAM,GAAG,qBAAqB,IAAI,2BAA2BA,IAAG,KAChJ;AACJ,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS,iCAA4B,YAAY,GAAGC,WAAU;AAAA,MAChE;AAAA,IACF;AAEA,UAAM,YAAY,OAAO;AAEzB,QAAI,UAAU,WAAW;AACvB,YAAM,SAAS,wBAAwB,UAAU,SAAS,KAAK,UAAU;AACzE,UAAI,WAAW,UAAU,SAAS;AAChC,eAAO,WAAW,qCAAqC,KAAK,GAAG,kCAA6B,QAAQ;AACpG,aAAK,SAAS,IAAI,WAAW,qBAAqB,KAAK,GAAG,iCAA4B;AAAA,MACxF;AACA,aAAO,EAAE,GAAG,MAAM,SAAS,OAAO;AAAA,IACpC;AAEA;AACA,WAAO,WAAW,6BAA6B,KAAK,GAAG,KAAK,UAAU,SAAS,gBAAgB,IAAI,QAAQ;AAC3G,SAAK,SAAS,IAAI,WAAW,8BAA8B,KAAK,GAAG,WAAM,UAAU,SAAS,gBAAgB,EAAE;AAC9G,UAAM,MAAM,KAAK,YAAY,KAAK;AAClC,UAAM,aAAa,MACf;AAAA;AAAA;AAAA;AAAA,EAAuC,IAAI,SAAS,wBAAwB,IAAI,MAAM,GAAG,qBAAqB,IAAI,2BAA2B,GAAG,KAChJ;AACJ,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS,iCAA4B,UAAU,SAAS,gBAAgB,GAAG,UAAU;AAAA,IACvF;AAAA,EACJ,CAAC;AAED,SAAO,EAAE,OAAO,gBAAgB,WAAW,cAAc,aAAa,OAAO;AAC/E;AASO,SAAS,uBAAuB,cAAiC,gBAA2C;AACjH,QAAM,iBAAiC,aAAa,IAAI,CAAC,SAAS;AAChE,QAAI,UAAU,KAAK;AACnB,QAAI;AACF,gBAAU,eAAe,OAAO;AAAA,IAClC,QAAQ;AAAA,IAER;AACA,WAAO,EAAE,OAAO,KAAK,OAAO,SAAS,MAAM,KAAK,GAAG;AAAA;AAAA,EAAO,OAAO,GAAG;AAAA,EACtE,CAAC;AAED,SAAO,CAAC,GAAG,gBAAgB,GAAG,cAAc,EACzC,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,IAAI,CAAC,UAAU,MAAM,OAAO;AACjC;AAEA,SAAS,oBACP,QACA,UACA,SACA,WACA,eACA,eACQ;AACR,QAAM,YAA6C,CAAC;AACpD,MAAI,cAAc,eAAe,GAAG;AAClC,UAAM,KAAK,cAAc,eAAe;AACxC,cAAU,gBAAgB,IAAI,GAAG,EAAE,IAAI,cAAc,YAAY;AACjE,QAAI,CAAC,cAAc,cAAc;AAC/B,gBAAU,YAAY,IAAI;AAAA,IAC5B;AAAA,EACF,WAAW,YAAY,GAAG;AACxB,cAAU,yBAAyB,IAAI;AAAA,EACzC;AAEA,QAAM,cAAc,kBAAkB;AAAA,IACpC,OAAO,oBAAoB,OAAO,KAAK,MAAM;AAAA,IAC7C,YAAY,OAAO,KAAK;AAAA,IACxB,YAAY,QAAQ;AAAA,IACpB,QAAQ,QAAQ;AAAA,IAChB,QAAQ;AAAA,MACN,gBAAgB,QAAQ;AAAA,MACxB,GAAG;AAAA,IACL;AAAA,EACF,CAAC;AAED,QAAM,mBAAmB,cAAc;AAAA,IACrC,OAAO;AAAA,IACP,SAAS;AAAA,IACT,MAAM,SAAS,KAAK,aAAa;AAAA,IACjC,UAAU;AAAA,MACR,kBAAkB,eAAe,aAAa;AAAA,IAChD;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAIA,eAAe,sBACb,QACA,WAAyB,eACuB;AAChD,QAAM,YAAY,KAAK,IAAI;AAE3B,MAAI,CAAC,OAAO,QAAQ,OAAO,KAAK,WAAW,GAAG;AAC5C,WAAO,0BAA0B,WAAW,oBAAoB,WAAW,OAAO,QAAQ;AAAA,EAC5F;AAEA,MAAI,OAAO,SAAS,CAAC,mBAAmB,GAAG;AACzC,WAAO,YAAY,qBAAqB,eAAe,CAAC;AAAA,EAC1D;AAEA,QAAM,EAAE,WAAW,cAAc,gBAAgB,eAAe,IAAI,cAAc,OAAO,IAAI;AAC7F,QAAM,aAAa,UAAU,SAAS,aAAa,SAAS,eAAe;AAE3E,QAAM,SAAS;AAAA,IACb;AAAA,IACA,eAAe,OAAO,KAAK,MAAM,YAAY,UAAU,MAAM,SAAS,aAAa,MAAM,YAAY,eAAe,MAAM,cAAc,eAAe,MAAM;AAAA,EAC/J;AAEA,MAAI,eAAe,GAAG;AACpB,WAAO;AAAA,MACL;AAAA,MACA,OAAO,OAAO,KAAK,MAAM;AAAA,MACzB;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,QACE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA;AAAA,IACE;AAAA,IACA,YAAY,OAAO,QAAQ,KAAK,UAAU,MAAM,UAAU,aAAa,MAAM,aAAa,eAAe,MAAM;AAAA,IAC/G;AAAA,EACF;AACA,QAAM,SAAS,SAAS,IAAI,KAAK,0BAA0B;AAE3D,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS;AACrB,oBAAgB,gBAAgB,EAAE;AAClC,uBAAmB,IAAI,kBAAkB,sBAAsB,IAAI,eAAe,IAAI;AAAA,EACxF,SAAS,OAAO;AACd,UAAM,MAAM,cAAc,KAAK;AAC/B,WAAO;AAAA,MACL;AAAA,MACA,0CAA0C,IAAI,OAAO;AAAA,MACrD;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,QACE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,sBAAsB,OAAO,QAAQ,6BAA6B,OAAO,OAAO,IAAI;AAE1F,QAAM,SAAS,SAAS,IAAI,KAAK,uBAAuB;AAExD,QAAM,aAAa,CAAC,GAAG,WAAW,GAAG,cAAc;AACnD,QAAM,CAAC,WAAW,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,IACjD,qBAAqB,YAAY,eAAe,gBAAgB;AAAA,IAChE,kBAAkB,YAAY;AAAA,EAChC,CAAC;AAED,QAAM,eAAe;AAAA,IACnB,GAAG,UAAU;AAAA,IACb,GAAG,YAAY;AAAA,EACjB;AACA,QAAM,gBAAgB,eAAe;AAAA,IACnC,CAAC,EAAE,KAAK,UAAU,OAAO,EAAE,OAAO,WAAW,SAAS,MAAM,GAAG;AAAA;AAAA,2BAA2B;AAAA,EAC5F;AACA,QAAM,iBAAiB;AAAA,IACrB,GAAG;AAAA,IACH,GAAG,UAAU;AAAA,IACb,GAAG,YAAY;AAAA,EACjB;AACA,QAAM,UAAyB;AAAA,IAC7B,YACE,UAAU,QAAQ,aAChB,YAAY,QAAQ;AAAA,IAExB,QACE,eAAe,SACb,UAAU,QAAQ,SAClB,YAAY,QAAQ;AAAA,IACxB,cAAc;AAAA,EAChB;AAEA,QAAM,SAAS,IAAI,QAAQ,WAAW,QAAQ,UAAU,aAAa,QAAQ,MAAM,SAAS;AAE5F,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,SAAS,SAAS,IAAI,KAAK,2CAA2C;AAAA,EAC9E;AAEA,QAAM,EAAE,OAAO,gBAAgB,WAAW,aAAa,IAAI,MAAM;AAAA,IAC/D;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,WAAW,uBAAuB,gBAAgB,cAAc;AACtE,QAAM,gBAAgB,KAAK,IAAI,IAAI;AAEnC;AAAA,IACE;AAAA,IACA,cAAc,QAAQ,UAAU,gBAAgB,QAAQ,MAAM,YAAY,QAAQ,YAAY;AAAA,IAC9F;AAAA,EACF;AAEA,QAAM,eAAe,eAAe,KAAK,YAAY;AACrD,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,cAAc,aAAa;AAAA,EAC/B;AAEA,MAAI,QAAQ,eAAe,KAAK,QAAQ,SAAS,GAAG;AAClD,WAAO,YAAY,OAAO;AAAA,EAC5B;AAEA,SAAO,YAAY,OAAO;AAC5B;AAEO,SAAS,qBACd,QACA,WAAyB,eACuB;AAChD,SAAO,sBAAsB,EAAE,GAAG,QAAQ,OAAO,OAAO,UAAU,mBAAmB,GAAG,QAAQ;AAClG;AAEO,SAAS,uBACd,QACA,WAAyB,eACuB;AAChD,SAAO,sBAAsB,EAAE,GAAG,QAAQ,OAAO,MAAM,UAAU,qBAAqB,GAAG,QAAQ;AACnG;AAEO,SAAS,yBAAyB,QAAyB;AAChE,SAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aACE;AAAA,MACF,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,cAAc;AAAA,QACd,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,eAAe;AAAA,MACjB;AAAA,IACF;AAAA,IACA,OAAO,MAAM,QAAQ;AACnB,YAAM,SAAS,2BAA2B,UAAU,IAAI;AACxD,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO,eAAe,YAAY,2BAA2B,oBAAoB,OAAO,MAAM,MAAM,CAAC,CAAC;AAAA,MACxG;AAEA,YAAM,WAAW,mBAAmB,KAAK,kBAAkB;AAC3D,YAAM,SAAS,MAAM,qBAAqB,OAAO,MAAM,QAAQ;AAE/D,YAAM,SAAS,SAAS,KAAK,KAAK,OAAO,UAAU,kBAAkB,iBAAiB;AACtF,aAAO,eAAe,MAAM;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aACE;AAAA,MACF,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,cAAc;AAAA,QACd,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,eAAe;AAAA,MACjB;AAAA,IACF;AAAA,IACA,OAAO,MAAM,QAAQ;AACnB,YAAM,SAAS,6BAA6B,UAAU,IAAI;AAC1D,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO,eAAe,YAAY,2BAA2B,sBAAsB,OAAO,MAAM,MAAM,CAAC,CAAC;AAAA,MAC1G;AAEA,YAAM,WAAW,mBAAmB,KAAK,oBAAoB;AAC7D,YAAM,SAAS,MAAM,uBAAuB,OAAO,MAAM,QAAQ;AAEjE,YAAM,SAAS,SAAS,KAAK,KAAK,OAAO,UAAU,kBAAkB,iBAAiB;AACtF,aAAO,eAAe,MAAM;AAAA,IAC9B;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["/**\n * Scrape Links Tool Handler\n *\n * Scrapes many URLs in parallel. Reddit permalinks (reddit.com/r/.../comments/...)\n * are auto-detected and routed through the Reddit API; all other URLs go through\n * Jina Reader first, then Scrape.do proxy-mode retry, then optional Kernel\n * browser rendering. Smart mode feeds successful items to the LLM extractor.\n *\n * NEVER throws \u2014 every error is returned as a tool-level failure response.\n */\n\nimport type { MCPServer } from 'mcp-use/server';\nimport { Effect, Either } from 'effect';\n\nimport {\n SCRAPER,\n CONCURRENCY,\n getCapabilities,\n getMissingEnvMessage,\n parseEnv,\n} from '../config/index.js';\nimport {\n rawScrapeLinksParamsSchema,\n smartScrapeLinksParamsSchema,\n type RawScrapeLinksParams,\n type SmartScrapeLinksParams,\n} from '../schemas/scrape-links.js';\nimport { type PostResult } from '../clients/reddit.js';\nimport { buildScrapeDoProxyUrl, isTerminalReaderError, type JinaConvertResponse } from '../clients/jina.js';\nimport { MarkdownCleaner } from '../services/markdown-cleaner.js';\nimport { createLLMProcessor } from '../services/llm-processor.js';\nimport { removeMetaTags } from '../utils/markdown-formatter.js';\nimport { extractReadableContent } from '../utils/content-extractor.js';\nimport { classifyError, ErrorCode } from '../utils/errors.js';\nimport { isDocumentUrl } from '../utils/source-type.js';\nimport { assessMarkdownQuality } from '../utils/content-quality.js';\nimport { runExternalEffect } from '../effect/runtime.js';\nimport {\n JinaService,\n JinaServiceLive,\n KernelService,\n KernelServiceLive,\n LlmService,\n LlmServiceLive,\n RedditService,\n RedditServiceLive,\n} from '../effect/services.js';\nimport { ProviderTimeoutError } from '../effect/errors.js';\nimport {\n mcpLog,\n formatSuccess,\n formatError,\n formatBatchHeader,\n formatDuration,\n} from './utils.js';\nimport {\n createToolReporter,\n NOOP_REPORTER,\n toolFailure,\n toolSuccess,\n toToolResponse,\n type ToolExecutionResult,\n type ToolReporter,\n} from './mcp-helpers.js';\n\nconst markdownCleaner = new MarkdownCleaner();\n\ntype ScrapeToolOutput = Record<string, never>;\n\ninterface ScrapeHandlerParams {\n urls: string[];\n extract?: string;\n smart: boolean;\n toolName: 'raw-scrape-links' | 'smart-scrape-links';\n}\n\nfunction formatInputValidationError(\n toolName: string,\n issues: readonly { message: string; path: readonly PropertyKey[] }[],\n): string {\n const details = issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.map(String).join('.') : '<root>';\n return `- ${path}: ${issue.message}`;\n })\n .join('\\n');\n return `Invalid ${toolName} input.\\n\\n${details}`;\n}\n\nfunction enhanceExtractionInstruction(instruction: string | undefined): string {\n const base = instruction || 'Extract the main content and key information from this page.';\n return `${SCRAPER.EXTRACTION_PREFIX}\\n\\n${base}\\n\\n${SCRAPER.EXTRACTION_SUFFIX}`;\n}\n\n// --- Types ---\n\ninterface ProcessedResult {\n url: string;\n content: string;\n index: number; // original position in params.urls[]\n /**\n * Cleaned markdown captured before LLM extraction. Preserved so the handler\n * can fall back to it when the LLM emits the terse \"Page did not load: X\"\n * escape line and would otherwise nuke the scraped body.\n */\n rawContent?: string;\n}\n\ninterface ScrapeMetrics {\n successful: number;\n failed: number;\n totalCredits: number;\n}\n\ninterface FailedContent {\n content: string;\n index: number;\n}\n\ninterface ScrapePhaseResult {\n successItems: ProcessedResult[];\n failedContents: FailedContent[];\n metrics: ScrapeMetrics;\n}\n\ninterface BranchInput {\n url: string;\n origIndex: number;\n}\n\ninterface KernelFallbackInput extends BranchInput {\n proxyError?: string;\n jinaError?: string;\n}\n\nfunction cleanFetchedContent(rawContent: string, url: string): string {\n try {\n const readable = extractReadableContent(rawContent, url);\n const sourceForCleaner = readable.extracted ? readable.content : rawContent;\n return markdownCleaner.processContent(sourceForCleaner);\n } catch {\n return rawContent;\n }\n}\n\nfunction effectErrorMessage(error: unknown): string {\n if (typeof error === 'object' && error !== null) {\n if ('error' in error) {\n const structured = (error as { error?: { message?: unknown } }).error;\n if (typeof structured?.message === 'string') return structured.message;\n }\n if ('message' in error && typeof (error as { message?: unknown }).message === 'string') {\n return (error as { message: string }).message;\n }\n if ('_tag' in error && typeof (error as { _tag?: unknown })._tag === 'string') {\n return (error as { _tag: string })._tag;\n }\n }\n return String(error);\n}\n\n// --- Reddit URL detection ---\n\nconst REDDIT_HOST = /(?:^|\\.)reddit\\.com$/i;\nconst REDDIT_POST_PERMALINK = /\\/r\\/[^/]+\\/comments\\/[a-z0-9]+/i;\n\nfunction isRedditUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname);\n } catch {\n return false;\n }\n}\n\nfunction isRedditPostPermalink(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname) && REDDIT_POST_PERMALINK.test(u.pathname);\n } catch {\n return false;\n }\n}\n\n// --- Error helper ---\n\nfunction createScrapeErrorResponse(\n code: string,\n message: string,\n startTime: number,\n toolName: ScrapeHandlerParams['toolName'] = 'raw-scrape-links',\n retryable = false,\n alternatives?: string[],\n): ToolExecutionResult<ScrapeToolOutput> {\n return toolFailure(\n `${formatError({\n code,\n message,\n retryable,\n toolName,\n howToFix: code === 'NO_URLS' ? ['Provide at least one valid URL'] : undefined,\n alternatives,\n })}\\n\\nExecution time: ${formatDuration(Date.now() - startTime)}`,\n );\n}\n\n// --- URL partitioning ---\n\ninterface PartitionedUrls {\n webInputs: BranchInput[];\n redditInputs: BranchInput[];\n documentInputs: BranchInput[];\n invalidEntries: { url: string; origIndex: number }[];\n}\n\nfunction partitionUrls(urls: string[]): PartitionedUrls {\n const webInputs: BranchInput[] = [];\n const redditInputs: BranchInput[] = [];\n const documentInputs: BranchInput[] = [];\n const invalidEntries: { url: string; origIndex: number }[] = [];\n\n for (let i = 0; i < urls.length; i++) {\n const url = urls[i]!;\n try {\n new URL(url);\n } catch {\n invalidEntries.push({ url, origIndex: i });\n continue;\n }\n // Document URLs (.pdf/.docx/.pptx/.xlsx) go straight to Jina Reader \u2014\n // bypassing Scrape.do because it cannot decode binary bodies. Ordered\n // before the Reddit check so a hypothetical PDF on a reddit-adjacent host\n // still takes the document path.\n if (isDocumentUrl(url)) {\n documentInputs.push({ url, origIndex: i });\n } else if (isRedditUrl(url)) {\n redditInputs.push({ url, origIndex: i });\n } else {\n webInputs.push({ url, origIndex: i });\n }\n }\n\n return { webInputs, redditInputs, documentInputs, invalidEntries };\n}\n\n// --- Jina-first branch ---\n\n/**\n * Format a Jina-failure line. When Scrape.do proxy mode was attempted, surface\n * both direct and proxy layers so callers can see exactly where fallback ended.\n *\n * Exported for unit testing.\n */\nexport function formatJinaFailure(url: string, jinaError: string, proxyError?: string): string {\n if (proxyError) {\n return `## ${url}\\n\\n\u274C Jina Reader failed. Direct: ${jinaError}. Scrape.do proxy: ${proxyError}.`;\n }\n return `## ${url}\\n\\n\u274C Document conversion failed: ${jinaError}`;\n}\n\nfunction jinaResultError(result: JinaConvertResponse): string | null {\n if (result.error || result.statusCode < 200 || result.statusCode >= 300) {\n return result.error?.message || `HTTP ${result.statusCode}`;\n }\n const quality = assessMarkdownQuality(result.content);\n if (quality.weak) {\n return `Weak Jina markdown (${quality.reason})`;\n }\n return null;\n}\n\nfunction canTryKernel(input: BranchInput, directError?: JinaConvertResponse['error']): boolean {\n if (isDocumentUrl(input.url)) return false;\n if (!directError) return true;\n if (directError.code === ErrorCode.NOT_FOUND || directError.code === ErrorCode.INVALID_INPUT) {\n return false;\n }\n return true;\n}\n\ninterface JinaFirstInput extends BranchInput {\n directError?: string;\n directStructuredError?: JinaConvertResponse['error'];\n proxyError?: string;\n}\n\nfunction formatJinaFirstFailure(input: JinaFirstInput): string {\n if (input.proxyError) {\n return formatJinaFailure(input.url, input.directError ?? 'Unknown direct failure', input.proxyError);\n }\n return formatJinaFailure(input.url, input.directError ?? 'Unknown direct failure');\n}\n\nasync function fetchJinaFirstBranch(\n inputs: BranchInput[],\n kernelEnabled: boolean,\n scrapeDoProxyUrl: string | undefined,\n): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n mcpLog(\n 'info',\n `[concurrency] jina direct branch: converting ${inputs.length} URL(s) with limit=${CONCURRENCY.JINA_READER}`,\n 'scrape',\n );\n\n const directResults = await runExternalEffect(\n Effect.gen(function* () {\n const jina = yield* JinaService;\n return yield* Effect.forEach(\n inputs,\n (input) =>\n jina.convert({ url: input.url, timeoutSeconds: 15, allowProxyRetry: false }).pipe(\n Effect.timeoutFail({\n duration: '20 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'jina',\n operation: 'convert:direct',\n durationMs: 20_000,\n }),\n }),\n Effect.either,\n ),\n { concurrency: CONCURRENCY.JINA_READER },\n );\n }),\n JinaServiceLive,\n );\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n const proxyInputs: JinaFirstInput[] = [];\n const kernelInputs: KernelFallbackInput[] = [];\n let successful = 0;\n let failed = 0;\n\n const enqueueFailure = (input: JinaFirstInput): void => {\n if (kernelEnabled && canTryKernel(input, input.directStructuredError)) {\n kernelInputs.push({\n url: input.url,\n origIndex: input.origIndex,\n proxyError: input.proxyError ? `Scrape.do proxy: ${input.proxyError}` : undefined,\n jinaError: input.directError,\n });\n return;\n }\n failed++;\n failedContents.push({ index: input.origIndex, content: formatJinaFirstFailure(input) });\n };\n\n for (let i = 0; i < directResults.length; i++) {\n const settled = directResults[i];\n const input = inputs[i]!;\n if (!settled) {\n proxyInputs.push({ ...input, directError: 'No result returned' });\n continue;\n }\n if (Either.isLeft(settled)) {\n const reason = effectErrorMessage(settled.left);\n proxyInputs.push({ ...input, directError: reason });\n continue;\n }\n\n const directError = jinaResultError(settled.right);\n if (!directError) {\n successful++;\n successItems.push({ url: input.url, content: settled.right.content, index: input.origIndex, rawContent: settled.right.content });\n continue;\n }\n\n const structuredError = settled.right.error;\n if (structuredError && isTerminalReaderError(structuredError)) {\n enqueueFailure({ ...input, directError, directStructuredError: structuredError });\n continue;\n }\n\n proxyInputs.push({ ...input, directError, directStructuredError: structuredError });\n }\n\n if (proxyInputs.length > 0) {\n if (!scrapeDoProxyUrl) {\n for (const input of proxyInputs) {\n enqueueFailure({ ...input, proxyError: 'SCRAPEDO_API_KEY is not configured' });\n }\n } else {\n mcpLog(\n 'info',\n `[concurrency] jina scrape.do proxy branch: retrying ${proxyInputs.length} URL(s) with limit=${CONCURRENCY.JINA_READER}`,\n 'scrape',\n );\n const proxyResults = await runExternalEffect(\n Effect.gen(function* () {\n const jina = yield* JinaService;\n return yield* Effect.forEach(\n proxyInputs,\n (input) =>\n jina.convert({\n url: input.url,\n timeoutSeconds: 15,\n proxyUrl: scrapeDoProxyUrl,\n noCache: true,\n allowProxyRetry: false,\n }).pipe(\n Effect.timeoutFail({\n duration: '20 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'jina',\n operation: 'convert:proxy',\n durationMs: 20_000,\n }),\n }),\n Effect.either,\n ),\n { concurrency: CONCURRENCY.JINA_READER },\n );\n }),\n JinaServiceLive,\n );\n\n for (let i = 0; i < proxyResults.length; i++) {\n const settled = proxyResults[i];\n const input = proxyInputs[i]!;\n if (!settled) {\n enqueueFailure({ ...input, proxyError: 'No result returned' });\n continue;\n }\n if (Either.isLeft(settled)) {\n enqueueFailure({ ...input, proxyError: effectErrorMessage(settled.left) });\n continue;\n }\n\n const proxyError = jinaResultError(settled.right);\n if (!proxyError) {\n successful++;\n successItems.push({ url: input.url, content: settled.right.content, index: input.origIndex, rawContent: settled.right.content });\n continue;\n }\n\n enqueueFailure({ ...input, proxyError });\n }\n }\n }\n\n if (kernelInputs.length > 0 && kernelEnabled) {\n const kernelPhase = await fetchKernelBranch(kernelInputs);\n successItems.push(...kernelPhase.successItems);\n failedContents.push(...kernelPhase.failedContents);\n successful += kernelPhase.metrics.successful;\n failed += kernelPhase.metrics.failed;\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\nfunction formatKernelFailure(\n url: string,\n kernelError: string,\n proxyError?: string,\n jinaError?: string,\n): string {\n const layers = [\n jinaError ? `Jina Reader: ${jinaError}` : undefined,\n proxyError ? proxyError : undefined,\n `Kernel: ${kernelError}`,\n ].filter((line): line is string => Boolean(line));\n return `## ${url}\\n\\n\u274C All scrape providers failed. ${layers.join('. ')}.`;\n}\n\nasync function fetchKernelBranch(\n inputs: KernelFallbackInput[],\n): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n mcpLog(\n 'info',\n `[concurrency] kernel branch: rendering ${inputs.length} URL(s) with limit=${CONCURRENCY.KERNEL}`,\n 'scrape',\n );\n\n const results = await runExternalEffect(\n Effect.gen(function* () {\n const kernel = yield* KernelService;\n return yield* Effect.forEach(\n inputs,\n (input) =>\n kernel.render({ url: input.url, timeoutSeconds: 15 }).pipe(\n Effect.timeoutFail({\n duration: '25 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'kernel',\n operation: 'render',\n durationMs: 25_000,\n }),\n }),\n Effect.either,\n ),\n { concurrency: CONCURRENCY.KERNEL },\n );\n }),\n KernelServiceLive,\n );\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n let successful = 0;\n let failed = 0;\n\n for (let i = 0; i < results.length; i++) {\n const settled = results[i];\n const input = inputs[i]!;\n if (!settled) {\n failed++;\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(input.url, 'No result returned', input.proxyError, input.jinaError),\n });\n continue;\n }\n if (Either.isLeft(settled)) {\n failed++;\n const reason = effectErrorMessage(settled.left);\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(input.url, reason, input.proxyError, input.jinaError),\n });\n continue;\n }\n\n const result = settled.right;\n if (result.error || result.statusCode < 200 || result.statusCode >= 300) {\n failed++;\n const errorMsg = result.error?.message || `HTTP ${result.statusCode}`;\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(input.url, errorMsg, input.proxyError, input.jinaError),\n });\n continue;\n }\n\n const content = cleanFetchedContent(result.content, result.finalUrl ?? input.url);\n const quality = assessMarkdownQuality(content);\n if (quality.weak) {\n failed++;\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(\n input.url,\n `Weak Kernel markdown (${quality.reason})`,\n input.proxyError,\n input.jinaError,\n ),\n });\n continue;\n }\n\n successful++;\n const finalUrl = result.finalUrl ?? input.url;\n successItems.push({ url: finalUrl, content, index: input.origIndex, rawContent: content });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Reddit branch ---\n\nfunction formatRedditPostAsMarkdown(result: PostResult): string {\n const { post, comments } = result;\n const lines: string[] = [];\n lines.push(`# ${post.title}`);\n lines.push('');\n lines.push(`**r/${post.subreddit}** \u2022 u/${post.author} \u2022 \u2B06\uFE0F ${post.score} \u2022 \uD83D\uDCAC ${post.commentCount} comments`);\n lines.push(`\uD83D\uDD17 ${post.url}`);\n lines.push('');\n if (post.body) {\n lines.push('## Post content');\n lines.push('');\n lines.push(post.body);\n lines.push('');\n }\n if (comments.length > 0) {\n lines.push(`## Top comments (${comments.length} total)`);\n lines.push('');\n for (const c of comments) {\n const indent = ' '.repeat(c.depth);\n const op = c.isOP ? ' **[OP]**' : '';\n const score = c.score >= 0 ? `+${c.score}` : `${c.score}`;\n lines.push(`${indent}- **u/${c.author}**${op} _(${score})_`);\n for (const line of c.body.split('\\n')) {\n lines.push(`${indent} ${line}`);\n }\n lines.push('');\n }\n }\n return lines.join('\\n');\n}\n\nasync function fetchRedditBranch(inputs: BranchInput[]): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n const env = parseEnv();\n if (!env.REDDIT_CLIENT_ID || !env.REDDIT_CLIENT_SECRET) {\n const failedContents = inputs.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Reddit URL detected, but Reddit API is not configured. Set \\`REDDIT_CLIENT_ID\\` and \\`REDDIT_CLIENT_SECRET\\` in the server env to enable threaded Reddit scraping.`,\n }),\n );\n return {\n successItems: [],\n failedContents,\n metrics: { successful: 0, failed: inputs.length, totalCredits: 0 },\n };\n }\n\n // Warn for non-permalink Reddit URLs (subreddit homepages, /new, /top, /hot,\n // user profiles). The Reddit API path we call requires /r/.../comments/... \u2014\n // reject upfront so the caller sees a helpful message instead of a 404.\n const [postInputs, nonPermalinks] = inputs.reduce<[BranchInput[], BranchInput[]]>(\n ([posts, rest], input) => {\n if (isRedditPostPermalink(input.url)) posts.push(input);\n else rest.push(input);\n return [posts, rest];\n },\n [[], []],\n );\n\n const nonPermalinkFailed = nonPermalinks.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Only Reddit post permalinks (/r/<sub>/comments/<id>/...) are supported. Use raw-web-search with explicit Reddit permalink probes or smart-web-search with scope:\"reddit\" to discover post permalinks first.`,\n }),\n );\n\n if (postInputs.length === 0) {\n return {\n successItems: [],\n failedContents: nonPermalinkFailed,\n metrics: { successful: 0, failed: nonPermalinks.length, totalCredits: 0 },\n };\n }\n\n mcpLog('info', `[concurrency] reddit branch: fetching ${postInputs.length} post(s) with limit=${CONCURRENCY.REDDIT}`, 'scrape');\n const urls = postInputs.map((i) => i.url);\n const batchResult = await runExternalEffect(\n Effect.gen(function* () {\n const reddit = yield* RedditService;\n return yield* reddit.batchGetPosts(urls, true).pipe(\n Effect.timeoutFail({\n duration: '60 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'reddit',\n operation: 'batchGetPosts',\n durationMs: 60_000,\n }),\n }),\n );\n }),\n RedditServiceLive(env.REDDIT_CLIENT_ID, env.REDDIT_CLIENT_SECRET),\n );\n const urlToIndex = new Map(postInputs.map((i) => [i.url, i.origIndex]));\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [...nonPermalinkFailed];\n let successful = 0;\n let failed = nonPermalinks.length;\n\n for (const [url, result] of batchResult.results) {\n const origIndex = urlToIndex.get(url) ?? -1;\n if (result instanceof Error) {\n failed++;\n failedContents.push({ index: origIndex, content: `## ${url}\\n\\n\u274C Reddit fetch failed: ${result.message}` });\n continue;\n }\n successful++;\n const md = formatRedditPostAsMarkdown(result);\n successItems.push({ url, content: md, index: origIndex, rawContent: md });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Terse-LLM-escape detection + raw fallback merger ---\n\n/**\n * The LLM extraction prompt tells the model to emit a single terse line when\n * a page \"clearly failed to load\" (login walls, JS-render-empty, paywalls,\n * etc.). In practice the LLM over-triggers this on partially-rendered pages,\n * causing smart scrape to return a one-line verdict and discard the cleaned\n * markdown. This detector + merger keep the verdict but re-attach a capped\n * slice of the raw markdown so the caller always has something to work with.\n */\nconst TERSE_LLM_FAILURE_RE =\n /^\\s*##\\s*Matches\\s*\\n+\\s*_Page did not load:\\s*([a-z0-9_-]+)_\\s*\\.?\\s*$/i;\n\n/** Cap on the raw-markdown slice appended under \"## Raw content ...\" */\nexport const RAW_FALLBACK_CHAR_CAP = 4000;\n\n/**\n * If `llmOutput` is exactly the terse \"## Matches\\n_Page did not load: X_\"\n * line, return the reason token (e.g. \"login-wall\"). Otherwise null.\n */\nexport function detectTerseFailure(llmOutput: string): string | null {\n const m = llmOutput.trim().match(TERSE_LLM_FAILURE_RE);\n return m ? m[1]! : null;\n}\n\n/**\n * When the LLM emitted the terse escape line, append a capped slice of the\n * raw cleaned markdown under a `## Raw content (...)` section so the caller\n * still has the actual scraped body to inspect. No-op otherwise.\n */\nexport function mergeLlmWithRawFallback(\n llmOutput: string,\n rawContent: string | undefined,\n): string {\n const reason = detectTerseFailure(llmOutput);\n if (!reason) return llmOutput;\n const trimmed = rawContent?.trim();\n if (!trimmed) return llmOutput;\n const snippet =\n trimmed.length > RAW_FALLBACK_CHAR_CAP\n ? trimmed.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n\u2026[raw truncated]'\n : trimmed;\n return `${llmOutput.trim()}\\n\\n## Raw content (LLM flagged page as ${reason})\\n\\n${snippet}`;\n}\n\n// --- LLM extraction (shared by both branches) ---\n\nasync function processItemsWithLlm(\n successItems: ProcessedResult[],\n enhancedInstruction: string | undefined,\n llmProcessor: ReturnType<typeof createLLMProcessor>,\n reporter: ToolReporter,\n): Promise<{ items: ProcessedResult[]; llmErrors: number; llmAttempted: number }> {\n let llmErrors = 0;\n\n // Raw-mode bypass: caller omitted `extract` \u2192 return cleaned markdown as-is.\n if (!enhancedInstruction) {\n if (successItems.length > 0) {\n mcpLog('info', 'Raw mode: extract omitted \u2014 returning cleaned scraped content without LLM pass', 'scrape');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n if (!llmProcessor || successItems.length === 0) {\n if (!llmProcessor && successItems.length > 0) {\n mcpLog('warning', 'LLM unavailable (LLM_API_KEY not set). Returning raw scraped content.', 'scrape');\n void reporter.log('warning', 'llm_extractor_unreachable: planner not configured; raw scraped content returned');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n mcpLog('info', `[concurrency] llm extraction: fanning out ${successItems.length} item(s) with limit=${CONCURRENCY.LLM_EXTRACTION}`, 'scrape');\n\n const llmResults = await runExternalEffect(\n Effect.gen(function* () {\n const llm = yield* LlmService;\n return yield* Effect.forEach(\n successItems,\n (item) =>\n llm.extractContent(\n item.content,\n { enabled: true, extract: enhancedInstruction, url: item.url },\n ).pipe(\n Effect.timeoutFail({\n duration: '155 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'llm',\n operation: 'extractContent',\n durationMs: 155_000,\n }),\n }),\n Effect.either,\n Effect.map((result) => ({ item, result })),\n ),\n { concurrency: CONCURRENCY.LLM_EXTRACTION },\n );\n }),\n LlmServiceLive,\n );\n\n const processedItems = llmResults.map(({ item, result }) => {\n mcpLog('debug', `LLM extracting ${item.url}...`, 'scrape');\n\n if (Either.isLeft(result)) {\n llmErrors++;\n const errorMessage = effectErrorMessage(result.left);\n mcpLog('warning', `LLM extraction failed for ${item.url}: ${errorMessage}`, 'scrape');\n void reporter.log('warning', `llm_extractor_unreachable: ${item.url} \u2014 ${errorMessage}`);\n const raw = item.rawContent?.trim();\n const rawSnippet = raw\n ? `\\n\\n## Raw content (unextracted)\\n\\n${raw.length > RAW_FALLBACK_CHAR_CAP ? raw.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n...[raw truncated]' : raw}`\n : '';\n return {\n ...item,\n content: `\u274C LLM extraction failed: ${errorMessage}${rawSnippet}`,\n };\n }\n\n const llmResult = result.right;\n\n if (llmResult.processed) {\n const merged = mergeLlmWithRawFallback(llmResult.content, item.rawContent);\n if (merged !== llmResult.content) {\n mcpLog('warning', `LLM emitted terse escape line for ${item.url} \u2014 preserved raw fallback`, 'scrape');\n void reporter.log('warning', `llm_terse_escape: ${item.url} \u2014 preserving raw fallback`);\n }\n return { ...item, content: merged };\n }\n\n llmErrors++;\n mcpLog('warning', `LLM extraction failed for ${item.url}: ${llmResult.error || 'unknown reason'}`, 'scrape');\n void reporter.log('warning', `llm_extractor_unreachable: ${item.url} \u2014 ${llmResult.error || 'unknown reason'}`);\n const raw = item.rawContent?.trim();\n const rawSnippet = raw\n ? `\\n\\n## Raw content (unextracted)\\n\\n${raw.length > RAW_FALLBACK_CHAR_CAP ? raw.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n...[raw truncated]' : raw}`\n : '';\n return {\n ...item,\n content: `\u274C LLM extraction failed: ${llmResult.error || 'unknown reason'}${rawSnippet}`,\n };\n });\n\n return { items: processedItems, llmErrors, llmAttempted: successItems.length };\n}\n\n// --- Output assembly ---\n\ninterface ContentEntry {\n content: string;\n index: number;\n}\n\nexport function assembleContentEntries(successItems: ProcessedResult[], failedContents: FailedContent[]): string[] {\n const successEntries: ContentEntry[] = successItems.map((item) => {\n let content = item.content;\n try {\n content = removeMetaTags(content);\n } catch {\n // Use content as-is\n }\n return { index: item.index, content: `## ${item.url}\\n\\n${content}` };\n });\n\n return [...failedContents, ...successEntries]\n .sort((a, b) => a.index - b.index)\n .map((entry) => entry.content);\n}\n\nfunction buildScrapeResponse(\n params: ScrapeHandlerParams,\n contents: string[],\n metrics: ScrapeMetrics,\n llmErrors: number,\n executionTime: number,\n llmAccounting: { llmAttempted: number; llmSucceeded: boolean },\n): string {\n const llmExtras: Record<string, string | number> = {};\n if (llmAccounting.llmAttempted > 0) {\n const ok = llmAccounting.llmAttempted - llmErrors;\n llmExtras['LLM extraction'] = `${ok}/${llmAccounting.llmAttempted} succeeded`;\n if (!llmAccounting.llmSucceeded) {\n llmExtras['LLM credit'] = '0 charged (no extraction produced)';\n }\n } else if (llmErrors > 0) {\n llmExtras['LLM extraction failures'] = llmErrors;\n }\n\n const batchHeader = formatBatchHeader({\n title: `Scraped Content (${params.urls.length} URLs)`,\n totalItems: params.urls.length,\n successful: metrics.successful,\n failed: metrics.failed,\n extras: {\n 'Credits used': metrics.totalCredits,\n ...llmExtras,\n },\n });\n\n const formattedContent = formatSuccess({\n title: 'Scraping Complete',\n summary: batchHeader,\n data: contents.join('\\n\\n---\\n\\n'),\n metadata: {\n 'Execution time': formatDuration(executionTime),\n },\n });\n\n return formattedContent;\n}\n\n// --- Handler ---\n\nasync function handleScrapeLinksMode(\n params: ScrapeHandlerParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeToolOutput>> {\n const startTime = Date.now();\n\n if (!params.urls || params.urls.length === 0) {\n return createScrapeErrorResponse('NO_URLS', 'No URLs provided', startTime, params.toolName);\n }\n\n if (params.smart && !createLLMProcessor()) {\n return toolFailure(getMissingEnvMessage('llmExtraction'));\n }\n\n const { webInputs, redditInputs, documentInputs, invalidEntries } = partitionUrls(params.urls);\n const validCount = webInputs.length + redditInputs.length + documentInputs.length;\n\n await reporter.log(\n 'info',\n `Partitioned ${params.urls.length} URL(s): ${webInputs.length} web, ${redditInputs.length} reddit, ${documentInputs.length} document, ${invalidEntries.length} invalid`,\n );\n\n if (validCount === 0) {\n return createScrapeErrorResponse(\n 'INVALID_URLS',\n `All ${params.urls.length} URLs are invalid`,\n startTime,\n params.toolName,\n false,\n [\n 'raw-web-search(keywords=[...]) \u2014 search for valid URLs first, then scrape the results',\n ],\n );\n }\n\n mcpLog(\n 'info',\n `Starting ${params.toolName}: ${webInputs.length} web + ${redditInputs.length} reddit + ${documentInputs.length} document URL(s)`,\n 'scrape',\n );\n await reporter.progress(15, 100, 'Preparing scrape clients');\n\n let kernelEnabled: boolean;\n let scrapeDoProxyUrl: string | undefined;\n try {\n const env = parseEnv();\n kernelEnabled = getCapabilities().kernel;\n scrapeDoProxyUrl = env.SCRAPER_API_KEY ? buildScrapeDoProxyUrl(env.SCRAPER_API_KEY) : undefined;\n } catch (error) {\n const err = classifyError(error);\n return createScrapeErrorResponse(\n 'CLIENT_INIT_FAILED',\n `Failed to initialize scrape providers: ${err.message}`,\n startTime,\n params.toolName,\n false,\n [\n 'raw-web-search(keywords=[\"topic key findings\", \"topic summary\"]) \u2014 search instead of scraping',\n ],\n );\n }\n\n const enhancedInstruction = params.smart ? enhanceExtractionInstruction(params.extract) : undefined;\n\n await reporter.progress(35, 100, 'Fetching page content');\n\n const jinaInputs = [...webInputs, ...documentInputs];\n const [jinaPhase, redditPhase] = await Promise.all([\n fetchJinaFirstBranch(jinaInputs, kernelEnabled, scrapeDoProxyUrl),\n fetchRedditBranch(redditInputs),\n ]);\n\n const successItems = [\n ...jinaPhase.successItems,\n ...redditPhase.successItems,\n ];\n const invalidFailed = invalidEntries.map(\n ({ url, origIndex }) => ({ index: origIndex, content: `## ${url}\\n\\n\u274C Invalid URL format` }),\n );\n const failedContents = [\n ...invalidFailed,\n ...jinaPhase.failedContents,\n ...redditPhase.failedContents,\n ];\n const metrics: ScrapeMetrics = {\n successful:\n jinaPhase.metrics.successful\n + redditPhase.metrics.successful\n ,\n failed:\n invalidEntries.length\n + jinaPhase.metrics.failed\n + redditPhase.metrics.failed,\n totalCredits: 0,\n };\n\n await reporter.log('info', `Fetched ${metrics.successful} page(s), ${metrics.failed} failed`);\n\n if (successItems.length > 0) {\n await reporter.progress(80, 100, 'Running LLM extraction over fetched pages');\n }\n\n const { items: processedItems, llmErrors, llmAttempted } = await processItemsWithLlm(\n successItems,\n enhancedInstruction,\n createLLMProcessor(),\n reporter,\n );\n\n const contents = assembleContentEntries(processedItems, failedContents);\n const executionTime = Date.now() - startTime;\n\n mcpLog(\n 'info',\n `Completed: ${metrics.successful} successful, ${metrics.failed} failed, ${metrics.totalCredits} credits used`,\n 'scrape',\n );\n\n const llmSucceeded = llmAttempted > 0 && llmErrors < llmAttempted;\n const content = buildScrapeResponse(\n params,\n contents,\n metrics,\n llmErrors,\n executionTime,\n { llmAttempted, llmSucceeded },\n );\n\n if (metrics.successful === 0 && metrics.failed > 0) {\n return toolFailure(content);\n }\n\n if (params.smart && llmAttempted > 0 && llmErrors === llmAttempted) {\n return toolFailure(content);\n }\n\n return toolSuccess(content);\n}\n\nexport function handleRawScrapeLinks(\n params: RawScrapeLinksParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeToolOutput>> {\n return handleScrapeLinksMode({ ...params, smart: false, toolName: 'raw-scrape-links' }, reporter);\n}\n\nexport function handleSmartScrapeLinks(\n params: SmartScrapeLinksParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeToolOutput>> {\n return handleScrapeLinksMode({ ...params, smart: true, toolName: 'smart-scrape-links' }, reporter);\n}\n\nexport function registerScrapeLinksTools(server: MCPServer): void {\n server.tool(\n {\n name: 'raw-scrape-links',\n title: 'Raw Scrape Links',\n description:\n 'Fetch URLs in parallel and return full markdown directly. Input is only `urls` (1\u201350). Reddit post permalinks route through the Reddit API with threaded comments. Non-Reddit URLs use Jina Reader first, then Jina Reader with Scrape.do proxy mode when SCRAPEDO_API_KEY is configured, then optional Kernel browser rendering for web pages. Use this for full source capture, Reddit comment harvesting, and raw evidence before synthesis.',\n schema: rawScrapeLinksParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = rawScrapeLinksParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('raw-scrape-links', parsed.error.issues)));\n }\n\n const reporter = createToolReporter(ctx, 'raw-scrape-links');\n const result = await handleRawScrapeLinks(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Scrape failed' : 'Scrape complete');\n return toToolResponse(result);\n },\n );\n\n server.tool(\n {\n name: 'smart-scrape-links',\n title: 'Smart Scrape Links',\n description:\n 'Fetch URLs in parallel, then always run per-URL LLM extraction. Input is `urls` (1\u201350) plus required `extract`. Reddit post permalinks route through the Reddit API with threaded comments. Non-Reddit URLs use Jina Reader first, then Jina Reader with Scrape.do proxy mode when SCRAPEDO_API_KEY is configured, then optional Kernel browser rendering for web pages. Each extracted page returns markdown sections such as `## Source`, `## Matches`, `## Not found`, and `## Follow-up signals`.',\n schema: smartScrapeLinksParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = smartScrapeLinksParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('smart-scrape-links', parsed.error.issues)));\n }\n\n const reporter = createToolReporter(ctx, 'smart-scrape-links');\n const result = await handleSmartScrapeLinks(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Scrape failed' : 'Scrape complete');\n return toToolResponse(result);\n },\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["failedContents", "raw", "rawSnippet"]
|
|
7
7
|
}
|
package/dist/src/tools/search.js
CHANGED
|
@@ -33,7 +33,10 @@ import {
|
|
|
33
33
|
import { runExternalEffect } from "../effect/runtime.js";
|
|
34
34
|
import { LlmService, LlmServiceLive, SearchService, SearchServiceLive } from "../effect/services.js";
|
|
35
35
|
function formatInputValidationError(toolName, issues) {
|
|
36
|
-
const details = issues.map((issue) =>
|
|
36
|
+
const details = issues.map((issue) => {
|
|
37
|
+
const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "<root>";
|
|
38
|
+
return `- ${path}: ${issue.message}`;
|
|
39
|
+
}).join("\n");
|
|
37
40
|
return `Invalid ${toolName} input.
|
|
38
41
|
|
|
39
42
|
${details}`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/tools/search.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Raw/smart web search tool handlers.\n * NEVER throws - always returns markdown tool responses for graceful degradation.\n */\n\nimport type { MCPServer } from 'mcp-use/server';\nimport { Effect } from 'effect';\n\nimport { getCapabilities, getMissingEnvMessage, parseEnv } from '../config/index.js';\nimport {\n QUERY_REWRITE_PAIR_GUIDANCE_TEXT,\n rawWebSearchParamsSchema,\n smartWebSearchParamsSchema,\n type RawWebSearchParams,\n type SmartWebSearchParams,\n} from '../schemas/web-search.js';\nimport { type MultipleSearchResponse } from '../clients/search.js';\nimport {\n aggregateAndRank,\n generateUnifiedOutput,\n} from '../utils/url-aggregator.js';\nimport {\n createLLMProcessor,\n type ClassificationEntry,\n type ClassificationResult,\n type RefineQuerySuggestion,\n} from '../services/llm-processor.js';\nimport { classifyError, ErrorCode, type StructuredError } from '../utils/errors.js';\nimport {\n mcpLog,\n formatError,\n formatDuration,\n} from './utils.js';\nimport {\n createToolReporter,\n NOOP_REPORTER,\n toolFailure,\n toolSuccess,\n toToolResponse,\n type ToolExecutionResult,\n type ToolReporter,\n} from './mcp-helpers.js';\nimport { sanitizeSuggestion } from '../utils/sanitize.js';\nimport {\n normalizeQueryForDispatch,\n relaxQueryForRetry,\n} from '../utils/query-relax.js';\nimport { runExternalEffect } from '../effect/runtime.js';\nimport { LlmService, LlmServiceLive, SearchService, SearchServiceLive } from '../effect/services.js';\n\n// --- Internal types ---\n\ntype SearchToolOutput = Record<string, never>;\n\ninterface SearchHandlerParams {\n keywords: string[];\n extract?: string;\n scope: 'web' | 'reddit' | 'both';\n verbose: boolean;\n smart: boolean;\n toolName: 'raw-web-search' | 'smart-web-search';\n}\n\ninterface SearchAggregation {\n readonly rankedUrls: ReturnType<typeof aggregateAndRank>['rankedUrls'];\n readonly totalUniqueUrls: number;\n readonly frequencyThreshold: number;\n readonly thresholdNote?: string;\n}\n\nexport type SearchResponse = MultipleSearchResponse;\nexport type SearchExecutor = (queries: string[]) => Promise<SearchResponse>;\n\ntype SearchFailurePhase = 'initial' | 'relax-retry';\ntype SearchResultScope = 'web' | 'reddit';\n\nfunction formatInputValidationError(toolName: string, issues: readonly { message: string }[]): string {\n const details = issues.map((issue) => `- ${issue.message}`).join('\\n');\n return `Invalid ${toolName} input.\\n\\n${details}`;\n}\n\n// --- Helpers ---\n\n/** Reddit post permalink: /r/{sub}/comments/{id}/ \u2014 drops subreddit\n * homepages, /rising, /new, /top, etc. so only post URLs reach the agent.\n * See mcp-revisions/tool-surface/02-extend-web-search-with-reddit-scope.md. */\nconst REDDIT_POST_PERMALINK = /\\/r\\/[^/]+\\/comments\\/[a-z0-9]+\\//i;\nconst REDDIT_HOST = /(?:^|\\.)reddit\\.com$/i;\n\ninterface ScopedQuery {\n query: string;\n resultScope: SearchResultScope;\n dropSiteOnRetry: boolean;\n}\n\nfunction redditScopedQuery(query: string): string {\n return /\\bsite:reddit\\.com\\b/i.test(query) ? query : `${query} site:reddit.com`;\n}\n\nfunction buildScopedQueries(queries: string[], scope: 'web' | 'reddit' | 'both'): ScopedQuery[] {\n if (scope === 'web') {\n return queries.map((query) => ({ query, resultScope: 'web', dropSiteOnRetry: true }));\n }\n\n const reddited = queries.map((q) =>\n ({ query: redditScopedQuery(q), resultScope: 'reddit' as const, dropSiteOnRetry: false }),\n );\n\n if (scope === 'reddit') return reddited;\n\n return [\n ...queries.map((query) => ({ query, resultScope: 'web' as const, dropSiteOnRetry: true })),\n ...reddited,\n ];\n}\n\nasync function executeSearches(queries: string[]): Promise<SearchResponse> {\n const env = parseEnv();\n const hasSerper = Boolean(env.SEARCH_API_KEY);\n const hasJina = Boolean(env.JINA_API_KEY);\n\n if (!hasSerper && hasJina) {\n mcpLog('info', 'SERPER_API_KEY missing; using Jina Search fallback as primary search provider', 'search');\n return runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.jinaSearchMultiple(queries);\n }),\n SearchServiceLive,\n );\n }\n\n if (!hasSerper) {\n return {\n searches: [],\n totalQueries: queries.length,\n executionTime: 0,\n error: {\n code: ErrorCode.AUTH_ERROR,\n message: 'No search provider configured. Set SERPER_API_KEY or JINA_API_KEY.',\n retryable: false,\n },\n };\n }\n\n const serperResponse = await runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.serperSearchMultiple(queries);\n }),\n SearchServiceLive,\n );\n if (!hasJina) return serperResponse;\n\n if (serperResponse.error) {\n mcpLog('warning', `Serper failed (${serperResponse.error.message}); trying Jina Search fallback`, 'search');\n const jinaResponse = await runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.jinaSearchMultiple(queries);\n }),\n SearchServiceLive,\n );\n return jinaResponse.error ? serperResponse : jinaResponse;\n }\n\n const emptyIndices = serperResponse.searches\n .map((search, index) => (search.results.length === 0 ? index : -1))\n .filter((index) => index !== -1);\n if (emptyIndices.length === 0) return serperResponse;\n\n const fallbackQueries = emptyIndices\n .map((index) => serperResponse.searches[index]?.query)\n .filter((query): query is string => typeof query === 'string' && query.length > 0);\n if (fallbackQueries.length === 0) return serperResponse;\n\n mcpLog('info', `${fallbackQueries.length} zero-result Serper query/queries; trying Jina Search fallback`, 'search');\n const jinaResponse = await runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.jinaSearchMultiple(fallbackQueries);\n }),\n SearchServiceLive,\n );\n if (jinaResponse.error) return serperResponse;\n\n const fallbackByQuery = new Map(jinaResponse.searches.map((search) => [search.query, search]));\n const mergedSearches = serperResponse.searches.map((search) => {\n if (search.results.length > 0) return search;\n const fallback = fallbackByQuery.get(search.query);\n if (!fallback || fallback.results.length === 0) return search;\n return { ...fallback, query: search.query };\n });\n\n return {\n ...serperResponse,\n searches: mergedSearches,\n executionTime: serperResponse.executionTime + jinaResponse.executionTime,\n };\n}\n\ninterface QueryRewriteRecord {\n original: string;\n rewritten: string;\n rules: string[];\n}\n\ninterface RetriedQueryRecord {\n original: string;\n retried_with: string;\n rules: string[];\n recovered_results: number;\n}\n\n/** Run Serper, then for each query that returned 0 results build a relaxed\n * retry (Phase B) and reissue them in a single second batch. Replace the\n * empty slot with the retry's results when the retry recovered \u22651 hit, but\n * keep the original query string in the slot so downstream aggregation and\n * follow-up rendering stay consistent. */\nasync function executeWithRelaxRetry(\n dispatched: string[],\n reporter: ToolReporter,\n searchExecutor: SearchExecutor = executeSearches,\n retryOptions: { readonly dropSiteOnRetry?: readonly boolean[] } = {},\n): Promise<{\n response: SearchResponse;\n retried: RetriedQueryRecord[];\n failurePhase?: SearchFailurePhase;\n retryError?: StructuredError;\n}> {\n const initial = await searchExecutor(dispatched);\n\n if (initial.error) {\n return { response: initial, retried: [], failurePhase: 'initial' };\n }\n\n const emptyIndices = initial.searches\n .map((s, i) => (s.results.length === 0 ? i : -1))\n .filter((i) => i !== -1);\n\n if (emptyIndices.length === 0) {\n return { response: initial, retried: [] };\n }\n\n interface Plan { index: number; original: string; relaxed: string; rules: string[] }\n const plans: Plan[] = [];\n for (const idx of emptyIndices) {\n const dq = dispatched[idx];\n if (typeof dq !== 'string') continue;\n const r = relaxQueryForRetry(dq, { dropSite: retryOptions.dropSiteOnRetry?.[idx] ?? true });\n if (r.changed && r.rewritten !== dq) {\n plans.push({ index: idx, original: dq, relaxed: r.rewritten, rules: [...r.rules] });\n }\n }\n\n if (plans.length === 0) {\n return { response: initial, retried: [] };\n }\n\n mcpLog(\n 'info',\n `${plans.length}/${emptyIndices.length} empty-result queries eligible for relaxation retry`,\n 'search',\n );\n await reporter.log(\n 'info',\n `${plans.length} queries returned 0 results; retrying with relaxation`,\n );\n\n const retryResp = await searchExecutor(plans.map((p) => p.relaxed));\n const retried: RetriedQueryRecord[] = [];\n const retryByIndex = new Map<number, SearchResponse['searches'][number]>();\n\n plans.forEach((plan, i) => {\n const r = retryResp.searches[i];\n if (r) retryByIndex.set(plan.index, r);\n retried.push({\n original: plan.original,\n retried_with: plan.relaxed,\n rules: plan.rules,\n recovered_results: r?.results.length ?? 0,\n });\n });\n\n if (retryResp.error) {\n mcpLog(\n 'warning',\n `Relaxed retry batch failed; preserving initial search results: ${retryResp.error.message}`,\n 'search',\n );\n await reporter.log(\n 'warning',\n `search_relax_retry_failed: ${retryResp.error.message}`,\n );\n return {\n response: initial,\n retried,\n retryError: retryResp.error,\n };\n }\n\n const mergedSearches = initial.searches.map((s, idx) => {\n const r = retryByIndex.get(idx);\n if (r && r.results.length > 0) {\n return { ...r, query: s.query };\n }\n return s;\n });\n\n return {\n response: { ...initial, searches: mergedSearches },\n retried,\n };\n}\n\nfunction filterScopedSearches(\n response: SearchResponse,\n scope: 'web' | 'reddit' | 'both',\n resultScopes: readonly SearchResultScope[] = [],\n): SearchResponse {\n if (scope === 'web') return response;\n const filtered = response.searches.map((search, index) => {\n const resultScope = resultScopes[index] ?? (scope === 'reddit' ? 'reddit' : 'web');\n return {\n ...search,\n results: search.results.filter((r) => {\n let host: string;\n try { host = new URL(r.link).hostname; } catch { return true; }\n if (resultScope === 'reddit') {\n return REDDIT_HOST.test(host) && REDDIT_POST_PERMALINK.test(r.link);\n }\n // Web-side results pass through; reddit URLs still must be post permalinks.\n if (!REDDIT_HOST.test(host)) return true;\n return REDDIT_POST_PERMALINK.test(r.link);\n }),\n };\n });\n return { ...response, searches: filtered };\n}\n\nfunction processResults(response: SearchResponse): {\n aggregation: SearchAggregation;\n} {\n const aggregation = aggregateAndRank(response.searches, 5);\n return { aggregation };\n}\n\n// --- Raw output (traditional unified ranked list) ---\n\nfunction buildRawOutput(\n queries: string[],\n aggregation: SearchAggregation,\n searches: SearchResponse['searches'],\n verbose: boolean = false,\n): string {\n return generateUnifiedOutput(\n aggregation.rankedUrls, queries, searches,\n aggregation.totalUniqueUrls,\n aggregation.frequencyThreshold, aggregation.thresholdNote,\n verbose,\n );\n}\n\nfunction buildSignalsSection(\n aggregation: SearchAggregation,\n searches: SearchResponse['searches'],\n totalQueries: number,\n): string {\n const coverageCount = searches.filter((search) => search.results.length >= 3).length;\n const lowYield = searches\n .filter((search) => search.results.length <= 1)\n .map((search) => `\"${search.query}\"`);\n const consensusCount = aggregation.rankedUrls.filter((url) => url.isConsensus).length;\n\n const lines = [\n '**Signals**',\n `- Coverage: ${coverageCount}/${totalQueries} queries returned \u22653 results`,\n `- Consensus URLs: ${consensusCount}`,\n ];\n\n if (lowYield.length > 0) {\n lines.push(`- Low-yield: ${lowYield.join(', ')}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function buildSuggestedFollowUpsSection(\n refineQueries: Array<{ query: string; rationale?: string; gap_id?: number; gap_description?: string }> | undefined,\n): string {\n if (!refineQueries || refineQueries.length === 0) {\n return '';\n }\n\n const lines = ['## Suggested follow-up searches', ''];\n\n for (const item of refineQueries) {\n const query = sanitizeSuggestion(item.query ?? '');\n if (!query) continue;\n const rationale = sanitizeSuggestion(item.rationale ?? '');\n const gapTag = typeof item.gap_id === 'number'\n ? ` _(closes gap [${item.gap_id}])_`\n : item.gap_description\n ? ` _(${sanitizeSuggestion(item.gap_description)})_`\n : '';\n lines.push(rationale\n ? `- ${query} \u2014 ${rationale}${gapTag}`\n : `- ${query}${gapTag}`,\n );\n }\n\n return lines.length === 2 ? '' : lines.join('\\n');\n}\n\nexport function appendSignalsAndFollowUps(\n markdown: string,\n signalsSection: string,\n refineQueries: RefineQuerySuggestion[] | undefined,\n options: { includeSignals?: boolean } = {},\n): string {\n const includeSignals = options.includeSignals ?? false;\n const sections = [markdown];\n if (includeSignals && signalsSection) {\n sections.push('', '---', signalsSection);\n }\n const followUps = buildSuggestedFollowUpsSection(refineQueries);\n if (followUps) {\n sections.push('', followUps);\n }\n return sections.join('\\n');\n}\n\n// --- \"Start here\" section ---\n//\n// Surfaces the best 3-5 URLs at the top of the classified response so an agent\n// skimming the first screen sees them before tier tables. Deterministic: uses\n// existing `tier` + `rank` + `reason` from the classifier, no extra LLM call.\n//\n// Algorithm: take HIGHLY_RELEVANT by rank up to MAX_START_HERE; if fewer than\n// MIN_START_HERE, pad from top MAYBE_RELEVANT; skip entirely if no entries\n// above OTHER.\n\nconst MIN_START_HERE = 3;\nconst MAX_START_HERE = 5;\n\n/** Minimal structural shape \u2014 avoids coupling to private `RankedUrl` type. */\ninterface StartHereCandidate {\n readonly rank: number;\n readonly url: string;\n readonly title: string;\n}\n\ninterface StartHereTiers {\n readonly high: readonly StartHereCandidate[];\n readonly maybe: readonly StartHereCandidate[];\n}\n\nexport function buildStartHereSection(\n tiers: StartHereTiers,\n entryByRank: Map<number, ClassificationEntry>,\n opts: { min?: number; max?: number } = {},\n): string {\n const min = opts.min ?? MIN_START_HERE;\n const max = opts.max ?? MAX_START_HERE;\n\n const picks: Array<{ candidate: StartHereCandidate; tier: 'HIGHLY_RELEVANT' | 'MAYBE_RELEVANT' }> = [];\n\n for (const candidate of tiers.high) {\n if (picks.length >= max) break;\n picks.push({ candidate, tier: 'HIGHLY_RELEVANT' });\n }\n\n if (picks.length < min) {\n const target = Math.min(min, max);\n for (const candidate of tiers.maybe) {\n if (picks.length >= target) break;\n picks.push({ candidate, tier: 'MAYBE_RELEVANT' });\n }\n }\n\n if (picks.length === 0) return '';\n\n const lines: string[] = [];\n lines.push('## Start here \u2014 best candidates for your extract');\n picks.forEach((pick, i) => {\n const entry = entryByRank.get(pick.candidate.rank);\n const reason = entry?.reason && entry.reason.trim().length > 0 ? entry.reason : '\u2014';\n let domain: string;\n try {\n domain = new URL(pick.candidate.url).hostname.replace(/^www\\./, '');\n } catch {\n domain = pick.candidate.url;\n }\n lines.push(\n `${i + 1}. **[${pick.candidate.title}](${pick.candidate.url})** \u2014 ${domain} \u2014 ${reason} *(${pick.tier}, rank ${pick.candidate.rank})*`,\n );\n });\n return lines.join('\\n');\n}\n\n// --- Classified output (3-tier LLM-classified table) ---\n\nfunction buildClassifiedOutput(\n classification: ClassificationResult,\n aggregation: SearchAggregation,\n extract: string,\n searches: SearchResponse['searches'],\n totalQueries: number,\n verbose: boolean = false,\n): string {\n const rankedUrls = aggregation.rankedUrls;\n\n // Build tier \u2192 entries mapping (keep url data alongside classifier metadata)\n const entryByRank = new Map(classification.results.map((r) => [r.rank, r]));\n\n const tiers = {\n high: [] as typeof rankedUrls,\n maybe: [] as typeof rankedUrls,\n other: [] as typeof rankedUrls,\n };\n\n for (const url of rankedUrls) {\n const entry = entryByRank.get(url.rank);\n const tier = entry?.tier;\n if (tier === 'HIGHLY_RELEVANT') {\n tiers.high.push(url);\n } else if (tier === 'MAYBE_RELEVANT') {\n tiers.maybe.push(url);\n } else {\n tiers.other.push(url);\n }\n }\n\n const lines: string[] = [];\n\n // Header with generated title, synthesis, and confidence\n lines.push(`## ${classification.title}`);\n lines.push(`> Looking for: ${extract}`);\n lines.push(`> ${totalQueries} queries \u2192 ${rankedUrls.length} URLs \u2192 ${tiers.high.length} highly relevant, ${tiers.maybe.length} possibly relevant`);\n if (classification.confidence) {\n const confReason = classification.confidence_reason ? ` \u2014 ${classification.confidence_reason}` : '';\n lines.push(`> Confidence: \\`${classification.confidence}\\`${confReason}`);\n }\n lines.push('');\n\n // \"Start here\" block: surface the top 3-5 URLs above the synthesis so an\n // agent skimming the first screen sees scrape candidates before prose.\n const startHere = buildStartHereSection(\n { high: tiers.high, maybe: tiers.maybe },\n entryByRank,\n );\n if (startHere) {\n lines.push(startHere);\n lines.push('');\n }\n\n lines.push(`**Summary:** ${classification.synthesis}`);\n lines.push('');\n\n // Helper: render one row with optional source_type + reason\n const renderRichRow = (url: typeof rankedUrls[number]): string => {\n const entry = entryByRank.get(url.rank);\n const coveragePct = Math.round(url.coverageRatio * 100);\n const seenIn = `${url.frequency}/${totalQueries} (${coveragePct}%)`;\n const sourceType = entry?.source_type ? `\\`${entry.source_type}\\`` : '\u2014';\n const reason = entry?.reason ? entry.reason.replace(/\\|/g, '\\\\|') : '\u2014';\n return `| ${url.rank} | [${url.title}](${url.url}) | ${sourceType} | ${seenIn} | ${reason} |`;\n };\n\n // Highly Relevant tier\n if (tiers.high.length > 0) {\n lines.push(`### Highly Relevant (${tiers.high.length})`);\n lines.push('| # | URL | Source | Seen in | Why |');\n lines.push('|---|-----|--------|---------|-----|');\n for (const url of tiers.high) lines.push(renderRichRow(url));\n lines.push('');\n }\n\n // Maybe Relevant tier\n if (tiers.maybe.length > 0) {\n lines.push(`### Maybe Relevant (${tiers.maybe.length})`);\n lines.push('| # | URL | Source | Seen in | Why |');\n lines.push('|---|-----|--------|---------|-----|');\n for (const url of tiers.maybe) lines.push(renderRichRow(url));\n lines.push('');\n }\n\n // Other tier \u2014 with query attribution\n if (tiers.other.length > 0) {\n lines.push(`### Other Results (${tiers.other.length})`);\n lines.push('| # | URL | Source | Score | Queries |');\n lines.push('|---|-----|--------|-------|---------|');\n for (const url of tiers.other) {\n const entry = entryByRank.get(url.rank);\n const queryList = url.queries.map((q) => `\"${q}\"`).join(', ');\n const sourceType = entry?.source_type ? `\\`${entry.source_type}\\`` : '\u2014';\n let domain: string;\n try {\n domain = new URL(url.url).hostname.replace(/^www\\./, '');\n } catch {\n domain = url.url;\n }\n lines.push(`| ${url.rank} | ${domain} | ${sourceType} | ${url.score.toFixed(1)} | ${queryList} |`);\n }\n lines.push('');\n }\n\n // Signals block is gated behind verbose \u2014 it duplicates info already\n // present in the per-row metadata for callers who care.\n // See: docs/code-review/context/05-output-formatting-patterns.md.\n if (verbose) {\n lines.push(buildSignalsSection(aggregation, searches, totalQueries));\n }\n\n // Gaps section \u2014 what the current results don't answer\n if (classification.gaps && classification.gaps.length > 0) {\n lines.push('');\n lines.push('## Gaps');\n for (const gap of classification.gaps) {\n lines.push(`- **[${gap.id}]** ${gap.description}`);\n }\n }\n\n const followUps = buildSuggestedFollowUpsSection(classification.refine_queries);\n if (followUps) {\n lines.push('');\n lines.push(followUps);\n }\n\n return lines.join('\\n');\n}\n\n// --- Error builder ---\n\nfunction formatSearchFailureMessage(\n error: StructuredError,\n phase?: SearchFailurePhase,\n): string {\n if (phase === 'initial') {\n return `Search provider failed during initial batch: ${error.message}`;\n }\n\n if (phase === 'relax-retry') {\n return `Search provider failed during relaxed retry batch: ${error.message}`;\n }\n\n return error.message;\n}\n\nfunction buildWebSearchError(\n error: StructuredError,\n params: SearchHandlerParams,\n startTime: number,\n phase?: SearchFailurePhase,\n): ToolExecutionResult<SearchToolOutput> {\n const message = formatSearchFailureMessage(error, phase);\n const executionTime = Date.now() - startTime;\n\n mcpLog('error', `${params.toolName}: ${message}`, 'search');\n\n const errorContent = formatError({\n code: error.code,\n message,\n retryable: error.retryable,\n toolName: params.toolName,\n howToFix: ['Verify SERPER_API_KEY or JINA_API_KEY is set correctly'],\n alternatives: [\n 'raw-web-search(keywords=[\"topic recommendations site:reddit.com\"]) \u2014 unclassified search results',\n 'raw-scrape-links(urls=[...]) \u2014 if you already have URLs, fetch their markdown now',\n ],\n });\n\n return toolFailure(\n `${errorContent}\\n\\nExecution time: ${formatDuration(executionTime)}\\nKeywords: ${params.keywords.length}`,\n );\n}\n\n// --- Main handler ---\n\nasync function handleSearch(\n params: SearchHandlerParams,\n reporter: ToolReporter = NOOP_REPORTER,\n searchExecutor: SearchExecutor = executeSearches,\n): Promise<ToolExecutionResult<SearchToolOutput>> {\n const startTime = Date.now();\n\n try {\n if (params.smart && !createLLMProcessor()) {\n return toolFailure(getMissingEnvMessage('llmExtraction'));\n }\n\n const scopedQueries = buildScopedQueries(params.keywords, params.scope);\n const effectiveQueries = scopedQueries.map((entry) => entry.query);\n if (params.scope !== 'web') {\n mcpLog('info', `Searching scope=${params.scope}: ${params.keywords.length} input keywords \u2192 ${effectiveQueries.length} dispatched`, 'search');\n } else {\n mcpLog('info', `Searching for ${params.keywords.length} keyword(s)`, 'search');\n }\n await reporter.log('info', `Searching for ${effectiveQueries.length} query/queries (scope=${params.scope})`);\n await reporter.progress(15, 100, 'Submitting search queries');\n\n // Phase A \u2014 pre-dispatch normalizer. Rewrites the small fraction of\n // queries Google was statistically going to mis-handle (3+ phrase AND,\n // operator chars in quotes, paths in quotes). See src/utils/query-relax.ts.\n const dispatchPlan = effectiveQueries.map((q) => {\n const r = normalizeQueryForDispatch(q);\n return { original: q, dispatched: r.rewritten, rules: [...r.rules], changed: r.changed };\n });\n const dispatchedQueries = dispatchPlan.map((p) => p.dispatched);\n const resultScopes = scopedQueries.map((entry) => entry.resultScope);\n const dropSiteOnRetry = scopedQueries.map((entry) => entry.dropSiteOnRetry);\n const queryRewrites: QueryRewriteRecord[] = dispatchPlan\n .filter((p) => p.changed)\n .map((p) => ({ original: p.original, rewritten: p.dispatched, rules: p.rules }));\n\n if (queryRewrites.length > 0) {\n mcpLog(\n 'info',\n `Pre-dispatch normalized ${queryRewrites.length}/${effectiveQueries.length} queries`,\n 'search',\n );\n await reporter.log(\n 'info',\n `Normalized ${queryRewrites.length} queries pre-dispatch`,\n );\n }\n\n // Phase B \u2014 on-empty retry: any query returning 0 results gets one\n // relaxed retry (drop quotes, drop site:). Recovered hits replace the\n // empty slot transparently.\n const {\n response: rawResponse,\n retried: retriedQueries,\n failurePhase,\n retryError,\n } = await executeWithRelaxRetry(\n dispatchedQueries,\n reporter,\n searchExecutor,\n { dropSiteOnRetry },\n );\n\n if (rawResponse.error) {\n await reporter.log('error', `search_provider_failed: ${rawResponse.error.message}`);\n return buildWebSearchError(rawResponse.error, params, startTime, failurePhase);\n }\n\n const response = filterScopedSearches(rawResponse, params.scope, resultScopes);\n await reporter.progress(50, 100, 'Collected search results');\n\n const { aggregation } = processResults(response);\n await reporter.log(\n 'info',\n `Collected ${aggregation.totalUniqueUrls} unique URLs across ${response.totalQueries} queries`,\n );\n\n let markdown: string;\n\n if (!params.smart) {\n markdown = appendSignalsAndFollowUps(\n buildRawOutput(params.keywords, aggregation, response.searches, false),\n buildSignalsSection(aggregation, response.searches, response.totalQueries),\n undefined,\n { includeSignals: false },\n );\n await reporter.progress(80, 100, 'Ranking search results');\n } else {\n const llmProcessor = createLLMProcessor();\n if (!llmProcessor) {\n return toolFailure(getMissingEnvMessage('llmExtraction'));\n }\n\n await reporter.progress(65, 100, 'Classifying results by relevance');\n const classification = await runExternalEffect(\n Effect.gen(function* () {\n const llm = yield* LlmService;\n return yield* llm.classifySearchResults(\n aggregation.rankedUrls,\n params.extract ?? '',\n response.totalQueries,\n llmProcessor,\n params.keywords,\n );\n }),\n LlmServiceLive,\n );\n\n if (classification.result) {\n markdown = buildClassifiedOutput(\n classification.result, aggregation, params.extract ?? '', response.searches, response.totalQueries, params.verbose,\n );\n await reporter.progress(85, 100, 'Formatted classified results');\n } else {\n const llmError = classification.error ?? 'Unknown classification error';\n mcpLog('warning', `Classification failed for smart-web-search: ${llmError}`, 'search');\n await reporter.log('warning', `llm_classifier_failed: ${llmError}`);\n return toolFailure(\n `${formatError({\n code: ErrorCode.SERVICE_UNAVAILABLE,\n message: `LLM classification failed: ${llmError}`,\n retryable: true,\n toolName: params.toolName,\n alternatives: ['raw-web-search(keywords=[...]) \u2014 return unclassified search results'],\n })}\\n\\nExecution time: ${formatDuration(Date.now() - startTime)}`,\n );\n }\n }\n\n const executionTime = Date.now() - startTime;\n\n mcpLog('info', `Search completed: ${aggregation.rankedUrls.length} URLs, smart=${params.smart}`, 'search');\n await reporter.log('info', `Search completed with ${aggregation.rankedUrls.length} URLs (smart: ${params.smart})`);\n\n const footerParts = [\n formatDuration(executionTime),\n `${aggregation.totalUniqueUrls} unique URLs`,\n params.smart ? 'LLM classified' : 'raw search',\n ];\n if (queryRewrites.length > 0) footerParts.push(`${queryRewrites.length} normalized`);\n if (retriedQueries.length > 0) footerParts.push(`${retriedQueries.length} retried`);\n if (retryError) footerParts.push(`retry warning: ${retryError.code}`);\n const footer = `\\n---\\n*${footerParts.join(' | ')}*`;\n const fullMarkdown = markdown + footer;\n\n return toolSuccess(fullMarkdown);\n } catch (error) {\n return buildWebSearchError(classifyError(error), params, startTime);\n }\n}\n\nexport function handleRawWebSearch(\n params: RawWebSearchParams,\n reporter: ToolReporter = NOOP_REPORTER,\n searchExecutor: SearchExecutor = executeSearches,\n): Promise<ToolExecutionResult<SearchToolOutput>> {\n return handleSearch(\n {\n ...params,\n scope: 'web',\n verbose: false,\n smart: false,\n toolName: 'raw-web-search',\n },\n reporter,\n searchExecutor,\n );\n}\n\nexport function handleSmartWebSearch(\n params: SmartWebSearchParams,\n reporter: ToolReporter = NOOP_REPORTER,\n searchExecutor: SearchExecutor = executeSearches,\n): Promise<ToolExecutionResult<SearchToolOutput>> {\n return handleSearch(\n {\n ...params,\n smart: true,\n toolName: 'smart-web-search',\n },\n reporter,\n searchExecutor,\n );\n}\n\nexport function registerWebSearchTools(server: MCPServer): void {\n server.tool(\n {\n name: 'raw-web-search',\n title: 'Raw Web Search',\n description:\n `Fan out raw search keywords in parallel and return the ranked markdown list directly. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. Input is only \\`keywords\\` (1\u201350 items). ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Use this when you need unclassified result data, Reddit permalink discovery via explicit \\`site:reddit.com/r/.../comments\\` keywords, or broad reconnaissance before synthesis.`,\n schema: rawWebSearchParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = rawWebSearchParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('raw-web-search', parsed.error.issues)));\n }\n\n if (!getCapabilities().search) {\n return toToolResponse(toolFailure(getMissingEnvMessage('search')));\n }\n\n const reporter = createToolReporter(ctx, 'raw-web-search');\n const result = await handleRawWebSearch(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Search failed' : 'Search complete');\n return toToolResponse(result);\n },\n );\n\n server.tool(\n {\n name: 'smart-web-search',\n title: 'Smart Web Search',\n description:\n `Fan out search keywords in parallel, then always run LLM classification and synthesis against \\`extract\\`. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. Input carries 1\u201350 \\`keywords\\`, required \\`extract\\`, optional \\`scope: \"web\" | \"reddit\" | \"both\"\\`, and optional \\`verbose\\`. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Use this for comprehensive research passes where you need HIGHLY_RELEVANT/MAYBE/OTHER tiers, a grounded synthesis with rank citations, gaps, and suggested follow-up searches.`,\n schema: smartWebSearchParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = smartWebSearchParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('smart-web-search', parsed.error.issues)));\n }\n\n if (!getCapabilities().search) {\n return toToolResponse(toolFailure(getMissingEnvMessage('search')));\n }\n\n const reporter = createToolReporter(ctx, 'smart-web-search');\n const result = await handleSmartWebSearch(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Search failed' : 'Search complete');\n return toToolResponse(result);\n },\n );\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Raw/smart web search tool handlers.\n * NEVER throws - always returns markdown tool responses for graceful degradation.\n */\n\nimport type { MCPServer } from 'mcp-use/server';\nimport { Effect } from 'effect';\n\nimport { getCapabilities, getMissingEnvMessage, parseEnv } from '../config/index.js';\nimport {\n QUERY_REWRITE_PAIR_GUIDANCE_TEXT,\n rawWebSearchParamsSchema,\n smartWebSearchParamsSchema,\n type RawWebSearchParams,\n type SmartWebSearchParams,\n} from '../schemas/web-search.js';\nimport { type MultipleSearchResponse } from '../clients/search.js';\nimport {\n aggregateAndRank,\n generateUnifiedOutput,\n} from '../utils/url-aggregator.js';\nimport {\n createLLMProcessor,\n type ClassificationEntry,\n type ClassificationResult,\n type RefineQuerySuggestion,\n} from '../services/llm-processor.js';\nimport { classifyError, ErrorCode, type StructuredError } from '../utils/errors.js';\nimport {\n mcpLog,\n formatError,\n formatDuration,\n} from './utils.js';\nimport {\n createToolReporter,\n NOOP_REPORTER,\n toolFailure,\n toolSuccess,\n toToolResponse,\n type ToolExecutionResult,\n type ToolReporter,\n} from './mcp-helpers.js';\nimport { sanitizeSuggestion } from '../utils/sanitize.js';\nimport {\n normalizeQueryForDispatch,\n relaxQueryForRetry,\n} from '../utils/query-relax.js';\nimport { runExternalEffect } from '../effect/runtime.js';\nimport { LlmService, LlmServiceLive, SearchService, SearchServiceLive } from '../effect/services.js';\n\n// --- Internal types ---\n\ntype SearchToolOutput = Record<string, never>;\n\ninterface SearchHandlerParams {\n keywords: string[];\n extract?: string;\n scope: 'web' | 'reddit' | 'both';\n verbose: boolean;\n smart: boolean;\n toolName: 'raw-web-search' | 'smart-web-search';\n}\n\ninterface SearchAggregation {\n readonly rankedUrls: ReturnType<typeof aggregateAndRank>['rankedUrls'];\n readonly totalUniqueUrls: number;\n readonly frequencyThreshold: number;\n readonly thresholdNote?: string;\n}\n\nexport type SearchResponse = MultipleSearchResponse;\nexport type SearchExecutor = (queries: string[]) => Promise<SearchResponse>;\n\ntype SearchFailurePhase = 'initial' | 'relax-retry';\ntype SearchResultScope = 'web' | 'reddit';\n\nfunction formatInputValidationError(\n toolName: string,\n issues: readonly { message: string; path: readonly PropertyKey[] }[],\n): string {\n const details = issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.map(String).join('.') : '<root>';\n return `- ${path}: ${issue.message}`;\n })\n .join('\\n');\n return `Invalid ${toolName} input.\\n\\n${details}`;\n}\n\n// --- Helpers ---\n\n/** Reddit post permalink: /r/{sub}/comments/{id}/ \u2014 drops subreddit\n * homepages, /rising, /new, /top, etc. so only post URLs reach the agent.\n * See mcp-revisions/tool-surface/02-extend-web-search-with-reddit-scope.md. */\nconst REDDIT_POST_PERMALINK = /\\/r\\/[^/]+\\/comments\\/[a-z0-9]+\\//i;\nconst REDDIT_HOST = /(?:^|\\.)reddit\\.com$/i;\n\ninterface ScopedQuery {\n query: string;\n resultScope: SearchResultScope;\n dropSiteOnRetry: boolean;\n}\n\nfunction redditScopedQuery(query: string): string {\n return /\\bsite:reddit\\.com\\b/i.test(query) ? query : `${query} site:reddit.com`;\n}\n\nfunction buildScopedQueries(queries: string[], scope: 'web' | 'reddit' | 'both'): ScopedQuery[] {\n if (scope === 'web') {\n return queries.map((query) => ({ query, resultScope: 'web', dropSiteOnRetry: true }));\n }\n\n const reddited = queries.map((q) =>\n ({ query: redditScopedQuery(q), resultScope: 'reddit' as const, dropSiteOnRetry: false }),\n );\n\n if (scope === 'reddit') return reddited;\n\n return [\n ...queries.map((query) => ({ query, resultScope: 'web' as const, dropSiteOnRetry: true })),\n ...reddited,\n ];\n}\n\nasync function executeSearches(queries: string[]): Promise<SearchResponse> {\n const env = parseEnv();\n const hasSerper = Boolean(env.SEARCH_API_KEY);\n const hasJina = Boolean(env.JINA_API_KEY);\n\n if (!hasSerper && hasJina) {\n mcpLog('info', 'SERPER_API_KEY missing; using Jina Search fallback as primary search provider', 'search');\n return runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.jinaSearchMultiple(queries);\n }),\n SearchServiceLive,\n );\n }\n\n if (!hasSerper) {\n return {\n searches: [],\n totalQueries: queries.length,\n executionTime: 0,\n error: {\n code: ErrorCode.AUTH_ERROR,\n message: 'No search provider configured. Set SERPER_API_KEY or JINA_API_KEY.',\n retryable: false,\n },\n };\n }\n\n const serperResponse = await runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.serperSearchMultiple(queries);\n }),\n SearchServiceLive,\n );\n if (!hasJina) return serperResponse;\n\n if (serperResponse.error) {\n mcpLog('warning', `Serper failed (${serperResponse.error.message}); trying Jina Search fallback`, 'search');\n const jinaResponse = await runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.jinaSearchMultiple(queries);\n }),\n SearchServiceLive,\n );\n return jinaResponse.error ? serperResponse : jinaResponse;\n }\n\n const emptyIndices = serperResponse.searches\n .map((search, index) => (search.results.length === 0 ? index : -1))\n .filter((index) => index !== -1);\n if (emptyIndices.length === 0) return serperResponse;\n\n const fallbackQueries = emptyIndices\n .map((index) => serperResponse.searches[index]?.query)\n .filter((query): query is string => typeof query === 'string' && query.length > 0);\n if (fallbackQueries.length === 0) return serperResponse;\n\n mcpLog('info', `${fallbackQueries.length} zero-result Serper query/queries; trying Jina Search fallback`, 'search');\n const jinaResponse = await runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.jinaSearchMultiple(fallbackQueries);\n }),\n SearchServiceLive,\n );\n if (jinaResponse.error) return serperResponse;\n\n const fallbackByQuery = new Map(jinaResponse.searches.map((search) => [search.query, search]));\n const mergedSearches = serperResponse.searches.map((search) => {\n if (search.results.length > 0) return search;\n const fallback = fallbackByQuery.get(search.query);\n if (!fallback || fallback.results.length === 0) return search;\n return { ...fallback, query: search.query };\n });\n\n return {\n ...serperResponse,\n searches: mergedSearches,\n executionTime: serperResponse.executionTime + jinaResponse.executionTime,\n };\n}\n\ninterface QueryRewriteRecord {\n original: string;\n rewritten: string;\n rules: string[];\n}\n\ninterface RetriedQueryRecord {\n original: string;\n retried_with: string;\n rules: string[];\n recovered_results: number;\n}\n\n/** Run Serper, then for each query that returned 0 results build a relaxed\n * retry (Phase B) and reissue them in a single second batch. Replace the\n * empty slot with the retry's results when the retry recovered \u22651 hit, but\n * keep the original query string in the slot so downstream aggregation and\n * follow-up rendering stay consistent. */\nasync function executeWithRelaxRetry(\n dispatched: string[],\n reporter: ToolReporter,\n searchExecutor: SearchExecutor = executeSearches,\n retryOptions: { readonly dropSiteOnRetry?: readonly boolean[] } = {},\n): Promise<{\n response: SearchResponse;\n retried: RetriedQueryRecord[];\n failurePhase?: SearchFailurePhase;\n retryError?: StructuredError;\n}> {\n const initial = await searchExecutor(dispatched);\n\n if (initial.error) {\n return { response: initial, retried: [], failurePhase: 'initial' };\n }\n\n const emptyIndices = initial.searches\n .map((s, i) => (s.results.length === 0 ? i : -1))\n .filter((i) => i !== -1);\n\n if (emptyIndices.length === 0) {\n return { response: initial, retried: [] };\n }\n\n interface Plan { index: number; original: string; relaxed: string; rules: string[] }\n const plans: Plan[] = [];\n for (const idx of emptyIndices) {\n const dq = dispatched[idx];\n if (typeof dq !== 'string') continue;\n const r = relaxQueryForRetry(dq, { dropSite: retryOptions.dropSiteOnRetry?.[idx] ?? true });\n if (r.changed && r.rewritten !== dq) {\n plans.push({ index: idx, original: dq, relaxed: r.rewritten, rules: [...r.rules] });\n }\n }\n\n if (plans.length === 0) {\n return { response: initial, retried: [] };\n }\n\n mcpLog(\n 'info',\n `${plans.length}/${emptyIndices.length} empty-result queries eligible for relaxation retry`,\n 'search',\n );\n await reporter.log(\n 'info',\n `${plans.length} queries returned 0 results; retrying with relaxation`,\n );\n\n const retryResp = await searchExecutor(plans.map((p) => p.relaxed));\n const retried: RetriedQueryRecord[] = [];\n const retryByIndex = new Map<number, SearchResponse['searches'][number]>();\n\n plans.forEach((plan, i) => {\n const r = retryResp.searches[i];\n if (r) retryByIndex.set(plan.index, r);\n retried.push({\n original: plan.original,\n retried_with: plan.relaxed,\n rules: plan.rules,\n recovered_results: r?.results.length ?? 0,\n });\n });\n\n if (retryResp.error) {\n mcpLog(\n 'warning',\n `Relaxed retry batch failed; preserving initial search results: ${retryResp.error.message}`,\n 'search',\n );\n await reporter.log(\n 'warning',\n `search_relax_retry_failed: ${retryResp.error.message}`,\n );\n return {\n response: initial,\n retried,\n retryError: retryResp.error,\n };\n }\n\n const mergedSearches = initial.searches.map((s, idx) => {\n const r = retryByIndex.get(idx);\n if (r && r.results.length > 0) {\n return { ...r, query: s.query };\n }\n return s;\n });\n\n return {\n response: { ...initial, searches: mergedSearches },\n retried,\n };\n}\n\nfunction filterScopedSearches(\n response: SearchResponse,\n scope: 'web' | 'reddit' | 'both',\n resultScopes: readonly SearchResultScope[] = [],\n): SearchResponse {\n if (scope === 'web') return response;\n const filtered = response.searches.map((search, index) => {\n const resultScope = resultScopes[index] ?? (scope === 'reddit' ? 'reddit' : 'web');\n return {\n ...search,\n results: search.results.filter((r) => {\n let host: string;\n try { host = new URL(r.link).hostname; } catch { return true; }\n if (resultScope === 'reddit') {\n return REDDIT_HOST.test(host) && REDDIT_POST_PERMALINK.test(r.link);\n }\n // Web-side results pass through; reddit URLs still must be post permalinks.\n if (!REDDIT_HOST.test(host)) return true;\n return REDDIT_POST_PERMALINK.test(r.link);\n }),\n };\n });\n return { ...response, searches: filtered };\n}\n\nfunction processResults(response: SearchResponse): {\n aggregation: SearchAggregation;\n} {\n const aggregation = aggregateAndRank(response.searches, 5);\n return { aggregation };\n}\n\n// --- Raw output (traditional unified ranked list) ---\n\nfunction buildRawOutput(\n queries: string[],\n aggregation: SearchAggregation,\n searches: SearchResponse['searches'],\n verbose: boolean = false,\n): string {\n return generateUnifiedOutput(\n aggregation.rankedUrls, queries, searches,\n aggregation.totalUniqueUrls,\n aggregation.frequencyThreshold, aggregation.thresholdNote,\n verbose,\n );\n}\n\nfunction buildSignalsSection(\n aggregation: SearchAggregation,\n searches: SearchResponse['searches'],\n totalQueries: number,\n): string {\n const coverageCount = searches.filter((search) => search.results.length >= 3).length;\n const lowYield = searches\n .filter((search) => search.results.length <= 1)\n .map((search) => `\"${search.query}\"`);\n const consensusCount = aggregation.rankedUrls.filter((url) => url.isConsensus).length;\n\n const lines = [\n '**Signals**',\n `- Coverage: ${coverageCount}/${totalQueries} queries returned \u22653 results`,\n `- Consensus URLs: ${consensusCount}`,\n ];\n\n if (lowYield.length > 0) {\n lines.push(`- Low-yield: ${lowYield.join(', ')}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function buildSuggestedFollowUpsSection(\n refineQueries: Array<{ query: string; rationale?: string; gap_id?: number; gap_description?: string }> | undefined,\n): string {\n if (!refineQueries || refineQueries.length === 0) {\n return '';\n }\n\n const lines = ['## Suggested follow-up searches', ''];\n\n for (const item of refineQueries) {\n const query = sanitizeSuggestion(item.query ?? '');\n if (!query) continue;\n const rationale = sanitizeSuggestion(item.rationale ?? '');\n const gapTag = typeof item.gap_id === 'number'\n ? ` _(closes gap [${item.gap_id}])_`\n : item.gap_description\n ? ` _(${sanitizeSuggestion(item.gap_description)})_`\n : '';\n lines.push(rationale\n ? `- ${query} \u2014 ${rationale}${gapTag}`\n : `- ${query}${gapTag}`,\n );\n }\n\n return lines.length === 2 ? '' : lines.join('\\n');\n}\n\nexport function appendSignalsAndFollowUps(\n markdown: string,\n signalsSection: string,\n refineQueries: RefineQuerySuggestion[] | undefined,\n options: { includeSignals?: boolean } = {},\n): string {\n const includeSignals = options.includeSignals ?? false;\n const sections = [markdown];\n if (includeSignals && signalsSection) {\n sections.push('', '---', signalsSection);\n }\n const followUps = buildSuggestedFollowUpsSection(refineQueries);\n if (followUps) {\n sections.push('', followUps);\n }\n return sections.join('\\n');\n}\n\n// --- \"Start here\" section ---\n//\n// Surfaces the best 3-5 URLs at the top of the classified response so an agent\n// skimming the first screen sees them before tier tables. Deterministic: uses\n// existing `tier` + `rank` + `reason` from the classifier, no extra LLM call.\n//\n// Algorithm: take HIGHLY_RELEVANT by rank up to MAX_START_HERE; if fewer than\n// MIN_START_HERE, pad from top MAYBE_RELEVANT; skip entirely if no entries\n// above OTHER.\n\nconst MIN_START_HERE = 3;\nconst MAX_START_HERE = 5;\n\n/** Minimal structural shape \u2014 avoids coupling to private `RankedUrl` type. */\ninterface StartHereCandidate {\n readonly rank: number;\n readonly url: string;\n readonly title: string;\n}\n\ninterface StartHereTiers {\n readonly high: readonly StartHereCandidate[];\n readonly maybe: readonly StartHereCandidate[];\n}\n\nexport function buildStartHereSection(\n tiers: StartHereTiers,\n entryByRank: Map<number, ClassificationEntry>,\n opts: { min?: number; max?: number } = {},\n): string {\n const min = opts.min ?? MIN_START_HERE;\n const max = opts.max ?? MAX_START_HERE;\n\n const picks: Array<{ candidate: StartHereCandidate; tier: 'HIGHLY_RELEVANT' | 'MAYBE_RELEVANT' }> = [];\n\n for (const candidate of tiers.high) {\n if (picks.length >= max) break;\n picks.push({ candidate, tier: 'HIGHLY_RELEVANT' });\n }\n\n if (picks.length < min) {\n const target = Math.min(min, max);\n for (const candidate of tiers.maybe) {\n if (picks.length >= target) break;\n picks.push({ candidate, tier: 'MAYBE_RELEVANT' });\n }\n }\n\n if (picks.length === 0) return '';\n\n const lines: string[] = [];\n lines.push('## Start here \u2014 best candidates for your extract');\n picks.forEach((pick, i) => {\n const entry = entryByRank.get(pick.candidate.rank);\n const reason = entry?.reason && entry.reason.trim().length > 0 ? entry.reason : '\u2014';\n let domain: string;\n try {\n domain = new URL(pick.candidate.url).hostname.replace(/^www\\./, '');\n } catch {\n domain = pick.candidate.url;\n }\n lines.push(\n `${i + 1}. **[${pick.candidate.title}](${pick.candidate.url})** \u2014 ${domain} \u2014 ${reason} *(${pick.tier}, rank ${pick.candidate.rank})*`,\n );\n });\n return lines.join('\\n');\n}\n\n// --- Classified output (3-tier LLM-classified table) ---\n\nfunction buildClassifiedOutput(\n classification: ClassificationResult,\n aggregation: SearchAggregation,\n extract: string,\n searches: SearchResponse['searches'],\n totalQueries: number,\n verbose: boolean = false,\n): string {\n const rankedUrls = aggregation.rankedUrls;\n\n // Build tier \u2192 entries mapping (keep url data alongside classifier metadata)\n const entryByRank = new Map(classification.results.map((r) => [r.rank, r]));\n\n const tiers = {\n high: [] as typeof rankedUrls,\n maybe: [] as typeof rankedUrls,\n other: [] as typeof rankedUrls,\n };\n\n for (const url of rankedUrls) {\n const entry = entryByRank.get(url.rank);\n const tier = entry?.tier;\n if (tier === 'HIGHLY_RELEVANT') {\n tiers.high.push(url);\n } else if (tier === 'MAYBE_RELEVANT') {\n tiers.maybe.push(url);\n } else {\n tiers.other.push(url);\n }\n }\n\n const lines: string[] = [];\n\n // Header with generated title, synthesis, and confidence\n lines.push(`## ${classification.title}`);\n lines.push(`> Looking for: ${extract}`);\n lines.push(`> ${totalQueries} queries \u2192 ${rankedUrls.length} URLs \u2192 ${tiers.high.length} highly relevant, ${tiers.maybe.length} possibly relevant`);\n if (classification.confidence) {\n const confReason = classification.confidence_reason ? ` \u2014 ${classification.confidence_reason}` : '';\n lines.push(`> Confidence: \\`${classification.confidence}\\`${confReason}`);\n }\n lines.push('');\n\n // \"Start here\" block: surface the top 3-5 URLs above the synthesis so an\n // agent skimming the first screen sees scrape candidates before prose.\n const startHere = buildStartHereSection(\n { high: tiers.high, maybe: tiers.maybe },\n entryByRank,\n );\n if (startHere) {\n lines.push(startHere);\n lines.push('');\n }\n\n lines.push(`**Summary:** ${classification.synthesis}`);\n lines.push('');\n\n // Helper: render one row with optional source_type + reason\n const renderRichRow = (url: typeof rankedUrls[number]): string => {\n const entry = entryByRank.get(url.rank);\n const coveragePct = Math.round(url.coverageRatio * 100);\n const seenIn = `${url.frequency}/${totalQueries} (${coveragePct}%)`;\n const sourceType = entry?.source_type ? `\\`${entry.source_type}\\`` : '\u2014';\n const reason = entry?.reason ? entry.reason.replace(/\\|/g, '\\\\|') : '\u2014';\n return `| ${url.rank} | [${url.title}](${url.url}) | ${sourceType} | ${seenIn} | ${reason} |`;\n };\n\n // Highly Relevant tier\n if (tiers.high.length > 0) {\n lines.push(`### Highly Relevant (${tiers.high.length})`);\n lines.push('| # | URL | Source | Seen in | Why |');\n lines.push('|---|-----|--------|---------|-----|');\n for (const url of tiers.high) lines.push(renderRichRow(url));\n lines.push('');\n }\n\n // Maybe Relevant tier\n if (tiers.maybe.length > 0) {\n lines.push(`### Maybe Relevant (${tiers.maybe.length})`);\n lines.push('| # | URL | Source | Seen in | Why |');\n lines.push('|---|-----|--------|---------|-----|');\n for (const url of tiers.maybe) lines.push(renderRichRow(url));\n lines.push('');\n }\n\n // Other tier \u2014 with query attribution\n if (tiers.other.length > 0) {\n lines.push(`### Other Results (${tiers.other.length})`);\n lines.push('| # | URL | Source | Score | Queries |');\n lines.push('|---|-----|--------|-------|---------|');\n for (const url of tiers.other) {\n const entry = entryByRank.get(url.rank);\n const queryList = url.queries.map((q) => `\"${q}\"`).join(', ');\n const sourceType = entry?.source_type ? `\\`${entry.source_type}\\`` : '\u2014';\n let domain: string;\n try {\n domain = new URL(url.url).hostname.replace(/^www\\./, '');\n } catch {\n domain = url.url;\n }\n lines.push(`| ${url.rank} | ${domain} | ${sourceType} | ${url.score.toFixed(1)} | ${queryList} |`);\n }\n lines.push('');\n }\n\n // Signals block is gated behind verbose \u2014 it duplicates info already\n // present in the per-row metadata for callers who care.\n // See: docs/code-review/context/05-output-formatting-patterns.md.\n if (verbose) {\n lines.push(buildSignalsSection(aggregation, searches, totalQueries));\n }\n\n // Gaps section \u2014 what the current results don't answer\n if (classification.gaps && classification.gaps.length > 0) {\n lines.push('');\n lines.push('## Gaps');\n for (const gap of classification.gaps) {\n lines.push(`- **[${gap.id}]** ${gap.description}`);\n }\n }\n\n const followUps = buildSuggestedFollowUpsSection(classification.refine_queries);\n if (followUps) {\n lines.push('');\n lines.push(followUps);\n }\n\n return lines.join('\\n');\n}\n\n// --- Error builder ---\n\nfunction formatSearchFailureMessage(\n error: StructuredError,\n phase?: SearchFailurePhase,\n): string {\n if (phase === 'initial') {\n return `Search provider failed during initial batch: ${error.message}`;\n }\n\n if (phase === 'relax-retry') {\n return `Search provider failed during relaxed retry batch: ${error.message}`;\n }\n\n return error.message;\n}\n\nfunction buildWebSearchError(\n error: StructuredError,\n params: SearchHandlerParams,\n startTime: number,\n phase?: SearchFailurePhase,\n): ToolExecutionResult<SearchToolOutput> {\n const message = formatSearchFailureMessage(error, phase);\n const executionTime = Date.now() - startTime;\n\n mcpLog('error', `${params.toolName}: ${message}`, 'search');\n\n const errorContent = formatError({\n code: error.code,\n message,\n retryable: error.retryable,\n toolName: params.toolName,\n howToFix: ['Verify SERPER_API_KEY or JINA_API_KEY is set correctly'],\n alternatives: [\n 'raw-web-search(keywords=[\"topic recommendations site:reddit.com\"]) \u2014 unclassified search results',\n 'raw-scrape-links(urls=[...]) \u2014 if you already have URLs, fetch their markdown now',\n ],\n });\n\n return toolFailure(\n `${errorContent}\\n\\nExecution time: ${formatDuration(executionTime)}\\nKeywords: ${params.keywords.length}`,\n );\n}\n\n// --- Main handler ---\n\nasync function handleSearch(\n params: SearchHandlerParams,\n reporter: ToolReporter = NOOP_REPORTER,\n searchExecutor: SearchExecutor = executeSearches,\n): Promise<ToolExecutionResult<SearchToolOutput>> {\n const startTime = Date.now();\n\n try {\n if (params.smart && !createLLMProcessor()) {\n return toolFailure(getMissingEnvMessage('llmExtraction'));\n }\n\n const scopedQueries = buildScopedQueries(params.keywords, params.scope);\n const effectiveQueries = scopedQueries.map((entry) => entry.query);\n if (params.scope !== 'web') {\n mcpLog('info', `Searching scope=${params.scope}: ${params.keywords.length} input keywords \u2192 ${effectiveQueries.length} dispatched`, 'search');\n } else {\n mcpLog('info', `Searching for ${params.keywords.length} keyword(s)`, 'search');\n }\n await reporter.log('info', `Searching for ${effectiveQueries.length} query/queries (scope=${params.scope})`);\n await reporter.progress(15, 100, 'Submitting search queries');\n\n // Phase A \u2014 pre-dispatch normalizer. Rewrites the small fraction of\n // queries Google was statistically going to mis-handle (3+ phrase AND,\n // operator chars in quotes, paths in quotes). See src/utils/query-relax.ts.\n const dispatchPlan = effectiveQueries.map((q) => {\n const r = normalizeQueryForDispatch(q);\n return { original: q, dispatched: r.rewritten, rules: [...r.rules], changed: r.changed };\n });\n const dispatchedQueries = dispatchPlan.map((p) => p.dispatched);\n const resultScopes = scopedQueries.map((entry) => entry.resultScope);\n const dropSiteOnRetry = scopedQueries.map((entry) => entry.dropSiteOnRetry);\n const queryRewrites: QueryRewriteRecord[] = dispatchPlan\n .filter((p) => p.changed)\n .map((p) => ({ original: p.original, rewritten: p.dispatched, rules: p.rules }));\n\n if (queryRewrites.length > 0) {\n mcpLog(\n 'info',\n `Pre-dispatch normalized ${queryRewrites.length}/${effectiveQueries.length} queries`,\n 'search',\n );\n await reporter.log(\n 'info',\n `Normalized ${queryRewrites.length} queries pre-dispatch`,\n );\n }\n\n // Phase B \u2014 on-empty retry: any query returning 0 results gets one\n // relaxed retry (drop quotes, drop site:). Recovered hits replace the\n // empty slot transparently.\n const {\n response: rawResponse,\n retried: retriedQueries,\n failurePhase,\n retryError,\n } = await executeWithRelaxRetry(\n dispatchedQueries,\n reporter,\n searchExecutor,\n { dropSiteOnRetry },\n );\n\n if (rawResponse.error) {\n await reporter.log('error', `search_provider_failed: ${rawResponse.error.message}`);\n return buildWebSearchError(rawResponse.error, params, startTime, failurePhase);\n }\n\n const response = filterScopedSearches(rawResponse, params.scope, resultScopes);\n await reporter.progress(50, 100, 'Collected search results');\n\n const { aggregation } = processResults(response);\n await reporter.log(\n 'info',\n `Collected ${aggregation.totalUniqueUrls} unique URLs across ${response.totalQueries} queries`,\n );\n\n let markdown: string;\n\n if (!params.smart) {\n markdown = appendSignalsAndFollowUps(\n buildRawOutput(params.keywords, aggregation, response.searches, false),\n buildSignalsSection(aggregation, response.searches, response.totalQueries),\n undefined,\n { includeSignals: false },\n );\n await reporter.progress(80, 100, 'Ranking search results');\n } else {\n const llmProcessor = createLLMProcessor();\n if (!llmProcessor) {\n return toolFailure(getMissingEnvMessage('llmExtraction'));\n }\n\n await reporter.progress(65, 100, 'Classifying results by relevance');\n const classification = await runExternalEffect(\n Effect.gen(function* () {\n const llm = yield* LlmService;\n return yield* llm.classifySearchResults(\n aggregation.rankedUrls,\n params.extract ?? '',\n response.totalQueries,\n llmProcessor,\n params.keywords,\n );\n }),\n LlmServiceLive,\n );\n\n if (classification.result) {\n markdown = buildClassifiedOutput(\n classification.result, aggregation, params.extract ?? '', response.searches, response.totalQueries, params.verbose,\n );\n await reporter.progress(85, 100, 'Formatted classified results');\n } else {\n const llmError = classification.error ?? 'Unknown classification error';\n mcpLog('warning', `Classification failed for smart-web-search: ${llmError}`, 'search');\n await reporter.log('warning', `llm_classifier_failed: ${llmError}`);\n return toolFailure(\n `${formatError({\n code: ErrorCode.SERVICE_UNAVAILABLE,\n message: `LLM classification failed: ${llmError}`,\n retryable: true,\n toolName: params.toolName,\n alternatives: ['raw-web-search(keywords=[...]) \u2014 return unclassified search results'],\n })}\\n\\nExecution time: ${formatDuration(Date.now() - startTime)}`,\n );\n }\n }\n\n const executionTime = Date.now() - startTime;\n\n mcpLog('info', `Search completed: ${aggregation.rankedUrls.length} URLs, smart=${params.smart}`, 'search');\n await reporter.log('info', `Search completed with ${aggregation.rankedUrls.length} URLs (smart: ${params.smart})`);\n\n const footerParts = [\n formatDuration(executionTime),\n `${aggregation.totalUniqueUrls} unique URLs`,\n params.smart ? 'LLM classified' : 'raw search',\n ];\n if (queryRewrites.length > 0) footerParts.push(`${queryRewrites.length} normalized`);\n if (retriedQueries.length > 0) footerParts.push(`${retriedQueries.length} retried`);\n if (retryError) footerParts.push(`retry warning: ${retryError.code}`);\n const footer = `\\n---\\n*${footerParts.join(' | ')}*`;\n const fullMarkdown = markdown + footer;\n\n return toolSuccess(fullMarkdown);\n } catch (error) {\n return buildWebSearchError(classifyError(error), params, startTime);\n }\n}\n\nexport function handleRawWebSearch(\n params: RawWebSearchParams,\n reporter: ToolReporter = NOOP_REPORTER,\n searchExecutor: SearchExecutor = executeSearches,\n): Promise<ToolExecutionResult<SearchToolOutput>> {\n return handleSearch(\n {\n ...params,\n scope: 'web',\n verbose: false,\n smart: false,\n toolName: 'raw-web-search',\n },\n reporter,\n searchExecutor,\n );\n}\n\nexport function handleSmartWebSearch(\n params: SmartWebSearchParams,\n reporter: ToolReporter = NOOP_REPORTER,\n searchExecutor: SearchExecutor = executeSearches,\n): Promise<ToolExecutionResult<SearchToolOutput>> {\n return handleSearch(\n {\n ...params,\n smart: true,\n toolName: 'smart-web-search',\n },\n reporter,\n searchExecutor,\n );\n}\n\nexport function registerWebSearchTools(server: MCPServer): void {\n server.tool(\n {\n name: 'raw-web-search',\n title: 'Raw Web Search',\n description:\n `Fan out raw search keywords in parallel and return the ranked markdown list directly. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. Input is only \\`keywords\\` (1\u201350 items). ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Use this when you need unclassified result data, Reddit permalink discovery via explicit \\`site:reddit.com/r/.../comments\\` keywords, or broad reconnaissance before synthesis.`,\n schema: rawWebSearchParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = rawWebSearchParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('raw-web-search', parsed.error.issues)));\n }\n\n if (!getCapabilities().search) {\n return toToolResponse(toolFailure(getMissingEnvMessage('search')));\n }\n\n const reporter = createToolReporter(ctx, 'raw-web-search');\n const result = await handleRawWebSearch(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Search failed' : 'Search complete');\n return toToolResponse(result);\n },\n );\n\n server.tool(\n {\n name: 'smart-web-search',\n title: 'Smart Web Search',\n description:\n `Fan out search keywords in parallel, then always run LLM classification and synthesis against \\`extract\\`. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. Input carries 1\u201350 \\`keywords\\`, required \\`extract\\`, optional \\`scope: \"web\" | \"reddit\" | \"both\"\\`, and optional \\`verbose\\`. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Use this for comprehensive research passes where you need HIGHLY_RELEVANT/MAYBE/OTHER tiers, a grounded synthesis with rank citations, gaps, and suggested follow-up searches.`,\n schema: smartWebSearchParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = smartWebSearchParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('smart-web-search', parsed.error.issues)));\n }\n\n if (!getCapabilities().search) {\n return toToolResponse(toolFailure(getMissingEnvMessage('search')));\n }\n\n const reporter = createToolReporter(ctx, 'smart-web-search');\n const result = await handleSmartWebSearch(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Search failed' : 'Search complete');\n return toToolResponse(result);\n },\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["jinaResponse"]
|
|
7
7
|
}
|
package/package.json
CHANGED