@agent-native/core 0.53.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.d.ts +40 -1
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +69 -2
- package/dist/action.js.map +1 -1
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +1 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/observational-memory/index.d.ts +6 -6
- package/dist/agent/observational-memory/index.js +6 -6
- package/dist/agent/observational-memory/index.js.map +1 -1
- package/dist/agent/observational-memory/read.d.ts +7 -9
- package/dist/agent/observational-memory/read.d.ts.map +1 -1
- package/dist/agent/observational-memory/read.js +7 -9
- package/dist/agent/observational-memory/read.js.map +1 -1
- package/dist/agent/processors.d.ts +146 -0
- package/dist/agent/processors.d.ts.map +1 -0
- package/dist/agent/processors.js +122 -0
- package/dist/agent/processors.js.map +1 -0
- package/dist/agent/production-agent.d.ts +10 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +101 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +4 -5
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/tool-call-journal.d.ts +6 -8
- package/dist/agent/tool-call-journal.d.ts.map +1 -1
- package/dist/agent/tool-call-journal.js +6 -8
- package/dist/agent/tool-call-journal.js.map +1 -1
- package/dist/agent/types.d.ts +11 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +129 -4
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +38 -3
- package/dist/cli/skills.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +18 -2
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/extensions/fetch-tool.d.ts.map +1 -1
- package/dist/extensions/fetch-tool.js +80 -15
- package/dist/extensions/fetch-tool.js.map +1 -1
- package/dist/extensions/web-content.d.ts +61 -0
- package/dist/extensions/web-content.d.ts.map +1 -0
- package/dist/extensions/web-content.js +468 -0
- package/dist/extensions/web-content.js.map +1 -0
- package/dist/extensions/web-search-tool.js +3 -3
- package/dist/extensions/web-search-tool.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +4 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +80 -0
- package/dist/provider-api/corpus-jobs.d.ts.map +1 -1
- package/dist/provider-api/corpus-jobs.js +219 -22
- package/dist/provider-api/corpus-jobs.js.map +1 -1
- package/dist/provider-api/index.d.ts +24 -32
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -1
- package/dist/provider-api/index.js.map +1 -1
- package/dist/server/agent-chat-plugin.js +1 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +7 -0
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +90 -0
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/deep-link.d.ts +7 -0
- package/dist/server/deep-link.d.ts.map +1 -1
- package/dist/server/deep-link.js +13 -2
- package/dist/server/deep-link.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/templates/default/.agents/skills/actions/SKILL.md +52 -1
- package/dist/templates/default/.agents/skills/security/SKILL.md +22 -0
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +52 -1
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +6 -4
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +11 -0
- package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +22 -0
- package/docs/content/actions.md +50 -0
- package/docs/content/durable-resume.md +49 -0
- package/docs/content/external-agents.md +2 -2
- package/docs/content/human-approval.md +101 -0
- package/docs/content/observability.md +21 -0
- package/docs/content/observational-memory.md +63 -0
- package/docs/content/plan-plugin.md +5 -0
- package/docs/content/pr-visual-recap.md +4 -3
- package/docs/content/processors.md +99 -0
- package/docs/content/template-plan.md +78 -14
- package/package.json +6 -1
- package/src/templates/default/.agents/skills/actions/SKILL.md +52 -1
- package/src/templates/default/.agents/skills/security/SKILL.md +22 -0
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +52 -1
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +6 -4
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +11 -0
- package/src/templates/workspace-core/.agents/skills/security/SKILL.md +22 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-tool.js","sourceRoot":"","sources":["../../src/extensions/fetch-tool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fetch tool — outbound HTTP for automations and agent use.\n *\n * NOTE: this is an *agent* tool (LLM function call), not an *extension* (the\n * sandboxed Alpine.js mini-app primitive). It lives in this directory because\n * it shares SSRF-safe URL/proxy helpers with the extension iframe proxy.\n *\n * Supports ${keys.NAME} reference substitution in URL, headers, and body.\n * Values are resolved server-side AFTER the model emits the tool call —\n * the raw secret never enters the model's context.\n */\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport {\n collectSecretValues,\n MAX_EXTENSION_PROXY_RESPONSE_SIZE,\n normalizeExtensionProxyMethod,\n readResponseTextWithLimit,\n redactSecrets,\n redactString,\n sanitizeOutboundHeaders,\n} from \"./proxy-security.js\";\nimport {\n createSsrfSafeDispatcher,\n isBlockedExtensionUrlWithDns,\n} from \"./url-safety.js\";\n\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Headers that mimic a current Chrome on macOS so anti-bot middleware (Cloudflare,\n * PerimeterX, Akamai) treats the request as a real user. We only fill in fields\n * the caller hasn't supplied — explicit headers (e.g. an `Authorization` header\n * for an API call) always win.\n *\n * `Accept-Encoding` deliberately omits `zstd` because Node's undici fetch only\n * decompresses `gzip`, `deflate`, and `br`. Advertising `zstd` would let some\n * servers send bytes we can't decode.\n */\nconst BROWSER_DEFAULT_HEADERS: Record<string, string> = {\n \"User-Agent\":\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\n Accept:\n \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\",\n \"Accept-Language\": \"en-US,en;q=0.9\",\n \"Accept-Encoding\": \"gzip, deflate, br\",\n \"Sec-Ch-Ua\":\n '\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"',\n \"Sec-Ch-Ua-Mobile\": \"?0\",\n \"Sec-Ch-Ua-Platform\": '\"macOS\"',\n \"Sec-Fetch-Dest\": \"document\",\n \"Sec-Fetch-Mode\": \"navigate\",\n \"Sec-Fetch-Site\": \"none\",\n \"Sec-Fetch-User\": \"?1\",\n \"Upgrade-Insecure-Requests\": \"1\",\n};\n\nfunction applyBrowserDefaults(\n headers: Record<string, string>,\n): Record<string, string> {\n const seen = new Set(Object.keys(headers).map((k) => k.toLowerCase()));\n const merged = { ...headers };\n for (const [name, value] of Object.entries(BROWSER_DEFAULT_HEADERS)) {\n if (!seen.has(name.toLowerCase())) merged[name] = value;\n }\n return merged;\n}\n\nexport interface FetchToolOptions {\n /** Resolve ${keys.NAME} references. Injected by the plugin at setup time. */\n resolveKeys?: (text: string) => Promise<{\n resolved: string;\n usedKeys: string[];\n secretValues?: string[];\n }>;\n /** Validate URL against per-key allowlists. */\n validateUrl?: (url: string, usedKeys: string[]) => Promise<boolean>;\n}\n\n/**\n * Create the fetch tool entry for the agent tool registry.\n */\nexport function createFetchToolEntry(\n opts: FetchToolOptions = {},\n): Record<string, ActionEntry> {\n return {\n \"web-request\": {\n tool: {\n description: `Make an outbound HTTP request to any EXTERNAL URL — APIs, webhooks, and arbitrary web pages (HTML, RSS, JSON, etc.). Use this to fetch the contents of a URL the user pastes in chat. Sends realistic Chrome-on-macOS headers by default (User-Agent, Accept, Sec-Fetch-*) so most sites that block obvious bots will respond normally; pass an explicit header to override any default. Supports \\${keys.NAME} placeholders in url, headers, and body — these are resolved server-side from the user's saved keys (the raw value never enters your context). Example: \\${keys.SLACK_WEBHOOK} in the url field. IMPORTANT: Never use this to call internal /_agent-native/ endpoints or localhost action URLs — use the registered actions directly (e.g. \\`search-records\\`, \\`provider-api-request\\`, \\`update-resource\\`). Actions are already available as native tools; calling them via HTTP is slower and bypasses validation.`,\n parameters: {\n type: \"object\" as const,\n properties: {\n url: {\n type: \"string\",\n description:\n 'Full URL. May contain ${keys.NAME} references, e.g. \"${keys.SLACK_WEBHOOK}\".',\n },\n method: {\n type: \"string\",\n description: \"HTTP method. Default: GET.\",\n enum: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\"],\n },\n headers: {\n type: \"string\",\n description:\n 'JSON object of headers. May contain ${keys.NAME} references. Example: \\'{\"Authorization\": \"Bearer ${keys.API_TOKEN}\"}\\'.',\n },\n body: {\n type: \"string\",\n description:\n \"Request body (for POST/PUT/PATCH). May contain ${keys.NAME} references.\",\n },\n timeout_ms: {\n type: \"number\",\n description: `Timeout in milliseconds. Default: ${DEFAULT_TIMEOUT_MS}. Max: 30000.`,\n },\n maxChars: {\n type: \"number\",\n description:\n \"Maximum response body characters to return. Default: 32000. Max: 200000. Increase when you need to read a large document, API response, or dataset.\",\n },\n saveToFile: {\n type: \"string\",\n description:\n \"Workspace file path to save the full response body to instead of returning it in context (e.g. 'analysis/page.html'). When set, returns only a compact summary {savedTo, status, bytes, preview}. Useful for large web pages or API responses that would overflow context.\",\n },\n },\n required: [\"url\"],\n },\n },\n run: async (args: Record<string, string>) => {\n const startTime = Date.now();\n const rawUrl = args.url;\n const method = normalizeExtensionProxyMethod(args.method || \"GET\");\n if (!method) {\n return \"Unsupported HTTP method. Allowed methods: GET, POST, PUT, PATCH, DELETE, HEAD.\";\n }\n const rawHeaders = args.headers || \"{}\";\n const rawBody = args.body;\n const timeoutMs = Math.min(\n Number(args.timeout_ms) || DEFAULT_TIMEOUT_MS,\n 30_000,\n );\n const requestedMaxChars = Number(args.maxChars);\n const maxChars =\n Number.isFinite(requestedMaxChars) && requestedMaxChars > 0\n ? Math.min(requestedMaxChars, 200_000)\n : 32_000;\n\n // Resolve key references\n let resolvedUrl = rawUrl;\n let resolvedHeaders = rawHeaders;\n let resolvedBody = rawBody;\n const allUsedKeys: string[] = [];\n const allSecretValues: string[] = [];\n\n if (opts.resolveKeys) {\n try {\n const urlResult = await opts.resolveKeys(rawUrl);\n resolvedUrl = urlResult.resolved;\n allUsedKeys.push(...urlResult.usedKeys);\n allSecretValues.push(...(urlResult.secretValues ?? []));\n\n const headerResult = await opts.resolveKeys(rawHeaders);\n resolvedHeaders = headerResult.resolved;\n allUsedKeys.push(...headerResult.usedKeys);\n allSecretValues.push(...(headerResult.secretValues ?? []));\n\n if (rawBody) {\n const bodyResult = await opts.resolveKeys(rawBody);\n resolvedBody = bodyResult.resolved;\n allUsedKeys.push(...bodyResult.usedKeys);\n allSecretValues.push(...(bodyResult.secretValues ?? []));\n }\n } catch (err: any) {\n return `Error resolving key references: ${err?.message ?? err}`;\n }\n }\n const secretValues = collectSecretValues(allSecretValues);\n\n // Block SSRF targets regardless of key usage\n if (await isBlockedExtensionUrlWithDns(resolvedUrl)) {\n return `Requests to private/internal addresses are not allowed: \"${rawUrl}\".`;\n }\n\n // Validate URL against per-key allowlists\n if (opts.validateUrl && allUsedKeys.length > 0) {\n try {\n const allowed = await opts.validateUrl(resolvedUrl, allUsedKeys);\n if (!allowed) {\n return `URL \"${rawUrl}\" is not in the allowlist for the referenced keys. Check your key settings.`;\n }\n } catch (err: any) {\n return `URL validation error: ${err?.message ?? err}`;\n }\n }\n\n // Parse headers, then merge in browser-like defaults for any header the\n // caller didn't already specify. Real-browser headers (User-Agent,\n // Accept, Sec-Fetch-*) are what gets you past Cloudflare / PerimeterX /\n // generic UA-sniffing middleware on sites the user pastes in chat;\n // explicit caller headers always win so API calls keep their auth\n // headers untouched.\n let headers: Record<string, string>;\n try {\n headers = sanitizeOutboundHeaders(JSON.parse(resolvedHeaders));\n } catch {\n return `Invalid headers JSON: ${rawHeaders}`;\n }\n headers = applyBrowserDefaults(headers);\n\n // Make the request\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;\n const fetchOpts: RequestInit & { dispatcher?: unknown } = {\n method,\n headers,\n signal: controller.signal,\n redirect: \"manual\",\n };\n if (dispatcher) fetchOpts.dispatcher = dispatcher;\n if (resolvedBody && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOpts.body = resolvedBody;\n if (!headers[\"content-type\"] && !headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n }\n\n const response = await fetch(resolvedUrl, fetchOpts);\n const elapsed = Date.now() - startTime;\n\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get(\"location\");\n const redirectUrl = location\n ? new URL(location, resolvedUrl).href\n : null;\n if (\n redirectUrl &&\n (await isBlockedExtensionUrlWithDns(redirectUrl))\n ) {\n return \"Redirect to private/internal address blocked.\";\n }\n if (redirectUrl && opts.validateUrl && allUsedKeys.length > 0) {\n const allowed = await opts.validateUrl(redirectUrl, allUsedKeys);\n if (!allowed) {\n return \"Redirect URL is not in the allowlist for the referenced keys.\";\n }\n }\n return `HTTP ${response.status} ${response.statusText}\\n\\nRedirect: ${\n redirectUrl ? redactString(redirectUrl, secretValues) : \"(none)\"\n }`;\n }\n\n // Check if caller wants to save to workspace file (before truncation).\n const saveToFilePath =\n typeof (args as Record<string, unknown>).saveToFile === \"string\"\n ? ((args as Record<string, unknown>).saveToFile as string).trim()\n : \"\";\n\n let body: string;\n try {\n // When saving to file allow larger reads (20MB), otherwise cap at proxy limit.\n const readLimit = saveToFilePath\n ? 20 * 1024 * 1024\n : MAX_EXTENSION_PROXY_RESPONSE_SIZE;\n const result = await readResponseTextWithLimit(response, readLimit);\n body = result.text;\n } catch {\n body = \"(could not read response body)\";\n }\n body = redactString(body, secretValues);\n\n // Audit log\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ${response.status} (${elapsed}ms, keys: ${allUsedKeys.join(\",\") || \"none\"})`,\n );\n\n // saveToFile: write full body to workspace and return compact summary.\n if (saveToFilePath) {\n try {\n const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES } =\n await import(\"../workspace-files/store.js\");\n const { getRequestOrgId, getRequestUserEmail } =\n await import(\"../server/request-context.js\");\n const orgId = getRequestOrgId();\n const email = getRequestUserEmail();\n const scope = orgId\n ? { scope: \"org\" as const, scopeId: orgId }\n : email\n ? { scope: \"user\" as const, scopeId: email }\n : null;\n if (!scope)\n throw new Error(\"No authenticated context for saveToFile\");\n const contentType =\n response.headers.get(\"content-type\")?.split(\";\")[0].trim() ??\n \"text/plain\";\n await writeWorkspaceFile(\n scope,\n saveToFilePath,\n body,\n contentType,\n {\n maxFileBytes: SAVE_TO_FILE_MAX_BYTES,\n },\n );\n const bytes = Buffer.byteLength(body, \"utf8\");\n const preview = body.slice(0, 2000);\n return JSON.stringify({\n savedToFile: true,\n savedTo: saveToFilePath,\n status: response.status,\n bytes,\n contentType,\n preview: preview.length < body.length ? `${preview}…` : preview,\n });\n } catch (saveErr: any) {\n return `saveToFile error: ${saveErr?.message ?? String(saveErr)}\\n\\nHTTP ${response.status} ${response.statusText}\\n\\n${body.slice(0, maxChars)}`;\n }\n }\n\n // Truncate very long responses for the agent. Default cap is 32 k\n // chars (~8 k tokens), enough to read a full article or scrape a\n // stats table without blowing out the model's context window. The\n // caller may request up to 200 000 chars via the maxChars input.\n if (body.length > maxChars) {\n body = body.slice(0, maxChars) + \"\\n... (truncated)\";\n }\n\n return `HTTP ${response.status} ${response.statusText}\\n\\n${body}`;\n } catch (err: any) {\n const elapsed = Date.now() - startTime;\n if (err?.name === \"AbortError\") {\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → TIMEOUT (${elapsed}ms)`,\n );\n return `Request timed out after ${timeoutMs}ms.`;\n }\n const message = redactSecrets(\n err?.message ?? String(err),\n secretValues,\n );\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ERROR: ${message} (${elapsed}ms)`,\n );\n return `Request failed: ${message}`;\n } finally {\n clearTimeout(timeout);\n }\n },\n readOnly: true,\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fetch-tool.js","sourceRoot":"","sources":["../../src/extensions/fetch-tool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fetch tool — outbound HTTP for automations and agent use.\n *\n * NOTE: this is an *agent* tool (LLM function call), not an *extension* (the\n * sandboxed Alpine.js mini-app primitive). It lives in this directory because\n * it shares SSRF-safe URL/proxy helpers with the extension iframe proxy.\n *\n * Supports ${keys.NAME} reference substitution in URL, headers, and body.\n * Values are resolved server-side AFTER the model emits the tool call —\n * the raw secret never enters the model's context.\n */\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport {\n collectSecretValues,\n MAX_EXTENSION_PROXY_RESPONSE_SIZE,\n normalizeExtensionProxyMethod,\n readResponseTextWithLimit,\n redactSecrets,\n redactString,\n sanitizeOutboundHeaders,\n} from \"./proxy-security.js\";\nimport {\n createSsrfSafeDispatcher,\n isBlockedExtensionUrlWithDns,\n} from \"./url-safety.js\";\nimport {\n formatWebContentResult,\n parseWebContentSearchOptions,\n processWebContent,\n} from \"./web-content.js\";\n\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Headers that mimic a current Chrome on macOS so anti-bot middleware (Cloudflare,\n * PerimeterX, Akamai) treats the request as a real user. We only fill in fields\n * the caller hasn't supplied — explicit headers (e.g. an `Authorization` header\n * for an API call) always win.\n *\n * `Accept-Encoding` deliberately omits `zstd` because Node's undici fetch only\n * decompresses `gzip`, `deflate`, and `br`. Advertising `zstd` would let some\n * servers send bytes we can't decode.\n */\nconst BROWSER_DEFAULT_HEADERS: Record<string, string> = {\n \"User-Agent\":\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\n Accept:\n \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8\",\n \"Accept-Language\": \"en-US,en;q=0.9\",\n \"Accept-Encoding\": \"gzip, deflate, br\",\n \"Sec-Ch-Ua\":\n '\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"',\n \"Sec-Ch-Ua-Mobile\": \"?0\",\n \"Sec-Ch-Ua-Platform\": '\"macOS\"',\n \"Sec-Fetch-Dest\": \"document\",\n \"Sec-Fetch-Mode\": \"navigate\",\n \"Sec-Fetch-Site\": \"none\",\n \"Sec-Fetch-User\": \"?1\",\n \"Upgrade-Insecure-Requests\": \"1\",\n};\n\nfunction applyBrowserDefaults(\n headers: Record<string, string>,\n): Record<string, string> {\n const seen = new Set(Object.keys(headers).map((k) => k.toLowerCase()));\n const merged = { ...headers };\n for (const [name, value] of Object.entries(BROWSER_DEFAULT_HEADERS)) {\n if (!seen.has(name.toLowerCase())) merged[name] = value;\n }\n return merged;\n}\n\nexport interface FetchToolOptions {\n /** Resolve ${keys.NAME} references. Injected by the plugin at setup time. */\n resolveKeys?: (text: string) => Promise<{\n resolved: string;\n usedKeys: string[];\n secretValues?: string[];\n }>;\n /** Validate URL against per-key allowlists. */\n validateUrl?: (url: string, usedKeys: string[]) => Promise<boolean>;\n}\n\n/**\n * Create the fetch tool entry for the agent tool registry.\n */\nexport function createFetchToolEntry(\n opts: FetchToolOptions = {},\n): Record<string, ActionEntry> {\n return {\n \"web-request\": {\n tool: {\n description: `Make an outbound HTTP request to any EXTERNAL URL — APIs, webhooks, and arbitrary web pages (HTML, RSS, JSON, etc.). Use this to fetch the contents of a URL the user pastes in chat. Sends realistic Chrome-on-macOS headers by default (User-Agent, Accept, Sec-Fetch-*) so most sites that block obvious bots will respond normally; pass an explicit header to override any default. Supports \\${keys.NAME} placeholders in url, headers, and body — these are resolved server-side from the user's saved keys (the raw value never enters your context). Example: \\${keys.SLACK_WEBHOOK} in the url field. IMPORTANT: Never use this to call internal /_agent-native/ endpoints or localhost action URLs — use the registered actions directly (e.g. \\`search-records\\`, \\`provider-api-request\\`, \\`update-resource\\`). Actions are already available as native tools; calling them via HTTP is slower and bypasses validation.`,\n parameters: {\n type: \"object\" as const,\n properties: {\n url: {\n type: \"string\",\n description:\n 'Full URL. May contain ${keys.NAME} references, e.g. \"${keys.SLACK_WEBHOOK}\".',\n },\n method: {\n type: \"string\",\n description: \"HTTP method. Default: GET.\",\n enum: [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\"],\n },\n headers: {\n type: \"string\",\n description:\n 'JSON object of headers. May contain ${keys.NAME} references. Example: \\'{\"Authorization\": \"Bearer ${keys.API_TOKEN}\"}\\'.',\n },\n body: {\n type: \"string\",\n description:\n \"Request body (for POST/PUT/PATCH). May contain ${keys.NAME} references.\",\n },\n timeout_ms: {\n type: \"number\",\n description: `Timeout in milliseconds. Default: ${DEFAULT_TIMEOUT_MS}. Max: 30000.`,\n },\n maxChars: {\n type: \"number\",\n description:\n \"Maximum response body characters to return. Default: 32000. Max: 200000. Increase when you need to read a large document, API response, or dataset.\",\n },\n responseMode: {\n type: \"string\",\n description:\n \"How to return the response. Default: auto (HTML pages become clean markdown; JSON/text stays raw). Use raw for exact bytes, markdown/text for extracted readable content, links for just links, metadata for page metadata, or matches with search.\",\n enum: [\n \"auto\",\n \"raw\",\n \"text\",\n \"markdown\",\n \"links\",\n \"metadata\",\n \"matches\",\n ],\n },\n extract: {\n type: \"string\",\n description:\n \"HTML extraction strategy. Default: readability. Use all-visible for visible body text/markdown, or none to convert the full HTML document.\",\n enum: [\"readability\", \"all-visible\", \"none\"],\n },\n includeLinks: {\n type: \"boolean\",\n description:\n \"Whether extracted HTML responses should include a compact links list. Default: true for extracted pages.\",\n },\n search: {\n type: \"object\",\n description:\n \"Optional post-fetch search over extracted content by default. Supports {query, queries, terms, regex, regexFlags, source:'extracted'|'raw', maxMatches, contextChars, caseSensitive}. Regex is safety-checked and bounded; prefer query/terms for simple grep-like searches.\",\n properties: {\n query: { type: \"string\" },\n queries: { type: \"array\", items: { type: \"string\" } },\n terms: { type: \"array\", items: { type: \"string\" } },\n regex: { type: \"string\" },\n regexFlags: { type: \"string\" },\n source: { type: \"string\", enum: [\"extracted\", \"raw\"] },\n maxMatches: { type: \"number\" },\n contextChars: { type: \"number\" },\n caseSensitive: { type: \"boolean\" },\n },\n } as any,\n saveToFile: {\n type: \"string\",\n description:\n \"Workspace file path to save the full response body to instead of returning it in context (e.g. 'analysis/page.html'). When set, returns only a compact summary {savedTo, status, bytes, preview}. Useful for large web pages or API responses that would overflow context.\",\n },\n },\n required: [\"url\"],\n },\n },\n run: async (args: Record<string, unknown>) => {\n const startTime = Date.now();\n const rawUrl = String(args.url ?? \"\");\n const method = normalizeExtensionProxyMethod(args.method || \"GET\");\n if (!method) {\n return \"Unsupported HTTP method. Allowed methods: GET, POST, PUT, PATCH, DELETE, HEAD.\";\n }\n const rawHeaders =\n typeof args.headers === \"string\"\n ? args.headers\n : JSON.stringify(args.headers ?? {});\n const rawBody =\n typeof args.body === \"string\"\n ? args.body\n : args.body === undefined || args.body === null\n ? undefined\n : JSON.stringify(args.body);\n const timeoutMs = Math.min(\n Number(args.timeout_ms) || DEFAULT_TIMEOUT_MS,\n 30_000,\n );\n const requestedMaxChars = Number(args.maxChars);\n const maxChars =\n Number.isFinite(requestedMaxChars) && requestedMaxChars > 0\n ? Math.min(requestedMaxChars, 200_000)\n : 32_000;\n\n // Resolve key references\n let resolvedUrl = rawUrl;\n let resolvedHeaders = rawHeaders;\n let resolvedBody = rawBody;\n const allUsedKeys: string[] = [];\n const allSecretValues: string[] = [];\n\n if (opts.resolveKeys) {\n try {\n const urlResult = await opts.resolveKeys(rawUrl);\n resolvedUrl = urlResult.resolved;\n allUsedKeys.push(...urlResult.usedKeys);\n allSecretValues.push(...(urlResult.secretValues ?? []));\n\n const headerResult = await opts.resolveKeys(rawHeaders);\n resolvedHeaders = headerResult.resolved;\n allUsedKeys.push(...headerResult.usedKeys);\n allSecretValues.push(...(headerResult.secretValues ?? []));\n\n if (rawBody) {\n const bodyResult = await opts.resolveKeys(rawBody);\n resolvedBody = bodyResult.resolved;\n allUsedKeys.push(...bodyResult.usedKeys);\n allSecretValues.push(...(bodyResult.secretValues ?? []));\n }\n } catch (err: any) {\n return `Error resolving key references: ${err?.message ?? err}`;\n }\n }\n const secretValues = collectSecretValues(allSecretValues);\n\n // Block SSRF targets regardless of key usage\n if (await isBlockedExtensionUrlWithDns(resolvedUrl)) {\n return `Requests to private/internal addresses are not allowed: \"${rawUrl}\".`;\n }\n\n // Validate URL against per-key allowlists\n if (opts.validateUrl && allUsedKeys.length > 0) {\n try {\n const allowed = await opts.validateUrl(resolvedUrl, allUsedKeys);\n if (!allowed) {\n return `URL \"${rawUrl}\" is not in the allowlist for the referenced keys. Check your key settings.`;\n }\n } catch (err: any) {\n return `URL validation error: ${err?.message ?? err}`;\n }\n }\n\n // Parse headers, then merge in browser-like defaults for any header the\n // caller didn't already specify. Real-browser headers (User-Agent,\n // Accept, Sec-Fetch-*) are what gets you past Cloudflare / PerimeterX /\n // generic UA-sniffing middleware on sites the user pastes in chat;\n // explicit caller headers always win so API calls keep their auth\n // headers untouched.\n let headers: Record<string, string>;\n try {\n headers = sanitizeOutboundHeaders(JSON.parse(resolvedHeaders));\n } catch {\n return `Invalid headers JSON: ${rawHeaders}`;\n }\n headers = applyBrowserDefaults(headers);\n\n // Make the request\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const dispatcher = (await createSsrfSafeDispatcher()) ?? undefined;\n const fetchOpts: RequestInit & { dispatcher?: unknown } = {\n method,\n headers,\n signal: controller.signal,\n redirect: \"manual\",\n };\n if (dispatcher) fetchOpts.dispatcher = dispatcher;\n if (resolvedBody && [\"POST\", \"PUT\", \"PATCH\"].includes(method)) {\n fetchOpts.body = resolvedBody;\n if (!headers[\"content-type\"] && !headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n }\n\n const response = await fetch(resolvedUrl, fetchOpts);\n const elapsed = Date.now() - startTime;\n\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get(\"location\");\n const redirectUrl = location\n ? new URL(location, resolvedUrl).href\n : null;\n if (\n redirectUrl &&\n (await isBlockedExtensionUrlWithDns(redirectUrl))\n ) {\n return \"Redirect to private/internal address blocked.\";\n }\n if (redirectUrl && opts.validateUrl && allUsedKeys.length > 0) {\n const allowed = await opts.validateUrl(redirectUrl, allUsedKeys);\n if (!allowed) {\n return \"Redirect URL is not in the allowlist for the referenced keys.\";\n }\n }\n return `HTTP ${response.status} ${response.statusText}\\n\\nRedirect: ${\n redirectUrl ? redactString(redirectUrl, secretValues) : \"(none)\"\n }`;\n }\n\n // Check if caller wants to save to workspace file (before truncation).\n const saveToFilePath =\n typeof (args as Record<string, unknown>).saveToFile === \"string\"\n ? ((args as Record<string, unknown>).saveToFile as string).trim()\n : \"\";\n\n let body: string;\n try {\n // When saving to file allow larger reads (20MB), otherwise cap at proxy limit.\n const readLimit = saveToFilePath\n ? 20 * 1024 * 1024\n : MAX_EXTENSION_PROXY_RESPONSE_SIZE;\n const result = await readResponseTextWithLimit(response, readLimit);\n body = result.text;\n } catch {\n body = \"(could not read response body)\";\n }\n body = redactString(body, secretValues);\n const contentType =\n response.headers.get(\"content-type\")?.split(\";\")[0].trim() ??\n \"text/plain\";\n let displayBody: string;\n let processedMode = \"raw\";\n try {\n const processed = processWebContent({\n url: resolvedUrl,\n body,\n contentType,\n responseMode: String(args.responseMode ?? \"auto\"),\n extract: String(args.extract ?? \"readability\"),\n includeLinks:\n args.includeLinks === undefined\n ? true\n : parseBooleanArg(args.includeLinks),\n search: parseWebContentSearchOptions(args.search),\n maxChars,\n });\n processedMode = processed.mode;\n displayBody = formatWebContentResult(processed);\n } catch (err: any) {\n return `web-request post-processing error: ${err?.message ?? String(err)}`;\n }\n\n // Audit log\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ${response.status} (${elapsed}ms, keys: ${allUsedKeys.join(\",\") || \"none\"})`,\n );\n\n // saveToFile: write full body to workspace and return compact summary.\n if (saveToFilePath) {\n try {\n const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES } =\n await import(\"../workspace-files/store.js\");\n const { getRequestOrgId, getRequestUserEmail } =\n await import(\"../server/request-context.js\");\n const orgId = getRequestOrgId();\n const email = getRequestUserEmail();\n const scope = orgId\n ? { scope: \"org\" as const, scopeId: orgId }\n : email\n ? { scope: \"user\" as const, scopeId: email }\n : null;\n if (!scope)\n throw new Error(\"No authenticated context for saveToFile\");\n await writeWorkspaceFile(\n scope,\n saveToFilePath,\n body,\n contentType,\n {\n maxFileBytes: SAVE_TO_FILE_MAX_BYTES,\n },\n );\n const bytes = Buffer.byteLength(body, \"utf8\");\n const preview = displayBody.slice(0, 2000);\n return JSON.stringify({\n savedToFile: true,\n savedTo: saveToFilePath,\n status: response.status,\n bytes,\n contentType,\n responseMode: processedMode,\n preview:\n preview.length < displayBody.length ? `${preview}…` : preview,\n });\n } catch (saveErr: any) {\n return `saveToFile error: ${saveErr?.message ?? String(saveErr)}\\n\\nHTTP ${response.status} ${response.statusText}\\n\\n${body.slice(0, maxChars)}`;\n }\n }\n\n return `HTTP ${response.status} ${response.statusText}\\n\\n${displayBody}`;\n } catch (err: any) {\n const elapsed = Date.now() - startTime;\n if (err?.name === \"AbortError\") {\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → TIMEOUT (${elapsed}ms)`,\n );\n return `Request timed out after ${timeoutMs}ms.`;\n }\n const message = redactSecrets(\n err?.message ?? String(err),\n secretValues,\n );\n console.log(\n `[fetch-tool] ${method} ${rawUrl} → ERROR: ${message} (${elapsed}ms)`,\n );\n return `Request failed: ${message}`;\n } finally {\n clearTimeout(timeout);\n }\n },\n readOnly: true,\n },\n };\n}\n\nfunction parseBooleanArg(value: unknown): boolean {\n if (typeof value === \"boolean\") return value;\n const normalized = String(value).trim().toLowerCase();\n return normalized === \"true\" || normalized === \"1\" || normalized === \"yes\";\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type WebResponseMode = "auto" | "raw" | "text" | "markdown" | "links" | "metadata" | "matches";
|
|
2
|
+
export type WebExtractMode = "readability" | "all-visible" | "none";
|
|
3
|
+
export type WebSearchSource = "extracted" | "raw";
|
|
4
|
+
export interface WebContentSearchOptions {
|
|
5
|
+
query?: string | string[];
|
|
6
|
+
queries?: string[];
|
|
7
|
+
terms?: string[];
|
|
8
|
+
regex?: string;
|
|
9
|
+
regexFlags?: string;
|
|
10
|
+
caseSensitive?: boolean;
|
|
11
|
+
source?: WebSearchSource;
|
|
12
|
+
maxMatches?: number;
|
|
13
|
+
contextChars?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface WebContentProcessOptions {
|
|
16
|
+
url: string;
|
|
17
|
+
body: string;
|
|
18
|
+
contentType?: string | null;
|
|
19
|
+
responseMode?: string;
|
|
20
|
+
extract?: string;
|
|
21
|
+
includeLinks?: boolean;
|
|
22
|
+
search?: WebContentSearchOptions | null;
|
|
23
|
+
maxChars?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface WebContentLink {
|
|
26
|
+
text: string;
|
|
27
|
+
url: string;
|
|
28
|
+
}
|
|
29
|
+
export interface WebContentMatch {
|
|
30
|
+
kind: "query" | "term" | "regex";
|
|
31
|
+
query: string;
|
|
32
|
+
match: string;
|
|
33
|
+
index: number;
|
|
34
|
+
snippet: string;
|
|
35
|
+
}
|
|
36
|
+
export interface WebContentResult {
|
|
37
|
+
mode: Exclude<WebResponseMode, "auto">;
|
|
38
|
+
extract: WebExtractMode;
|
|
39
|
+
contentType: string | null;
|
|
40
|
+
title?: string;
|
|
41
|
+
excerpt?: string;
|
|
42
|
+
byline?: string;
|
|
43
|
+
siteName?: string;
|
|
44
|
+
lang?: string;
|
|
45
|
+
publishedTime?: string;
|
|
46
|
+
content?: string;
|
|
47
|
+
links?: WebContentLink[];
|
|
48
|
+
matches?: WebContentMatch[];
|
|
49
|
+
totalMatches?: number;
|
|
50
|
+
omittedMatches?: number;
|
|
51
|
+
searchSource?: WebSearchSource;
|
|
52
|
+
truncated?: boolean;
|
|
53
|
+
searchTruncated?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export declare function hasWebContentSearch(search: WebContentSearchOptions | null | undefined): boolean;
|
|
56
|
+
export declare function normalizeWebResponseMode(value: unknown, fallback?: WebResponseMode): WebResponseMode;
|
|
57
|
+
export declare function normalizeWebExtractMode(value: unknown, fallback?: WebExtractMode): WebExtractMode;
|
|
58
|
+
export declare function parseWebContentSearchOptions(value: unknown): WebContentSearchOptions | null;
|
|
59
|
+
export declare function processWebContent(options: WebContentProcessOptions): WebContentResult;
|
|
60
|
+
export declare function formatWebContentResult(result: WebContentResult): string;
|
|
61
|
+
//# sourceMappingURL=web-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-content.d.ts","sourceRoot":"","sources":["../../src/extensions/web-content.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { Readability } from "@mozilla/readability";
|
|
2
|
+
import { parseHTML } from "linkedom/worker";
|
|
3
|
+
import safeRegex from "safe-regex2";
|
|
4
|
+
import TurndownService from "turndown";
|
|
5
|
+
const DEFAULT_MAX_CONTENT_CHARS = 32_000;
|
|
6
|
+
const MAX_SEARCH_SOURCE_CHARS = 500_000;
|
|
7
|
+
const DEFAULT_MAX_MATCHES = 50;
|
|
8
|
+
const MAX_MATCHES = 500;
|
|
9
|
+
const DEFAULT_CONTEXT_CHARS = 160;
|
|
10
|
+
const MAX_CONTEXT_CHARS = 1_000;
|
|
11
|
+
const turndown = new TurndownService({
|
|
12
|
+
headingStyle: "atx",
|
|
13
|
+
bulletListMarker: "-",
|
|
14
|
+
codeBlockStyle: "fenced",
|
|
15
|
+
linkStyle: "inlined",
|
|
16
|
+
});
|
|
17
|
+
turndown.remove(["script", "style", "noscript"]);
|
|
18
|
+
export function hasWebContentSearch(search) {
|
|
19
|
+
if (!search)
|
|
20
|
+
return false;
|
|
21
|
+
return Boolean(normalizeSearchList(search.query).length ||
|
|
22
|
+
normalizeSearchList(search.queries).length ||
|
|
23
|
+
normalizeSearchList(search.terms).length ||
|
|
24
|
+
String(search.regex ?? "").trim());
|
|
25
|
+
}
|
|
26
|
+
export function normalizeWebResponseMode(value, fallback = "auto") {
|
|
27
|
+
const normalized = String(value || fallback).toLowerCase();
|
|
28
|
+
if (normalized === "auto" ||
|
|
29
|
+
normalized === "raw" ||
|
|
30
|
+
normalized === "text" ||
|
|
31
|
+
normalized === "markdown" ||
|
|
32
|
+
normalized === "links" ||
|
|
33
|
+
normalized === "metadata" ||
|
|
34
|
+
normalized === "matches") {
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`Invalid responseMode "${String(value)}". Expected auto, raw, text, markdown, links, metadata, or matches.`);
|
|
38
|
+
}
|
|
39
|
+
export function normalizeWebExtractMode(value, fallback = "readability") {
|
|
40
|
+
const normalized = String(value || fallback).toLowerCase();
|
|
41
|
+
if (normalized === "readability" ||
|
|
42
|
+
normalized === "all-visible" ||
|
|
43
|
+
normalized === "none") {
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Invalid extract "${String(value)}". Expected readability, all-visible, or none.`);
|
|
47
|
+
}
|
|
48
|
+
export function parseWebContentSearchOptions(value) {
|
|
49
|
+
if (!value)
|
|
50
|
+
return null;
|
|
51
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
if (typeof value !== "string")
|
|
55
|
+
return null;
|
|
56
|
+
const trimmed = value.trim();
|
|
57
|
+
if (!trimmed)
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(trimmed);
|
|
61
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return { query: trimmed };
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
export function processWebContent(options) {
|
|
71
|
+
const contentType = options.contentType?.split(";")[0]?.trim() || null;
|
|
72
|
+
const extract = normalizeWebExtractMode(options.extract);
|
|
73
|
+
const requestedMode = normalizeWebResponseMode(options.responseMode);
|
|
74
|
+
const search = options.search ?? null;
|
|
75
|
+
const html = isHtmlResponse(contentType, options.body);
|
|
76
|
+
const mode = resolveMode(requestedMode, html, search);
|
|
77
|
+
const maxChars = normalizeBoundedNumber(options.maxChars, DEFAULT_MAX_CONTENT_CHARS, 1, 200_000);
|
|
78
|
+
const extracted = html && (mode !== "raw" || hasWebContentSearch(search))
|
|
79
|
+
? extractHtml(options.body, options.url, extract)
|
|
80
|
+
: null;
|
|
81
|
+
const baseContent = contentForMode(mode, options.body, extracted);
|
|
82
|
+
const result = {
|
|
83
|
+
mode,
|
|
84
|
+
extract,
|
|
85
|
+
contentType,
|
|
86
|
+
...metadataFromExtraction(extracted),
|
|
87
|
+
};
|
|
88
|
+
if (mode === "links") {
|
|
89
|
+
result.links = extracted?.links ?? [];
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
if (mode === "metadata") {
|
|
93
|
+
if (options.includeLinks)
|
|
94
|
+
result.links = extracted?.links ?? [];
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
if (mode === "matches") {
|
|
98
|
+
const matchResult = findWebContentMatches({
|
|
99
|
+
raw: options.body,
|
|
100
|
+
extracted: baseContent,
|
|
101
|
+
search,
|
|
102
|
+
fallbackSearchSource: html ? "extracted" : "raw",
|
|
103
|
+
});
|
|
104
|
+
Object.assign(result, matchResult);
|
|
105
|
+
if (options.includeLinks)
|
|
106
|
+
result.links = extracted?.links ?? [];
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
const truncated = baseContent.length > maxChars;
|
|
110
|
+
result.content = truncated
|
|
111
|
+
? `${baseContent.slice(0, maxChars)}\n... (truncated)`
|
|
112
|
+
: baseContent;
|
|
113
|
+
result.truncated = truncated;
|
|
114
|
+
if (options.includeLinks)
|
|
115
|
+
result.links = extracted?.links ?? [];
|
|
116
|
+
if (hasWebContentSearch(search)) {
|
|
117
|
+
const matchResult = findWebContentMatches({
|
|
118
|
+
raw: options.body,
|
|
119
|
+
extracted: baseContent,
|
|
120
|
+
search,
|
|
121
|
+
fallbackSearchSource: html ? "extracted" : "raw",
|
|
122
|
+
});
|
|
123
|
+
result.matches = matchResult.matches;
|
|
124
|
+
result.totalMatches = matchResult.totalMatches;
|
|
125
|
+
result.omittedMatches = matchResult.omittedMatches;
|
|
126
|
+
result.searchSource = matchResult.searchSource;
|
|
127
|
+
result.searchTruncated = matchResult.searchTruncated;
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
export function formatWebContentResult(result) {
|
|
132
|
+
if (result.mode === "raw")
|
|
133
|
+
return result.content ?? "";
|
|
134
|
+
const lines = [];
|
|
135
|
+
if (result.title)
|
|
136
|
+
lines.push(`# ${result.title}`, "");
|
|
137
|
+
if (result.siteName)
|
|
138
|
+
lines.push(`Site: ${result.siteName}`);
|
|
139
|
+
if (result.publishedTime)
|
|
140
|
+
lines.push(`Published: ${result.publishedTime}`);
|
|
141
|
+
if (result.excerpt && result.mode !== "metadata") {
|
|
142
|
+
lines.push(`Excerpt: ${result.excerpt}`);
|
|
143
|
+
}
|
|
144
|
+
if (lines.length && lines[lines.length - 1] !== "")
|
|
145
|
+
lines.push("");
|
|
146
|
+
if (result.mode === "matches") {
|
|
147
|
+
lines.push(formatMatches(result));
|
|
148
|
+
}
|
|
149
|
+
else if (result.mode === "links") {
|
|
150
|
+
lines.push(formatLinks(result.links ?? []));
|
|
151
|
+
}
|
|
152
|
+
else if (result.mode === "metadata") {
|
|
153
|
+
lines.push(formatMetadata(result));
|
|
154
|
+
}
|
|
155
|
+
else if (result.content) {
|
|
156
|
+
lines.push(result.content);
|
|
157
|
+
}
|
|
158
|
+
if (result.mode !== "links" &&
|
|
159
|
+
result.mode !== "matches" &&
|
|
160
|
+
result.links?.length) {
|
|
161
|
+
lines.push("", "Links:", formatLinks(result.links));
|
|
162
|
+
}
|
|
163
|
+
if (result.matches?.length && result.mode !== "matches") {
|
|
164
|
+
lines.push("", formatMatches(result));
|
|
165
|
+
}
|
|
166
|
+
return lines.join("\n").trim();
|
|
167
|
+
}
|
|
168
|
+
function resolveMode(requestedMode, html, search) {
|
|
169
|
+
if (requestedMode === "auto") {
|
|
170
|
+
if (hasWebContentSearch(search))
|
|
171
|
+
return "matches";
|
|
172
|
+
return html ? "markdown" : "raw";
|
|
173
|
+
}
|
|
174
|
+
return requestedMode;
|
|
175
|
+
}
|
|
176
|
+
function isHtmlResponse(contentType, body) {
|
|
177
|
+
if (contentType) {
|
|
178
|
+
return (contentType === "text/html" ||
|
|
179
|
+
contentType === "application/xhtml+xml" ||
|
|
180
|
+
contentType.endsWith("+html"));
|
|
181
|
+
}
|
|
182
|
+
return /<!doctype html|<html[\s>]|<body[\s>]|<article[\s>]/i.test(body.slice(0, 2_000));
|
|
183
|
+
}
|
|
184
|
+
function extractHtml(body, url, extract) {
|
|
185
|
+
const document = parseFullDocument(body);
|
|
186
|
+
removeNonContentNodes(document);
|
|
187
|
+
const pageTitle = textOrUndefined(document.title);
|
|
188
|
+
const article = extract === "readability"
|
|
189
|
+
? new Readability(document.cloneNode(true)).parse()
|
|
190
|
+
: null;
|
|
191
|
+
const sourceHtml = extract === "none"
|
|
192
|
+
? body
|
|
193
|
+
: article?.content ||
|
|
194
|
+
document.body?.innerHTML ||
|
|
195
|
+
document.documentElement.innerHTML ||
|
|
196
|
+
body;
|
|
197
|
+
const absoluteHtml = absolutizeHtmlUrls(sourceHtml, url);
|
|
198
|
+
const sourceText = extract === "none"
|
|
199
|
+
? htmlToPlainText(body)
|
|
200
|
+
: article?.textContent ||
|
|
201
|
+
document.body?.textContent ||
|
|
202
|
+
document.documentElement.textContent ||
|
|
203
|
+
"";
|
|
204
|
+
const markdown = htmlToMarkdown(absoluteHtml);
|
|
205
|
+
return {
|
|
206
|
+
html: absoluteHtml,
|
|
207
|
+
text: normalizeWhitespace(sourceText),
|
|
208
|
+
markdown,
|
|
209
|
+
links: collectLinks(absoluteHtml, url),
|
|
210
|
+
title: textOrUndefined(article?.title) ?? pageTitle,
|
|
211
|
+
excerpt: textOrUndefined(article?.excerpt),
|
|
212
|
+
byline: textOrUndefined(article?.byline),
|
|
213
|
+
siteName: textOrUndefined(article?.siteName),
|
|
214
|
+
lang: textOrUndefined(article?.lang),
|
|
215
|
+
publishedTime: textOrUndefined(article?.publishedTime),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function htmlToPlainText(html) {
|
|
219
|
+
const document = parseFullDocument(html);
|
|
220
|
+
removeNonContentNodes(document);
|
|
221
|
+
return normalizeWhitespace(document.body?.textContent ?? "");
|
|
222
|
+
}
|
|
223
|
+
function htmlToMarkdown(html) {
|
|
224
|
+
return normalizeMarkdown(turndown.turndown(html));
|
|
225
|
+
}
|
|
226
|
+
function removeNonContentNodes(document) {
|
|
227
|
+
for (const selector of [
|
|
228
|
+
"script",
|
|
229
|
+
"style",
|
|
230
|
+
"noscript",
|
|
231
|
+
"svg",
|
|
232
|
+
"template",
|
|
233
|
+
"iframe",
|
|
234
|
+
]) {
|
|
235
|
+
for (const node of [...document.querySelectorAll(selector)]) {
|
|
236
|
+
node.remove();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function parseFullDocument(html) {
|
|
241
|
+
return parseHTML(html).document;
|
|
242
|
+
}
|
|
243
|
+
function parseHtmlFragment(html) {
|
|
244
|
+
return parseHTML(`<!doctype html><html><head></head><body>${html}</body></html>`).document;
|
|
245
|
+
}
|
|
246
|
+
function absolutizeHtmlUrls(html, url) {
|
|
247
|
+
const document = parseHtmlFragment(html);
|
|
248
|
+
for (const anchor of [...document.querySelectorAll("a[href]")]) {
|
|
249
|
+
const href = anchor.getAttribute("href");
|
|
250
|
+
if (!href)
|
|
251
|
+
continue;
|
|
252
|
+
try {
|
|
253
|
+
anchor.setAttribute("href", new URL(href, url).href);
|
|
254
|
+
}
|
|
255
|
+
catch { }
|
|
256
|
+
}
|
|
257
|
+
for (const image of [...document.querySelectorAll("img[src]")]) {
|
|
258
|
+
const src = image.getAttribute("src");
|
|
259
|
+
if (!src)
|
|
260
|
+
continue;
|
|
261
|
+
try {
|
|
262
|
+
image.setAttribute("src", new URL(src, url).href);
|
|
263
|
+
}
|
|
264
|
+
catch { }
|
|
265
|
+
}
|
|
266
|
+
return document.body?.innerHTML || html;
|
|
267
|
+
}
|
|
268
|
+
function collectLinks(html, url) {
|
|
269
|
+
const document = parseHtmlFragment(html);
|
|
270
|
+
const links = [];
|
|
271
|
+
const seen = new Set();
|
|
272
|
+
for (const anchor of [...document.querySelectorAll("a[href]")]) {
|
|
273
|
+
const href = anchor.getAttribute("href");
|
|
274
|
+
if (!href)
|
|
275
|
+
continue;
|
|
276
|
+
let absolute;
|
|
277
|
+
try {
|
|
278
|
+
absolute = new URL(href, url).href;
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (seen.has(absolute))
|
|
284
|
+
continue;
|
|
285
|
+
seen.add(absolute);
|
|
286
|
+
links.push({
|
|
287
|
+
text: normalizeWhitespace(anchor.textContent || absolute).slice(0, 200),
|
|
288
|
+
url: absolute,
|
|
289
|
+
});
|
|
290
|
+
if (links.length >= 200)
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
return links;
|
|
294
|
+
}
|
|
295
|
+
function contentForMode(mode, raw, extracted) {
|
|
296
|
+
if (mode === "raw")
|
|
297
|
+
return raw;
|
|
298
|
+
if (mode === "text")
|
|
299
|
+
return extracted?.text ?? raw;
|
|
300
|
+
if (mode === "markdown" || mode === "matches") {
|
|
301
|
+
return extracted?.markdown || extracted?.text || raw;
|
|
302
|
+
}
|
|
303
|
+
return "";
|
|
304
|
+
}
|
|
305
|
+
function metadataFromExtraction(extracted) {
|
|
306
|
+
if (!extracted)
|
|
307
|
+
return {};
|
|
308
|
+
return {
|
|
309
|
+
...(extracted.title ? { title: extracted.title } : {}),
|
|
310
|
+
...(extracted.excerpt ? { excerpt: extracted.excerpt } : {}),
|
|
311
|
+
...(extracted.byline ? { byline: extracted.byline } : {}),
|
|
312
|
+
...(extracted.siteName ? { siteName: extracted.siteName } : {}),
|
|
313
|
+
...(extracted.lang ? { lang: extracted.lang } : {}),
|
|
314
|
+
...(extracted.publishedTime
|
|
315
|
+
? { publishedTime: extracted.publishedTime }
|
|
316
|
+
: {}),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function findWebContentMatches(options) {
|
|
320
|
+
const search = options.search ?? {};
|
|
321
|
+
const sourceMode = search.source ?? options.fallbackSearchSource;
|
|
322
|
+
const source = sourceMode === "raw" ? options.raw : options.extracted;
|
|
323
|
+
const searchTruncated = source.length > MAX_SEARCH_SOURCE_CHARS;
|
|
324
|
+
const searchable = searchTruncated
|
|
325
|
+
? source.slice(0, MAX_SEARCH_SOURCE_CHARS)
|
|
326
|
+
: source;
|
|
327
|
+
const maxMatches = normalizeBoundedNumber(search.maxMatches, DEFAULT_MAX_MATCHES, 1, MAX_MATCHES);
|
|
328
|
+
const contextChars = normalizeBoundedNumber(search.contextChars, DEFAULT_CONTEXT_CHARS, 0, MAX_CONTEXT_CHARS);
|
|
329
|
+
const matches = [];
|
|
330
|
+
let totalMatches = 0;
|
|
331
|
+
const caseSensitive = Boolean(search.caseSensitive);
|
|
332
|
+
const addMatch = (match) => {
|
|
333
|
+
totalMatches += 1;
|
|
334
|
+
if (matches.length >= maxMatches)
|
|
335
|
+
return;
|
|
336
|
+
matches.push({
|
|
337
|
+
...match,
|
|
338
|
+
snippet: makeSnippet(searchable, match.index, contextChars),
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
for (const query of [
|
|
342
|
+
...normalizeSearchList(search.query),
|
|
343
|
+
...normalizeSearchList(search.queries),
|
|
344
|
+
]) {
|
|
345
|
+
findLiteralMatches(searchable, query, caseSensitive, (index, match) => addMatch({ kind: "query", query, match, index }));
|
|
346
|
+
}
|
|
347
|
+
for (const term of normalizeSearchList(search.terms)) {
|
|
348
|
+
findLiteralMatches(searchable, term, caseSensitive, (index, match) => addMatch({ kind: "term", query: term, match, index }));
|
|
349
|
+
}
|
|
350
|
+
const regexPattern = String(search.regex ?? "").trim();
|
|
351
|
+
if (regexPattern) {
|
|
352
|
+
if (!safeRegex(regexPattern, { limit: 25 })) {
|
|
353
|
+
throw new Error("Unsafe regex rejected. Use a simpler literal query/terms search or a bounded run-code workflow.");
|
|
354
|
+
}
|
|
355
|
+
const regex = new RegExp(regexPattern, normalizeRegexFlags(search.regexFlags, caseSensitive));
|
|
356
|
+
let match;
|
|
357
|
+
while ((match = regex.exec(searchable)) &&
|
|
358
|
+
typeof match.index === "number") {
|
|
359
|
+
addMatch({
|
|
360
|
+
kind: "regex",
|
|
361
|
+
query: regexPattern,
|
|
362
|
+
match: match[0],
|
|
363
|
+
index: match.index,
|
|
364
|
+
});
|
|
365
|
+
if (match[0] === "")
|
|
366
|
+
regex.lastIndex += 1;
|
|
367
|
+
if (totalMatches >= MAX_MATCHES * 10)
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
matches.sort((a, b) => a.index - b.index);
|
|
372
|
+
return {
|
|
373
|
+
matches,
|
|
374
|
+
totalMatches,
|
|
375
|
+
omittedMatches: Math.max(0, totalMatches - matches.length),
|
|
376
|
+
searchSource: sourceMode,
|
|
377
|
+
searchTruncated,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function findLiteralMatches(source, query, caseSensitive, onMatch) {
|
|
381
|
+
if (!query)
|
|
382
|
+
return;
|
|
383
|
+
const haystack = caseSensitive ? source : source.toLowerCase();
|
|
384
|
+
const needle = caseSensitive ? query : query.toLowerCase();
|
|
385
|
+
let from = 0;
|
|
386
|
+
while (from <= haystack.length) {
|
|
387
|
+
const index = haystack.indexOf(needle, from);
|
|
388
|
+
if (index < 0)
|
|
389
|
+
break;
|
|
390
|
+
onMatch(index, source.slice(index, index + query.length));
|
|
391
|
+
from = index + Math.max(1, needle.length);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function normalizeRegexFlags(flags, caseSensitive) {
|
|
395
|
+
const allowed = new Set(["d", "g", "i", "m", "s", "u", "v", "y"]);
|
|
396
|
+
const result = new Set(["g"]);
|
|
397
|
+
if (!caseSensitive)
|
|
398
|
+
result.add("i");
|
|
399
|
+
for (const flag of String(flags ?? "")) {
|
|
400
|
+
if (allowed.has(flag))
|
|
401
|
+
result.add(flag);
|
|
402
|
+
}
|
|
403
|
+
result.delete("y");
|
|
404
|
+
return [...result].join("");
|
|
405
|
+
}
|
|
406
|
+
function normalizeSearchList(value) {
|
|
407
|
+
if (value === undefined || value === null)
|
|
408
|
+
return [];
|
|
409
|
+
const values = Array.isArray(value) ? value : [value];
|
|
410
|
+
return values
|
|
411
|
+
.map((item) => String(item).trim())
|
|
412
|
+
.filter((item) => item.length > 0);
|
|
413
|
+
}
|
|
414
|
+
function normalizeBoundedNumber(value, fallback, min, max) {
|
|
415
|
+
const numeric = Number(value);
|
|
416
|
+
if (!Number.isFinite(numeric))
|
|
417
|
+
return fallback;
|
|
418
|
+
return Math.max(min, Math.min(Math.floor(numeric), max));
|
|
419
|
+
}
|
|
420
|
+
function makeSnippet(source, index, contextChars) {
|
|
421
|
+
const start = Math.max(0, index - contextChars);
|
|
422
|
+
const end = Math.min(source.length, index + contextChars);
|
|
423
|
+
const prefix = start > 0 ? "..." : "";
|
|
424
|
+
const suffix = end < source.length ? "..." : "";
|
|
425
|
+
return `${prefix}${normalizeWhitespace(source.slice(start, end))}${suffix}`;
|
|
426
|
+
}
|
|
427
|
+
function normalizeMarkdown(value) {
|
|
428
|
+
return value
|
|
429
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
430
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
431
|
+
.trim();
|
|
432
|
+
}
|
|
433
|
+
function normalizeWhitespace(value) {
|
|
434
|
+
return value.replace(/\s+/g, " ").trim();
|
|
435
|
+
}
|
|
436
|
+
function textOrUndefined(value) {
|
|
437
|
+
const normalized = normalizeWhitespace(value ?? "");
|
|
438
|
+
return normalized || undefined;
|
|
439
|
+
}
|
|
440
|
+
function formatMatches(result) {
|
|
441
|
+
const matches = result.matches ?? [];
|
|
442
|
+
const header = `Matches: ${matches.length}${result.totalMatches !== undefined ? ` shown of ${result.totalMatches}` : ""}${result.omittedMatches ? ` (${result.omittedMatches} omitted)` : ""}`;
|
|
443
|
+
if (!matches.length)
|
|
444
|
+
return header;
|
|
445
|
+
return [
|
|
446
|
+
header,
|
|
447
|
+
...matches.map((match, index) => `${index + 1}. ${match.kind} ${JSON.stringify(match.query)} at ${match.index}: ${match.snippet}`),
|
|
448
|
+
].join("\n");
|
|
449
|
+
}
|
|
450
|
+
function formatLinks(links) {
|
|
451
|
+
if (!links.length)
|
|
452
|
+
return "(none)";
|
|
453
|
+
return links
|
|
454
|
+
.map((link, index) => `${index + 1}. [${link.text || link.url}](${link.url})`)
|
|
455
|
+
.join("\n");
|
|
456
|
+
}
|
|
457
|
+
function formatMetadata(result) {
|
|
458
|
+
return JSON.stringify({
|
|
459
|
+
title: result.title,
|
|
460
|
+
excerpt: result.excerpt,
|
|
461
|
+
byline: result.byline,
|
|
462
|
+
siteName: result.siteName,
|
|
463
|
+
lang: result.lang,
|
|
464
|
+
publishedTime: result.publishedTime,
|
|
465
|
+
contentType: result.contentType,
|
|
466
|
+
}, null, 2);
|
|
467
|
+
}
|
|
468
|
+
//# sourceMappingURL=web-content.js.map
|