@ai-agent-forge/plugin-search 0.0.0-stage → 0.88.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +56 -2
  2. package/agent-forge.json +19 -0
  3. package/dist/cache.d.ts +103 -0
  4. package/dist/cache.d.ts.map +1 -0
  5. package/dist/cache.js +666 -0
  6. package/dist/cache.js.map +1 -0
  7. package/dist/capability.d.ts +22 -0
  8. package/dist/capability.d.ts.map +1 -0
  9. package/dist/capability.js +213 -0
  10. package/dist/capability.js.map +1 -0
  11. package/dist/entry.d.ts +3 -0
  12. package/dist/entry.d.ts.map +1 -0
  13. package/dist/entry.js +3 -0
  14. package/dist/entry.js.map +1 -0
  15. package/dist/extract.d.ts +18 -0
  16. package/dist/extract.d.ts.map +1 -0
  17. package/dist/extract.js +120 -0
  18. package/dist/extract.js.map +1 -0
  19. package/dist/http.d.ts +65 -0
  20. package/dist/http.d.ts.map +1 -0
  21. package/dist/http.js +307 -0
  22. package/dist/http.js.map +1 -0
  23. package/dist/index.d.ts +5 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +5 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/limits.d.ts +39 -0
  28. package/dist/limits.d.ts.map +1 -0
  29. package/dist/limits.js +43 -0
  30. package/dist/limits.js.map +1 -0
  31. package/dist/model-call.d.ts +10 -0
  32. package/dist/model-call.d.ts.map +1 -0
  33. package/dist/model-call.js +46 -0
  34. package/dist/model-call.js.map +1 -0
  35. package/dist/model-search.d.ts +14 -0
  36. package/dist/model-search.d.ts.map +1 -0
  37. package/dist/model-search.js +85 -0
  38. package/dist/model-search.js.map +1 -0
  39. package/dist/summarize.d.ts +40 -0
  40. package/dist/summarize.d.ts.map +1 -0
  41. package/dist/summarize.js +148 -0
  42. package/dist/summarize.js.map +1 -0
  43. package/dist/text.d.ts +38 -0
  44. package/dist/text.d.ts.map +1 -0
  45. package/dist/text.js +135 -0
  46. package/dist/text.js.map +1 -0
  47. package/dist/types.d.ts +38 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +2 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/url-search.d.ts +36 -0
  52. package/dist/url-search.d.ts.map +1 -0
  53. package/dist/url-search.js +335 -0
  54. package/dist/url-search.js.map +1 -0
  55. package/package.json +57 -5
  56. package/plugin.json +12 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Small deterministic helpers shared by the URL and model search paths.\n */\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function byteLength(text: string): number {\n\treturn Buffer.byteLength(text, \"utf8\");\n}\n\n/**\n * AbortSignal.aborted is mutable, but TS narrows a readonly property to its\n * checked literal after an early return; read it through a call so later checks\n * still see a cancellation that arrives while awaiting.\n */\nexport function isAborted(signal: AbortSignal | undefined): boolean {\n\treturn signal?.aborted === true;\n}\n\n/** Code-point truncation: never splits a surrogate pair. */\nexport function truncateText(text: string, maxChars: number): string {\n\tif (maxChars <= 0) return \"\";\n\tconst points = [...text];\n\tif (points.length <= maxChars) return text;\n\treturn `${points.slice(0, Math.max(maxChars - 1, 0)).join(\"\")}…`;\n}\n\n/** Truncate by UTF-8 bytes without splitting a code point; appends one ellipsis. */\nexport function truncateToBytes(text: string, maxBytes: number): { text: string; truncated: boolean } {\n\tif (maxBytes <= 0) return { text: \"\", truncated: text !== \"\" };\n\tif (byteLength(text) <= maxBytes) return { text, truncated: false };\n\tconst buffer = Buffer.from(text, \"utf8\");\n\tlet end = Math.min(maxBytes, buffer.length);\n\t// Back up to a byte boundary; a lead byte whose sequence extends past the\n\t// limit is removed by dropping the replacement character below.\n\twhile (end > 0 && ((buffer[end] ?? 0) & 0xc0) === 0x80) end -= 1;\n\tlet kept = buffer.subarray(0, Math.max(0, end)).toString(\"utf8\");\n\tif (kept.endsWith(\"�\")) kept = kept.slice(0, -1);\n\treturn { text: `${kept}…`, truncated: true };\n}\n\n/** True when a URL carries a username or password; such URLs never enter the cache. */\nexport function urlHasUserInfo(raw: string): boolean {\n\ttry {\n\t\tconst url = new URL(raw);\n\t\treturn url.username !== \"\" || url.password !== \"\";\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Normalize for dedupe/citation: http(s) only, lowercase host, default port and\n * fragment removed, query preserved verbatim.\n */\nexport function normalizeUrl(raw: string): string {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(raw);\n\t} catch {\n\t\tthrow new Error(`unparseable URL: ${JSON.stringify(raw)}`);\n\t}\n\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n\t\tthrow new Error(`only http(s) URLs are supported: ${JSON.stringify(raw)}`);\n\t}\n\tlet host = url.hostname.toLowerCase();\n\tif (host.endsWith(\".\")) host = host.slice(0, -1);\n\tif ((url.protocol === \"http:\" && url.port === \"80\") || (url.protocol === \"https:\" && url.port === \"443\")) {\n\t\turl.port = \"\";\n\t}\n\turl.hostname = host;\n\turl.hash = \"\";\n\treturn url.toString();\n}\n\nexport interface UrlValidationResultV1 {\n\treadonly urls: readonly string[];\n\treadonly invalid: readonly { readonly url: string; readonly message: string }[];\n}\n\nexport function dedupeUrls(rawUrls: readonly string[]): UrlValidationResultV1 {\n\tconst seen = new Set<string>();\n\tconst urls: string[] = [];\n\tconst invalid: { url: string; message: string }[] = [];\n\tfor (const raw of rawUrls) {\n\t\ttry {\n\t\t\tconst normalized = normalizeUrl(raw);\n\t\t\tif (seen.has(normalized)) continue;\n\t\t\tseen.add(normalized);\n\t\t\turls.push(normalized);\n\t\t} catch (error) {\n\t\t\tinvalid.push({ url: raw, message: error instanceof Error ? error.message : String(error) });\n\t\t}\n\t}\n\treturn { urls, invalid };\n}\n\nconst QUERY_SEPARATOR = /[\\s,,。;;::!!??()[\\]{}\"'“”‘’<>《》/\\\\|]+/u;\n\n/** Lexical terms for deterministic passage scoring; CJK substrings work as-is. */\nexport function queryTerms(query: string): readonly string[] {\n\tconst lowered = query.toLowerCase();\n\tconst terms = lowered\n\t\t.split(QUERY_SEPARATOR)\n\t\t.map((term) => term.trim())\n\t\t.filter((term) => term.length >= 2);\n\tconst unique = [...new Set(terms)];\n\tif (unique.length === 0 && lowered.trim() !== \"\") unique.push(lowered.trim());\n\treturn unique.slice(0, 64);\n}\n\n/** Parse one JSON object, tolerating a single surrounding markdown fence. */\nexport function parseJsonObject(text: string): Record<string, unknown> {\n\tlet value = text.trim();\n\tif (value.startsWith(\"\\uFEFF\")) value = value.slice(1).trim();\n\tif (value.startsWith(\"```\")) {\n\t\tconst firstNewline = value.indexOf(\"\\n\");\n\t\tif (firstNewline === -1) throw new Error(\"JSON fence is not closed\");\n\t\tvalue = value.slice(firstNewline + 1).trimEnd();\n\t\tif (!value.endsWith(\"```\")) throw new Error(\"JSON fence is not closed\");\n\t\tvalue = value.slice(0, -3).trim();\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(value);\n\t} catch (error) {\n\t\tthrow new Error(`invalid JSON: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (!isRecord(parsed)) throw new Error(\"JSON output must be an object\");\n\treturn parsed;\n}\n"]}
