@iflow-mcp/jakeliume-webpeel 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (547) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +313 -0
  3. package/dist/cache.d.ts +30 -0
  4. package/dist/cache.js +139 -0
  5. package/dist/cli/commands/auth.d.ts +5 -0
  6. package/dist/cli/commands/auth.js +411 -0
  7. package/dist/cli/commands/doctor.d.ts +37 -0
  8. package/dist/cli/commands/doctor.js +371 -0
  9. package/dist/cli/commands/fetch.d.ts +6 -0
  10. package/dist/cli/commands/fetch.js +1345 -0
  11. package/dist/cli/commands/guide.d.ts +2 -0
  12. package/dist/cli/commands/guide.js +183 -0
  13. package/dist/cli/commands/interact.d.ts +5 -0
  14. package/dist/cli/commands/interact.js +840 -0
  15. package/dist/cli/commands/jobs.d.ts +5 -0
  16. package/dist/cli/commands/jobs.js +997 -0
  17. package/dist/cli/commands/monitor.d.ts +12 -0
  18. package/dist/cli/commands/monitor.js +197 -0
  19. package/dist/cli/commands/observe.d.ts +12 -0
  20. package/dist/cli/commands/observe.js +158 -0
  21. package/dist/cli/commands/screenshot.d.ts +5 -0
  22. package/dist/cli/commands/screenshot.js +282 -0
  23. package/dist/cli/commands/search.d.ts +5 -0
  24. package/dist/cli/commands/search.js +1021 -0
  25. package/dist/cli/commands/setup.d.ts +13 -0
  26. package/dist/cli/commands/setup.js +244 -0
  27. package/dist/cli/commands/skill.d.ts +15 -0
  28. package/dist/cli/commands/skill.js +195 -0
  29. package/dist/cli/utils.d.ts +84 -0
  30. package/dist/cli/utils.js +806 -0
  31. package/dist/cli-auth.d.ts +75 -0
  32. package/dist/cli-auth.js +369 -0
  33. package/dist/cli.d.ts +17 -0
  34. package/dist/cli.js +99 -0
  35. package/dist/core/actions.d.ts +69 -0
  36. package/dist/core/actions.js +495 -0
  37. package/dist/core/agent.d.ts +98 -0
  38. package/dist/core/agent.js +558 -0
  39. package/dist/core/answer.d.ts +42 -0
  40. package/dist/core/answer.js +395 -0
  41. package/dist/core/application-tracker.d.ts +84 -0
  42. package/dist/core/application-tracker.js +184 -0
  43. package/dist/core/apply.d.ts +162 -0
  44. package/dist/core/apply.js +816 -0
  45. package/dist/core/auth-detection.d.ts +35 -0
  46. package/dist/core/auth-detection.js +358 -0
  47. package/dist/core/auto-extract.d.ts +82 -0
  48. package/dist/core/auto-extract.js +604 -0
  49. package/dist/core/auto-interact.d.ts +23 -0
  50. package/dist/core/auto-interact.js +246 -0
  51. package/dist/core/bm25-filter.d.ts +66 -0
  52. package/dist/core/bm25-filter.js +288 -0
  53. package/dist/core/branding.d.ts +54 -0
  54. package/dist/core/branding.js +234 -0
  55. package/dist/core/browser-fetch.d.ts +323 -0
  56. package/dist/core/browser-fetch.js +1600 -0
  57. package/dist/core/browser-pool.d.ts +91 -0
  58. package/dist/core/browser-pool.js +550 -0
  59. package/dist/core/budget.d.ts +42 -0
  60. package/dist/core/budget.js +324 -0
  61. package/dist/core/business-intel.d.ts +47 -0
  62. package/dist/core/business-intel.js +279 -0
  63. package/dist/core/cache.d.ts +13 -0
  64. package/dist/core/cache.js +121 -0
  65. package/dist/core/cf-worker-proxy.d.ts +32 -0
  66. package/dist/core/cf-worker-proxy.js +87 -0
  67. package/dist/core/challenge-detection.d.ts +26 -0
  68. package/dist/core/challenge-detection.js +468 -0
  69. package/dist/core/change-tracking.d.ts +75 -0
  70. package/dist/core/change-tracking.js +276 -0
  71. package/dist/core/chunker.d.ts +46 -0
  72. package/dist/core/chunker.js +249 -0
  73. package/dist/core/chunking.d.ts +42 -0
  74. package/dist/core/chunking.js +181 -0
  75. package/dist/core/circuit-breaker.d.ts +44 -0
  76. package/dist/core/circuit-breaker.js +85 -0
  77. package/dist/core/content-pruner.d.ts +47 -0
  78. package/dist/core/content-pruner.js +425 -0
  79. package/dist/core/cookie-cache.d.ts +60 -0
  80. package/dist/core/cookie-cache.js +163 -0
  81. package/dist/core/crawl-checkpoint.d.ts +54 -0
  82. package/dist/core/crawl-checkpoint.js +104 -0
  83. package/dist/core/crawler.d.ts +84 -0
  84. package/dist/core/crawler.js +349 -0
  85. package/dist/core/cross-verify.d.ts +27 -0
  86. package/dist/core/cross-verify.js +93 -0
  87. package/dist/core/deep-fetch.d.ts +74 -0
  88. package/dist/core/deep-fetch.js +405 -0
  89. package/dist/core/deep-research.d.ts +141 -0
  90. package/dist/core/deep-research.js +972 -0
  91. package/dist/core/design-analysis.d.ts +70 -0
  92. package/dist/core/design-analysis.js +490 -0
  93. package/dist/core/design-compare.d.ts +38 -0
  94. package/dist/core/design-compare.js +264 -0
  95. package/dist/core/diff.d.ts +61 -0
  96. package/dist/core/diff.js +289 -0
  97. package/dist/core/dns-cache.d.ts +20 -0
  98. package/dist/core/dns-cache.js +198 -0
  99. package/dist/core/documents.d.ts +23 -0
  100. package/dist/core/documents.js +123 -0
  101. package/dist/core/domain-memory.d.ts +66 -0
  102. package/dist/core/domain-memory.js +163 -0
  103. package/dist/core/domain-verify.d.ts +40 -0
  104. package/dist/core/domain-verify.js +379 -0
  105. package/dist/core/engine-ranker.d.ts +112 -0
  106. package/dist/core/engine-ranker.js +395 -0
  107. package/dist/core/extract-inline.d.ts +38 -0
  108. package/dist/core/extract-inline.js +215 -0
  109. package/dist/core/extract-listings.d.ts +38 -0
  110. package/dist/core/extract-listings.js +461 -0
  111. package/dist/core/extract.d.ts +9 -0
  112. package/dist/core/extract.js +139 -0
  113. package/dist/core/fetch-cache.d.ts +57 -0
  114. package/dist/core/fetch-cache.js +95 -0
  115. package/dist/core/fetcher.d.ts +13 -0
  116. package/dist/core/fetcher.js +12 -0
  117. package/dist/core/google-cache.d.ts +29 -0
  118. package/dist/core/google-cache.js +180 -0
  119. package/dist/core/google-serp-parser.d.ts +82 -0
  120. package/dist/core/google-serp-parser.js +287 -0
  121. package/dist/core/hotel-search.d.ts +122 -0
  122. package/dist/core/hotel-search.js +382 -0
  123. package/dist/core/http-fetch.d.ts +72 -0
  124. package/dist/core/http-fetch.js +820 -0
  125. package/dist/core/human.d.ts +175 -0
  126. package/dist/core/human.js +680 -0
  127. package/dist/core/image-caption.d.ts +44 -0
  128. package/dist/core/image-caption.js +271 -0
  129. package/dist/core/jobs.d.ts +75 -0
  130. package/dist/core/jobs.js +634 -0
  131. package/dist/core/json-ld.d.ts +15 -0
  132. package/dist/core/json-ld.js +617 -0
  133. package/dist/core/language-detect.d.ts +18 -0
  134. package/dist/core/language-detect.js +135 -0
  135. package/dist/core/links.d.ts +10 -0
  136. package/dist/core/links.js +44 -0
  137. package/dist/core/llm-extract.d.ts +71 -0
  138. package/dist/core/llm-extract.js +507 -0
  139. package/dist/core/llm-provider.d.ts +100 -0
  140. package/dist/core/llm-provider.js +702 -0
  141. package/dist/core/local-search.d.ts +60 -0
  142. package/dist/core/local-search.js +308 -0
  143. package/dist/core/logger.d.ts +28 -0
  144. package/dist/core/logger.js +104 -0
  145. package/dist/core/map.d.ts +33 -0
  146. package/dist/core/map.js +127 -0
  147. package/dist/core/markdown.d.ts +92 -0
  148. package/dist/core/markdown.js +809 -0
  149. package/dist/core/metadata.d.ts +34 -0
  150. package/dist/core/metadata.js +422 -0
  151. package/dist/core/observe.d.ts +113 -0
  152. package/dist/core/observe.js +395 -0
  153. package/dist/core/ocr.d.ts +12 -0
  154. package/dist/core/ocr.js +33 -0
  155. package/dist/core/paginate.d.ts +31 -0
  156. package/dist/core/paginate.js +106 -0
  157. package/dist/core/pdf.d.ts +8 -0
  158. package/dist/core/pdf.js +25 -0
  159. package/dist/core/peel-tls.d.ts +25 -0
  160. package/dist/core/peel-tls.js +220 -0
  161. package/dist/core/pipeline.d.ts +132 -0
  162. package/dist/core/pipeline.js +1666 -0
  163. package/dist/core/profiles.d.ts +61 -0
  164. package/dist/core/profiles.js +350 -0
  165. package/dist/core/prompt-guard.d.ts +30 -0
  166. package/dist/core/prompt-guard.js +119 -0
  167. package/dist/core/proxy-config.d.ts +90 -0
  168. package/dist/core/proxy-config.js +172 -0
  169. package/dist/core/quick-answer.d.ts +53 -0
  170. package/dist/core/quick-answer.js +833 -0
  171. package/dist/core/rate-governor.d.ts +80 -0
  172. package/dist/core/rate-governor.js +238 -0
  173. package/dist/core/readability.d.ts +57 -0
  174. package/dist/core/readability.js +533 -0
  175. package/dist/core/research.d.ts +66 -0
  176. package/dist/core/research.js +270 -0
  177. package/dist/core/retry.d.ts +60 -0
  178. package/dist/core/retry.js +119 -0
  179. package/dist/core/safe-browsing.d.ts +30 -0
  180. package/dist/core/safe-browsing.js +206 -0
  181. package/dist/core/schema-extraction.d.ts +66 -0
  182. package/dist/core/schema-extraction.js +352 -0
  183. package/dist/core/schema-postprocess.d.ts +32 -0
  184. package/dist/core/schema-postprocess.js +469 -0
  185. package/dist/core/schema-templates.d.ts +19 -0
  186. package/dist/core/schema-templates.js +143 -0
  187. package/dist/core/screenshot.d.ts +224 -0
  188. package/dist/core/screenshot.js +207 -0
  189. package/dist/core/search-engines.d.ts +25 -0
  190. package/dist/core/search-engines.js +182 -0
  191. package/dist/core/search-provider.d.ts +243 -0
  192. package/dist/core/search-provider.js +1629 -0
  193. package/dist/core/searxng-provider.d.ts +35 -0
  194. package/dist/core/searxng-provider.js +105 -0
  195. package/dist/core/selective-evidence.d.ts +151 -0
  196. package/dist/core/selective-evidence.js +389 -0
  197. package/dist/core/site-search.d.ts +44 -0
  198. package/dist/core/site-search.js +252 -0
  199. package/dist/core/sitemap.d.ts +23 -0
  200. package/dist/core/sitemap.js +105 -0
  201. package/dist/core/source-credibility.d.ts +29 -0
  202. package/dist/core/source-credibility.js +584 -0
  203. package/dist/core/source-scoring.d.ts +166 -0
  204. package/dist/core/source-scoring.js +396 -0
  205. package/dist/core/stemmer.d.ts +38 -0
  206. package/dist/core/stemmer.js +509 -0
  207. package/dist/core/strategies.d.ts +104 -0
  208. package/dist/core/strategies.js +1044 -0
  209. package/dist/core/strategy-hooks.d.ts +145 -0
  210. package/dist/core/strategy-hooks.js +74 -0
  211. package/dist/core/structured-extract.d.ts +43 -0
  212. package/dist/core/structured-extract.js +550 -0
  213. package/dist/core/summarize.d.ts +17 -0
  214. package/dist/core/summarize.js +78 -0
  215. package/dist/core/synonyms.d.ts +42 -0
  216. package/dist/core/synonyms.js +184 -0
  217. package/dist/core/system-monitor.d.ts +61 -0
  218. package/dist/core/system-monitor.js +133 -0
  219. package/dist/core/table-format.d.ts +30 -0
  220. package/dist/core/table-format.js +146 -0
  221. package/dist/core/threat-feeds.d.ts +23 -0
  222. package/dist/core/threat-feeds.js +104 -0
  223. package/dist/core/timing.d.ts +21 -0
  224. package/dist/core/timing.js +33 -0
  225. package/dist/core/transcript-export.d.ts +47 -0
  226. package/dist/core/transcript-export.js +107 -0
  227. package/dist/core/user-agents.d.ts +82 -0
  228. package/dist/core/user-agents.js +239 -0
  229. package/dist/core/vertical-search.d.ts +54 -0
  230. package/dist/core/vertical-search.js +158 -0
  231. package/dist/core/watch-manager.d.ts +175 -0
  232. package/dist/core/watch-manager.js +416 -0
  233. package/dist/core/watch.d.ts +101 -0
  234. package/dist/core/watch.js +389 -0
  235. package/dist/core/youtube.d.ts +130 -0
  236. package/dist/core/youtube.js +1175 -0
  237. package/dist/ee/challenge-re-export.d.ts +1 -0
  238. package/dist/ee/challenge-re-export.js +1 -0
  239. package/dist/ee/challenge-solver.d.ts +72 -0
  240. package/dist/ee/challenge-solver.js +720 -0
  241. package/dist/ee/domain-extractors.d.ts +8 -0
  242. package/dist/ee/domain-extractors.js +8 -0
  243. package/dist/ee/domain-intel.d.ts +16 -0
  244. package/dist/ee/domain-intel.js +133 -0
  245. package/dist/ee/extractors/allrecipes.d.ts +2 -0
  246. package/dist/ee/extractors/allrecipes.js +120 -0
  247. package/dist/ee/extractors/amazon.d.ts +2 -0
  248. package/dist/ee/extractors/amazon.js +78 -0
  249. package/dist/ee/extractors/arxiv.d.ts +2 -0
  250. package/dist/ee/extractors/arxiv.js +137 -0
  251. package/dist/ee/extractors/bestbuy.d.ts +2 -0
  252. package/dist/ee/extractors/bestbuy.js +78 -0
  253. package/dist/ee/extractors/carscom.d.ts +2 -0
  254. package/dist/ee/extractors/carscom.js +121 -0
  255. package/dist/ee/extractors/coingecko.d.ts +2 -0
  256. package/dist/ee/extractors/coingecko.js +134 -0
  257. package/dist/ee/extractors/craigslist.d.ts +2 -0
  258. package/dist/ee/extractors/craigslist.js +92 -0
  259. package/dist/ee/extractors/devto.d.ts +2 -0
  260. package/dist/ee/extractors/devto.js +135 -0
  261. package/dist/ee/extractors/ebay.d.ts +2 -0
  262. package/dist/ee/extractors/ebay.js +90 -0
  263. package/dist/ee/extractors/espn.d.ts +2 -0
  264. package/dist/ee/extractors/espn.js +260 -0
  265. package/dist/ee/extractors/etsy.d.ts +2 -0
  266. package/dist/ee/extractors/etsy.js +52 -0
  267. package/dist/ee/extractors/facebook.d.ts +2 -0
  268. package/dist/ee/extractors/facebook.js +46 -0
  269. package/dist/ee/extractors/github.d.ts +2 -0
  270. package/dist/ee/extractors/github.js +196 -0
  271. package/dist/ee/extractors/google-flights.d.ts +2 -0
  272. package/dist/ee/extractors/google-flights.js +176 -0
  273. package/dist/ee/extractors/hackernews.d.ts +2 -0
  274. package/dist/ee/extractors/hackernews.js +147 -0
  275. package/dist/ee/extractors/imdb.d.ts +2 -0
  276. package/dist/ee/extractors/imdb.js +172 -0
  277. package/dist/ee/extractors/index.d.ts +26 -0
  278. package/dist/ee/extractors/index.js +247 -0
  279. package/dist/ee/extractors/instagram.d.ts +2 -0
  280. package/dist/ee/extractors/instagram.js +102 -0
  281. package/dist/ee/extractors/kalshi.d.ts +2 -0
  282. package/dist/ee/extractors/kalshi.js +121 -0
  283. package/dist/ee/extractors/kayak-cars.d.ts +2 -0
  284. package/dist/ee/extractors/kayak-cars.js +270 -0
  285. package/dist/ee/extractors/linkedin.d.ts +2 -0
  286. package/dist/ee/extractors/linkedin.js +113 -0
  287. package/dist/ee/extractors/medium.d.ts +2 -0
  288. package/dist/ee/extractors/medium.js +130 -0
  289. package/dist/ee/extractors/news.d.ts +4 -0
  290. package/dist/ee/extractors/news.js +173 -0
  291. package/dist/ee/extractors/npm.d.ts +2 -0
  292. package/dist/ee/extractors/npm.js +86 -0
  293. package/dist/ee/extractors/pdf.d.ts +2 -0
  294. package/dist/ee/extractors/pdf.js +108 -0
  295. package/dist/ee/extractors/pinterest.d.ts +2 -0
  296. package/dist/ee/extractors/pinterest.js +34 -0
  297. package/dist/ee/extractors/polymarket.d.ts +2 -0
  298. package/dist/ee/extractors/polymarket.js +358 -0
  299. package/dist/ee/extractors/producthunt.d.ts +2 -0
  300. package/dist/ee/extractors/producthunt.js +88 -0
  301. package/dist/ee/extractors/pubmed.d.ts +2 -0
  302. package/dist/ee/extractors/pubmed.js +162 -0
  303. package/dist/ee/extractors/pypi.d.ts +2 -0
  304. package/dist/ee/extractors/pypi.js +80 -0
  305. package/dist/ee/extractors/reddit.d.ts +2 -0
  306. package/dist/ee/extractors/reddit.js +438 -0
  307. package/dist/ee/extractors/redfin.d.ts +2 -0
  308. package/dist/ee/extractors/redfin.js +156 -0
  309. package/dist/ee/extractors/semanticscholar.d.ts +2 -0
  310. package/dist/ee/extractors/semanticscholar.js +131 -0
  311. package/dist/ee/extractors/shared.d.ts +12 -0
  312. package/dist/ee/extractors/shared.js +76 -0
  313. package/dist/ee/extractors/soundcloud.d.ts +2 -0
  314. package/dist/ee/extractors/soundcloud.js +34 -0
  315. package/dist/ee/extractors/sportsbetting.d.ts +2 -0
  316. package/dist/ee/extractors/sportsbetting.js +37 -0
  317. package/dist/ee/extractors/spotify.d.ts +2 -0
  318. package/dist/ee/extractors/spotify.js +34 -0
  319. package/dist/ee/extractors/stackoverflow.d.ts +2 -0
  320. package/dist/ee/extractors/stackoverflow.js +61 -0
  321. package/dist/ee/extractors/substack.d.ts +2 -0
  322. package/dist/ee/extractors/substack.js +115 -0
  323. package/dist/ee/extractors/substackroot.d.ts +2 -0
  324. package/dist/ee/extractors/substackroot.js +46 -0
  325. package/dist/ee/extractors/tiktok.d.ts +2 -0
  326. package/dist/ee/extractors/tiktok.js +29 -0
  327. package/dist/ee/extractors/tradingview.d.ts +2 -0
  328. package/dist/ee/extractors/tradingview.js +182 -0
  329. package/dist/ee/extractors/twitch.d.ts +2 -0
  330. package/dist/ee/extractors/twitch.js +36 -0
  331. package/dist/ee/extractors/twitter.d.ts +2 -0
  332. package/dist/ee/extractors/twitter.js +327 -0
  333. package/dist/ee/extractors/types.d.ts +14 -0
  334. package/dist/ee/extractors/types.js +1 -0
  335. package/dist/ee/extractors/walmart.d.ts +2 -0
  336. package/dist/ee/extractors/walmart.js +50 -0
  337. package/dist/ee/extractors/weather.d.ts +2 -0
  338. package/dist/ee/extractors/weather.js +133 -0
  339. package/dist/ee/extractors/wikipedia.d.ts +4 -0
  340. package/dist/ee/extractors/wikipedia.js +235 -0
  341. package/dist/ee/extractors/yelp.d.ts +2 -0
  342. package/dist/ee/extractors/yelp.js +216 -0
  343. package/dist/ee/extractors/youtube.d.ts +2 -0
  344. package/dist/ee/extractors/youtube.js +189 -0
  345. package/dist/ee/extractors/zillow.d.ts +54 -0
  346. package/dist/ee/extractors/zillow.js +247 -0
  347. package/dist/ee/extractors-re-export.d.ts +1 -0
  348. package/dist/ee/extractors-re-export.js +1 -0
  349. package/dist/ee/premium-hooks.d.ts +20 -0
  350. package/dist/ee/premium-hooks.js +50 -0
  351. package/dist/ee/spa-detection.d.ts +2 -0
  352. package/dist/ee/spa-detection.js +2 -0
  353. package/dist/ee/stability.d.ts +4 -0
  354. package/dist/ee/stability.js +29 -0
  355. package/dist/ee/swr-cache.d.ts +14 -0
  356. package/dist/ee/swr-cache.js +34 -0
  357. package/dist/index.d.ts +143 -0
  358. package/dist/index.js +291 -0
  359. package/dist/integrations/index.d.ts +2 -0
  360. package/dist/integrations/index.js +2 -0
  361. package/dist/integrations/langchain.d.ts +64 -0
  362. package/dist/integrations/langchain.js +115 -0
  363. package/dist/integrations/llamaindex.d.ts +50 -0
  364. package/dist/integrations/llamaindex.js +91 -0
  365. package/dist/mcp/handlers/act.d.ts +5 -0
  366. package/dist/mcp/handlers/act.js +34 -0
  367. package/dist/mcp/handlers/definitions.d.ts +6 -0
  368. package/dist/mcp/handlers/definitions.js +395 -0
  369. package/dist/mcp/handlers/extract.d.ts +7 -0
  370. package/dist/mcp/handlers/extract.js +135 -0
  371. package/dist/mcp/handlers/fetch.d.ts +6 -0
  372. package/dist/mcp/handlers/fetch.js +98 -0
  373. package/dist/mcp/handlers/find.d.ts +5 -0
  374. package/dist/mcp/handlers/find.js +137 -0
  375. package/dist/mcp/handlers/index.d.ts +13 -0
  376. package/dist/mcp/handlers/index.js +63 -0
  377. package/dist/mcp/handlers/legacy.d.ts +25 -0
  378. package/dist/mcp/handlers/legacy.js +450 -0
  379. package/dist/mcp/handlers/meta.d.ts +6 -0
  380. package/dist/mcp/handlers/meta.js +40 -0
  381. package/dist/mcp/handlers/monitor.d.ts +5 -0
  382. package/dist/mcp/handlers/monitor.js +41 -0
  383. package/dist/mcp/handlers/observe.d.ts +8 -0
  384. package/dist/mcp/handlers/observe.js +37 -0
  385. package/dist/mcp/handlers/read.d.ts +6 -0
  386. package/dist/mcp/handlers/read.js +78 -0
  387. package/dist/mcp/handlers/see.d.ts +5 -0
  388. package/dist/mcp/handlers/see.js +75 -0
  389. package/dist/mcp/handlers/types.d.ts +29 -0
  390. package/dist/mcp/handlers/types.js +28 -0
  391. package/dist/mcp/server.d.ts +7 -0
  392. package/dist/mcp/server.js +108 -0
  393. package/dist/mcp/smart-router.d.ts +23 -0
  394. package/dist/mcp/smart-router.js +178 -0
  395. package/dist/server/app.d.ts +14 -0
  396. package/dist/server/app.js +632 -0
  397. package/dist/server/auth-store.d.ts +28 -0
  398. package/dist/server/auth-store.js +88 -0
  399. package/dist/server/bull-queues.d.ts +60 -0
  400. package/dist/server/bull-queues.js +90 -0
  401. package/dist/server/email-service.d.ts +55 -0
  402. package/dist/server/email-service.js +291 -0
  403. package/dist/server/job-queue.d.ts +100 -0
  404. package/dist/server/job-queue.js +145 -0
  405. package/dist/server/logger.d.ts +10 -0
  406. package/dist/server/logger.js +37 -0
  407. package/dist/server/middleware/audit-log.d.ts +14 -0
  408. package/dist/server/middleware/audit-log.js +73 -0
  409. package/dist/server/middleware/auth.d.ts +35 -0
  410. package/dist/server/middleware/auth.js +225 -0
  411. package/dist/server/middleware/rate-limit.d.ts +50 -0
  412. package/dist/server/middleware/rate-limit.js +270 -0
  413. package/dist/server/middleware/scope-guard.d.ts +25 -0
  414. package/dist/server/middleware/scope-guard.js +45 -0
  415. package/dist/server/middleware/url-validator.d.ts +15 -0
  416. package/dist/server/middleware/url-validator.js +201 -0
  417. package/dist/server/openapi.yaml +6418 -0
  418. package/dist/server/pg-auth-store.d.ts +146 -0
  419. package/dist/server/pg-auth-store.js +576 -0
  420. package/dist/server/pg-job-queue.d.ts +59 -0
  421. package/dist/server/pg-job-queue.js +375 -0
  422. package/dist/server/routes/activity.d.ts +6 -0
  423. package/dist/server/routes/activity.js +79 -0
  424. package/dist/server/routes/admin-active.d.ts +7 -0
  425. package/dist/server/routes/admin-active.js +120 -0
  426. package/dist/server/routes/admin-stats.d.ts +7 -0
  427. package/dist/server/routes/admin-stats.js +176 -0
  428. package/dist/server/routes/agent.d.ts +24 -0
  429. package/dist/server/routes/agent.js +480 -0
  430. package/dist/server/routes/answer.d.ts +5 -0
  431. package/dist/server/routes/answer.js +125 -0
  432. package/dist/server/routes/ask.d.ts +28 -0
  433. package/dist/server/routes/ask.js +295 -0
  434. package/dist/server/routes/batch.d.ts +6 -0
  435. package/dist/server/routes/batch.js +493 -0
  436. package/dist/server/routes/cache-warm.d.ts +25 -0
  437. package/dist/server/routes/cache-warm.js +212 -0
  438. package/dist/server/routes/cli-usage.d.ts +6 -0
  439. package/dist/server/routes/cli-usage.js +127 -0
  440. package/dist/server/routes/compat.d.ts +23 -0
  441. package/dist/server/routes/compat.js +652 -0
  442. package/dist/server/routes/crawl.d.ts +13 -0
  443. package/dist/server/routes/crawl.js +287 -0
  444. package/dist/server/routes/deep-fetch.d.ts +8 -0
  445. package/dist/server/routes/deep-fetch.js +57 -0
  446. package/dist/server/routes/deep-research.d.ts +11 -0
  447. package/dist/server/routes/deep-research.js +232 -0
  448. package/dist/server/routes/demo.d.ts +24 -0
  449. package/dist/server/routes/demo.js +517 -0
  450. package/dist/server/routes/do.d.ts +8 -0
  451. package/dist/server/routes/do.js +72 -0
  452. package/dist/server/routes/extract.d.ts +14 -0
  453. package/dist/server/routes/extract.js +325 -0
  454. package/dist/server/routes/feed.d.ts +15 -0
  455. package/dist/server/routes/feed.js +311 -0
  456. package/dist/server/routes/fetch-queue.d.ts +13 -0
  457. package/dist/server/routes/fetch-queue.js +357 -0
  458. package/dist/server/routes/fetch.d.ts +7 -0
  459. package/dist/server/routes/fetch.js +1274 -0
  460. package/dist/server/routes/go.d.ts +14 -0
  461. package/dist/server/routes/go.js +81 -0
  462. package/dist/server/routes/health.d.ts +11 -0
  463. package/dist/server/routes/health.js +141 -0
  464. package/dist/server/routes/jobs.d.ts +7 -0
  465. package/dist/server/routes/jobs.js +574 -0
  466. package/dist/server/routes/map.d.ts +11 -0
  467. package/dist/server/routes/map.js +116 -0
  468. package/dist/server/routes/mcp.d.ts +14 -0
  469. package/dist/server/routes/mcp.js +197 -0
  470. package/dist/server/routes/metrics.d.ts +37 -0
  471. package/dist/server/routes/metrics.js +149 -0
  472. package/dist/server/routes/oauth.d.ts +9 -0
  473. package/dist/server/routes/oauth.js +396 -0
  474. package/dist/server/routes/playground.d.ts +17 -0
  475. package/dist/server/routes/playground.js +283 -0
  476. package/dist/server/routes/reader.d.ts +18 -0
  477. package/dist/server/routes/reader.js +192 -0
  478. package/dist/server/routes/research.d.ts +14 -0
  479. package/dist/server/routes/research.js +482 -0
  480. package/dist/server/routes/screenshot.d.ts +22 -0
  481. package/dist/server/routes/screenshot.js +820 -0
  482. package/dist/server/routes/search.d.ts +6 -0
  483. package/dist/server/routes/search.js +874 -0
  484. package/dist/server/routes/session.d.ts +17 -0
  485. package/dist/server/routes/session.js +548 -0
  486. package/dist/server/routes/share.d.ts +18 -0
  487. package/dist/server/routes/share.js +462 -0
  488. package/dist/server/routes/smart-search/handlers/cars.d.ts +2 -0
  489. package/dist/server/routes/smart-search/handlers/cars.js +102 -0
  490. package/dist/server/routes/smart-search/handlers/flights.d.ts +2 -0
  491. package/dist/server/routes/smart-search/handlers/flights.js +72 -0
  492. package/dist/server/routes/smart-search/handlers/general.d.ts +13 -0
  493. package/dist/server/routes/smart-search/handlers/general.js +717 -0
  494. package/dist/server/routes/smart-search/handlers/hotels.d.ts +2 -0
  495. package/dist/server/routes/smart-search/handlers/hotels.js +88 -0
  496. package/dist/server/routes/smart-search/handlers/products.d.ts +2 -0
  497. package/dist/server/routes/smart-search/handlers/products.js +1309 -0
  498. package/dist/server/routes/smart-search/handlers/rental.d.ts +2 -0
  499. package/dist/server/routes/smart-search/handlers/rental.js +154 -0
  500. package/dist/server/routes/smart-search/handlers/restaurants.d.ts +2 -0
  501. package/dist/server/routes/smart-search/handlers/restaurants.js +225 -0
  502. package/dist/server/routes/smart-search/handlers/transit-verdict.d.ts +41 -0
  503. package/dist/server/routes/smart-search/handlers/transit-verdict.js +224 -0
  504. package/dist/server/routes/smart-search/index.d.ts +19 -0
  505. package/dist/server/routes/smart-search/index.js +546 -0
  506. package/dist/server/routes/smart-search/intent.d.ts +3 -0
  507. package/dist/server/routes/smart-search/intent.js +264 -0
  508. package/dist/server/routes/smart-search/llm.d.ts +16 -0
  509. package/dist/server/routes/smart-search/llm.js +70 -0
  510. package/dist/server/routes/smart-search/sources/reddit.d.ts +18 -0
  511. package/dist/server/routes/smart-search/sources/reddit.js +34 -0
  512. package/dist/server/routes/smart-search/sources/yelp.d.ts +25 -0
  513. package/dist/server/routes/smart-search/sources/yelp.js +171 -0
  514. package/dist/server/routes/smart-search/sources/youtube.d.ts +8 -0
  515. package/dist/server/routes/smart-search/sources/youtube.js +9 -0
  516. package/dist/server/routes/smart-search/types.d.ts +81 -0
  517. package/dist/server/routes/smart-search/types.js +1 -0
  518. package/dist/server/routes/smart-search/utils.d.ts +20 -0
  519. package/dist/server/routes/smart-search/utils.js +146 -0
  520. package/dist/server/routes/stats.d.ts +6 -0
  521. package/dist/server/routes/stats.js +71 -0
  522. package/dist/server/routes/stripe.d.ts +15 -0
  523. package/dist/server/routes/stripe.js +296 -0
  524. package/dist/server/routes/transcript-export.d.ts +10 -0
  525. package/dist/server/routes/transcript-export.js +178 -0
  526. package/dist/server/routes/usage.d.ts +9 -0
  527. package/dist/server/routes/usage.js +279 -0
  528. package/dist/server/routes/users.d.ts +8 -0
  529. package/dist/server/routes/users.js +1867 -0
  530. package/dist/server/routes/watch.d.ts +15 -0
  531. package/dist/server/routes/watch.js +309 -0
  532. package/dist/server/routes/webhooks.d.ts +26 -0
  533. package/dist/server/routes/webhooks.js +170 -0
  534. package/dist/server/routes/youtube.d.ts +6 -0
  535. package/dist/server/routes/youtube.js +130 -0
  536. package/dist/server/sentry.d.ts +14 -0
  537. package/dist/server/sentry.js +104 -0
  538. package/dist/server/types.d.ts +15 -0
  539. package/dist/server/types.js +7 -0
  540. package/dist/server/utils/response.d.ts +44 -0
  541. package/dist/server/utils/response.js +69 -0
  542. package/dist/server/utils/sse.d.ts +22 -0
  543. package/dist/server/utils/sse.js +38 -0
  544. package/dist/types.d.ts +552 -0
  545. package/dist/types.js +39 -0
  546. package/llms.txt +105 -0
  547. package/package.json +189 -0
