@ai-agent-forge/plugin-search 0.0.0-stage → 0.88.16
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/README.md +56 -2
- package/agent-forge.json +19 -0
- package/dist/cache.d.ts +103 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +666 -0
- package/dist/cache.js.map +1 -0
- package/dist/capability.d.ts +22 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +213 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +3 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +3 -0
- package/dist/entry.js.map +1 -0
- package/dist/extract.d.ts +18 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +120 -0
- package/dist/extract.js.map +1 -0
- package/dist/http.d.ts +65 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +307 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/limits.d.ts +39 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +43 -0
- package/dist/limits.js.map +1 -0
- package/dist/model-call.d.ts +10 -0
- package/dist/model-call.d.ts.map +1 -0
- package/dist/model-call.js +46 -0
- package/dist/model-call.js.map +1 -0
- package/dist/model-search.d.ts +14 -0
- package/dist/model-search.d.ts.map +1 -0
- package/dist/model-search.js +85 -0
- package/dist/model-search.js.map +1 -0
- package/dist/summarize.d.ts +40 -0
- package/dist/summarize.d.ts.map +1 -0
- package/dist/summarize.js +148 -0
- package/dist/summarize.js.map +1 -0
- package/dist/text.d.ts +38 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +135 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/url-search.d.ts +36 -0
- package/dist/url-search.d.ts.map +1 -0
- package/dist/url-search.js +335 -0
- package/dist/url-search.js.map +1 -0
- package/package.json +57 -5
- package/plugin.json +12 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * System-level disk page cache (搜索缓存持久化设计).\n *\n * Only extracted page content is persisted: title, text and the truncation flag.\n * URLs, response headers, cookies, validators and fetch metadata stay out of the\n * cache. Entries are shared by every agent/profile/session of the same OS user,\n * use a hit-count-based retention window and are evicted by LFU then LRU.\n *\n * All I/O is asynchronous. Writes and hit-count bookkeeping are queued so a\n * search call never waits for the disk; `flushWrites`/`flushUsage` exist for\n * tests and graceful shutdown.\n */\n\nimport { createHash, randomUUID } from \"node:crypto\";\nimport type { Dirent } from \"node:fs\";\nimport { mkdir, readdir, readFile, rename, rm, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { LoggerAPI } from \"@agent-forge/plugin-sdk\";\nimport { SEARCH_LIMITS } from \"./limits.ts\";\nimport { byteLength, isRecord, normalizeUrl, urlHasUserInfo } from \"./text.ts\";\n\nexport interface CachedPageV1 {\n\treadonly title?: string;\n\treadonly text: string;\n\treadonly truncated: boolean;\n}\n\nexport interface PageCacheOptionsV1 {\n\treadonly maxBytes?: number;\n\treadonly maxEntries?: number;\n\treadonly maxEntryBytes?: number;\n\treadonly baseTtlMs?: number;\n\treadonly ttlTiersMs?: readonly number[];\n\treadonly writeQueueEntries?: number;\n\treadonly writeQueueBytes?: number;\n\treadonly metaFlushMs?: number;\n\treadonly pruneIntervalMs?: number;\n\treadonly logger?: LoggerAPI;\n}\n\ninterface EntryMetaV1 {\n\treadonly version: 1;\n\treadonly hash: string;\n\treadonly bytes: number;\n\treadonly storedAt: number;\n\treadonly expiresAt: number;\n\treadonly hits: number;\n\treadonly lastAccess: number;\n}\n\ninterface EntryStateV1 {\n\treadonly hash: string;\n\treadonly pagePath: string;\n\treadonly metaPath: string;\n\treadonly bytes: number;\n\treadonly storedAt: number;\n\texpiresAt: number;\n\thits: number;\n\tlastAccess: number;\n}\n\ninterface PageFileV1 {\n\treadonly version: 1;\n\treadonly title?: string;\n\treadonly text: string;\n\treadonly truncated: boolean;\n}\n\ninterface CachedPageFileV1 {\n\treadonly version: 1;\n\treadonly hash: string;\n\treadonly bytes: number;\n\treadonly storedAt: number;\n\treadonly expiresAt: number;\n\treadonly hits: number;\n\treadonly lastAccess: number;\n}\n\nconst META_SUFFIX = \".meta.json\";\nconst PAGE_SUFFIX = \".page.json\";\nconst MAX_META_BYTES = 16 * 1024;\nconst HASH_PATTERN = /^[0-9a-f]{64}$/;\nconst SHARD_PATTERN = /^[0-9a-f]{2}$/;\n\nfunction errorCode(error: unknown): string | undefined {\n\tif (typeof error !== \"object\" || error === null || !(\"code\" in error)) return undefined;\n\tconst code = (error as { code?: unknown }).code;\n\treturn typeof code === \"string\" ? code : undefined;\n}\n\nfunction isMissing(error: unknown): boolean {\n\treturn errorCode(error) === \"ENOENT\";\n}\n\nfunction finiteNonNegative(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value >= 0;\n}\n\nfunction parseMeta(value: unknown): EntryMetaV1 | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst { version, hash, bytes, storedAt, expiresAt, hits, lastAccess } = value;\n\tif (version !== 1 || typeof hash !== \"string\" || !HASH_PATTERN.test(hash)) return undefined;\n\tif (!finiteNonNegative(bytes) || !finiteNonNegative(storedAt) || !finiteNonNegative(expiresAt)) return undefined;\n\tif (!finiteNonNegative(hits) || !Number.isInteger(hits) || !finiteNonNegative(lastAccess)) return undefined;\n\treturn { version: 1, hash, bytes, storedAt, expiresAt, hits, lastAccess };\n}\n\nfunction parsePage(value: unknown): PageFileV1 | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst { version, title, text, truncated } = value;\n\tif (version !== 1 || typeof text !== \"string\" || text === \"\" || typeof truncated !== \"boolean\") return undefined;\n\tif (title !== undefined && typeof title !== \"string\") return undefined;\n\treturn { version: 1, text, truncated, ...(title === undefined ? {} : { title }) };\n}\n\nfunction defaultTtlTiers(tiers: readonly number[] | undefined): readonly number[] {\n\tif (tiers !== undefined && tiers.length === 4) return tiers;\n\treturn SEARCH_LIMITS.cacheTtlTiersMs;\n}\n\nfunction serializePage(page: CachedPageV1): string {\n\tconst file: PageFileV1 = {\n\t\tversion: 1,\n\t\ttext: page.text,\n\t\ttruncated: page.truncated,\n\t\t...(page.title === undefined ? {} : { title: page.title }),\n\t};\n\treturn JSON.stringify(file);\n}\n\n/** OS cache directory shared by all profiles of the current OS user. */\nexport function defaultPageCacheDirectory(): string {\n\tconst home = homedir();\n\tif (process.platform === \"darwin\") {\n\t\treturn join(home, \"Library\", \"Caches\", \"agent-forge\", \"search\", \"v1\", \"pages\");\n\t}\n\tif (process.platform === \"win32\") {\n\t\tconst base = process.env.LOCALAPPDATA?.trim() || join(home, \"AppData\", \"Local\");\n\t\treturn join(base, \"agent-forge\", \"Cache\", \"search\", \"v1\", \"pages\");\n\t}\n\tconst base = process.env.XDG_CACHE_HOME?.trim() || join(home, \".cache\");\n\treturn join(base, \"agent-forge\", \"search\", \"v1\", \"pages\");\n}\n\nexport class PageCache {\n\tprivate readonly directory: string;\n\tprivate readonly maxBytes: number;\n\tprivate readonly maxEntries: number;\n\tprivate readonly maxEntryBytes: number;\n\tprivate readonly baseTtlMs: number;\n\tprivate readonly ttlTiersMs: readonly number[];\n\tprivate readonly writeQueueEntries: number;\n\tprivate readonly writeQueueBytes: number;\n\tprivate readonly metaFlushMs: number;\n\tprivate readonly pruneIntervalMs: number;\n\tprivate readonly logger: LoggerAPI | undefined;\n\n\tprivate readonly entries = new Map<string, EntryStateV1>();\n\tprivate readonly pendingWrites = new Map<string, Promise<void>>();\n\tprivate readonly dirtyMeta = new Map<string, EntryStateV1>();\n\tprivate pendingWriteBytes = 0;\n\tprivate totalBytes = 0;\n\tprivate initPromise: Promise<void> | undefined;\n\tprivate unavailable = false;\n\tprivate loggedUnavailable = false;\n\tprivate loggedSkippedWrite = false;\n\tprivate loggedWriteFailure = false;\n\tprivate metaTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate pruneTimer: ReturnType<typeof setTimeout> | undefined;\n\tprivate pruneChain: Promise<void> = Promise.resolve();\n\n\tconstructor(directory: string = defaultPageCacheDirectory(), options: PageCacheOptionsV1 = {}) {\n\t\tthis.directory = directory;\n\t\tthis.maxBytes = options.maxBytes ?? SEARCH_LIMITS.pageCacheBytes;\n\t\tthis.maxEntries = options.maxEntries ?? SEARCH_LIMITS.pageCacheEntries;\n\t\tthis.maxEntryBytes = options.maxEntryBytes ?? SEARCH_LIMITS.cacheMaxEntryBytes;\n\t\tthis.baseTtlMs = options.baseTtlMs ?? SEARCH_LIMITS.cacheBaseTtlMs;\n\t\tthis.ttlTiersMs = defaultTtlTiers(options.ttlTiersMs);\n\t\tthis.writeQueueEntries = options.writeQueueEntries ?? SEARCH_LIMITS.cacheWriteQueueEntries;\n\t\tthis.writeQueueBytes = options.writeQueueBytes ?? SEARCH_LIMITS.cacheWriteQueueBytes;\n\t\tthis.metaFlushMs = options.metaFlushMs ?? SEARCH_LIMITS.cacheMetaFlushMs;\n\t\tthis.pruneIntervalMs = options.pruneIntervalMs ?? SEARCH_LIMITS.cachePruneIntervalMs;\n\t\tthis.logger = options.logger;\n\t}\n\n\t/** Fresh content only; any cache failure degrades to a miss. */\n\tasync getFresh(rawUrl: string, now: number): Promise<CachedPageV1 | undefined> {\n\t\ttry {\n\t\t\treturn await this.getFreshUnsafe(rawUrl, now);\n\t\t} catch (error) {\n\t\t\tthis.logDebug(\"page cache read failed; treating it as a miss\", error);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate async getFreshUnsafe(rawUrl: string, now: number): Promise<CachedPageV1 | undefined> {\n\t\tconst hash = this.hashFor(rawUrl);\n\t\tif (hash === undefined) return undefined;\n\t\tawait this.ensureInitialized(now);\n\t\tif (this.unavailable) return undefined;\n\n\t\tconst pending = this.pendingWrites.get(hash);\n\t\tif (pending !== undefined) {\n\t\t\tawait pending;\n\t\t\tif (this.unavailable) return undefined;\n\t\t}\n\n\t\tlet entry = this.entries.get(hash);\n\t\tif (entry === undefined) {\n\t\t\tentry = await this.loadEntryFromDisk(hash, now);\n\t\t\tif (entry === undefined) return undefined;\n\t\t\tthis.entries.set(hash, entry);\n\t\t\tthis.totalBytes += entry.bytes;\n\t\t}\n\t\tif (now >= entry.expiresAt) {\n\t\t\tawait this.removeEntry(entry);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst content = await this.readPage(entry);\n\t\tif (content === undefined) {\n\t\t\tawait this.removeEntry(entry);\n\t\t\treturn undefined;\n\t\t}\n\t\tif (this.entries.get(hash) !== entry) return undefined;\n\n\t\tentry.hits += 1;\n\t\tentry.lastAccess = now;\n\t\tentry.expiresAt = Math.max(entry.expiresAt, now + this.ttlForHits(entry.hits));\n\t\tthis.dirtyMeta.set(hash, entry);\n\t\tthis.scheduleMetaFlush();\n\t\treturn content;\n\t}\n\n\t/** Queue a content write; any cache failure is ignored by the caller. */\n\tasync set(rawUrl: string, page: CachedPageV1, now: number): Promise<void> {\n\t\ttry {\n\t\t\tawait this.setUnsafe(rawUrl, page, now);\n\t\t} catch (error) {\n\t\t\tthis.logDebug(\"page cache write setup failed; search continues\", error);\n\t\t}\n\t}\n\n\tprivate async setUnsafe(rawUrl: string, page: CachedPageV1, now: number): Promise<void> {\n\t\tconst hash = this.hashFor(rawUrl);\n\t\tif (hash === undefined) return;\n\t\tif (page.text.trim() === \"\") return;\n\t\tconst bytes = byteLength(serializePage(page));\n\t\tif (bytes > this.maxEntryBytes) return;\n\t\tawait this.ensureInitialized(now);\n\t\tif (this.unavailable) return;\n\t\tif (this.pendingWrites.has(hash)) return;\n\t\tif (this.pendingWrites.size >= this.writeQueueEntries || this.pendingWriteBytes + bytes > this.writeQueueBytes) {\n\t\t\tthis.logSkippedWrite();\n\t\t\treturn;\n\t\t}\n\n\t\tconst paths = this.pathsFor(hash);\n\t\tconst entry: EntryStateV1 = {\n\t\t\thash,\n\t\t\tpagePath: paths.pagePath,\n\t\t\tmetaPath: paths.metaPath,\n\t\t\tbytes,\n\t\t\tstoredAt: now,\n\t\t\texpiresAt: now + this.baseTtlMs,\n\t\t\thits: 0,\n\t\t\tlastAccess: now,\n\t\t};\n\t\tconst previous = this.entries.get(hash);\n\t\tif (!this.makeRoom(hash, bytes - (previous?.bytes ?? 0), now)) {\n\t\t\tthis.schedulePrune();\n\t\t\treturn;\n\t\t}\n\t\tthis.entries.set(hash, entry);\n\t\tthis.totalBytes += bytes - (previous?.bytes ?? 0);\n\t\tthis.pendingWriteBytes += bytes;\n\t\tconst write = this.writeEntry(entry, page).finally(() => {\n\t\t\tthis.pendingWriteBytes = Math.max(0, this.pendingWriteBytes - bytes);\n\t\t\tthis.pendingWrites.delete(hash);\n\t\t});\n\t\tthis.pendingWrites.set(hash, write);\n\t\twrite.catch(() => {\n\t\t\t// writeEntry records failures and drops the index entry; never surface\n\t\t\t// an unhandled rejection from a background cache write.\n\t\t});\n\t}\n\n\t/** Persist queued writes and hit counts; used by tests and graceful shutdown. */\n\tasync flushWrites(): Promise<void> {\n\t\twhile (this.pendingWrites.size > 0) {\n\t\t\tawait Promise.allSettled([...this.pendingWrites.values()]);\n\t\t}\n\t\tawait this.flushUsage();\n\t}\n\n\tasync flushUsage(): Promise<void> {\n\t\tif (this.metaTimer !== undefined) {\n\t\t\tclearTimeout(this.metaTimer);\n\t\t\tthis.metaTimer = undefined;\n\t\t}\n\t\tconst snapshot = [...this.dirtyMeta.values()];\n\t\tthis.dirtyMeta.clear();\n\t\tawait Promise.all(\n\t\t\tsnapshot.map(async (entry) => {\n\t\t\t\tif (this.entries.get(entry.hash) !== entry) return;\n\t\t\t\ttry {\n\t\t\t\t\tawait this.writeMetaFile(entry);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (this.entries.get(entry.hash) === entry) this.dirtyMeta.set(entry.hash, entry);\n\t\t\t\t\tthis.logDebug(\"page cache could not flush hit bookkeeping\", error);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}\n\n\t/** Remove expired entries and enforce the size/count ceiling. */\n\tasync prune(now: number): Promise<void> {\n\t\tawait this.ensureInitialized(now);\n\t\tawait this.enqueuePrune(now, false);\n\t\tawait this.flushUsage();\n\t}\n\n\tprivate hashFor(rawUrl: string): string | undefined {\n\t\tif (urlHasUserInfo(rawUrl)) return undefined;\n\t\ttry {\n\t\t\treturn createHash(\"sha256\")\n\t\t\t\t.update(`search-page-v1\\n${normalizeUrl(rawUrl)}`)\n\t\t\t\t.digest(\"hex\");\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate pathsFor(hash: string): { readonly pagePath: string; readonly metaPath: string } {\n\t\tconst dir = join(this.directory, hash.slice(0, 2));\n\t\treturn {\n\t\t\tpagePath: join(dir, `${hash}${PAGE_SUFFIX}`),\n\t\t\tmetaPath: join(dir, `${hash}${META_SUFFIX}`),\n\t\t};\n\t}\n\n\t/** Evict low-value entries from the index before admitting a new one. */\n\tprivate makeRoom(replacingHash: string, bytesDelta: number, now: number): boolean {\n\t\tconst within = (): boolean =>\n\t\t\tthis.entries.size + (this.entries.has(replacingHash) ? 0 : 1) <= this.maxEntries &&\n\t\t\tthis.totalBytes + bytesDelta <= this.maxBytes;\n\t\tif (within()) return true;\n\t\tconst candidates = [...this.entries.values()]\n\t\t\t.filter((entry) => entry.hash !== replacingHash && !this.pendingWrites.has(entry.hash))\n\t\t\t.sort((left, right) => {\n\t\t\t\tconst leftExpired = now >= left.expiresAt ? 1 : 0;\n\t\t\t\tconst rightExpired = now >= right.expiresAt ? 1 : 0;\n\t\t\t\tif (leftExpired !== rightExpired) return rightExpired - leftExpired;\n\t\t\t\treturn left.hits - right.hits || left.lastAccess - right.lastAccess;\n\t\t\t});\n\t\tfor (const entry of candidates) {\n\t\t\tif (within()) break;\n\t\t\tthis.entries.delete(entry.hash);\n\t\t\tthis.totalBytes = Math.max(0, this.totalBytes - entry.bytes);\n\t\t\tthis.dirtyMeta.delete(entry.hash);\n\t\t\tvoid this.removeFiles(entry.pagePath, entry.metaPath);\n\t\t}\n\t\treturn within();\n\t}\n\n\tprivate ttlForHits(hits: number): number {\n\t\tif (hits >= 50) return this.ttlTiersMs[3] ?? this.baseTtlMs;\n\t\tif (hits >= 10) return this.ttlTiersMs[2] ?? this.baseTtlMs;\n\t\tif (hits >= 3) return this.ttlTiersMs[1] ?? this.baseTtlMs;\n\t\tif (hits >= 1) return this.ttlTiersMs[0] ?? this.baseTtlMs;\n\t\treturn this.baseTtlMs;\n\t}\n\n\tprivate ensureInitialized(now: number): Promise<void> {\n\t\tthis.initPromise ??= this.loadIndex(now);\n\t\treturn this.initPromise;\n\t}\n\n\tprivate async loadIndex(now: number): Promise<void> {\n\t\tlet shards: Dirent[];\n\t\ttry {\n\t\t\tshards = await readdir(this.directory, { withFileTypes: true });\n\t\t} catch (error) {\n\t\t\tif (isMissing(error)) return;\n\t\t\tthis.unavailable = true;\n\t\t\tthis.logUnavailable(error);\n\t\t\treturn;\n\t\t}\n\t\tfor (const shard of shards) {\n\t\t\tif (!shard.isDirectory() || !SHARD_PATTERN.test(shard.name)) continue;\n\t\t\tconst shardDir = join(this.directory, shard.name);\n\t\t\tlet files: readonly string[];\n\t\t\ttry {\n\t\t\t\tfiles = await readdir(shardDir);\n\t\t\t} catch (error) {\n\t\t\t\tif (!isMissing(error)) this.logDebug(\"page cache shard is unreadable\", error);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (const file of files) {\n\t\t\t\tif (!file.endsWith(META_SUFFIX)) continue;\n\t\t\t\tconst hash = file.slice(0, -META_SUFFIX.length);\n\t\t\t\tif (!HASH_PATTERN.test(hash)) continue;\n\t\t\t\tconst entry = await this.loadEntryFromDisk(hash, now);\n\t\t\t\tif (entry === undefined) continue;\n\t\t\t\tthis.entries.set(hash, entry);\n\t\t\t\tthis.totalBytes += entry.bytes;\n\t\t\t}\n\t\t}\n\t\tawait this.enqueuePrune(now, false);\n\t}\n\n\tprivate async loadEntryFromDisk(hash: string, now: number): Promise<EntryStateV1 | undefined> {\n\t\tconst paths = this.pathsFor(hash);\n\t\tconst meta = await this.readMetaFile(paths.metaPath);\n\t\tif (meta === undefined) {\n\t\t\t// A page file can be visible for a moment before its meta file is\n\t\t\t// renamed into place by another process; never delete that fresh file.\n\t\t\treturn undefined;\n\t\t}\n\t\tif (meta.hash !== hash) {\n\t\t\tawait this.removeFiles(paths.pagePath, paths.metaPath);\n\t\t\treturn undefined;\n\t\t}\n\t\tlet pageSize: number;\n\t\ttry {\n\t\t\tconst pageStat = await stat(paths.pagePath);\n\t\t\tpageSize = pageStat.size;\n\t\t} catch (error) {\n\t\t\tif (!isMissing(error)) this.logDebug(\"page cache entry has no readable page file\", error);\n\t\t\tawait this.removeFiles(paths.pagePath, paths.metaPath);\n\t\t\treturn undefined;\n\t\t}\n\t\tif (pageSize !== meta.bytes || pageSize > this.maxEntryBytes) {\n\t\t\tawait this.removeFiles(paths.pagePath, paths.metaPath);\n\t\t\treturn undefined;\n\t\t}\n\t\tif (now >= meta.expiresAt) {\n\t\t\tawait this.removeFiles(paths.pagePath, paths.metaPath);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\thash,\n\t\t\tpagePath: paths.pagePath,\n\t\t\tmetaPath: paths.metaPath,\n\t\t\tbytes: meta.bytes,\n\t\t\tstoredAt: meta.storedAt,\n\t\t\texpiresAt: meta.expiresAt,\n\t\t\thits: meta.hits,\n\t\t\tlastAccess: meta.lastAccess,\n\t\t};\n\t}\n\n\tprivate async readMetaFile(path: string): Promise<EntryMetaV1 | undefined> {\n\t\tlet size: number;\n\t\ttry {\n\t\t\tsize = (await stat(path)).size;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (size > MAX_META_BYTES) return undefined;\n\t\ttry {\n\t\t\tconst raw = await readFile(path, \"utf8\");\n\t\t\treturn parseMeta(JSON.parse(raw));\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate async readPage(entry: EntryStateV1): Promise<CachedPageV1 | undefined> {\n\t\ttry {\n\t\t\tconst info = await stat(entry.pagePath);\n\t\t\tif (info.size !== entry.bytes) return undefined;\n\t\t\tconst raw = await readFile(entry.pagePath, \"utf8\");\n\t\t\tconst parsed = parsePage(JSON.parse(raw));\n\t\t\tif (parsed === undefined) return undefined;\n\t\t\treturn {\n\t\t\t\ttext: parsed.text,\n\t\t\t\ttruncated: parsed.truncated,\n\t\t\t\t...(parsed.title === undefined ? {} : { title: parsed.title }),\n\t\t\t};\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate async writeEntry(entry: EntryStateV1, page: CachedPageV1): Promise<void> {\n\t\ttry {\n\t\t\tawait mkdir(join(this.directory, entry.hash.slice(0, 2)), { recursive: true, mode: 0o700 });\n\t\t\tawait this.atomicWrite(entry.pagePath, serializePage(page));\n\t\t\tawait this.writeMetaFile(entry);\n\t\t\tthis.schedulePrune();\n\t\t} catch (error) {\n\t\t\tawait this.removeFiles(entry.pagePath, entry.metaPath);\n\t\t\tif (this.entries.get(entry.hash) === entry) {\n\t\t\t\tthis.entries.delete(entry.hash);\n\t\t\t\tthis.totalBytes = Math.max(0, this.totalBytes - entry.bytes);\n\t\t\t}\n\t\t\tif (!this.loggedWriteFailure) {\n\t\t\t\tthis.loggedWriteFailure = true;\n\t\t\t\tthis.logger?.warn(\"page cache write failed; search continues without caching\", {\n\t\t\t\t\tcause: error instanceof Error ? error.message : String(error),\n\t\t\t\t});\n\t\t\t}\n\t\t\tthis.logDebug(\"page cache write failed\", error);\n\t\t}\n\t}\n\n\tprivate async writeMetaFile(entry: EntryStateV1): Promise<void> {\n\t\tconst meta: CachedPageFileV1 = {\n\t\t\tversion: 1,\n\t\t\thash: entry.hash,\n\t\t\tbytes: entry.bytes,\n\t\t\tstoredAt: entry.storedAt,\n\t\t\texpiresAt: entry.expiresAt,\n\t\t\thits: entry.hits,\n\t\t\tlastAccess: entry.lastAccess,\n\t\t};\n\t\tawait mkdir(join(this.directory, entry.hash.slice(0, 2)), { recursive: true, mode: 0o700 });\n\t\tawait this.atomicWrite(entry.metaPath, JSON.stringify(meta));\n\t}\n\n\tprivate async atomicWrite(path: string, content: string): Promise<void> {\n\t\tconst temp = `${path}.${process.pid}.${randomUUID()}.tmp`;\n\t\ttry {\n\t\t\tawait writeFile(temp, content, { encoding: \"utf8\", mode: 0o600 });\n\t\t\ttry {\n\t\t\t\tawait rename(temp, path);\n\t\t\t} catch {\n\t\t\t\t// Windows can refuse to replace an existing file; remove and retry.\n\t\t\t\tawait rm(path, { force: true });\n\t\t\t\tawait rename(temp, path);\n\t\t\t}\n\t\t} finally {\n\t\t\tawait rm(temp, { force: true }).catch(() => {});\n\t\t}\n\t}\n\n\tprivate async removeEntry(entry: EntryStateV1): Promise<void> {\n\t\tif (this.entries.get(entry.hash) === entry) {\n\t\t\tthis.entries.delete(entry.hash);\n\t\t\tthis.totalBytes = Math.max(0, this.totalBytes - entry.bytes);\n\t\t}\n\t\tthis.dirtyMeta.delete(entry.hash);\n\t\tawait this.removeFiles(entry.pagePath, entry.metaPath);\n\t}\n\n\tprivate async removeFiles(pagePath: string, metaPath: string): Promise<void> {\n\t\tawait Promise.all([\n\t\t\tunlink(pagePath).catch((error: unknown) => {\n\t\t\t\tif (!isMissing(error)) this.logDebug(\"page cache could not remove a page file\", error);\n\t\t\t}),\n\t\t\tunlink(metaPath).catch((error: unknown) => {\n\t\t\t\tif (!isMissing(error)) this.logDebug(\"page cache could not remove a meta file\", error);\n\t\t\t}),\n\t\t]);\n\t}\n\n\tprivate enqueuePrune(now: number, capacityOnly: boolean): Promise<void> {\n\t\tconst run = (): Promise<void> => this.pruneNow(now, capacityOnly);\n\t\tthis.pruneChain = this.pruneChain.then(run, run);\n\t\treturn this.pruneChain;\n\t}\n\n\tprivate async pruneNow(now: number, capacityOnly: boolean): Promise<void> {\n\t\tif (this.unavailable) return;\n\t\tconst removable = [...this.entries.values()].filter((entry) => !this.pendingWrites.has(entry.hash));\n\t\tconst victims: EntryStateV1[] = [];\n\t\tif (!capacityOnly) {\n\t\t\tfor (const entry of removable) {\n\t\t\t\tif (now >= entry.expiresAt) victims.push(entry);\n\t\t\t}\n\t\t}\n\t\tlet count = this.entries.size - victims.length;\n\t\tlet bytes = this.totalBytes - victims.reduce((sum, entry) => sum + entry.bytes, 0);\n\t\tif (count > this.maxEntries || bytes > this.maxBytes) {\n\t\t\tconst candidates = removable\n\t\t\t\t.filter((entry) => !victims.includes(entry))\n\t\t\t\t.sort((left, right) => left.hits - right.hits || left.lastAccess - right.lastAccess);\n\t\t\tfor (const entry of candidates) {\n\t\t\t\tif (count <= this.maxEntries && bytes <= this.maxBytes) break;\n\t\t\t\tvictims.push(entry);\n\t\t\t\tcount -= 1;\n\t\t\t\tbytes -= entry.bytes;\n\t\t\t}\n\t\t}\n\t\tawait Promise.all(victims.map((entry) => this.removeEntry(entry)));\n\t\tawait this.cleanupOrphanFiles(now);\n\t}\n\n\tprivate async cleanupOrphanFiles(now: number): Promise<void> {\n\t\tconst staleBefore = now - 60 * 60 * 1000;\n\t\tlet shards: Dirent[];\n\t\ttry {\n\t\t\tshards = await readdir(this.directory, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const shard of shards) {\n\t\t\tif (!shard.isDirectory() || !SHARD_PATTERN.test(shard.name)) continue;\n\t\t\tconst shardDir = join(this.directory, shard.name);\n\t\t\tlet files: readonly string[];\n\t\t\ttry {\n\t\t\t\tfiles = await readdir(shardDir);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (const file of files) {\n\t\t\t\tconst isPage = file.endsWith(PAGE_SUFFIX);\n\t\t\t\tconst isTemp = file.endsWith(\".tmp\");\n\t\t\t\tif (!isPage && !isTemp) continue;\n\t\t\t\tif (isPage) {\n\t\t\t\t\tconst hash = file.slice(0, -PAGE_SUFFIX.length);\n\t\t\t\t\tif (!HASH_PATTERN.test(hash) || this.entries.has(hash)) continue;\n\t\t\t\t}\n\t\t\t\tconst path = join(shardDir, file);\n\t\t\t\ttry {\n\t\t\t\t\tconst info = await stat(path);\n\t\t\t\t\tif (info.mtimeMs < staleBefore) await unlink(path);\n\t\t\t\t} catch {\n\t\t\t\t\t// A concurrent process may have removed it already.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate scheduleMetaFlush(): void {\n\t\tif (this.metaTimer !== undefined) return;\n\t\tthis.metaTimer = setTimeout(() => {\n\t\t\tthis.metaTimer = undefined;\n\t\t\tvoid this.flushUsage();\n\t\t}, this.metaFlushMs);\n\t\tthis.metaTimer.unref?.();\n\t}\n\n\tprivate schedulePrune(): void {\n\t\tif (this.pruneTimer !== undefined) return;\n\t\tthis.pruneTimer = setTimeout(() => {\n\t\t\tthis.pruneTimer = undefined;\n\t\t\tvoid this.enqueuePrune(Date.now(), true).catch((error: unknown) => {\n\t\t\t\tthis.logDebug(\"page cache prune failed\", error);\n\t\t\t});\n\t\t}, this.pruneIntervalMs);\n\t\tthis.pruneTimer.unref?.();\n\t}\n\n\tprivate logUnavailable(error: unknown): void {\n\t\tif (this.loggedUnavailable) return;\n\t\tthis.loggedUnavailable = true;\n\t\tthis.logger?.warn(\"page cache directory is unreadable; page caching is disabled\", {\n\t\t\tcause: error instanceof Error ? error.message : String(error),\n\t\t});\n\t}\n\n\tprivate logSkippedWrite(): void {\n\t\tif (this.loggedSkippedWrite) return;\n\t\tthis.loggedSkippedWrite = true;\n\t\tthis.logger?.debug(\"page cache write queue is full; skipping background writes\");\n\t}\n\n\tprivate logDebug(message: string, error?: unknown): void {\n\t\t// A logger failure must never affect the search result.\n\t\ttry {\n\t\t\tthis.logger?.debug(\n\t\t\t\tmessage,\n\t\t\t\terror === undefined\n\t\t\t\t\t? undefined\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tcause: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t},\n\t\t\t);\n\t\t} catch {\n\t\t\t// Ignore logger failures.\n\t\t}\n\t}\n}\n\ninterface SummaryEntryV1<T> {\n\treadonly value: T;\n\treadonly expiresAt: number;\n}\n\n/** Small in-memory LRU for successful summaries; failures are never stored. */\nexport class SummaryCache<T> {\n\tprivate readonly entries = new Map<string, SummaryEntryV1<T>>();\n\tprivate readonly maxEntries: number;\n\tprivate readonly ttlMs: number;\n\n\tconstructor(\n\t\tmaxEntries: number = SEARCH_LIMITS.summaryCacheEntries,\n\t\tttlMs: number = SEARCH_LIMITS.summaryCacheTtlMs,\n\t) {\n\t\tthis.maxEntries = maxEntries;\n\t\tthis.ttlMs = ttlMs;\n\t}\n\n\tget(key: string, now: number): T | undefined {\n\t\tconst entry = this.entries.get(key);\n\t\tif (entry === undefined) return undefined;\n\t\tif (now >= entry.expiresAt) {\n\t\t\tthis.entries.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.entries.delete(key);\n\t\tthis.entries.set(key, entry);\n\t\treturn entry.value;\n\t}\n\n\tset(key: string, value: T, now: number): void {\n\t\tthis.entries.delete(key);\n\t\tthis.entries.set(key, { value, expiresAt: now + this.ttlMs });\n\t\twhile (this.entries.size > this.maxEntries) {\n\t\t\tconst oldest = this.entries.keys().next();\n\t\t\tif (oldest.done === true) break;\n\t\t\tthis.entries.delete(oldest.value);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search capability: one implementation, two model-facing registrations.
|
|
3
|
+
*
|
|
4
|
+
* `search` is the merged entry (model + url) and is projected only when the
|
|
5
|
+
* main request has no usable native search. `search_url` is the URL-only entry
|
|
6
|
+
* and is projected when native search is available. The host capability
|
|
7
|
+
* projection selects exactly one.
|
|
8
|
+
*/
|
|
9
|
+
import type { CapabilityPluginSyncFactory } from "@agent-forge/plugin-sdk";
|
|
10
|
+
import { PageCache, SummaryCache } from "./cache.ts";
|
|
11
|
+
import type { FetchImpl } from "./http.ts";
|
|
12
|
+
import { type CachedSummaryV1 } from "./url-search.ts";
|
|
13
|
+
export interface SearchPluginOptionsV1 {
|
|
14
|
+
readonly fetchImpl?: FetchImpl;
|
|
15
|
+
readonly now?: () => number;
|
|
16
|
+
readonly pageCache?: PageCache;
|
|
17
|
+
readonly summaryCache?: SummaryCache<CachedSummaryV1>;
|
|
18
|
+
/** Test/embedding override; plugin config `cacheDirectory` takes precedence. */
|
|
19
|
+
readonly cacheDirectory?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function createSearchCapabilityFactoryV1(options?: SearchPluginOptionsV1): CapabilityPluginSyncFactory;
|
|
22
|
+
//# sourceMappingURL=capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAiB,2BAA2B,EAAwB,MAAM,yBAAyB,CAAC;AAGhH,OAAO,EAA6B,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,OAAO,EAAE,KAAK,eAAe,EAAgB,MAAM,iBAAiB,CAAC;AAErE,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IACtD,gFAAgF;IAChF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AA6JD,wBAAgB,+BAA+B,CAAC,OAAO,GAAE,qBAA0B,GAAG,2BAA2B,CAgFhH","sourcesContent":["/**\n * Search capability: one implementation, two model-facing registrations.\n *\n * `search` is the merged entry (model + url) and is projected only when the\n * main request has no usable native search. `search_url` is the URL-only entry\n * and is projected when native search is available. The host capability\n * projection selects exactly one.\n */\n\nimport type { CapabilityAPI, CapabilityPluginSyncFactory, NetworkRouteConfigV1 } from \"@agent-forge/plugin-sdk\";\nimport { parseNetworkRouteConfigV1 } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { defaultPageCacheDirectory, PageCache, SummaryCache } from \"./cache.ts\";\nimport type { FetchImpl } from \"./http.ts\";\nimport { SEARCH_LIMITS, type SearchMode } from \"./limits.ts\";\nimport { runModelSearch } from \"./model-search.ts\";\nimport { isRecord, truncateText } from \"./text.ts\";\nimport type { SearchDetailsV1 } from \"./types.ts\";\nimport { type CachedSummaryV1, runUrlSearch } from \"./url-search.ts\";\n\nexport interface SearchPluginOptionsV1 {\n\treadonly fetchImpl?: FetchImpl;\n\treadonly now?: () => number;\n\treadonly pageCache?: PageCache;\n\treadonly summaryCache?: SummaryCache<CachedSummaryV1>;\n\t/** Test/embedding override; plugin config `cacheDirectory` takes precedence. */\n\treadonly cacheDirectory?: string;\n}\n\ninterface ToolResultV1 {\n\treadonly content: Array<{ type: \"text\"; text: string }>;\n\treadonly details: unknown;\n}\n\ninterface ParsedInputV1 {\n\treadonly mode: SearchMode;\n\treadonly query: string;\n\treadonly urls?: readonly string[];\n}\n\nconst URL_LIST_SCHEMA = Type.Array(Type.String({ minLength: 1, maxLength: SEARCH_LIMITS.maxUrlLength }), {\n\tminItems: 1,\n\tmaxItems: SEARCH_LIMITS.maxUrls,\n});\n\nconst SEARCH_PARAMETERS = Type.Object(\n\t{\n\t\tmode: Type.Union([Type.Literal(\"model\"), Type.Literal(\"url\")]),\n\t\tquery: Type.String({ minLength: 1, maxLength: SEARCH_LIMITS.maxQueryLength }),\n\t\turls: Type.Optional(URL_LIST_SCHEMA),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst SEARCH_URL_PARAMETERS = Type.Object(\n\t{\n\t\tquery: Type.String({ minLength: 1, maxLength: SEARCH_LIMITS.maxQueryLength }),\n\t\turls: URL_LIST_SCHEMA,\n\t},\n\t{ additionalProperties: false },\n);\n\nconst SEARCH_DESCRIPTION =\n\t\"Search the web when the current model has no usable native search: mode=model uses the configured search target, mode=url reads the given URLs and returns a bounded evidence summary. Do not use it when native search is available.\";\nconst SEARCH_URL_DESCRIPTION =\n\t\"Read the given URLs and return a bounded evidence summary. This tool does not discover new URLs; use the built-in search for discovery.\";\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = isRecord(first) ? first : isRecord(second) ? second : undefined;\n\tif (value === undefined) throw new Error(\"input must be an object\");\n\treturn value;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combinedSignal(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction assertOnlyKeys(input: Record<string, unknown>, allowed: readonly string[]): void {\n\tconst allowedSet = new Set(allowed);\n\tfor (const key of Object.keys(input)) {\n\t\tif (!allowedSet.has(key)) throw new Error(`unknown field \"${key}\"`);\n\t}\n}\n\nfunction parseQuery(value: unknown): string {\n\tif (typeof value !== \"string\") throw new Error(\"query must be a string\");\n\tconst query = value.trim();\n\tif (query === \"\") throw new Error(\"query must be non-empty\");\n\tif ([...query].length > SEARCH_LIMITS.maxQueryLength) {\n\t\tthrow new Error(`query must be at most ${SEARCH_LIMITS.maxQueryLength} characters`);\n\t}\n\treturn query;\n}\n\nfunction parseUrls(value: unknown): readonly string[] {\n\tif (!Array.isArray(value)) throw new Error(\"urls must be an array\");\n\tif (value.length < 1 || value.length > SEARCH_LIMITS.maxUrls) {\n\t\tthrow new Error(`urls must contain 1-${SEARCH_LIMITS.maxUrls} entries`);\n\t}\n\treturn value.map((entry, index) => {\n\t\tif (typeof entry !== \"string\" || entry.trim() === \"\") {\n\t\t\tthrow new Error(`urls[${index}] must be a non-empty string`);\n\t\t}\n\t\tif (entry.length > SEARCH_LIMITS.maxUrlLength) {\n\t\t\tthrow new Error(`urls[${index}] exceeds ${SEARCH_LIMITS.maxUrlLength} characters`);\n\t\t}\n\t\treturn entry;\n\t});\n}\n\nfunction parseInput(first: unknown, second: unknown, forceUrlOnly: boolean): ParsedInputV1 {\n\tconst input = objectInput(first, second);\n\tif (forceUrlOnly) {\n\t\tassertOnlyKeys(input, [\"query\", \"urls\"]);\n\t\treturn { mode: \"url\", query: parseQuery(input.query), urls: parseUrls(input.urls) };\n\t}\n\tassertOnlyKeys(input, [\"mode\", \"query\", \"urls\"]);\n\tif (input.mode !== \"model\" && input.mode !== \"url\") throw new Error('mode must be \"model\" or \"url\"');\n\tconst mode: SearchMode = input.mode;\n\tconst query = parseQuery(input.query);\n\tif (mode === \"model\") {\n\t\tif (input.urls !== undefined) throw new Error('urls is not allowed when mode is \"model\"');\n\t\treturn { mode, query };\n\t}\n\treturn { mode, query, urls: parseUrls(input.urls) };\n}\n\nfunction failedDetails(\n\tmode: SearchMode,\n\tquery: string,\n\tnow: () => number,\n\tcode: string,\n\tmessage: string,\n): SearchDetailsV1 {\n\treturn {\n\t\tversion: 1,\n\t\tmode,\n\t\tquery,\n\t\tfetchedAt: new Date(now()).toISOString(),\n\t\tstatus: \"failed\",\n\t\tsummary: \"\",\n\t\tsources: [],\n\t\ttotalBytes: 0,\n\t\ttruncated: false,\n\t\terror: { code, message },\n\t};\n}\n\nfunction formatText(details: SearchDetailsV1): string {\n\tif (details.status === \"failed\") {\n\t\tconst error = details.error;\n\t\treturn `search failed (${error?.code ?? \"internal_error\"}): ${truncateText(error?.message ?? \"unknown error\", 2000)}`;\n\t}\n\tconst header = details.mode === \"model\" ? `Model search: \"${details.query}\"` : `URL search: \"${details.query}\"`;\n\tconst lines = [\n\t\theader,\n\t\t`Fetched at ${details.fetchedAt}`,\n\t\t\"\",\n\t\tdetails.mode === \"model\" ? \"Answer:\" : \"Summary:\",\n\t\tdetails.summary,\n\t];\n\tif (details.sources.length > 0) {\n\t\tlines.push(\"\", \"Sources:\");\n\t\tdetails.sources.forEach((source, index) => {\n\t\t\tlines.push(`[${index + 1}] ${source.title ?? source.url}`);\n\t\t\tlines.push(` ${source.url}`);\n\t\t});\n\t}\n\tif (details.status === \"partial\") lines.push(\"\", \"Status: partial (some pages were skipped or failed)\");\n\tif (details.summaryCache === \"hit\") lines.push(\"Summary cache: hit\");\n\treturn lines.join(\"\\n\");\n}\n\nfunction toolResult(details: SearchDetailsV1): ToolResultV1 {\n\treturn { content: [{ type: \"text\", text: formatText(details) }], details };\n}\n\nexport function createSearchCapabilityFactoryV1(options: SearchPluginOptionsV1 = {}): CapabilityPluginSyncFactory {\n\tconst now = options.now ?? Date.now;\n\n\treturn (api: CapabilityAPI) => {\n\t\tconst configuredDirectory =\n\t\t\ttypeof api.config.cacheDirectory === \"string\" && api.config.cacheDirectory.trim() !== \"\"\n\t\t\t\t? api.config.cacheDirectory\n\t\t\t\t: options.cacheDirectory;\n\t\tconst pageCache =\n\t\t\toptions.pageCache ??\n\t\t\tnew PageCache(configuredDirectory ?? defaultPageCacheDirectory(), {\n\t\t\t\t...(api.logger === undefined ? {} : { logger: api.logger }),\n\t\t\t});\n\t\tconst summaryCache = options.summaryCache ?? new SummaryCache<CachedSummaryV1>();\n\t\tlet network: NetworkRouteConfigV1 | undefined;\n\t\tlet networkError: string | undefined;\n\t\ttry {\n\t\t\tif (api.config.network !== undefined) network = parseNetworkRouteConfigV1(api.config.network);\n\t\t} catch (error) {\n\t\t\tnetworkError = error instanceof Error ? error.message : String(error);\n\t\t}\n\n\t\tconst execute = async (\n\t\t\tmode: SearchMode,\n\t\t\tfirst: unknown,\n\t\t\tsecond?: unknown,\n\t\t\tthird?: unknown,\n\t\t): Promise<ToolResultV1> => {\n\t\t\tlet input: ParsedInputV1;\n\t\t\ttry {\n\t\t\t\tinput = parseInput(first, second, mode === \"url\");\n\t\t\t} catch (error) {\n\t\t\t\tconst query = isRecord(first) && typeof first.query === \"string\" ? first.query : \"\";\n\t\t\t\treturn toolResult(\n\t\t\t\t\tfailedDetails(mode, query, now, \"invalid_input\", error instanceof Error ? error.message : String(error)),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst signal = combinedSignal([signalFrom(third) ?? signalFrom(second), api.sessionAbortSignal?.()]);\n\t\t\tif (input.mode === \"model\") {\n\t\t\t\treturn toolResult(await runModelSearch({ query: input.query, api, signal, now }));\n\t\t\t}\n\t\t\tif (networkError !== undefined) {\n\t\t\t\treturn toolResult(failedDetails(\"url\", input.query, now, \"network_config_invalid\", networkError));\n\t\t\t}\n\t\t\treturn toolResult(\n\t\t\t\tawait runUrlSearch({\n\t\t\t\t\tquery: input.query,\n\t\t\t\t\turls: input.urls ?? [],\n\t\t\t\t\tapi,\n\t\t\t\t\tsignal,\n\t\t\t\t\tdeps: {\n\t\t\t\t\t\tnow,\n\t\t\t\t\t\tpageCache,\n\t\t\t\t\t\tsummaryCache,\n\t\t\t\t\t\tnetwork,\n\t\t\t\t\t\t...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t};\n\n\t\tapi.registerTool({\n\t\t\tname: \"search\",\n\t\t\tlabel: \"Search\",\n\t\t\tdescription: SEARCH_DESCRIPTION,\n\t\t\tparameters: SEARCH_PARAMETERS,\n\t\t\treadonlySafe: true,\n\t\t\tcapability: { key: \"search\", hideWhenNative: true },\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => execute(\"model\", first, second, third),\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"search_url\",\n\t\t\tlabel: \"URL search\",\n\t\t\tdescription: SEARCH_URL_DESCRIPTION,\n\t\t\tparameters: SEARCH_URL_PARAMETERS,\n\t\t\treadonlySafe: true,\n\t\t\tcapability: { key: \"search\", showWhenNative: true },\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => execute(\"url\", first, second, third),\n\t\t});\n\t};\n}\n"]}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search capability: one implementation, two model-facing registrations.
|
|
3
|
+
*
|
|
4
|
+
* `search` is the merged entry (model + url) and is projected only when the
|
|
5
|
+
* main request has no usable native search. `search_url` is the URL-only entry
|
|
6
|
+
* and is projected when native search is available. The host capability
|
|
7
|
+
* projection selects exactly one.
|
|
8
|
+
*/
|
|
9
|
+
import { parseNetworkRouteConfigV1 } from "@agent-forge/plugin-sdk";
|
|
10
|
+
import { Type } from "typebox";
|
|
11
|
+
import { defaultPageCacheDirectory, PageCache, SummaryCache } from "./cache.js";
|
|
12
|
+
import { SEARCH_LIMITS } from "./limits.js";
|
|
13
|
+
import { runModelSearch } from "./model-search.js";
|
|
14
|
+
import { isRecord, truncateText } from "./text.js";
|
|
15
|
+
import { runUrlSearch } from "./url-search.js";
|
|
16
|
+
const URL_LIST_SCHEMA = Type.Array(Type.String({ minLength: 1, maxLength: SEARCH_LIMITS.maxUrlLength }), {
|
|
17
|
+
minItems: 1,
|
|
18
|
+
maxItems: SEARCH_LIMITS.maxUrls,
|
|
19
|
+
});
|
|
20
|
+
const SEARCH_PARAMETERS = Type.Object({
|
|
21
|
+
mode: Type.Union([Type.Literal("model"), Type.Literal("url")]),
|
|
22
|
+
query: Type.String({ minLength: 1, maxLength: SEARCH_LIMITS.maxQueryLength }),
|
|
23
|
+
urls: Type.Optional(URL_LIST_SCHEMA),
|
|
24
|
+
}, { additionalProperties: false });
|
|
25
|
+
const SEARCH_URL_PARAMETERS = Type.Object({
|
|
26
|
+
query: Type.String({ minLength: 1, maxLength: SEARCH_LIMITS.maxQueryLength }),
|
|
27
|
+
urls: URL_LIST_SCHEMA,
|
|
28
|
+
}, { additionalProperties: false });
|
|
29
|
+
const SEARCH_DESCRIPTION = "Search the web when the current model has no usable native search: mode=model uses the configured search target, mode=url reads the given URLs and returns a bounded evidence summary. Do not use it when native search is available.";
|
|
30
|
+
const SEARCH_URL_DESCRIPTION = "Read the given URLs and return a bounded evidence summary. This tool does not discover new URLs; use the built-in search for discovery.";
|
|
31
|
+
function objectInput(first, second) {
|
|
32
|
+
const value = isRecord(first) ? first : isRecord(second) ? second : undefined;
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
throw new Error("input must be an object");
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function signalFrom(value) {
|
|
38
|
+
return value instanceof AbortSignal ? value : undefined;
|
|
39
|
+
}
|
|
40
|
+
function combinedSignal(signals) {
|
|
41
|
+
const present = signals.filter((signal) => signal !== undefined);
|
|
42
|
+
if (present.length === 0)
|
|
43
|
+
return undefined;
|
|
44
|
+
if (present.length === 1)
|
|
45
|
+
return present[0];
|
|
46
|
+
return AbortSignal.any(present);
|
|
47
|
+
}
|
|
48
|
+
function assertOnlyKeys(input, allowed) {
|
|
49
|
+
const allowedSet = new Set(allowed);
|
|
50
|
+
for (const key of Object.keys(input)) {
|
|
51
|
+
if (!allowedSet.has(key))
|
|
52
|
+
throw new Error(`unknown field "${key}"`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function parseQuery(value) {
|
|
56
|
+
if (typeof value !== "string")
|
|
57
|
+
throw new Error("query must be a string");
|
|
58
|
+
const query = value.trim();
|
|
59
|
+
if (query === "")
|
|
60
|
+
throw new Error("query must be non-empty");
|
|
61
|
+
if ([...query].length > SEARCH_LIMITS.maxQueryLength) {
|
|
62
|
+
throw new Error(`query must be at most ${SEARCH_LIMITS.maxQueryLength} characters`);
|
|
63
|
+
}
|
|
64
|
+
return query;
|
|
65
|
+
}
|
|
66
|
+
function parseUrls(value) {
|
|
67
|
+
if (!Array.isArray(value))
|
|
68
|
+
throw new Error("urls must be an array");
|
|
69
|
+
if (value.length < 1 || value.length > SEARCH_LIMITS.maxUrls) {
|
|
70
|
+
throw new Error(`urls must contain 1-${SEARCH_LIMITS.maxUrls} entries`);
|
|
71
|
+
}
|
|
72
|
+
return value.map((entry, index) => {
|
|
73
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
74
|
+
throw new Error(`urls[${index}] must be a non-empty string`);
|
|
75
|
+
}
|
|
76
|
+
if (entry.length > SEARCH_LIMITS.maxUrlLength) {
|
|
77
|
+
throw new Error(`urls[${index}] exceeds ${SEARCH_LIMITS.maxUrlLength} characters`);
|
|
78
|
+
}
|
|
79
|
+
return entry;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function parseInput(first, second, forceUrlOnly) {
|
|
83
|
+
const input = objectInput(first, second);
|
|
84
|
+
if (forceUrlOnly) {
|
|
85
|
+
assertOnlyKeys(input, ["query", "urls"]);
|
|
86
|
+
return { mode: "url", query: parseQuery(input.query), urls: parseUrls(input.urls) };
|
|
87
|
+
}
|
|
88
|
+
assertOnlyKeys(input, ["mode", "query", "urls"]);
|
|
89
|
+
if (input.mode !== "model" && input.mode !== "url")
|
|
90
|
+
throw new Error('mode must be "model" or "url"');
|
|
91
|
+
const mode = input.mode;
|
|
92
|
+
const query = parseQuery(input.query);
|
|
93
|
+
if (mode === "model") {
|
|
94
|
+
if (input.urls !== undefined)
|
|
95
|
+
throw new Error('urls is not allowed when mode is "model"');
|
|
96
|
+
return { mode, query };
|
|
97
|
+
}
|
|
98
|
+
return { mode, query, urls: parseUrls(input.urls) };
|
|
99
|
+
}
|
|
100
|
+
function failedDetails(mode, query, now, code, message) {
|
|
101
|
+
return {
|
|
102
|
+
version: 1,
|
|
103
|
+
mode,
|
|
104
|
+
query,
|
|
105
|
+
fetchedAt: new Date(now()).toISOString(),
|
|
106
|
+
status: "failed",
|
|
107
|
+
summary: "",
|
|
108
|
+
sources: [],
|
|
109
|
+
totalBytes: 0,
|
|
110
|
+
truncated: false,
|
|
111
|
+
error: { code, message },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function formatText(details) {
|
|
115
|
+
if (details.status === "failed") {
|
|
116
|
+
const error = details.error;
|
|
117
|
+
return `search failed (${error?.code ?? "internal_error"}): ${truncateText(error?.message ?? "unknown error", 2000)}`;
|
|
118
|
+
}
|
|
119
|
+
const header = details.mode === "model" ? `Model search: "${details.query}"` : `URL search: "${details.query}"`;
|
|
120
|
+
const lines = [
|
|
121
|
+
header,
|
|
122
|
+
`Fetched at ${details.fetchedAt}`,
|
|
123
|
+
"",
|
|
124
|
+
details.mode === "model" ? "Answer:" : "Summary:",
|
|
125
|
+
details.summary,
|
|
126
|
+
];
|
|
127
|
+
if (details.sources.length > 0) {
|
|
128
|
+
lines.push("", "Sources:");
|
|
129
|
+
details.sources.forEach((source, index) => {
|
|
130
|
+
lines.push(`[${index + 1}] ${source.title ?? source.url}`);
|
|
131
|
+
lines.push(` ${source.url}`);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (details.status === "partial")
|
|
135
|
+
lines.push("", "Status: partial (some pages were skipped or failed)");
|
|
136
|
+
if (details.summaryCache === "hit")
|
|
137
|
+
lines.push("Summary cache: hit");
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|
|
140
|
+
function toolResult(details) {
|
|
141
|
+
return { content: [{ type: "text", text: formatText(details) }], details };
|
|
142
|
+
}
|
|
143
|
+
export function createSearchCapabilityFactoryV1(options = {}) {
|
|
144
|
+
const now = options.now ?? Date.now;
|
|
145
|
+
return (api) => {
|
|
146
|
+
const configuredDirectory = typeof api.config.cacheDirectory === "string" && api.config.cacheDirectory.trim() !== ""
|
|
147
|
+
? api.config.cacheDirectory
|
|
148
|
+
: options.cacheDirectory;
|
|
149
|
+
const pageCache = options.pageCache ??
|
|
150
|
+
new PageCache(configuredDirectory ?? defaultPageCacheDirectory(), {
|
|
151
|
+
...(api.logger === undefined ? {} : { logger: api.logger }),
|
|
152
|
+
});
|
|
153
|
+
const summaryCache = options.summaryCache ?? new SummaryCache();
|
|
154
|
+
let network;
|
|
155
|
+
let networkError;
|
|
156
|
+
try {
|
|
157
|
+
if (api.config.network !== undefined)
|
|
158
|
+
network = parseNetworkRouteConfigV1(api.config.network);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
networkError = error instanceof Error ? error.message : String(error);
|
|
162
|
+
}
|
|
163
|
+
const execute = async (mode, first, second, third) => {
|
|
164
|
+
let input;
|
|
165
|
+
try {
|
|
166
|
+
input = parseInput(first, second, mode === "url");
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const query = isRecord(first) && typeof first.query === "string" ? first.query : "";
|
|
170
|
+
return toolResult(failedDetails(mode, query, now, "invalid_input", error instanceof Error ? error.message : String(error)));
|
|
171
|
+
}
|
|
172
|
+
const signal = combinedSignal([signalFrom(third) ?? signalFrom(second), api.sessionAbortSignal?.()]);
|
|
173
|
+
if (input.mode === "model") {
|
|
174
|
+
return toolResult(await runModelSearch({ query: input.query, api, signal, now }));
|
|
175
|
+
}
|
|
176
|
+
if (networkError !== undefined) {
|
|
177
|
+
return toolResult(failedDetails("url", input.query, now, "network_config_invalid", networkError));
|
|
178
|
+
}
|
|
179
|
+
return toolResult(await runUrlSearch({
|
|
180
|
+
query: input.query,
|
|
181
|
+
urls: input.urls ?? [],
|
|
182
|
+
api,
|
|
183
|
+
signal,
|
|
184
|
+
deps: {
|
|
185
|
+
now,
|
|
186
|
+
pageCache,
|
|
187
|
+
summaryCache,
|
|
188
|
+
network,
|
|
189
|
+
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
190
|
+
},
|
|
191
|
+
}));
|
|
192
|
+
};
|
|
193
|
+
api.registerTool({
|
|
194
|
+
name: "search",
|
|
195
|
+
label: "Search",
|
|
196
|
+
description: SEARCH_DESCRIPTION,
|
|
197
|
+
parameters: SEARCH_PARAMETERS,
|
|
198
|
+
readonlySafe: true,
|
|
199
|
+
capability: { key: "search", hideWhenNative: true },
|
|
200
|
+
execute: async (first, second, third) => execute("model", first, second, third),
|
|
201
|
+
});
|
|
202
|
+
api.registerTool({
|
|
203
|
+
name: "search_url",
|
|
204
|
+
label: "URL search",
|
|
205
|
+
description: SEARCH_URL_DESCRIPTION,
|
|
206
|
+
parameters: SEARCH_URL_PARAMETERS,
|
|
207
|
+
readonlySafe: true,
|
|
208
|
+
capability: { key: "search", showWhenNative: true },
|
|
209
|
+
execute: async (first, second, third) => execute("url", first, second, third),
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Search capability: one implementation, two model-facing registrations.\n *\n * `search` is the merged entry (model + url) and is projected only when the\n * main request has no usable native search. `search_url` is the URL-only entry\n * and is projected when native search is available. The host capability\n * projection selects exactly one.\n */\n\nimport type { CapabilityAPI, CapabilityPluginSyncFactory, NetworkRouteConfigV1 } from \"@agent-forge/plugin-sdk\";\nimport { parseNetworkRouteConfigV1 } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { defaultPageCacheDirectory, PageCache, SummaryCache } from \"./cache.ts\";\nimport type { FetchImpl } from \"./http.ts\";\nimport { SEARCH_LIMITS, type SearchMode } from \"./limits.ts\";\nimport { runModelSearch } from \"./model-search.ts\";\nimport { isRecord, truncateText } from \"./text.ts\";\nimport type { SearchDetailsV1 } from \"./types.ts\";\nimport { type CachedSummaryV1, runUrlSearch } from \"./url-search.ts\";\n\nexport interface SearchPluginOptionsV1 {\n\treadonly fetchImpl?: FetchImpl;\n\treadonly now?: () => number;\n\treadonly pageCache?: PageCache;\n\treadonly summaryCache?: SummaryCache<CachedSummaryV1>;\n\t/** Test/embedding override; plugin config `cacheDirectory` takes precedence. */\n\treadonly cacheDirectory?: string;\n}\n\ninterface ToolResultV1 {\n\treadonly content: Array<{ type: \"text\"; text: string }>;\n\treadonly details: unknown;\n}\n\ninterface ParsedInputV1 {\n\treadonly mode: SearchMode;\n\treadonly query: string;\n\treadonly urls?: readonly string[];\n}\n\nconst URL_LIST_SCHEMA = Type.Array(Type.String({ minLength: 1, maxLength: SEARCH_LIMITS.maxUrlLength }), {\n\tminItems: 1,\n\tmaxItems: SEARCH_LIMITS.maxUrls,\n});\n\nconst SEARCH_PARAMETERS = Type.Object(\n\t{\n\t\tmode: Type.Union([Type.Literal(\"model\"), Type.Literal(\"url\")]),\n\t\tquery: Type.String({ minLength: 1, maxLength: SEARCH_LIMITS.maxQueryLength }),\n\t\turls: Type.Optional(URL_LIST_SCHEMA),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst SEARCH_URL_PARAMETERS = Type.Object(\n\t{\n\t\tquery: Type.String({ minLength: 1, maxLength: SEARCH_LIMITS.maxQueryLength }),\n\t\turls: URL_LIST_SCHEMA,\n\t},\n\t{ additionalProperties: false },\n);\n\nconst SEARCH_DESCRIPTION =\n\t\"Search the web when the current model has no usable native search: mode=model uses the configured search target, mode=url reads the given URLs and returns a bounded evidence summary. Do not use it when native search is available.\";\nconst SEARCH_URL_DESCRIPTION =\n\t\"Read the given URLs and return a bounded evidence summary. This tool does not discover new URLs; use the built-in search for discovery.\";\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = isRecord(first) ? first : isRecord(second) ? second : undefined;\n\tif (value === undefined) throw new Error(\"input must be an object\");\n\treturn value;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction combinedSignal(signals: readonly (AbortSignal | undefined)[]): AbortSignal | undefined {\n\tconst present = signals.filter((signal): signal is AbortSignal => signal !== undefined);\n\tif (present.length === 0) return undefined;\n\tif (present.length === 1) return present[0];\n\treturn AbortSignal.any(present);\n}\n\nfunction assertOnlyKeys(input: Record<string, unknown>, allowed: readonly string[]): void {\n\tconst allowedSet = new Set(allowed);\n\tfor (const key of Object.keys(input)) {\n\t\tif (!allowedSet.has(key)) throw new Error(`unknown field \"${key}\"`);\n\t}\n}\n\nfunction parseQuery(value: unknown): string {\n\tif (typeof value !== \"string\") throw new Error(\"query must be a string\");\n\tconst query = value.trim();\n\tif (query === \"\") throw new Error(\"query must be non-empty\");\n\tif ([...query].length > SEARCH_LIMITS.maxQueryLength) {\n\t\tthrow new Error(`query must be at most ${SEARCH_LIMITS.maxQueryLength} characters`);\n\t}\n\treturn query;\n}\n\nfunction parseUrls(value: unknown): readonly string[] {\n\tif (!Array.isArray(value)) throw new Error(\"urls must be an array\");\n\tif (value.length < 1 || value.length > SEARCH_LIMITS.maxUrls) {\n\t\tthrow new Error(`urls must contain 1-${SEARCH_LIMITS.maxUrls} entries`);\n\t}\n\treturn value.map((entry, index) => {\n\t\tif (typeof entry !== \"string\" || entry.trim() === \"\") {\n\t\t\tthrow new Error(`urls[${index}] must be a non-empty string`);\n\t\t}\n\t\tif (entry.length > SEARCH_LIMITS.maxUrlLength) {\n\t\t\tthrow new Error(`urls[${index}] exceeds ${SEARCH_LIMITS.maxUrlLength} characters`);\n\t\t}\n\t\treturn entry;\n\t});\n}\n\nfunction parseInput(first: unknown, second: unknown, forceUrlOnly: boolean): ParsedInputV1 {\n\tconst input = objectInput(first, second);\n\tif (forceUrlOnly) {\n\t\tassertOnlyKeys(input, [\"query\", \"urls\"]);\n\t\treturn { mode: \"url\", query: parseQuery(input.query), urls: parseUrls(input.urls) };\n\t}\n\tassertOnlyKeys(input, [\"mode\", \"query\", \"urls\"]);\n\tif (input.mode !== \"model\" && input.mode !== \"url\") throw new Error('mode must be \"model\" or \"url\"');\n\tconst mode: SearchMode = input.mode;\n\tconst query = parseQuery(input.query);\n\tif (mode === \"model\") {\n\t\tif (input.urls !== undefined) throw new Error('urls is not allowed when mode is \"model\"');\n\t\treturn { mode, query };\n\t}\n\treturn { mode, query, urls: parseUrls(input.urls) };\n}\n\nfunction failedDetails(\n\tmode: SearchMode,\n\tquery: string,\n\tnow: () => number,\n\tcode: string,\n\tmessage: string,\n): SearchDetailsV1 {\n\treturn {\n\t\tversion: 1,\n\t\tmode,\n\t\tquery,\n\t\tfetchedAt: new Date(now()).toISOString(),\n\t\tstatus: \"failed\",\n\t\tsummary: \"\",\n\t\tsources: [],\n\t\ttotalBytes: 0,\n\t\ttruncated: false,\n\t\terror: { code, message },\n\t};\n}\n\nfunction formatText(details: SearchDetailsV1): string {\n\tif (details.status === \"failed\") {\n\t\tconst error = details.error;\n\t\treturn `search failed (${error?.code ?? \"internal_error\"}): ${truncateText(error?.message ?? \"unknown error\", 2000)}`;\n\t}\n\tconst header = details.mode === \"model\" ? `Model search: \"${details.query}\"` : `URL search: \"${details.query}\"`;\n\tconst lines = [\n\t\theader,\n\t\t`Fetched at ${details.fetchedAt}`,\n\t\t\"\",\n\t\tdetails.mode === \"model\" ? \"Answer:\" : \"Summary:\",\n\t\tdetails.summary,\n\t];\n\tif (details.sources.length > 0) {\n\t\tlines.push(\"\", \"Sources:\");\n\t\tdetails.sources.forEach((source, index) => {\n\t\t\tlines.push(`[${index + 1}] ${source.title ?? source.url}`);\n\t\t\tlines.push(` ${source.url}`);\n\t\t});\n\t}\n\tif (details.status === \"partial\") lines.push(\"\", \"Status: partial (some pages were skipped or failed)\");\n\tif (details.summaryCache === \"hit\") lines.push(\"Summary cache: hit\");\n\treturn lines.join(\"\\n\");\n}\n\nfunction toolResult(details: SearchDetailsV1): ToolResultV1 {\n\treturn { content: [{ type: \"text\", text: formatText(details) }], details };\n}\n\nexport function createSearchCapabilityFactoryV1(options: SearchPluginOptionsV1 = {}): CapabilityPluginSyncFactory {\n\tconst now = options.now ?? Date.now;\n\n\treturn (api: CapabilityAPI) => {\n\t\tconst configuredDirectory =\n\t\t\ttypeof api.config.cacheDirectory === \"string\" && api.config.cacheDirectory.trim() !== \"\"\n\t\t\t\t? api.config.cacheDirectory\n\t\t\t\t: options.cacheDirectory;\n\t\tconst pageCache =\n\t\t\toptions.pageCache ??\n\t\t\tnew PageCache(configuredDirectory ?? defaultPageCacheDirectory(), {\n\t\t\t\t...(api.logger === undefined ? {} : { logger: api.logger }),\n\t\t\t});\n\t\tconst summaryCache = options.summaryCache ?? new SummaryCache<CachedSummaryV1>();\n\t\tlet network: NetworkRouteConfigV1 | undefined;\n\t\tlet networkError: string | undefined;\n\t\ttry {\n\t\t\tif (api.config.network !== undefined) network = parseNetworkRouteConfigV1(api.config.network);\n\t\t} catch (error) {\n\t\t\tnetworkError = error instanceof Error ? error.message : String(error);\n\t\t}\n\n\t\tconst execute = async (\n\t\t\tmode: SearchMode,\n\t\t\tfirst: unknown,\n\t\t\tsecond?: unknown,\n\t\t\tthird?: unknown,\n\t\t): Promise<ToolResultV1> => {\n\t\t\tlet input: ParsedInputV1;\n\t\t\ttry {\n\t\t\t\tinput = parseInput(first, second, mode === \"url\");\n\t\t\t} catch (error) {\n\t\t\t\tconst query = isRecord(first) && typeof first.query === \"string\" ? first.query : \"\";\n\t\t\t\treturn toolResult(\n\t\t\t\t\tfailedDetails(mode, query, now, \"invalid_input\", error instanceof Error ? error.message : String(error)),\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst signal = combinedSignal([signalFrom(third) ?? signalFrom(second), api.sessionAbortSignal?.()]);\n\t\t\tif (input.mode === \"model\") {\n\t\t\t\treturn toolResult(await runModelSearch({ query: input.query, api, signal, now }));\n\t\t\t}\n\t\t\tif (networkError !== undefined) {\n\t\t\t\treturn toolResult(failedDetails(\"url\", input.query, now, \"network_config_invalid\", networkError));\n\t\t\t}\n\t\t\treturn toolResult(\n\t\t\t\tawait runUrlSearch({\n\t\t\t\t\tquery: input.query,\n\t\t\t\t\turls: input.urls ?? [],\n\t\t\t\t\tapi,\n\t\t\t\t\tsignal,\n\t\t\t\t\tdeps: {\n\t\t\t\t\t\tnow,\n\t\t\t\t\t\tpageCache,\n\t\t\t\t\t\tsummaryCache,\n\t\t\t\t\t\tnetwork,\n\t\t\t\t\t\t...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t};\n\n\t\tapi.registerTool({\n\t\t\tname: \"search\",\n\t\t\tlabel: \"Search\",\n\t\t\tdescription: SEARCH_DESCRIPTION,\n\t\t\tparameters: SEARCH_PARAMETERS,\n\t\t\treadonlySafe: true,\n\t\t\tcapability: { key: \"search\", hideWhenNative: true },\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => execute(\"model\", first, second, third),\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"search_url\",\n\t\t\tlabel: \"URL search\",\n\t\t\tdescription: SEARCH_URL_DESCRIPTION,\n\t\t\tparameters: SEARCH_URL_PARAMETERS,\n\t\t\treadonlySafe: true,\n\t\t\tcapability: { key: \"search\", showWhenNative: true },\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => execute(\"url\", first, second, third),\n\t\t});\n\t};\n}\n"]}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":";AAGA,wBAAuG","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { createSearchCapabilityFactoryV1 } from \"./capability.ts\";\n\nexport default ((api) => createSearchCapabilityFactoryV1()(api)) satisfies CapabilityPluginSyncFactory;\n"]}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAC;AAElE,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,+BAA+B,EAAE,CAAC,GAAG,CAAC,CAAuC,CAAC","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { createSearchCapabilityFactoryV1 } from \"./capability.ts\";\n\nexport default ((api) => createSearchCapabilityFactoryV1()(api)) satisfies CapabilityPluginSyncFactory;\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded readability extraction (搜索工具设计 §6): pinned
|
|
3
|
+
* @mozilla/readability over linkedom, no in-house HTML parser.
|
|
4
|
+
*/
|
|
5
|
+
export interface ReadablePageV1 {
|
|
6
|
+
readonly title?: string;
|
|
7
|
+
readonly text: string;
|
|
8
|
+
}
|
|
9
|
+
/** Extract readable text; undefined means no usable article (JS shell/empty). */
|
|
10
|
+
export declare function extractReadablePage(html: string): ReadablePageV1 | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Deterministic query-relevant passage selection. Paragraphs are scored by
|
|
13
|
+
* query-term hits; when anything hits, only scoring paragraphs are packed
|
|
14
|
+
* (highest score first) while the returned array keeps original order. With no
|
|
15
|
+
* hits, leading paragraphs are used. Total length never exceeds maxChars.
|
|
16
|
+
*/
|
|
17
|
+
export declare function selectPassages(text: string, terms: readonly string[], maxChars: number): readonly string[];
|
|
18
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AA4BD,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAuB5E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAsD1G","sourcesContent":["/**\n * Bounded readability extraction (搜索工具设计 §6): pinned\n * @mozilla/readability over linkedom, no in-house HTML parser.\n */\n\nimport { Readability } from \"@mozilla/readability\";\nimport { parseHTML } from \"linkedom\";\n\nexport interface ReadablePageV1 {\n\treadonly title?: string;\n\treadonly text: string;\n}\n\nconst PARAGRAPH_SELECTOR = \"p, li, pre, blockquote, h1, h2, h3, h4, h5, h6\";\n\ntype LinkedomDocument = ReturnType<typeof parseHTML>[\"document\"];\n\nfunction stripUnwanted(document: LinkedomDocument): void {\n\tfor (const element of document.querySelectorAll(\"script, style, noscript, template, nav, footer\")) {\n\t\telement.remove();\n\t}\n}\n\nfunction paragraphsFromArticle(contentHtml: string): readonly string[] {\n\tconst { document } = parseHTML(contentHtml);\n\tconst seen = new Set<string>();\n\tconst paragraphs: string[] = [];\n\tfor (const element of document.querySelectorAll(PARAGRAPH_SELECTOR)) {\n\t\tif (element.querySelector(PARAGRAPH_SELECTOR) !== null) continue;\n\t\tconst text = element.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\";\n\t\tif (text === \"\" || seen.has(text)) continue;\n\t\tseen.add(text);\n\t\tparagraphs.push(text);\n\t}\n\tif (paragraphs.length > 0) return paragraphs;\n\tconst fallback = document.body?.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\";\n\treturn fallback === \"\" ? [] : [fallback];\n}\n\n/** Extract readable text; undefined means no usable article (JS shell/empty). */\nexport function extractReadablePage(html: string): ReadablePageV1 | undefined {\n\tlet document: LinkedomDocument;\n\ttry {\n\t\tdocument = parseHTML(html).document;\n\t} catch {\n\t\treturn undefined;\n\t}\n\tstripUnwanted(document);\n\tlet article: ReturnType<Readability[\"parse\"]>;\n\ttry {\n\t\tarticle = new Readability(document).parse();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (article === null || typeof article.content !== \"string\") return undefined;\n\tconst paragraphs = paragraphsFromArticle(article.content);\n\tconst text = paragraphs.join(\"\\n\\n\");\n\tif (text.trim() === \"\") return undefined;\n\tconst title = article.title?.trim();\n\treturn {\n\t\t...(title === undefined || title === \"\" ? {} : { title }),\n\t\ttext,\n\t};\n}\n\n/**\n * Deterministic query-relevant passage selection. Paragraphs are scored by\n * query-term hits; when anything hits, only scoring paragraphs are packed\n * (highest score first) while the returned array keeps original order. With no\n * hits, leading paragraphs are used. Total length never exceeds maxChars.\n */\nexport function selectPassages(text: string, terms: readonly string[], maxChars: number): readonly string[] {\n\tif (maxChars <= 0) return [];\n\tconst paragraphs = text\n\t\t.split(/\\n{2,}/)\n\t\t.map((paragraph) => paragraph.replace(/\\s+/g, \" \").trim())\n\t\t.filter((paragraph) => paragraph !== \"\");\n\tif (paragraphs.length === 0) return [];\n\n\tconst normalizedTerms = terms.map((term) => term.toLowerCase().trim()).filter((term) => term !== \"\");\n\tconst scores = paragraphs.map((paragraph) => {\n\t\tconst lowered = paragraph.toLowerCase();\n\t\tlet score = 0;\n\t\tfor (const term of normalizedTerms) {\n\t\t\tif (lowered.includes(term)) score += 1;\n\t\t}\n\t\treturn score;\n\t});\n\n\tlet candidates = paragraphs.map((_, index) => index);\n\tif (normalizedTerms.length > 0 && scores.some((score) => score > 0)) {\n\t\tcandidates = candidates.filter((index) => (scores[index] ?? 0) > 0);\n\t\tcandidates.sort((a, b) => (scores[b] ?? 0) - (scores[a] ?? 0));\n\t}\n\n\tconst kept: number[] = [];\n\tlet spent = 0;\n\tlet truncatedAny = false;\n\tfor (const index of candidates) {\n\t\tconst paragraph = paragraphs[index] ?? \"\";\n\t\tconst cost = paragraph.length + (kept.length === 0 ? 0 : 2);\n\t\tif (spent + cost > maxChars) {\n\t\t\tconst remaining = maxChars - spent - (kept.length === 0 ? 0 : 2);\n\t\t\tif (remaining >= 1) {\n\t\t\t\tkept.push(index);\n\t\t\t\ttruncatedAny = true;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tspent += cost;\n\t\tkept.push(index);\n\t}\n\n\tconst result = kept\n\t\t.sort((a, b) => a - b)\n\t\t.map((index) => paragraphs[index] ?? \"\")\n\t\t.filter((paragraph) => paragraph !== \"\");\n\tif (truncatedAny && result.length > 0) {\n\t\tconst last = result.length - 1;\n\t\tconst used = result.slice(0, last).reduce((sum, passage) => sum + passage.length + 2, 0);\n\t\tconst budget = maxChars - used;\n\t\tconst current = result[last] ?? \"\";\n\t\tresult[last] = `${current.slice(0, Math.max(budget - 1, 0))}…`;\n\t}\n\treturn result;\n}\n"]}
|
package/dist/extract.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded readability extraction (搜索工具设计 §6): pinned
|
|
3
|
+
* @mozilla/readability over linkedom, no in-house HTML parser.
|
|
4
|
+
*/
|
|
5
|
+
import { Readability } from "@mozilla/readability";
|
|
6
|
+
import { parseHTML } from "linkedom";
|
|
7
|
+
const PARAGRAPH_SELECTOR = "p, li, pre, blockquote, h1, h2, h3, h4, h5, h6";
|
|
8
|
+
function stripUnwanted(document) {
|
|
9
|
+
for (const element of document.querySelectorAll("script, style, noscript, template, nav, footer")) {
|
|
10
|
+
element.remove();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function paragraphsFromArticle(contentHtml) {
|
|
14
|
+
const { document } = parseHTML(contentHtml);
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const paragraphs = [];
|
|
17
|
+
for (const element of document.querySelectorAll(PARAGRAPH_SELECTOR)) {
|
|
18
|
+
if (element.querySelector(PARAGRAPH_SELECTOR) !== null)
|
|
19
|
+
continue;
|
|
20
|
+
const text = element.textContent?.replace(/\s+/g, " ").trim() ?? "";
|
|
21
|
+
if (text === "" || seen.has(text))
|
|
22
|
+
continue;
|
|
23
|
+
seen.add(text);
|
|
24
|
+
paragraphs.push(text);
|
|
25
|
+
}
|
|
26
|
+
if (paragraphs.length > 0)
|
|
27
|
+
return paragraphs;
|
|
28
|
+
const fallback = document.body?.textContent?.replace(/\s+/g, " ").trim() ?? "";
|
|
29
|
+
return fallback === "" ? [] : [fallback];
|
|
30
|
+
}
|
|
31
|
+
/** Extract readable text; undefined means no usable article (JS shell/empty). */
|
|
32
|
+
export function extractReadablePage(html) {
|
|
33
|
+
let document;
|
|
34
|
+
try {
|
|
35
|
+
document = parseHTML(html).document;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
stripUnwanted(document);
|
|
41
|
+
let article;
|
|
42
|
+
try {
|
|
43
|
+
article = new Readability(document).parse();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
if (article === null || typeof article.content !== "string")
|
|
49
|
+
return undefined;
|
|
50
|
+
const paragraphs = paragraphsFromArticle(article.content);
|
|
51
|
+
const text = paragraphs.join("\n\n");
|
|
52
|
+
if (text.trim() === "")
|
|
53
|
+
return undefined;
|
|
54
|
+
const title = article.title?.trim();
|
|
55
|
+
return {
|
|
56
|
+
...(title === undefined || title === "" ? {} : { title }),
|
|
57
|
+
text,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Deterministic query-relevant passage selection. Paragraphs are scored by
|
|
62
|
+
* query-term hits; when anything hits, only scoring paragraphs are packed
|
|
63
|
+
* (highest score first) while the returned array keeps original order. With no
|
|
64
|
+
* hits, leading paragraphs are used. Total length never exceeds maxChars.
|
|
65
|
+
*/
|
|
66
|
+
export function selectPassages(text, terms, maxChars) {
|
|
67
|
+
if (maxChars <= 0)
|
|
68
|
+
return [];
|
|
69
|
+
const paragraphs = text
|
|
70
|
+
.split(/\n{2,}/)
|
|
71
|
+
.map((paragraph) => paragraph.replace(/\s+/g, " ").trim())
|
|
72
|
+
.filter((paragraph) => paragraph !== "");
|
|
73
|
+
if (paragraphs.length === 0)
|
|
74
|
+
return [];
|
|
75
|
+
const normalizedTerms = terms.map((term) => term.toLowerCase().trim()).filter((term) => term !== "");
|
|
76
|
+
const scores = paragraphs.map((paragraph) => {
|
|
77
|
+
const lowered = paragraph.toLowerCase();
|
|
78
|
+
let score = 0;
|
|
79
|
+
for (const term of normalizedTerms) {
|
|
80
|
+
if (lowered.includes(term))
|
|
81
|
+
score += 1;
|
|
82
|
+
}
|
|
83
|
+
return score;
|
|
84
|
+
});
|
|
85
|
+
let candidates = paragraphs.map((_, index) => index);
|
|
86
|
+
if (normalizedTerms.length > 0 && scores.some((score) => score > 0)) {
|
|
87
|
+
candidates = candidates.filter((index) => (scores[index] ?? 0) > 0);
|
|
88
|
+
candidates.sort((a, b) => (scores[b] ?? 0) - (scores[a] ?? 0));
|
|
89
|
+
}
|
|
90
|
+
const kept = [];
|
|
91
|
+
let spent = 0;
|
|
92
|
+
let truncatedAny = false;
|
|
93
|
+
for (const index of candidates) {
|
|
94
|
+
const paragraph = paragraphs[index] ?? "";
|
|
95
|
+
const cost = paragraph.length + (kept.length === 0 ? 0 : 2);
|
|
96
|
+
if (spent + cost > maxChars) {
|
|
97
|
+
const remaining = maxChars - spent - (kept.length === 0 ? 0 : 2);
|
|
98
|
+
if (remaining >= 1) {
|
|
99
|
+
kept.push(index);
|
|
100
|
+
truncatedAny = true;
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
spent += cost;
|
|
105
|
+
kept.push(index);
|
|
106
|
+
}
|
|
107
|
+
const result = kept
|
|
108
|
+
.sort((a, b) => a - b)
|
|
109
|
+
.map((index) => paragraphs[index] ?? "")
|
|
110
|
+
.filter((paragraph) => paragraph !== "");
|
|
111
|
+
if (truncatedAny && result.length > 0) {
|
|
112
|
+
const last = result.length - 1;
|
|
113
|
+
const used = result.slice(0, last).reduce((sum, passage) => sum + passage.length + 2, 0);
|
|
114
|
+
const budget = maxChars - used;
|
|
115
|
+
const current = result[last] ?? "";
|
|
116
|
+
result[last] = `${current.slice(0, Math.max(budget - 1, 0))}…`;
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bounded readability extraction (搜索工具设计 §6): pinned\n * @mozilla/readability over linkedom, no in-house HTML parser.\n */\n\nimport { Readability } from \"@mozilla/readability\";\nimport { parseHTML } from \"linkedom\";\n\nexport interface ReadablePageV1 {\n\treadonly title?: string;\n\treadonly text: string;\n}\n\nconst PARAGRAPH_SELECTOR = \"p, li, pre, blockquote, h1, h2, h3, h4, h5, h6\";\n\ntype LinkedomDocument = ReturnType<typeof parseHTML>[\"document\"];\n\nfunction stripUnwanted(document: LinkedomDocument): void {\n\tfor (const element of document.querySelectorAll(\"script, style, noscript, template, nav, footer\")) {\n\t\telement.remove();\n\t}\n}\n\nfunction paragraphsFromArticle(contentHtml: string): readonly string[] {\n\tconst { document } = parseHTML(contentHtml);\n\tconst seen = new Set<string>();\n\tconst paragraphs: string[] = [];\n\tfor (const element of document.querySelectorAll(PARAGRAPH_SELECTOR)) {\n\t\tif (element.querySelector(PARAGRAPH_SELECTOR) !== null) continue;\n\t\tconst text = element.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\";\n\t\tif (text === \"\" || seen.has(text)) continue;\n\t\tseen.add(text);\n\t\tparagraphs.push(text);\n\t}\n\tif (paragraphs.length > 0) return paragraphs;\n\tconst fallback = document.body?.textContent?.replace(/\\s+/g, \" \").trim() ?? \"\";\n\treturn fallback === \"\" ? [] : [fallback];\n}\n\n/** Extract readable text; undefined means no usable article (JS shell/empty). */\nexport function extractReadablePage(html: string): ReadablePageV1 | undefined {\n\tlet document: LinkedomDocument;\n\ttry {\n\t\tdocument = parseHTML(html).document;\n\t} catch {\n\t\treturn undefined;\n\t}\n\tstripUnwanted(document);\n\tlet article: ReturnType<Readability[\"parse\"]>;\n\ttry {\n\t\tarticle = new Readability(document).parse();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (article === null || typeof article.content !== \"string\") return undefined;\n\tconst paragraphs = paragraphsFromArticle(article.content);\n\tconst text = paragraphs.join(\"\\n\\n\");\n\tif (text.trim() === \"\") return undefined;\n\tconst title = article.title?.trim();\n\treturn {\n\t\t...(title === undefined || title === \"\" ? {} : { title }),\n\t\ttext,\n\t};\n}\n\n/**\n * Deterministic query-relevant passage selection. Paragraphs are scored by\n * query-term hits; when anything hits, only scoring paragraphs are packed\n * (highest score first) while the returned array keeps original order. With no\n * hits, leading paragraphs are used. Total length never exceeds maxChars.\n */\nexport function selectPassages(text: string, terms: readonly string[], maxChars: number): readonly string[] {\n\tif (maxChars <= 0) return [];\n\tconst paragraphs = text\n\t\t.split(/\\n{2,}/)\n\t\t.map((paragraph) => paragraph.replace(/\\s+/g, \" \").trim())\n\t\t.filter((paragraph) => paragraph !== \"\");\n\tif (paragraphs.length === 0) return [];\n\n\tconst normalizedTerms = terms.map((term) => term.toLowerCase().trim()).filter((term) => term !== \"\");\n\tconst scores = paragraphs.map((paragraph) => {\n\t\tconst lowered = paragraph.toLowerCase();\n\t\tlet score = 0;\n\t\tfor (const term of normalizedTerms) {\n\t\t\tif (lowered.includes(term)) score += 1;\n\t\t}\n\t\treturn score;\n\t});\n\n\tlet candidates = paragraphs.map((_, index) => index);\n\tif (normalizedTerms.length > 0 && scores.some((score) => score > 0)) {\n\t\tcandidates = candidates.filter((index) => (scores[index] ?? 0) > 0);\n\t\tcandidates.sort((a, b) => (scores[b] ?? 0) - (scores[a] ?? 0));\n\t}\n\n\tconst kept: number[] = [];\n\tlet spent = 0;\n\tlet truncatedAny = false;\n\tfor (const index of candidates) {\n\t\tconst paragraph = paragraphs[index] ?? \"\";\n\t\tconst cost = paragraph.length + (kept.length === 0 ? 0 : 2);\n\t\tif (spent + cost > maxChars) {\n\t\t\tconst remaining = maxChars - spent - (kept.length === 0 ? 0 : 2);\n\t\t\tif (remaining >= 1) {\n\t\t\t\tkept.push(index);\n\t\t\t\ttruncatedAny = true;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tspent += cost;\n\t\tkept.push(index);\n\t}\n\n\tconst result = kept\n\t\t.sort((a, b) => a - b)\n\t\t.map((index) => paragraphs[index] ?? \"\")\n\t\t.filter((paragraph) => paragraph !== \"\");\n\tif (truncatedAny && result.length > 0) {\n\t\tconst last = result.length - 1;\n\t\tconst used = result.slice(0, last).reduce((sum, passage) => sum + passage.length + 2, 0);\n\t\tconst budget = maxChars - used;\n\t\tconst current = result[last] ?? \"\";\n\t\tresult[last] = `${current.slice(0, Math.max(budget - 1, 0))}…`;\n\t}\n\treturn result;\n}\n"]}
|