@@ -0,0 +1,38 @@
1
+ import type { SearchMode } from "./limits.ts";
2
+ import type { SearchSourceV1 } from "./summarize.ts";
3
+ export interface SearchErrorV1 {
4
+ readonly code: string;
5
+ readonly message: string;
6
+ readonly httpStatus?: number;
7
+ }
8
+ export interface SearchPageDetailsV1 {
9
+ readonly url: string;
10
+ readonly finalUrl?: string;
11
+ readonly title?: string;
12
+ readonly status: "ok" | "failed" | "skipped";
13
+ readonly error?: SearchErrorV1;
14
+ readonly contentType?: string;
15
+ readonly bytesFetched?: number;
16
+ readonly bytesReturned?: number;
17
+ readonly truncated: boolean;
18
+ readonly cache: "hit" | "miss";
19
+ }
20
+ export interface SearchDetailsV1 {
21
+ readonly version: 1;
22
+ readonly mode: SearchMode;
23
+ readonly query: string;
24
+ readonly fetchedAt: string;
25
+ readonly status: "ok" | "partial" | "failed";
26
+ readonly summary: string;
27
+ readonly sources: readonly SearchSourceV1[];
28
+ readonly summaryModel?: {
29
+ readonly provider: string;
30
+ readonly id: string;
31
+ };
32
+ readonly totalBytes: number;
33
+ readonly truncated: boolean;
34
+ readonly pages?: readonly SearchPageDetailsV1[];
35
+ readonly summaryCache?: "hit" | "miss";
36
+ readonly error?: SearchErrorV1;
37
+ }
38
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,QAAQ,CAAC,YAAY,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAChD,QAAQ,CAAC,YAAY,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAC/B","sourcesContent":["import type { SearchMode } from \"./limits.ts\";\nimport type { SearchSourceV1 } from \"./summarize.ts\";\n\nexport interface SearchErrorV1 {\n\treadonly code: string;\n\treadonly message: string;\n\treadonly httpStatus?: number;\n}\n\nexport interface SearchPageDetailsV1 {\n\treadonly url: string;\n\treadonly finalUrl?: string;\n\treadonly title?: string;\n\treadonly status: \"ok\" | \"failed\" | \"skipped\";\n\treadonly error?: SearchErrorV1;\n\treadonly contentType?: string;\n\treadonly bytesFetched?: number;\n\treadonly bytesReturned?: number;\n\treadonly truncated: boolean;\n\treadonly cache: \"hit\" | \"miss\";\n}\n\nexport interface SearchDetailsV1 {\n\treadonly version: 1;\n\treadonly mode: SearchMode;\n\treadonly query: string;\n\treadonly fetchedAt: string;\n\treadonly status: \"ok\" | \"partial\" | \"failed\";\n\treadonly summary: string;\n\treadonly sources: readonly SearchSourceV1[];\n\treadonly summaryModel?: { readonly provider: string; readonly id: string };\n\treadonly totalBytes: number;\n\treadonly truncated: boolean;\n\treadonly pages?: readonly SearchPageDetailsV1[];\n\treadonly summaryCache?: \"hit\" | \"miss\";\n\treadonly error?: SearchErrorV1;\n}\n"]}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { SearchMode } from \"./limits.ts\";\nimport type { SearchSourceV1 } from \"./summarize.ts\";\n\nexport interface SearchErrorV1 {\n\treadonly code: string;\n\treadonly message: string;\n\treadonly httpStatus?: number;\n}\n\nexport interface SearchPageDetailsV1 {\n\treadonly url: string;\n\treadonly finalUrl?: string;\n\treadonly title?: string;\n\treadonly status: \"ok\" | \"failed\" | \"skipped\";\n\treadonly error?: SearchErrorV1;\n\treadonly contentType?: string;\n\treadonly bytesFetched?: number;\n\treadonly bytesReturned?: number;\n\treadonly truncated: boolean;\n\treadonly cache: \"hit\" | \"miss\";\n}\n\nexport interface SearchDetailsV1 {\n\treadonly version: 1;\n\treadonly mode: SearchMode;\n\treadonly query: string;\n\treadonly fetchedAt: string;\n\treadonly status: \"ok\" | \"partial\" | \"failed\";\n\treadonly summary: string;\n\treadonly sources: readonly SearchSourceV1[];\n\treadonly summaryModel?: { readonly provider: string; readonly id: string };\n\treadonly totalBytes: number;\n\treadonly truncated: boolean;\n\treadonly pages?: readonly SearchPageDetailsV1[];\n\treadonly summaryCache?: \"hit\" | \"miss\";\n\treadonly error?: SearchErrorV1;\n}\n"]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * URL mode: fetch/extract/select passages, then summarize with the current main
3
+ * model in a clean isolated completion (no tools, webSearch off).
4
+ */
5
+ import type { CapabilityAPI, NetworkRouteConfigV1 } from "@agent-forge/plugin-sdk";
6
+ import type { PageCache, SummaryCache } from "./cache.ts";
7
+ import { type FetchImpl } from "./http.ts";
8
+ import { type SearchSourceV1 } from "./summarize.ts";
9
+ import type { SearchDetailsV1 } from "./types.ts";
10
+ export interface UrlSearchDepsV1 {
11
+ readonly now: () => number;
12
+ readonly pageCache: PageCache;
13
+ readonly summaryCache: SummaryCache<CachedSummaryV1>;
14
+ readonly fetchImpl?: FetchImpl;
15
+ readonly network?: NetworkRouteConfigV1;
16
+ }
17
+ export interface RunUrlSearchInputV1 {
18
+ readonly query: string;
19
+ readonly urls: readonly string[];
20
+ readonly api: CapabilityAPI;
21
+ readonly signal?: AbortSignal;
22
+ readonly deps: UrlSearchDepsV1;
23
+ }
24
+ export interface CachedSummaryV1 {
25
+ readonly summary: string;
26
+ readonly sources: readonly SearchSourceV1[];
27
+ readonly summaryModel: {
28
+ readonly provider: string;
29
+ readonly id: string;
30
+ };
31
+ readonly truncated: boolean;
32
+ }
33
+ export declare function runUrlSearch(input: RunUrlSearchInputV1): Promise<SearchDetailsV1>;
34
+ /** Normalize one URL for tests and callers that need the same canonical form. */
35
+ export declare function canonicalUrl(raw: string): string;
36
+ //# sourceMappingURL=url-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-search.d.ts","sourceRoot":"","sources":["../src/url-search.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACnF,OAAO,KAAK,EAAgB,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAExE,OAAO,EAAsC,KAAK,SAAS,EAAwB,MAAM,WAAW,CAAC;AAGrG,OAAO,EAGN,KAAK,cAAc,EAInB,MAAM,gBAAgB,CAAC;AAUxB,OAAO,KAAK,EAAE,eAAe,EAAuB,MAAM,YAAY,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,oBAAoB,CAAC;CACxC;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,QAAQ,CAAC,YAAY,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC5B;AA+GD,wBAAsB,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC,CA8SvF;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD","sourcesContent":["/**\n * URL mode: fetch/extract/select passages, then summarize with the current main\n * model in a clean isolated completion (no tools, webSearch off).\n */\n\nimport type { CapabilityAPI, NetworkRouteConfigV1 } from \"@agent-forge/plugin-sdk\";\nimport type { CachedPageV1, PageCache, SummaryCache } from \"./cache.ts\";\nimport { selectPassages } from \"./extract.ts\";\nimport { DispatcherPool, type FetchedPageV1, type FetchImpl, fetchPage, PageError } from \"./http.ts\";\nimport { SEARCH_LIMITS } from \"./limits.ts\";\nimport { createFetchBudgetSignal, createModelCallSignal } from \"./model-call.ts\";\nimport {\n\tbuildUrlSummaryPrompt,\n\tparseUrlSummaryOutput,\n\ttype SearchSourceV1,\n\tSUMMARY_PROMPT_VERSION,\n\tSummaryError,\n\tURL_SUMMARY_SYSTEM_PROMPT,\n} from \"./summarize.ts\";\nimport {\n\tbyteLength,\n\tdedupeUrls,\n\tisAborted,\n\tnormalizeUrl,\n\tqueryTerms,\n\ttruncateToBytes,\n\turlHasUserInfo,\n} from \"./text.ts\";\nimport type { SearchDetailsV1, SearchPageDetailsV1 } from \"./types.ts\";\n\nexport interface UrlSearchDepsV1 {\n\treadonly now: () => number;\n\treadonly pageCache: PageCache;\n\treadonly summaryCache: SummaryCache<CachedSummaryV1>;\n\treadonly fetchImpl?: FetchImpl;\n\treadonly network?: NetworkRouteConfigV1;\n}\n\nexport interface RunUrlSearchInputV1 {\n\treadonly query: string;\n\treadonly urls: readonly string[];\n\treadonly api: CapabilityAPI;\n\treadonly signal?: AbortSignal;\n\treadonly deps: UrlSearchDepsV1;\n}\n\nexport interface CachedSummaryV1 {\n\treadonly summary: string;\n\treadonly sources: readonly SearchSourceV1[];\n\treadonly summaryModel: { readonly provider: string; readonly id: string };\n\treadonly truncated: boolean;\n}\n\ninterface PageWorkV1 {\n\treadonly url: string;\n\tdetail: SearchPageDetailsV1;\n\ttext?: string;\n\treadonly title?: string;\n\texcerpt?: string;\n}\n\nconst EXCERPT_CHARS_PER_PAGE = 24 * 1200;\n\nfunction pageFailure(url: string, error: PageError | Error): PageWorkV1 {\n\tconst pageError =\n\t\terror instanceof PageError\n\t\t\t? error\n\t\t\t: new PageError(\"network_error\", error instanceof Error ? error.message : String(error));\n\treturn {\n\t\turl,\n\t\tdetail: {\n\t\t\turl,\n\t\t\tstatus: \"failed\",\n\t\t\terror: {\n\t\t\t\tcode: pageError.code,\n\t\t\t\tmessage: pageError.message,\n\t\t\t\t...(pageError.httpStatus === undefined ? {} : { httpStatus: pageError.httpStatus }),\n\t\t\t},\n\t\t\ttruncated: false,\n\t\t\tcache: \"miss\",\n\t\t},\n\t};\n}\n\nfunction detailFromCache(url: string, entry: CachedPageV1): PageWorkV1 {\n\treturn {\n\t\turl,\n\t\tdetail: {\n\t\t\turl,\n\t\t\t...(entry.title === undefined ? {} : { title: entry.title }),\n\t\t\tstatus: \"ok\",\n\t\t\tbytesReturned: byteLength(entry.text),\n\t\t\ttruncated: entry.truncated,\n\t\t\tcache: \"hit\",\n\t\t},\n\t\ttext: entry.text,\n\t\t...(entry.title === undefined ? {} : { title: entry.title }),\n\t};\n}\n\nfunction detailFromPage(url: string, page: FetchedPageV1): PageWorkV1 {\n\treturn {\n\t\turl,\n\t\tdetail: {\n\t\t\turl,\n\t\t\tfinalUrl: page.finalUrl,\n\t\t\t...(page.title === undefined ? {} : { title: page.title }),\n\t\t\tstatus: \"ok\",\n\t\t\tcontentType: page.contentType,\n\t\t\tbytesFetched: page.bytesFetched,\n\t\t\tbytesReturned: byteLength(page.text),\n\t\t\ttruncated: page.truncated,\n\t\t\tcache: \"miss\",\n\t\t},\n\t\ttext: page.text,\n\t\t...(page.title === undefined ? {} : { title: page.title }),\n\t};\n}\n\nasync function mapWithConcurrency<T, R>(\n\titems: readonly T[],\n\tlimit: number,\n\tfn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n\tconst results = new Array<R>(items.length);\n\tlet next = 0;\n\tconst workers = Array.from({ length: Math.min(limit, items.length) }, async () => {\n\t\twhile (true) {\n\t\t\tconst index = next;\n\t\t\tnext += 1;\n\t\t\tif (index >= items.length) return;\n\t\t\tconst item = items[index];\n\t\t\tif (item === undefined) return;\n\t\t\tresults[index] = await fn(item, index);\n\t\t}\n\t});\n\tawait Promise.all(workers);\n\treturn results;\n}\n\nfunction failedDetails(\n\tquery: string,\n\tnow: () => number,\n\tpages: readonly SearchPageDetailsV1[],\n\tcode: string,\n\tmessage: string,\n): SearchDetailsV1 {\n\treturn {\n\t\tversion: 1,\n\t\tmode: \"url\",\n\t\tquery,\n\t\tfetchedAt: new Date(now()).toISOString(),\n\t\tstatus: \"failed\",\n\t\tsummary: \"\",\n\t\tsources: [],\n\t\ttotalBytes: pages.reduce((sum, page) => sum + (page.bytesFetched ?? 0), 0),\n\t\ttruncated: pages.some((page) => page.truncated),\n\t\tpages,\n\t\terror: { code, message },\n\t};\n}\n\nexport async function runUrlSearch(input: RunUrlSearchInputV1): Promise<SearchDetailsV1> {\n\tconst now = input.deps.now;\n\tif (isAborted(input.signal)) {\n\t\treturn failedDetails(input.query, now, [], \"cancelled\", \"URL search was cancelled\");\n\t}\n\tconst validated = dedupeUrls(input.urls);\n\tconst terms = queryTerms(input.query);\n\tconst budget = createFetchBudgetSignal(input.signal);\n\tconst dispatchers = new DispatcherPool(input.deps.network, input.deps.fetchImpl === undefined);\n\tlet rawBudget = SEARCH_LIMITS.maxTotalRawBytes;\n\n\tconst fetchOne = async (url: string): Promise<PageWorkV1> => {\n\t\tlet fresh: CachedPageV1 | undefined;\n\t\ttry {\n\t\t\tfresh = await input.deps.pageCache.getFresh(url, now());\n\t\t} catch {\n\t\t\t// The page cache is an optimization; a broken cache must not fail the read.\n\t\t}\n\t\tif (fresh !== undefined) return detailFromCache(url, fresh);\n\t\tconst pageBudget = Math.min(SEARCH_LIMITS.maxRawBytesPerPage, rawBudget);\n\t\tif (pageBudget <= 0) {\n\t\t\treturn {\n\t\t\t\turl,\n\t\t\t\tdetail: { url, status: \"skipped\", truncated: true, cache: \"miss\" },\n\t\t\t};\n\t\t}\n\t\t// Reserve before dispatch so concurrent workers and failed reads cannot\n\t\t// exceed the shared raw-byte budget; refund only the unused part.\n\t\trawBudget -= pageBudget;\n\t\tconst refund = (used: number): void => {\n\t\t\trawBudget = Math.min(SEARCH_LIMITS.maxTotalRawBytes, rawBudget + Math.max(0, pageBudget - used));\n\t\t};\n\t\ttry {\n\t\t\tconst page = await fetchPage({\n\t\t\t\turl,\n\t\t\t\tmaxTextBytes: SEARCH_LIMITS.maxExtractedBytesPerPage,\n\t\t\t\tmaxRawBytes: pageBudget,\n\t\t\t\ttimeoutMs: SEARCH_LIMITS.urlTimeoutMs,\n\t\t\t\tsignal: budget.signal,\n\t\t\t\tnetwork: input.deps.network,\n\t\t\t\tfetchImpl: input.deps.fetchImpl,\n\t\t\t\tdispatchers,\n\t\t\t});\n\t\t\trefund(page.bytesFetched);\n\t\t\tif (!urlHasUserInfo(page.finalUrl)) {\n\t\t\t\tconst content: CachedPageV1 = {\n\t\t\t\t\ttext: page.text,\n\t\t\t\t\ttruncated: page.truncated,\n\t\t\t\t\t...(page.title === undefined ? {} : { title: page.title }),\n\t\t\t\t};\n\t\t\t\ttry {\n\t\t\t\t\tawait input.deps.pageCache.set(url, content, now());\n\t\t\t\t} catch {\n\t\t\t\t\t// Cache writes are best effort; the fetched page is still returned.\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn detailFromPage(url, page);\n\t\t} catch (error) {\n\t\t\trefund(error instanceof PageError ? (error.bytesRead ?? 0) : pageBudget);\n\t\t\tif (budget.timedOut()) {\n\t\t\t\treturn pageFailure(url, new PageError(\"timeout\", \"total fetch budget exceeded\"));\n\t\t\t}\n\t\t\treturn pageFailure(url, error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t};\n\n\ttry {\n\t\tconst fetched = await mapWithConcurrency(validated.urls, SEARCH_LIMITS.fetchConcurrency, fetchOne);\n\t\tconst invalidPages: PageWorkV1[] = validated.invalid.map((entry) => ({\n\t\t\turl: entry.url,\n\t\t\tdetail: {\n\t\t\t\turl: entry.url,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\terror: { code: \"invalid_url\", message: entry.message },\n\t\t\t\ttruncated: false,\n\t\t\t\tcache: \"miss\",\n\t\t\t},\n\t\t}));\n\t\tconst all = [...invalidPages, ...fetched];\n\t\tif (isAborted(input.signal)) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"cancelled\",\n\t\t\t\t\"URL search was cancelled\",\n\t\t\t);\n\t\t}\n\t\tif (budget.timedOut()) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"timeout\",\n\t\t\t\t\"total fetch budget exceeded\",\n\t\t\t);\n\t\t}\n\t\tconst okPages = all.filter((page) => page.detail.status === \"ok\" && page.text !== undefined);\n\t\tif (okPages.length === 0) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"no_content\",\n\t\t\t\t\"no page produced usable text\",\n\t\t\t);\n\t\t}\n\n\t\tfor (const page of okPages) {\n\t\t\tconst selected = selectPassages(page.text ?? \"\", terms, EXCERPT_CHARS_PER_PAGE);\n\t\t\tpage.excerpt = selected.join(\"\\n\\n\");\n\t\t\t// The full page is only needed for passage selection; release it before\n\t\t\t// the summary call so many large pages do not stay resident together.\n\t\t\tpage.text = undefined;\n\t\t}\n\n\t\tconst included: { url: string; title?: string; text: string }[] = [];\n\t\tlet used = 0;\n\t\tfor (const page of okPages) {\n\t\t\tconst excerpt = page.excerpt ?? \"\";\n\t\t\tconst remaining = SEARCH_LIMITS.maxSummaryInputBytes - used;\n\t\t\tif (remaining <= 256) break;\n\t\t\tlet text = excerpt;\n\t\t\tlet bytes = byteLength(text);\n\t\t\tif (bytes > remaining) {\n\t\t\t\tconst shortened = truncateToBytes(text, remaining - 64);\n\t\t\t\ttext = shortened.text;\n\t\t\t\tbytes = byteLength(text);\n\t\t\t\tpage.detail = { ...page.detail, truncated: true };\n\t\t\t}\n\t\t\tincluded.push({ url: page.url, ...(page.title === undefined ? {} : { title: page.title }), text });\n\t\t\tpage.detail = { ...page.detail, bytesReturned: bytes };\n\t\t\tused += bytes + 256;\n\t\t}\n\t\tconst includedUrls = new Set(included.map((page) => page.url));\n\t\tfor (const page of okPages) {\n\t\t\tif (!includedUrls.has(page.url)) {\n\t\t\t\tpage.detail = { ...page.detail, status: \"skipped\", truncated: true };\n\t\t\t}\n\t\t}\n\t\tif (included.length === 0) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"no_content\",\n\t\t\t\t\"summary input budget is exhausted\",\n\t\t\t);\n\t\t}\n\n\t\tconst mainModel = input.api.session?.getModel?.();\n\t\tif (mainModel === undefined) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"model_unavailable\",\n\t\t\t\t\"no main model selected\",\n\t\t\t);\n\t\t}\n\t\tconst invocation = input.api.modelInvocation;\n\t\tif (invocation === undefined) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"clean_invocation_unavailable\",\n\t\t\t\t\"the host does not expose isolated model invocation\",\n\t\t\t);\n\t\t}\n\t\tconst target = { provider: mainModel.provider, id: mainModel.id };\n\t\tlet prompt = buildUrlSummaryPrompt(input.query, included);\n\t\twhile (byteLength(prompt) > SEARCH_LIMITS.maxSummaryInputBytes && included.length > 1) {\n\t\t\tconst removed = included.pop();\n\t\t\tif (removed === undefined) break;\n\t\t\tconst page = all.find((entry) => entry.url === removed.url);\n\t\t\tif (page !== undefined) page.detail = { ...page.detail, status: \"skipped\", truncated: true };\n\t\t\tprompt = buildUrlSummaryPrompt(input.query, included);\n\t\t}\n\t\tconst summarySources: SearchSourceV1[] = included.map((page) => ({\n\t\t\turl: page.url,\n\t\t\t...(page.title === undefined ? {} : { title: page.title }),\n\t\t}));\n\t\tconst summaryKey = JSON.stringify({\n\t\t\turls: summarySources.map((source) => source.url).sort(),\n\t\t\tquery: input.query,\n\t\t\tmodel: `${target.provider}/${target.id}`,\n\t\t\tprompt: SUMMARY_PROMPT_VERSION,\n\t\t});\n\t\tconst summaryCacheable = summarySources.every((source) => !urlHasUserInfo(source.url));\n\t\tconst cachedSummary = summaryCacheable ? input.deps.summaryCache.get(summaryKey, now()) : undefined;\n\t\tif (cachedSummary !== undefined) {\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tmode: \"url\",\n\t\t\t\tquery: input.query,\n\t\t\t\tfetchedAt: new Date(now()).toISOString(),\n\t\t\t\tstatus: all.every((page) => page.detail.status === \"ok\") ? \"ok\" : \"partial\",\n\t\t\t\tsummary: cachedSummary.summary,\n\t\t\t\tsources: cachedSummary.sources,\n\t\t\t\tsummaryModel: cachedSummary.summaryModel,\n\t\t\t\ttotalBytes: all.reduce((sum, page) => sum + (page.detail.bytesFetched ?? 0), 0),\n\t\t\t\ttruncated: cachedSummary.truncated || all.some((page) => page.detail.truncated),\n\t\t\t\tpages: all.map((page) => page.detail),\n\t\t\t\tsummaryCache: \"hit\",\n\t\t\t};\n\t\t}\n\t\tconst deadline = createModelCallSignal(input.signal);\n\t\ttry {\n\t\t\tconst result = await invocation.complete(\n\t\t\t\t{\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttarget,\n\t\t\t\t\tsystemPrompt: URL_SUMMARY_SYSTEM_PROMPT,\n\t\t\t\t\tprompt,\n\t\t\t\t\twebSearch: \"off\",\n\t\t\t\t\tmaxTokens: SEARCH_LIMITS.maxSummaryOutputTokens,\n\t\t\t\t},\n\t\t\t\t{ signal: deadline.signal },\n\t\t\t);\n\t\t\tif (result.status === \"cancelled\") {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\tdeadline.timedOut() ? \"summary_timeout\" : \"cancelled\",\n\t\t\t\t\tdeadline.timedOut() ? \"summary model timed out\" : \"summary cancelled\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (result.status === \"failed\") {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\t\"summary_failed\",\n\t\t\t\t\t`${result.error.code}: ${result.error.message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (deadline.timedOut()) {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\t\"summary_timeout\",\n\t\t\t\t\t\"summary model timed out\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (isAborted(input.signal)) {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\t\"cancelled\",\n\t\t\t\t\t\"summary cancelled\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst parsed = parseUrlSummaryOutput(result.text, summarySources);\n\t\t\tconst summary: CachedSummaryV1 = {\n\t\t\t\tsummary: parsed.summary,\n\t\t\t\tsources: parsed.sources,\n\t\t\t\tsummaryModel: target,\n\t\t\t\ttruncated: parsed.truncated,\n\t\t\t};\n\t\t\tif (summaryCacheable) input.deps.summaryCache.set(summaryKey, summary, now());\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tmode: \"url\",\n\t\t\t\tquery: input.query,\n\t\t\t\tfetchedAt: new Date(now()).toISOString(),\n\t\t\t\tstatus: all.every((page) => page.detail.status === \"ok\") ? \"ok\" : \"partial\",\n\t\t\t\tsummary: parsed.summary,\n\t\t\t\tsources: parsed.sources,\n\t\t\t\tsummaryModel: target,\n\t\t\t\ttotalBytes: all.reduce((sum, page) => sum + (page.detail.bytesFetched ?? 0), 0),\n\t\t\t\ttruncated: parsed.truncated || all.some((page) => page.detail.truncated),\n\t\t\t\tpages: all.map((page) => page.detail),\n\t\t\t\tsummaryCache: \"miss\",\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (error instanceof SummaryError) {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\terror.code,\n\t\t\t\t\terror.message,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"summary_failed\",\n\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t);\n\t\t} finally {\n\t\t\tdeadline.dispose();\n\t\t}\n\t} finally {\n\t\tbudget.dispose();\n\t\tawait dispatchers.close();\n\t}\n}\n\n/** Normalize one URL for tests and callers that need the same canonical form. */\nexport function canonicalUrl(raw: string): string {\n\treturn normalizeUrl(raw);\n}\n"]}
@@ -0,0 +1,335 @@
1
+ /**
2
+ * URL mode: fetch/extract/select passages, then summarize with the current main
3
+ * model in a clean isolated completion (no tools, webSearch off).
4
+ */
5
+ import { selectPassages } from "./extract.js";
6
+ import { DispatcherPool, fetchPage, PageError } from "./http.js";
7
+ import { SEARCH_LIMITS } from "./limits.js";
8
+ import { createFetchBudgetSignal, createModelCallSignal } from "./model-call.js";
9
+ import { buildUrlSummaryPrompt, parseUrlSummaryOutput, SUMMARY_PROMPT_VERSION, SummaryError, URL_SUMMARY_SYSTEM_PROMPT, } from "./summarize.js";
10
+ import { byteLength, dedupeUrls, isAborted, normalizeUrl, queryTerms, truncateToBytes, urlHasUserInfo, } from "./text.js";
11
+ const EXCERPT_CHARS_PER_PAGE = 24 * 1200;
12
+ function pageFailure(url, error) {
13
+ const pageError = error instanceof PageError
14
+ ? error
15
+ : new PageError("network_error", error instanceof Error ? error.message : String(error));
16
+ return {
17
+ url,
18
+ detail: {
19
+ url,
20
+ status: "failed",
21
+ error: {
22
+ code: pageError.code,
23
+ message: pageError.message,
24
+ ...(pageError.httpStatus === undefined ? {} : { httpStatus: pageError.httpStatus }),
25
+ },
26
+ truncated: false,
27
+ cache: "miss",
28
+ },
29
+ };
30
+ }
31
+ function detailFromCache(url, entry) {
32
+ return {
33
+ url,
34
+ detail: {
35
+ url,
36
+ ...(entry.title === undefined ? {} : { title: entry.title }),
37
+ status: "ok",
38
+ bytesReturned: byteLength(entry.text),
39
+ truncated: entry.truncated,
40
+ cache: "hit",
41
+ },
42
+ text: entry.text,
43
+ ...(entry.title === undefined ? {} : { title: entry.title }),
44
+ };
45
+ }
46
+ function detailFromPage(url, page) {
47
+ return {
48
+ url,
49
+ detail: {
50
+ url,
51
+ finalUrl: page.finalUrl,
52
+ ...(page.title === undefined ? {} : { title: page.title }),
53
+ status: "ok",
54
+ contentType: page.contentType,
55
+ bytesFetched: page.bytesFetched,
56
+ bytesReturned: byteLength(page.text),
57
+ truncated: page.truncated,
58
+ cache: "miss",
59
+ },
60
+ text: page.text,
61
+ ...(page.title === undefined ? {} : { title: page.title }),
62
+ };
63
+ }
64
+ async function mapWithConcurrency(items, limit, fn) {
65
+ const results = new Array(items.length);
66
+ let next = 0;
67
+ const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
68
+ while (true) {
69
+ const index = next;
70
+ next += 1;
71
+ if (index >= items.length)
72
+ return;
73
+ const item = items[index];
74
+ if (item === undefined)
75
+ return;
76
+ results[index] = await fn(item, index);
77
+ }
78
+ });
79
+ await Promise.all(workers);
80
+ return results;
81
+ }
82
+ function failedDetails(query, now, pages, code, message) {
83
+ return {
84
+ version: 1,
85
+ mode: "url",
86
+ query,
87
+ fetchedAt: new Date(now()).toISOString(),
88
+ status: "failed",
89
+ summary: "",
90
+ sources: [],
91
+ totalBytes: pages.reduce((sum, page) => sum + (page.bytesFetched ?? 0), 0),
92
+ truncated: pages.some((page) => page.truncated),
93
+ pages,
94
+ error: { code, message },
95
+ };
96
+ }
97
+ export async function runUrlSearch(input) {
98
+ const now = input.deps.now;
99
+ if (isAborted(input.signal)) {
100
+ return failedDetails(input.query, now, [], "cancelled", "URL search was cancelled");
101
+ }
102
+ const validated = dedupeUrls(input.urls);
103
+ const terms = queryTerms(input.query);
104
+ const budget = createFetchBudgetSignal(input.signal);
105
+ const dispatchers = new DispatcherPool(input.deps.network, input.deps.fetchImpl === undefined);
106
+ let rawBudget = SEARCH_LIMITS.maxTotalRawBytes;
107
+ const fetchOne = async (url) => {
108
+ let fresh;
109
+ try {
110
+ fresh = await input.deps.pageCache.getFresh(url, now());
111
+ }
112
+ catch {
113
+ // The page cache is an optimization; a broken cache must not fail the read.
114
+ }
115
+ if (fresh !== undefined)
116
+ return detailFromCache(url, fresh);
117
+ const pageBudget = Math.min(SEARCH_LIMITS.maxRawBytesPerPage, rawBudget);
118
+ if (pageBudget <= 0) {
119
+ return {
120
+ url,
121
+ detail: { url, status: "skipped", truncated: true, cache: "miss" },
122
+ };
123
+ }
124
+ // Reserve before dispatch so concurrent workers and failed reads cannot
125
+ // exceed the shared raw-byte budget; refund only the unused part.
126
+ rawBudget -= pageBudget;
127
+ const refund = (used) => {
128
+ rawBudget = Math.min(SEARCH_LIMITS.maxTotalRawBytes, rawBudget + Math.max(0, pageBudget - used));
129
+ };
130
+ try {
131
+ const page = await fetchPage({
132
+ url,
133
+ maxTextBytes: SEARCH_LIMITS.maxExtractedBytesPerPage,
134
+ maxRawBytes: pageBudget,
135
+ timeoutMs: SEARCH_LIMITS.urlTimeoutMs,
136
+ signal: budget.signal,
137
+ network: input.deps.network,
138
+ fetchImpl: input.deps.fetchImpl,
139
+ dispatchers,
140
+ });
141
+ refund(page.bytesFetched);
142
+ if (!urlHasUserInfo(page.finalUrl)) {
143
+ const content = {
144
+ text: page.text,
145
+ truncated: page.truncated,
146
+ ...(page.title === undefined ? {} : { title: page.title }),
147
+ };
148
+ try {
149
+ await input.deps.pageCache.set(url, content, now());
150
+ }
151
+ catch {
152
+ // Cache writes are best effort; the fetched page is still returned.
153
+ }
154
+ }
155
+ return detailFromPage(url, page);
156
+ }
157
+ catch (error) {
158
+ refund(error instanceof PageError ? (error.bytesRead ?? 0) : pageBudget);
159
+ if (budget.timedOut()) {
160
+ return pageFailure(url, new PageError("timeout", "total fetch budget exceeded"));
161
+ }
162
+ return pageFailure(url, error instanceof Error ? error : new Error(String(error)));
163
+ }
164
+ };
165
+ try {
166
+ const fetched = await mapWithConcurrency(validated.urls, SEARCH_LIMITS.fetchConcurrency, fetchOne);
167
+ const invalidPages = validated.invalid.map((entry) => ({
168
+ url: entry.url,
169
+ detail: {
170
+ url: entry.url,
171
+ status: "failed",
172
+ error: { code: "invalid_url", message: entry.message },
173
+ truncated: false,
174
+ cache: "miss",
175
+ },
176
+ }));
177
+ const all = [...invalidPages, ...fetched];
178
+ if (isAborted(input.signal)) {
179
+ return failedDetails(input.query, now, all.map((page) => page.detail), "cancelled", "URL search was cancelled");
180
+ }
181
+ if (budget.timedOut()) {
182
+ return failedDetails(input.query, now, all.map((page) => page.detail), "timeout", "total fetch budget exceeded");
183
+ }
184
+ const okPages = all.filter((page) => page.detail.status === "ok" && page.text !== undefined);
185
+ if (okPages.length === 0) {
186
+ return failedDetails(input.query, now, all.map((page) => page.detail), "no_content", "no page produced usable text");
187
+ }
188
+ for (const page of okPages) {
189
+ const selected = selectPassages(page.text ?? "", terms, EXCERPT_CHARS_PER_PAGE);
190
+ page.excerpt = selected.join("\n\n");
191
+ // The full page is only needed for passage selection; release it before
192
+ // the summary call so many large pages do not stay resident together.
193
+ page.text = undefined;
194
+ }
195
+ const included = [];
196
+ let used = 0;
197
+ for (const page of okPages) {
198
+ const excerpt = page.excerpt ?? "";
199
+ const remaining = SEARCH_LIMITS.maxSummaryInputBytes - used;
200
+ if (remaining <= 256)
201
+ break;
202
+ let text = excerpt;
203
+ let bytes = byteLength(text);
204
+ if (bytes > remaining) {
205
+ const shortened = truncateToBytes(text, remaining - 64);
206
+ text = shortened.text;
207
+ bytes = byteLength(text);
208
+ page.detail = { ...page.detail, truncated: true };
209
+ }
210
+ included.push({ url: page.url, ...(page.title === undefined ? {} : { title: page.title }), text });
211
+ page.detail = { ...page.detail, bytesReturned: bytes };
212
+ used += bytes + 256;
213
+ }
214
+ const includedUrls = new Set(included.map((page) => page.url));
215
+ for (const page of okPages) {
216
+ if (!includedUrls.has(page.url)) {
217
+ page.detail = { ...page.detail, status: "skipped", truncated: true };
218
+ }
219
+ }
220
+ if (included.length === 0) {
221
+ return failedDetails(input.query, now, all.map((page) => page.detail), "no_content", "summary input budget is exhausted");
222
+ }
223
+ const mainModel = input.api.session?.getModel?.();
224
+ if (mainModel === undefined) {
225
+ return failedDetails(input.query, now, all.map((page) => page.detail), "model_unavailable", "no main model selected");
226
+ }
227
+ const invocation = input.api.modelInvocation;
228
+ if (invocation === undefined) {
229
+ return failedDetails(input.query, now, all.map((page) => page.detail), "clean_invocation_unavailable", "the host does not expose isolated model invocation");
230
+ }
231
+ const target = { provider: mainModel.provider, id: mainModel.id };
232
+ let prompt = buildUrlSummaryPrompt(input.query, included);
233
+ while (byteLength(prompt) > SEARCH_LIMITS.maxSummaryInputBytes && included.length > 1) {
234
+ const removed = included.pop();
235
+ if (removed === undefined)
236
+ break;
237
+ const page = all.find((entry) => entry.url === removed.url);
238
+ if (page !== undefined)
239
+ page.detail = { ...page.detail, status: "skipped", truncated: true };
240
+ prompt = buildUrlSummaryPrompt(input.query, included);
241
+ }
242
+ const summarySources = included.map((page) => ({
243
+ url: page.url,
244
+ ...(page.title === undefined ? {} : { title: page.title }),
245
+ }));
246
+ const summaryKey = JSON.stringify({
247
+ urls: summarySources.map((source) => source.url).sort(),
248
+ query: input.query,
249
+ model: `${target.provider}/${target.id}`,
250
+ prompt: SUMMARY_PROMPT_VERSION,
251
+ });
252
+ const summaryCacheable = summarySources.every((source) => !urlHasUserInfo(source.url));
253
+ const cachedSummary = summaryCacheable ? input.deps.summaryCache.get(summaryKey, now()) : undefined;
254
+ if (cachedSummary !== undefined) {
255
+ return {
256
+ version: 1,
257
+ mode: "url",
258
+ query: input.query,
259
+ fetchedAt: new Date(now()).toISOString(),
260
+ status: all.every((page) => page.detail.status === "ok") ? "ok" : "partial",
261
+ summary: cachedSummary.summary,
262
+ sources: cachedSummary.sources,
263
+ summaryModel: cachedSummary.summaryModel,
264
+ totalBytes: all.reduce((sum, page) => sum + (page.detail.bytesFetched ?? 0), 0),
265
+ truncated: cachedSummary.truncated || all.some((page) => page.detail.truncated),
266
+ pages: all.map((page) => page.detail),
267
+ summaryCache: "hit",
268
+ };
269
+ }
270
+ const deadline = createModelCallSignal(input.signal);
271
+ try {
272
+ const result = await invocation.complete({
273
+ version: 1,
274
+ target,
275
+ systemPrompt: URL_SUMMARY_SYSTEM_PROMPT,
276
+ prompt,
277
+ webSearch: "off",
278
+ maxTokens: SEARCH_LIMITS.maxSummaryOutputTokens,
279
+ }, { signal: deadline.signal });
280
+ if (result.status === "cancelled") {
281
+ return failedDetails(input.query, now, all.map((page) => page.detail), deadline.timedOut() ? "summary_timeout" : "cancelled", deadline.timedOut() ? "summary model timed out" : "summary cancelled");
282
+ }
283
+ if (result.status === "failed") {
284
+ return failedDetails(input.query, now, all.map((page) => page.detail), "summary_failed", `${result.error.code}: ${result.error.message}`);
285
+ }
286
+ if (deadline.timedOut()) {
287
+ return failedDetails(input.query, now, all.map((page) => page.detail), "summary_timeout", "summary model timed out");
288
+ }
289
+ if (isAborted(input.signal)) {
290
+ return failedDetails(input.query, now, all.map((page) => page.detail), "cancelled", "summary cancelled");
291
+ }
292
+ const parsed = parseUrlSummaryOutput(result.text, summarySources);
293
+ const summary = {
294
+ summary: parsed.summary,
295
+ sources: parsed.sources,
296
+ summaryModel: target,
297
+ truncated: parsed.truncated,
298
+ };
299
+ if (summaryCacheable)
300
+ input.deps.summaryCache.set(summaryKey, summary, now());
301
+ return {
302
+ version: 1,
303
+ mode: "url",
304
+ query: input.query,
305
+ fetchedAt: new Date(now()).toISOString(),
306
+ status: all.every((page) => page.detail.status === "ok") ? "ok" : "partial",
307
+ summary: parsed.summary,
308
+ sources: parsed.sources,
309
+ summaryModel: target,
310
+ totalBytes: all.reduce((sum, page) => sum + (page.detail.bytesFetched ?? 0), 0),
311
+ truncated: parsed.truncated || all.some((page) => page.detail.truncated),
312
+ pages: all.map((page) => page.detail),
313
+ summaryCache: "miss",
314
+ };
315
+ }
316
+ catch (error) {
317
+ if (error instanceof SummaryError) {
318
+ return failedDetails(input.query, now, all.map((page) => page.detail), error.code, error.message);
319
+ }
320
+ return failedDetails(input.query, now, all.map((page) => page.detail), "summary_failed", error instanceof Error ? error.message : String(error));
321
+ }
322
+ finally {
323
+ deadline.dispose();
324
+ }
325
+ }
326
+ finally {
327
+ budget.dispose();
328
+ await dispatchers.close();
329
+ }
330
+ }
331
+ /** Normalize one URL for tests and callers that need the same canonical form. */
332
+ export function canonicalUrl(raw) {
333
+ return normalizeUrl(raw);
334
+ }
335
+ //# sourceMappingURL=url-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-search.js","sourceRoot":"","sources":["../src/url-search.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * URL mode: fetch/extract/select passages, then summarize with the current main\n * model in a clean isolated completion (no tools, webSearch off).\n */\n\nimport type { CapabilityAPI, NetworkRouteConfigV1 } from \"@agent-forge/plugin-sdk\";\nimport type { CachedPageV1, PageCache, SummaryCache } from \"./cache.ts\";\nimport { selectPassages } from \"./extract.ts\";\nimport { DispatcherPool, type FetchedPageV1, type FetchImpl, fetchPage, PageError } from \"./http.ts\";\nimport { SEARCH_LIMITS } from \"./limits.ts\";\nimport { createFetchBudgetSignal, createModelCallSignal } from \"./model-call.ts\";\nimport {\n\tbuildUrlSummaryPrompt,\n\tparseUrlSummaryOutput,\n\ttype SearchSourceV1,\n\tSUMMARY_PROMPT_VERSION,\n\tSummaryError,\n\tURL_SUMMARY_SYSTEM_PROMPT,\n} from \"./summarize.ts\";\nimport {\n\tbyteLength,\n\tdedupeUrls,\n\tisAborted,\n\tnormalizeUrl,\n\tqueryTerms,\n\ttruncateToBytes,\n\turlHasUserInfo,\n} from \"./text.ts\";\nimport type { SearchDetailsV1, SearchPageDetailsV1 } from \"./types.ts\";\n\nexport interface UrlSearchDepsV1 {\n\treadonly now: () => number;\n\treadonly pageCache: PageCache;\n\treadonly summaryCache: SummaryCache<CachedSummaryV1>;\n\treadonly fetchImpl?: FetchImpl;\n\treadonly network?: NetworkRouteConfigV1;\n}\n\nexport interface RunUrlSearchInputV1 {\n\treadonly query: string;\n\treadonly urls: readonly string[];\n\treadonly api: CapabilityAPI;\n\treadonly signal?: AbortSignal;\n\treadonly deps: UrlSearchDepsV1;\n}\n\nexport interface CachedSummaryV1 {\n\treadonly summary: string;\n\treadonly sources: readonly SearchSourceV1[];\n\treadonly summaryModel: { readonly provider: string; readonly id: string };\n\treadonly truncated: boolean;\n}\n\ninterface PageWorkV1 {\n\treadonly url: string;\n\tdetail: SearchPageDetailsV1;\n\ttext?: string;\n\treadonly title?: string;\n\texcerpt?: string;\n}\n\nconst EXCERPT_CHARS_PER_PAGE = 24 * 1200;\n\nfunction pageFailure(url: string, error: PageError | Error): PageWorkV1 {\n\tconst pageError =\n\t\terror instanceof PageError\n\t\t\t? error\n\t\t\t: new PageError(\"network_error\", error instanceof Error ? error.message : String(error));\n\treturn {\n\t\turl,\n\t\tdetail: {\n\t\t\turl,\n\t\t\tstatus: \"failed\",\n\t\t\terror: {\n\t\t\t\tcode: pageError.code,\n\t\t\t\tmessage: pageError.message,\n\t\t\t\t...(pageError.httpStatus === undefined ? {} : { httpStatus: pageError.httpStatus }),\n\t\t\t},\n\t\t\ttruncated: false,\n\t\t\tcache: \"miss\",\n\t\t},\n\t};\n}\n\nfunction detailFromCache(url: string, entry: CachedPageV1): PageWorkV1 {\n\treturn {\n\t\turl,\n\t\tdetail: {\n\t\t\turl,\n\t\t\t...(entry.title === undefined ? {} : { title: entry.title }),\n\t\t\tstatus: \"ok\",\n\t\t\tbytesReturned: byteLength(entry.text),\n\t\t\ttruncated: entry.truncated,\n\t\t\tcache: \"hit\",\n\t\t},\n\t\ttext: entry.text,\n\t\t...(entry.title === undefined ? {} : { title: entry.title }),\n\t};\n}\n\nfunction detailFromPage(url: string, page: FetchedPageV1): PageWorkV1 {\n\treturn {\n\t\turl,\n\t\tdetail: {\n\t\t\turl,\n\t\t\tfinalUrl: page.finalUrl,\n\t\t\t...(page.title === undefined ? {} : { title: page.title }),\n\t\t\tstatus: \"ok\",\n\t\t\tcontentType: page.contentType,\n\t\t\tbytesFetched: page.bytesFetched,\n\t\t\tbytesReturned: byteLength(page.text),\n\t\t\ttruncated: page.truncated,\n\t\t\tcache: \"miss\",\n\t\t},\n\t\ttext: page.text,\n\t\t...(page.title === undefined ? {} : { title: page.title }),\n\t};\n}\n\nasync function mapWithConcurrency<T, R>(\n\titems: readonly T[],\n\tlimit: number,\n\tfn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n\tconst results = new Array<R>(items.length);\n\tlet next = 0;\n\tconst workers = Array.from({ length: Math.min(limit, items.length) }, async () => {\n\t\twhile (true) {\n\t\t\tconst index = next;\n\t\t\tnext += 1;\n\t\t\tif (index >= items.length) return;\n\t\t\tconst item = items[index];\n\t\t\tif (item === undefined) return;\n\t\t\tresults[index] = await fn(item, index);\n\t\t}\n\t});\n\tawait Promise.all(workers);\n\treturn results;\n}\n\nfunction failedDetails(\n\tquery: string,\n\tnow: () => number,\n\tpages: readonly SearchPageDetailsV1[],\n\tcode: string,\n\tmessage: string,\n): SearchDetailsV1 {\n\treturn {\n\t\tversion: 1,\n\t\tmode: \"url\",\n\t\tquery,\n\t\tfetchedAt: new Date(now()).toISOString(),\n\t\tstatus: \"failed\",\n\t\tsummary: \"\",\n\t\tsources: [],\n\t\ttotalBytes: pages.reduce((sum, page) => sum + (page.bytesFetched ?? 0), 0),\n\t\ttruncated: pages.some((page) => page.truncated),\n\t\tpages,\n\t\terror: { code, message },\n\t};\n}\n\nexport async function runUrlSearch(input: RunUrlSearchInputV1): Promise<SearchDetailsV1> {\n\tconst now = input.deps.now;\n\tif (isAborted(input.signal)) {\n\t\treturn failedDetails(input.query, now, [], \"cancelled\", \"URL search was cancelled\");\n\t}\n\tconst validated = dedupeUrls(input.urls);\n\tconst terms = queryTerms(input.query);\n\tconst budget = createFetchBudgetSignal(input.signal);\n\tconst dispatchers = new DispatcherPool(input.deps.network, input.deps.fetchImpl === undefined);\n\tlet rawBudget = SEARCH_LIMITS.maxTotalRawBytes;\n\n\tconst fetchOne = async (url: string): Promise<PageWorkV1> => {\n\t\tlet fresh: CachedPageV1 | undefined;\n\t\ttry {\n\t\t\tfresh = await input.deps.pageCache.getFresh(url, now());\n\t\t} catch {\n\t\t\t// The page cache is an optimization; a broken cache must not fail the read.\n\t\t}\n\t\tif (fresh !== undefined) return detailFromCache(url, fresh);\n\t\tconst pageBudget = Math.min(SEARCH_LIMITS.maxRawBytesPerPage, rawBudget);\n\t\tif (pageBudget <= 0) {\n\t\t\treturn {\n\t\t\t\turl,\n\t\t\t\tdetail: { url, status: \"skipped\", truncated: true, cache: \"miss\" },\n\t\t\t};\n\t\t}\n\t\t// Reserve before dispatch so concurrent workers and failed reads cannot\n\t\t// exceed the shared raw-byte budget; refund only the unused part.\n\t\trawBudget -= pageBudget;\n\t\tconst refund = (used: number): void => {\n\t\t\trawBudget = Math.min(SEARCH_LIMITS.maxTotalRawBytes, rawBudget + Math.max(0, pageBudget - used));\n\t\t};\n\t\ttry {\n\t\t\tconst page = await fetchPage({\n\t\t\t\turl,\n\t\t\t\tmaxTextBytes: SEARCH_LIMITS.maxExtractedBytesPerPage,\n\t\t\t\tmaxRawBytes: pageBudget,\n\t\t\t\ttimeoutMs: SEARCH_LIMITS.urlTimeoutMs,\n\t\t\t\tsignal: budget.signal,\n\t\t\t\tnetwork: input.deps.network,\n\t\t\t\tfetchImpl: input.deps.fetchImpl,\n\t\t\t\tdispatchers,\n\t\t\t});\n\t\t\trefund(page.bytesFetched);\n\t\t\tif (!urlHasUserInfo(page.finalUrl)) {\n\t\t\t\tconst content: CachedPageV1 = {\n\t\t\t\t\ttext: page.text,\n\t\t\t\t\ttruncated: page.truncated,\n\t\t\t\t\t...(page.title === undefined ? {} : { title: page.title }),\n\t\t\t\t};\n\t\t\t\ttry {\n\t\t\t\t\tawait input.deps.pageCache.set(url, content, now());\n\t\t\t\t} catch {\n\t\t\t\t\t// Cache writes are best effort; the fetched page is still returned.\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn detailFromPage(url, page);\n\t\t} catch (error) {\n\t\t\trefund(error instanceof PageError ? (error.bytesRead ?? 0) : pageBudget);\n\t\t\tif (budget.timedOut()) {\n\t\t\t\treturn pageFailure(url, new PageError(\"timeout\", \"total fetch budget exceeded\"));\n\t\t\t}\n\t\t\treturn pageFailure(url, error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t};\n\n\ttry {\n\t\tconst fetched = await mapWithConcurrency(validated.urls, SEARCH_LIMITS.fetchConcurrency, fetchOne);\n\t\tconst invalidPages: PageWorkV1[] = validated.invalid.map((entry) => ({\n\t\t\turl: entry.url,\n\t\t\tdetail: {\n\t\t\t\turl: entry.url,\n\t\t\t\tstatus: \"failed\",\n\t\t\t\terror: { code: \"invalid_url\", message: entry.message },\n\t\t\t\ttruncated: false,\n\t\t\t\tcache: \"miss\",\n\t\t\t},\n\t\t}));\n\t\tconst all = [...invalidPages, ...fetched];\n\t\tif (isAborted(input.signal)) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"cancelled\",\n\t\t\t\t\"URL search was cancelled\",\n\t\t\t);\n\t\t}\n\t\tif (budget.timedOut()) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"timeout\",\n\t\t\t\t\"total fetch budget exceeded\",\n\t\t\t);\n\t\t}\n\t\tconst okPages = all.filter((page) => page.detail.status === \"ok\" && page.text !== undefined);\n\t\tif (okPages.length === 0) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"no_content\",\n\t\t\t\t\"no page produced usable text\",\n\t\t\t);\n\t\t}\n\n\t\tfor (const page of okPages) {\n\t\t\tconst selected = selectPassages(page.text ?? \"\", terms, EXCERPT_CHARS_PER_PAGE);\n\t\t\tpage.excerpt = selected.join(\"\\n\\n\");\n\t\t\t// The full page is only needed for passage selection; release it before\n\t\t\t// the summary call so many large pages do not stay resident together.\n\t\t\tpage.text = undefined;\n\t\t}\n\n\t\tconst included: { url: string; title?: string; text: string }[] = [];\n\t\tlet used = 0;\n\t\tfor (const page of okPages) {\n\t\t\tconst excerpt = page.excerpt ?? \"\";\n\t\t\tconst remaining = SEARCH_LIMITS.maxSummaryInputBytes - used;\n\t\t\tif (remaining <= 256) break;\n\t\t\tlet text = excerpt;\n\t\t\tlet bytes = byteLength(text);\n\t\t\tif (bytes > remaining) {\n\t\t\t\tconst shortened = truncateToBytes(text, remaining - 64);\n\t\t\t\ttext = shortened.text;\n\t\t\t\tbytes = byteLength(text);\n\t\t\t\tpage.detail = { ...page.detail, truncated: true };\n\t\t\t}\n\t\t\tincluded.push({ url: page.url, ...(page.title === undefined ? {} : { title: page.title }), text });\n\t\t\tpage.detail = { ...page.detail, bytesReturned: bytes };\n\t\t\tused += bytes + 256;\n\t\t}\n\t\tconst includedUrls = new Set(included.map((page) => page.url));\n\t\tfor (const page of okPages) {\n\t\t\tif (!includedUrls.has(page.url)) {\n\t\t\t\tpage.detail = { ...page.detail, status: \"skipped\", truncated: true };\n\t\t\t}\n\t\t}\n\t\tif (included.length === 0) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"no_content\",\n\t\t\t\t\"summary input budget is exhausted\",\n\t\t\t);\n\t\t}\n\n\t\tconst mainModel = input.api.session?.getModel?.();\n\t\tif (mainModel === undefined) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"model_unavailable\",\n\t\t\t\t\"no main model selected\",\n\t\t\t);\n\t\t}\n\t\tconst invocation = input.api.modelInvocation;\n\t\tif (invocation === undefined) {\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"clean_invocation_unavailable\",\n\t\t\t\t\"the host does not expose isolated model invocation\",\n\t\t\t);\n\t\t}\n\t\tconst target = { provider: mainModel.provider, id: mainModel.id };\n\t\tlet prompt = buildUrlSummaryPrompt(input.query, included);\n\t\twhile (byteLength(prompt) > SEARCH_LIMITS.maxSummaryInputBytes && included.length > 1) {\n\t\t\tconst removed = included.pop();\n\t\t\tif (removed === undefined) break;\n\t\t\tconst page = all.find((entry) => entry.url === removed.url);\n\t\t\tif (page !== undefined) page.detail = { ...page.detail, status: \"skipped\", truncated: true };\n\t\t\tprompt = buildUrlSummaryPrompt(input.query, included);\n\t\t}\n\t\tconst summarySources: SearchSourceV1[] = included.map((page) => ({\n\t\t\turl: page.url,\n\t\t\t...(page.title === undefined ? {} : { title: page.title }),\n\t\t}));\n\t\tconst summaryKey = JSON.stringify({\n\t\t\turls: summarySources.map((source) => source.url).sort(),\n\t\t\tquery: input.query,\n\t\t\tmodel: `${target.provider}/${target.id}`,\n\t\t\tprompt: SUMMARY_PROMPT_VERSION,\n\t\t});\n\t\tconst summaryCacheable = summarySources.every((source) => !urlHasUserInfo(source.url));\n\t\tconst cachedSummary = summaryCacheable ? input.deps.summaryCache.get(summaryKey, now()) : undefined;\n\t\tif (cachedSummary !== undefined) {\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tmode: \"url\",\n\t\t\t\tquery: input.query,\n\t\t\t\tfetchedAt: new Date(now()).toISOString(),\n\t\t\t\tstatus: all.every((page) => page.detail.status === \"ok\") ? \"ok\" : \"partial\",\n\t\t\t\tsummary: cachedSummary.summary,\n\t\t\t\tsources: cachedSummary.sources,\n\t\t\t\tsummaryModel: cachedSummary.summaryModel,\n\t\t\t\ttotalBytes: all.reduce((sum, page) => sum + (page.detail.bytesFetched ?? 0), 0),\n\t\t\t\ttruncated: cachedSummary.truncated || all.some((page) => page.detail.truncated),\n\t\t\t\tpages: all.map((page) => page.detail),\n\t\t\t\tsummaryCache: \"hit\",\n\t\t\t};\n\t\t}\n\t\tconst deadline = createModelCallSignal(input.signal);\n\t\ttry {\n\t\t\tconst result = await invocation.complete(\n\t\t\t\t{\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttarget,\n\t\t\t\t\tsystemPrompt: URL_SUMMARY_SYSTEM_PROMPT,\n\t\t\t\t\tprompt,\n\t\t\t\t\twebSearch: \"off\",\n\t\t\t\t\tmaxTokens: SEARCH_LIMITS.maxSummaryOutputTokens,\n\t\t\t\t},\n\t\t\t\t{ signal: deadline.signal },\n\t\t\t);\n\t\t\tif (result.status === \"cancelled\") {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\tdeadline.timedOut() ? \"summary_timeout\" : \"cancelled\",\n\t\t\t\t\tdeadline.timedOut() ? \"summary model timed out\" : \"summary cancelled\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (result.status === \"failed\") {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\t\"summary_failed\",\n\t\t\t\t\t`${result.error.code}: ${result.error.message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (deadline.timedOut()) {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\t\"summary_timeout\",\n\t\t\t\t\t\"summary model timed out\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (isAborted(input.signal)) {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\t\"cancelled\",\n\t\t\t\t\t\"summary cancelled\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst parsed = parseUrlSummaryOutput(result.text, summarySources);\n\t\t\tconst summary: CachedSummaryV1 = {\n\t\t\t\tsummary: parsed.summary,\n\t\t\t\tsources: parsed.sources,\n\t\t\t\tsummaryModel: target,\n\t\t\t\ttruncated: parsed.truncated,\n\t\t\t};\n\t\t\tif (summaryCacheable) input.deps.summaryCache.set(summaryKey, summary, now());\n\t\t\treturn {\n\t\t\t\tversion: 1,\n\t\t\t\tmode: \"url\",\n\t\t\t\tquery: input.query,\n\t\t\t\tfetchedAt: new Date(now()).toISOString(),\n\t\t\t\tstatus: all.every((page) => page.detail.status === \"ok\") ? \"ok\" : \"partial\",\n\t\t\t\tsummary: parsed.summary,\n\t\t\t\tsources: parsed.sources,\n\t\t\t\tsummaryModel: target,\n\t\t\t\ttotalBytes: all.reduce((sum, page) => sum + (page.detail.bytesFetched ?? 0), 0),\n\t\t\t\ttruncated: parsed.truncated || all.some((page) => page.detail.truncated),\n\t\t\t\tpages: all.map((page) => page.detail),\n\t\t\t\tsummaryCache: \"miss\",\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (error instanceof SummaryError) {\n\t\t\t\treturn failedDetails(\n\t\t\t\t\tinput.query,\n\t\t\t\t\tnow,\n\t\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\terror.code,\n\t\t\t\t\terror.message,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn failedDetails(\n\t\t\t\tinput.query,\n\t\t\t\tnow,\n\t\t\t\tall.map((page) => page.detail),\n\t\t\t\t\"summary_failed\",\n\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t);\n\t\t} finally {\n\t\t\tdeadline.dispose();\n\t\t}\n\t} finally {\n\t\tbudget.dispose();\n\t\tawait dispatchers.close();\n\t}\n}\n\n/** Normalize one URL for tests and callers that need the same canonical form. */\nexport function canonicalUrl(raw: string): string {\n\treturn normalizeUrl(raw);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
- "name": "@ai-agent-forge/plugin-search",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@ai-agent-forge/plugin-search",
3
+ "version": "0.88.15",
4
+ "description": "Native-first search tool with URL-only and merged entry variants",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "agent-forge.json",
17
+ "plugin.json",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "clean": "shx rm -rf dist",
22
+ "build": "tsgo -p tsconfig.build.json",
23
+ "test": "vitest --run",
24
+ "prepublishOnly": "npm run clean && npm run build",
25
+ "test:coverage": "vitest --run --coverage"
26
+ },
27
+ "keywords": [
28
+ "agent-forge",
29
+ "plugin",
30
+ "search",
31
+ "url"
32
+ ],
33
+ "author": "Agent Forge contributors",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/agent-forge/agent-forge.git",
38
+ "directory": "plugins/search"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/agent-forge/agent-forge/issues"
42
+ },
43
+ "homepage": "https://github.com/agent-forge/agent-forge#readme",
44
+ "engines": {
45
+ "node": ">=22.19.0"
46
+ },
47
+ "dependencies": {
48
+ "@mozilla/readability": "0.6.0",
49
+ "linkedom": "0.18.13",
50
+ "typebox": "1.3.27",
51
+ "undici": "8.10.2",
52
+ "@ai-agent-forge/plugin-sdk": "^0.88.15"
53
+ },
54
+ "devDependencies": {
55
+ "shx": "0.3.4",
56
+ "vitest": "4.1.11"
57
+ }
58
+ }
package/plugin.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "agent-forge.plugin.search",
3
+ "version": "0.88.15",
4
+ "apiVersion": "1-draft",
5
+ "entry": "./dist/entry.js",
6
+ "hostVersion": ">=0.88.14",
7
+ "description": "搜索: 原生搜索可用时只读取给定 URL;原生搜索不可用时提供模型搜索与 URL 搜索",
8
+ "activation": "always",
9
+ "requiredCapabilities": [
10
+ "tools"
11
+ ]
12
+ }