@@ -0,0 +1,702 @@
1
+ /**
2
+ * Unified LLM Provider Abstraction
3
+ *
4
+ * Supports 7 providers:
5
+ * 1. Cloudflare Workers AI (free default, with daily neuron cap)
6
+ * 2. OpenAI (BYOK — also handles any OpenAI-compatible endpoint via baseUrl)
7
+ * 3. Anthropic (BYOK)
8
+ * 4. Google Gemini (BYOK)
9
+ * 5. Ollama (local, OpenAI-compatible)
10
+ * 6. Cerebras (fast inference)
11
+ * 7. Any OpenAI-compatible gateway (Glama, OpenRouter, etc.) via provider='openai' + baseUrl
12
+ */
13
+ // ---------------------------------------------------------------------------
14
+ // Cloudflare Neuron Tracker
15
+ // ---------------------------------------------------------------------------
16
+ // Neuron rates for llama-3.3-70b-instruct-fp8-fast:
17
+ // ~4,119 neurons per 1M input tokens
18
+ // ~204,805 neurons per 1M output tokens
19
+ // Daily cap: 9,500 neurons (reset 00:00 UTC)
20
+ // ---------------------------------------------------------------------------
21
+ const CF_INPUT_NEURONS_PER_TOKEN = 4119 / 1_000_000; // ~0.004119
22
+ const CF_OUTPUT_NEURONS_PER_TOKEN = 204805 / 1_000_000; // ~0.204805
23
+ const CF_DAILY_NEURON_CAP = 9_500;
24
+ // Module-level singleton
25
+ const _neuronUsage = {
26
+ date: currentUTCDate(),
27
+ neurons: 0,
28
+ };
29
+ function currentUTCDate() {
30
+ const now = new Date();
31
+ return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}-${String(now.getUTCDate()).padStart(2, '0')}`;
32
+ }
33
+ function resetIfNewDay() {
34
+ const today = currentUTCDate();
35
+ if (_neuronUsage.date !== today) {
36
+ _neuronUsage.date = today;
37
+ _neuronUsage.neurons = 0;
38
+ }
39
+ }
40
+ /**
41
+ * Estimate neuron cost for a Cloudflare Workers AI call.
42
+ * Token count: split by whitespace * 1.3
43
+ */
44
+ export function estimateNeurons(inputText, outputText) {
45
+ const inputWords = inputText.split(/\s+/).filter((s) => s.length > 0).length;
46
+ const outputWords = outputText.split(/\s+/).filter((s) => s.length > 0).length;
47
+ const inputTokens = Math.ceil(inputWords * 1.3);
48
+ const outputTokens = Math.ceil(outputWords * 1.3);
49
+ return inputTokens * CF_INPUT_NEURONS_PER_TOKEN + outputTokens * CF_OUTPUT_NEURONS_PER_TOKEN;
50
+ }
51
+ /** Get current neuron usage for today */
52
+ export function getNeuronUsage() {
53
+ resetIfNewDay();
54
+ return {
55
+ date: _neuronUsage.date,
56
+ neurons: _neuronUsage.neurons,
57
+ cap: CF_DAILY_NEURON_CAP,
58
+ remaining: Math.max(0, CF_DAILY_NEURON_CAP - _neuronUsage.neurons),
59
+ };
60
+ }
61
+ /** Add neurons to today's usage (for testing / external tracking) */
62
+ export function addNeuronUsage(neurons) {
63
+ resetIfNewDay();
64
+ _neuronUsage.neurons += neurons;
65
+ }
66
+ /** Reset neuron usage (for testing) */
67
+ export function resetNeuronUsage() {
68
+ _neuronUsage.date = currentUTCDate();
69
+ _neuronUsage.neurons = 0;
70
+ }
71
+ /**
72
+ * Check if Cloudflare free tier has capacity for the given estimated neurons.
73
+ * Returns null if OK, or a FreeTierLimitError if cap would be exceeded.
74
+ */
75
+ function checkCloudflareCapacity(estimatedNeurons) {
76
+ resetIfNewDay();
77
+ if (_neuronUsage.neurons + estimatedNeurons > CF_DAILY_NEURON_CAP) {
78
+ return {
79
+ error: 'free_tier_limit',
80
+ message: 'Free AI limit reached for today. Try again tomorrow, or provide your own API key for unlimited use.',
81
+ };
82
+ }
83
+ return null;
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Default models per provider
87
+ // ---------------------------------------------------------------------------
88
+ function defaultModel(provider) {
89
+ switch (provider) {
90
+ case 'cloudflare':
91
+ return '@cf/meta/llama-3.3-70b-instruct-fp8-fast';
92
+ case 'openai':
93
+ return 'gpt-4o-mini';
94
+ case 'anthropic':
95
+ return 'claude-3-5-sonnet-latest';
96
+ case 'google':
97
+ return 'gemini-1.5-flash';
98
+ case 'ollama':
99
+ return 'llama3';
100
+ case 'cerebras':
101
+ return 'llama-3.3-70b';
102
+ }
103
+ }
104
+ // ---------------------------------------------------------------------------
105
+ // SSE stream reader (shared utility, same as answer.ts)
106
+ // ---------------------------------------------------------------------------
107
+ async function readTextStream(body, onText, signal) {
108
+ const reader = body.getReader();
109
+ const decoder = new TextDecoder();
110
+ let buffer = '';
111
+ try {
112
+ while (true) {
113
+ if (signal?.aborted)
114
+ throw new Error('Aborted');
115
+ const { done, value } = await reader.read();
116
+ if (done)
117
+ break;
118
+ buffer += decoder.decode(value, { stream: true });
119
+ while (true) {
120
+ const idx = buffer.indexOf('\n\n');
121
+ if (idx === -1)
122
+ break;
123
+ const event = buffer.slice(0, idx);
124
+ buffer = buffer.slice(idx + 2);
125
+ const lines = event.split('\n');
126
+ for (const line of lines) {
127
+ const trimmed = line.trim();
128
+ if (!trimmed.startsWith('data:'))
129
+ continue;
130
+ const data = trimmed.slice(5).trim();
131
+ onText(data);
132
+ }
133
+ }
134
+ }
135
+ }
136
+ finally {
137
+ reader.releaseLock();
138
+ }
139
+ }
140
+ // ---------------------------------------------------------------------------
141
+ // Cloudflare Workers AI
142
+ // ---------------------------------------------------------------------------
143
+ async function callCloudflare(config, options) {
144
+ const { messages, signal, maxTokens = 2048, temperature = 0.2 } = options;
145
+ // Check neuron cap FIRST (fast early rejection before any I/O)
146
+ const inputText = messages.map((m) => m.content).join(' ');
147
+ // Assume ~500 output words as estimate for capacity check
148
+ const estimatedOutputText = Array(500).fill('word').join(' ');
149
+ const estimatedNeurons = estimateNeurons(inputText, estimatedOutputText);
150
+ const capError = checkCloudflareCapacity(estimatedNeurons);
151
+ if (capError) {
152
+ throw capError;
153
+ }
154
+ // Now validate env vars
155
+ const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
156
+ const apiToken = config.apiKey || process.env.CLOUDFLARE_API_TOKEN;
157
+ if (!accountId || !apiToken) {
158
+ throw new Error('Cloudflare Workers AI requires CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN environment variables');
159
+ }
160
+ const model = config.model || defaultModel('cloudflare');
161
+ const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/${model}`;
162
+ const resp = await fetch(url, {
163
+ method: 'POST',
164
+ headers: {
165
+ 'Content-Type': 'application/json',
166
+ 'Authorization': `Bearer ${apiToken}`,
167
+ },
168
+ body: JSON.stringify({
169
+ messages,
170
+ max_tokens: maxTokens,
171
+ temperature,
172
+ stream: false,
173
+ }),
174
+ signal,
175
+ });
176
+ if (!resp.ok) {
177
+ const text = await resp.text().catch(() => '');
178
+ throw new Error(`Cloudflare Workers AI error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
179
+ }
180
+ const json = await resp.json();
181
+ // Cloudflare response format: { result: { response: string }, success: true }
182
+ const text = String(json?.result?.response ||
183
+ json?.response ||
184
+ json?.result?.text ||
185
+ '').trim();
186
+ // Calculate actual neuron usage based on response
187
+ const actualNeurons = estimateNeurons(inputText, text);
188
+ addNeuronUsage(actualNeurons);
189
+ // Estimate token usage
190
+ const inputTokens = Math.ceil(inputText.split(/\s+/).length * 1.3);
191
+ const outputTokens = Math.ceil(text.split(/\s+/).length * 1.3);
192
+ return {
193
+ text,
194
+ usage: { input: inputTokens, output: outputTokens },
195
+ };
196
+ }
197
+ // ---------------------------------------------------------------------------
198
+ // OpenAI
199
+ // ---------------------------------------------------------------------------
200
+ async function callOpenAI(config, options) {
201
+ const apiKey = config.apiKey;
202
+ if (!apiKey)
203
+ throw new Error('OpenAI requires an API key (llm.apiKey)');
204
+ const model = config.model || defaultModel('openai');
205
+ const { messages, stream, onChunk, signal, maxTokens = 4096, temperature = 0.2 } = options;
206
+ const base = (config.baseUrl || 'https://api.openai.com/v1').replace(/\/$/, '');
207
+ const resp = await fetch(`${base}/chat/completions`, {
208
+ method: 'POST',
209
+ headers: {
210
+ 'Content-Type': 'application/json',
211
+ 'Authorization': `Bearer ${apiKey}`,
212
+ },
213
+ body: JSON.stringify({
214
+ model,
215
+ messages,
216
+ temperature,
217
+ max_tokens: maxTokens,
218
+ stream: stream ?? false,
219
+ }),
220
+ signal,
221
+ });
222
+ if (!resp.ok) {
223
+ const text = await resp.text().catch(() => '');
224
+ throw new Error(`OpenAI API error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
225
+ }
226
+ if (!stream) {
227
+ const json = await resp.json();
228
+ const text = String(json?.choices?.[0]?.message?.content || '').trim();
229
+ return {
230
+ text,
231
+ usage: {
232
+ input: Number(json?.usage?.prompt_tokens || 0),
233
+ output: Number(json?.usage?.completion_tokens || 0),
234
+ },
235
+ };
236
+ }
237
+ if (!resp.body)
238
+ throw new Error('OpenAI stream: missing body');
239
+ let out = '';
240
+ let usage = { input: 0, output: 0 };
241
+ await readTextStream(resp.body, (data) => {
242
+ if (data === '[DONE]')
243
+ return;
244
+ let obj;
245
+ try {
246
+ obj = JSON.parse(data);
247
+ }
248
+ catch {
249
+ return;
250
+ }
251
+ const delta = obj?.choices?.[0]?.delta?.content;
252
+ if (typeof delta === 'string' && delta.length > 0) {
253
+ out += delta;
254
+ onChunk?.(delta);
255
+ }
256
+ if (obj?.usage) {
257
+ usage = {
258
+ input: Number(obj.usage.prompt_tokens || usage.input),
259
+ output: Number(obj.usage.completion_tokens || usage.output),
260
+ };
261
+ }
262
+ }, signal);
263
+ return { text: out.trim(), usage };
264
+ }
265
+ // ---------------------------------------------------------------------------
266
+ // Anthropic
267
+ // ---------------------------------------------------------------------------
268
+ async function callAnthropic(config, options) {
269
+ const apiKey = config.apiKey;
270
+ if (!apiKey)
271
+ throw new Error('Anthropic requires an API key (llm.apiKey)');
272
+ const model = config.model || defaultModel('anthropic');
273
+ const { messages, stream, onChunk, signal, maxTokens = 4096, temperature = 0.2 } = options;
274
+ // Separate system prompt from user/assistant messages
275
+ const systemMsg = messages.find((m) => m.role === 'system')?.content || '';
276
+ const userMessages = messages.filter((m) => m.role !== 'system');
277
+ const resp = await fetch('https://api.anthropic.com/v1/messages', {
278
+ method: 'POST',
279
+ headers: {
280
+ 'Content-Type': 'application/json',
281
+ 'x-api-key': apiKey,
282
+ 'anthropic-version': '2023-06-01',
283
+ ...(stream ? { 'Accept': 'text/event-stream' } : {}),
284
+ },
285
+ body: JSON.stringify({
286
+ model,
287
+ system: systemMsg || undefined,
288
+ messages: userMessages,
289
+ max_tokens: maxTokens,
290
+ temperature,
291
+ stream: stream ?? false,
292
+ }),
293
+ signal,
294
+ });
295
+ if (!resp.ok) {
296
+ const text = await resp.text().catch(() => '');
297
+ throw new Error(`Anthropic API error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
298
+ }
299
+ if (!stream) {
300
+ const json = await resp.json();
301
+ const blocks = Array.isArray(json?.content) ? json.content : [];
302
+ const text = blocks.map((b) => (typeof b?.text === 'string' ? b.text : '')).join('').trim();
303
+ return {
304
+ text,
305
+ usage: {
306
+ input: Number(json?.usage?.input_tokens || 0),
307
+ output: Number(json?.usage?.output_tokens || 0),
308
+ },
309
+ };
310
+ }
311
+ if (!resp.body)
312
+ throw new Error('Anthropic stream: missing body');
313
+ let out = '';
314
+ let usage = { input: 0, output: 0 };
315
+ await readTextStream(resp.body, (data) => {
316
+ let obj;
317
+ try {
318
+ obj = JSON.parse(data);
319
+ }
320
+ catch {
321
+ return;
322
+ }
323
+ if (obj?.type === 'content_block_delta') {
324
+ const delta = obj?.delta?.text;
325
+ if (typeof delta === 'string' && delta.length > 0) {
326
+ out += delta;
327
+ onChunk?.(delta);
328
+ }
329
+ }
330
+ if (obj?.type === 'message_delta' && obj?.usage) {
331
+ usage = {
332
+ input: Number(obj.usage.input_tokens || usage.input),
333
+ output: Number(obj.usage.output_tokens || usage.output),
334
+ };
335
+ }
336
+ }, signal);
337
+ return { text: out.trim(), usage };
338
+ }
339
+ // ---------------------------------------------------------------------------
340
+ // Google Gemini
341
+ // ---------------------------------------------------------------------------
342
+ async function callGoogle(config, options) {
343
+ const apiKey = config.apiKey;
344
+ if (!apiKey)
345
+ throw new Error('Google requires an API key (llm.apiKey)');
346
+ const model = config.model || defaultModel('google');
347
+ const { messages, onChunk, signal, maxTokens = 4096, temperature = 0.2 } = options;
348
+ // Combine system + user messages for Google
349
+ const systemMsg = messages.find((m) => m.role === 'system')?.content;
350
+ const userMessages = messages.filter((m) => m.role !== 'system');
351
+ const combinedUser = systemMsg
352
+ ? `${systemMsg}\n\n${userMessages.map((m) => m.content).join('\n\n')}`
353
+ : userMessages.map((m) => m.content).join('\n\n');
354
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(apiKey)}`;
355
+ const resp = await fetch(url, {
356
+ method: 'POST',
357
+ headers: { 'Content-Type': 'application/json' },
358
+ body: JSON.stringify({
359
+ contents: [{ role: 'user', parts: [{ text: combinedUser }] }],
360
+ generationConfig: {
361
+ temperature,
362
+ maxOutputTokens: maxTokens,
363
+ },
364
+ }),
365
+ signal,
366
+ });
367
+ if (!resp.ok) {
368
+ const text = await resp.text().catch(() => '');
369
+ throw new Error(`Google API error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
370
+ }
371
+ const json = await resp.json();
372
+ const parts = json?.candidates?.[0]?.content?.parts;
373
+ const text = Array.isArray(parts)
374
+ ? parts.map((p) => (typeof p?.text === 'string' ? p.text : '')).join('')
375
+ : '';
376
+ const result = String(text || '').trim();
377
+ const usage = {
378
+ input: Number(json?.usageMetadata?.promptTokenCount || 0),
379
+ output: Number(json?.usageMetadata?.candidatesTokenCount || 0),
380
+ };
381
+ // Simulate streaming for Google (no native streaming in this impl)
382
+ if (onChunk && result) {
383
+ const chunkSize = 120;
384
+ for (let i = 0; i < result.length; i += chunkSize) {
385
+ onChunk(result.slice(i, i + chunkSize));
386
+ }
387
+ }
388
+ return { text: result, usage };
389
+ }
390
+ // ---------------------------------------------------------------------------
391
+ // Ollama (OpenAI-compatible)
392
+ // ---------------------------------------------------------------------------
393
+ async function callOllama(config, options) {
394
+ const endpoint = (config.endpoint || process.env.OLLAMA_URL || 'http://localhost:11434').replace(/\/$/, '');
395
+ const model = config.model || process.env.OLLAMA_MODEL || defaultModel('ollama');
396
+ const { messages, stream, onChunk, signal, maxTokens = 4096, temperature = 0.2 } = options;
397
+ // Support bearer token auth (for nginx reverse proxy on Hetzner)
398
+ const headers = { 'Content-Type': 'application/json' };
399
+ const secret = config.apiKey || process.env.OLLAMA_SECRET;
400
+ if (secret)
401
+ headers['Authorization'] = `Bearer ${secret}`;
402
+ // ── Non-streaming: use /api/generate with think:false for speed ──────
403
+ // Qwen3 thinking mode wastes 300-400 tokens on CoT and takes 25s+.
404
+ // With think:false via /api/generate, response comes in ~8s.
405
+ if (!stream) {
406
+ // Build a single prompt from messages (system + user)
407
+ const systemMsg = messages.find((m) => m.role === 'system')?.content || '';
408
+ const userMsg = messages.filter((m) => m.role === 'user').map((m) => m.content).join('\n\n');
409
+ const prompt = systemMsg ? `${systemMsg}\n\n${userMsg}` : userMsg;
410
+ const resp = await fetch(`${endpoint}/api/generate`, {
411
+ method: 'POST',
412
+ headers,
413
+ body: JSON.stringify({
414
+ model,
415
+ prompt,
416
+ stream: false,
417
+ think: false, // Critical: disables Qwen3 CoT thinking (8s vs 25s+)
418
+ options: {
419
+ temperature,
420
+ num_predict: maxTokens,
421
+ },
422
+ }),
423
+ signal,
424
+ });
425
+ if (!resp.ok) {
426
+ const text = await resp.text().catch(() => '');
427
+ throw new Error(`Ollama API error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
428
+ }
429
+ const json = await resp.json();
430
+ let text = String(json?.response || '').trim();
431
+ // Strip any residual <think> tags
432
+ text = text.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
433
+ return {
434
+ text,
435
+ usage: {
436
+ input: Number(json?.prompt_eval_count || 0),
437
+ output: Number(json?.eval_count || 0),
438
+ },
439
+ };
440
+ }
441
+ // ── Streaming: use OpenAI-compatible /v1/chat/completions ────────────
442
+ const resp = await fetch(`${endpoint}/v1/chat/completions`, {
443
+ method: 'POST',
444
+ headers,
445
+ body: JSON.stringify({
446
+ model,
447
+ messages,
448
+ temperature,
449
+ max_tokens: maxTokens,
450
+ stream: true,
451
+ }),
452
+ signal,
453
+ });
454
+ if (!resp.ok) {
455
+ const text = await resp.text().catch(() => '');
456
+ throw new Error(`Ollama API error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
457
+ }
458
+ if (!resp.body)
459
+ throw new Error('Ollama stream: missing body');
460
+ let out = '';
461
+ await readTextStream(resp.body, (data) => {
462
+ if (data === '[DONE]')
463
+ return;
464
+ let obj;
465
+ try {
466
+ obj = JSON.parse(data);
467
+ }
468
+ catch {
469
+ return;
470
+ }
471
+ const delta = obj?.choices?.[0]?.delta?.content;
472
+ if (typeof delta === 'string' && delta.length > 0) {
473
+ out += delta;
474
+ onChunk?.(delta);
475
+ }
476
+ }, signal);
477
+ // Strip thinking from streamed output
478
+ out = out.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
479
+ return { text: out, usage: { input: 0, output: 0 } };
480
+ }
481
+ // ---------------------------------------------------------------------------
482
+ // Cerebras (OpenAI-compatible)
483
+ // ---------------------------------------------------------------------------
484
+ async function callCerebras(config, options) {
485
+ const apiKey = config.apiKey;
486
+ if (!apiKey)
487
+ throw new Error('Cerebras requires an API key (llm.apiKey)');
488
+ const endpoint = (config.endpoint || 'https://api.cerebras.ai/v1/chat/completions');
489
+ const model = config.model || defaultModel('cerebras');
490
+ const { messages, stream, onChunk, signal, maxTokens = 4096, temperature = 0.2 } = options;
491
+ const resp = await fetch(endpoint, {
492
+ method: 'POST',
493
+ headers: {
494
+ 'Content-Type': 'application/json',
495
+ 'Authorization': `Bearer ${apiKey}`,
496
+ },
497
+ body: JSON.stringify({
498
+ model,
499
+ messages,
500
+ temperature,
501
+ max_tokens: maxTokens,
502
+ stream: stream ?? false,
503
+ }),
504
+ signal,
505
+ });
506
+ if (!resp.ok) {
507
+ const text = await resp.text().catch(() => '');
508
+ throw new Error(`Cerebras API error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
509
+ }
510
+ if (!stream) {
511
+ const json = await resp.json();
512
+ const text = String(json?.choices?.[0]?.message?.content || '').trim();
513
+ return {
514
+ text,
515
+ usage: {
516
+ input: Number(json?.usage?.prompt_tokens || 0),
517
+ output: Number(json?.usage?.completion_tokens || 0),
518
+ },
519
+ };
520
+ }
521
+ if (!resp.body)
522
+ throw new Error('Cerebras stream: missing body');
523
+ let out = '';
524
+ let usage = { input: 0, output: 0 };
525
+ await readTextStream(resp.body, (data) => {
526
+ if (data === '[DONE]')
527
+ return;
528
+ let obj;
529
+ try {
530
+ obj = JSON.parse(data);
531
+ }
532
+ catch {
533
+ return;
534
+ }
535
+ const delta = obj?.choices?.[0]?.delta?.content;
536
+ if (typeof delta === 'string' && delta.length > 0) {
537
+ out += delta;
538
+ onChunk?.(delta);
539
+ }
540
+ if (obj?.usage) {
541
+ usage = {
542
+ input: Number(obj.usage.prompt_tokens || usage.input),
543
+ output: Number(obj.usage.completion_tokens || usage.output),
544
+ };
545
+ }
546
+ }, signal);
547
+ return { text: out.trim(), usage };
548
+ }
549
+ // ---------------------------------------------------------------------------
550
+ // Main unified call function
551
+ // ---------------------------------------------------------------------------
552
+ /**
553
+ * Call an LLM using the unified provider abstraction.
554
+ *
555
+ * @throws {FreeTierLimitError} if Cloudflare free tier cap is exceeded
556
+ * @throws {Error} for other failures
557
+ */
558
+ export async function callLLM(config, options) {
559
+ const provider = config.provider;
560
+ switch (provider) {
561
+ case 'cloudflare':
562
+ return callCloudflare(config, options);
563
+ case 'openai':
564
+ return callOpenAI(config, options);
565
+ case 'anthropic':
566
+ return callAnthropic(config, options);
567
+ case 'google':
568
+ return callGoogle(config, options);
569
+ case 'ollama':
570
+ return callOllama(config, options);
571
+ case 'cerebras':
572
+ return callCerebras(config, options);
573
+ default: {
574
+ // TypeScript exhaustiveness
575
+ const _exhaustive = provider;
576
+ throw new Error(`Unknown LLM provider: ${String(_exhaustive)}`);
577
+ }
578
+ }
579
+ }
580
+ /**
581
+ * Get the default LLM config based on available environment variables.
582
+ *
583
+ * Priority order (high-quality models first):
584
+ * Anthropic → OpenAI → Google → Cerebras → Glama → OpenRouter
585
+ * → Ollama → Cloudflare (free tier fallback).
586
+ *
587
+ * Glama/OpenRouter/custom OPENAI_BASE_URL are routed through the 'openai'
588
+ * provider with a custom `baseUrl`, so any OpenAI-compatible gateway works.
589
+ *
590
+ * If no BYOK key and no Cloudflare credentials are configured, returns a
591
+ * cloudflare config that will throw a clear error when callLLM is invoked.
592
+ */
593
+ export function getDefaultLLMConfig() {
594
+ if (process.env.ANTHROPIC_API_KEY) {
595
+ return { provider: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY };
596
+ }
597
+ if (process.env.OPENAI_API_KEY) {
598
+ return {
599
+ provider: 'openai',
600
+ apiKey: process.env.OPENAI_API_KEY,
601
+ // Honor OPENAI_BASE_URL for Azure / custom deployments
602
+ ...(process.env.OPENAI_BASE_URL ? { baseUrl: process.env.OPENAI_BASE_URL } : {}),
603
+ };
604
+ }
605
+ if (process.env.GOOGLE_API_KEY) {
606
+ return { provider: 'google', apiKey: process.env.GOOGLE_API_KEY };
607
+ }
608
+ if (process.env.CEREBRAS_API_KEY) {
609
+ return { provider: 'cerebras', apiKey: process.env.CEREBRAS_API_KEY };
610
+ }
611
+ // Glama (OpenAI-compatible gateway)
612
+ if (process.env.GLAMA_API_KEY) {
613
+ return {
614
+ provider: 'openai',
615
+ apiKey: process.env.GLAMA_API_KEY,
616
+ baseUrl: 'https://glama.ai/api/gateway/openai/v1',
617
+ model: process.env.LLM_MODEL || 'google-vertex/gemini-2.5-flash',
618
+ };
619
+ }
620
+ // OpenRouter (OpenAI-compatible gateway)
621
+ if (process.env.OPENROUTER_API_KEY) {
622
+ return {
623
+ provider: 'openai',
624
+ apiKey: process.env.OPENROUTER_API_KEY,
625
+ baseUrl: 'https://openrouter.ai/api/v1',
626
+ model: process.env.LLM_MODEL || 'google/gemini-2.0-flash-exp:free',
627
+ };
628
+ }
629
+ // Ollama (local)
630
+ if (process.env.OLLAMA_URL) {
631
+ return {
632
+ provider: 'ollama',
633
+ endpoint: process.env.OLLAMA_URL,
634
+ apiKey: process.env.OLLAMA_SECRET,
635
+ model: process.env.OLLAMA_MODEL,
636
+ };
637
+ }
638
+ // Default: Cloudflare free tier (requires CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_TOKEN at call time)
639
+ return { provider: 'cloudflare' };
640
+ }
641
+ /**
642
+ * Get a fast/cheap LLM config suitable for quick classification & synthesis.
643
+ * Same priority as getDefaultLLMConfig but selects smaller models by default.
644
+ *
645
+ * Designed to replace ad-hoc provider detection in server routes.
646
+ * Returns null if no provider is configured.
647
+ */
648
+ export function getQuickLLMConfig() {
649
+ if (process.env.OPENAI_API_KEY) {
650
+ return {
651
+ provider: 'openai',
652
+ apiKey: process.env.OPENAI_API_KEY,
653
+ model: process.env.LLM_MODEL || 'gpt-4o-mini',
654
+ ...(process.env.OPENAI_BASE_URL ? { baseUrl: process.env.OPENAI_BASE_URL } : {}),
655
+ };
656
+ }
657
+ if (process.env.GLAMA_API_KEY) {
658
+ return {
659
+ provider: 'openai',
660
+ apiKey: process.env.GLAMA_API_KEY,
661
+ baseUrl: 'https://glama.ai/api/gateway/openai/v1',
662
+ model: process.env.LLM_MODEL || 'google-vertex/gemini-2.5-flash',
663
+ };
664
+ }
665
+ if (process.env.OPENROUTER_API_KEY) {
666
+ return {
667
+ provider: 'openai',
668
+ apiKey: process.env.OPENROUTER_API_KEY,
669
+ baseUrl: 'https://openrouter.ai/api/v1',
670
+ model: process.env.LLM_MODEL || 'google/gemini-2.0-flash-exp:free',
671
+ };
672
+ }
673
+ if (process.env.OLLAMA_URL) {
674
+ return {
675
+ provider: 'ollama',
676
+ endpoint: process.env.OLLAMA_URL,
677
+ apiKey: process.env.OLLAMA_SECRET,
678
+ model: process.env.OLLAMA_MODEL || 'qwen3:1.7b',
679
+ };
680
+ }
681
+ if (process.env.ANTHROPIC_API_KEY) {
682
+ return {
683
+ provider: 'anthropic',
684
+ apiKey: process.env.ANTHROPIC_API_KEY,
685
+ model: 'claude-3-5-haiku-latest',
686
+ };
687
+ }
688
+ if (process.env.GOOGLE_API_KEY) {
689
+ return { provider: 'google', apiKey: process.env.GOOGLE_API_KEY, model: 'gemini-1.5-flash' };
690
+ }
691
+ if (process.env.CEREBRAS_API_KEY) {
692
+ return { provider: 'cerebras', apiKey: process.env.CEREBRAS_API_KEY };
693
+ }
694
+ // No provider available — callers should handle null gracefully
695
+ return null;
696
+ }
697
+ /** Type guard: check if a thrown value is a FreeTierLimitError */
698
+ export function isFreeTierLimitError(err) {
699
+ return (typeof err === 'object' &&
700
+ err !== null &&
701
+ err.error === 'free_tier_limit');
702
+ }