@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.
- package/LICENSE +15 -0
- package/README.md +313 -0
- package/dist/cache.d.ts +30 -0
- package/dist/cache.js +139 -0
- package/dist/cli/commands/auth.d.ts +5 -0
- package/dist/cli/commands/auth.js +411 -0
- package/dist/cli/commands/doctor.d.ts +37 -0
- package/dist/cli/commands/doctor.js +371 -0
- package/dist/cli/commands/fetch.d.ts +6 -0
- package/dist/cli/commands/fetch.js +1345 -0
- package/dist/cli/commands/guide.d.ts +2 -0
- package/dist/cli/commands/guide.js +183 -0
- package/dist/cli/commands/interact.d.ts +5 -0
- package/dist/cli/commands/interact.js +840 -0
- package/dist/cli/commands/jobs.d.ts +5 -0
- package/dist/cli/commands/jobs.js +997 -0
- package/dist/cli/commands/monitor.d.ts +12 -0
- package/dist/cli/commands/monitor.js +197 -0
- package/dist/cli/commands/observe.d.ts +12 -0
- package/dist/cli/commands/observe.js +158 -0
- package/dist/cli/commands/screenshot.d.ts +5 -0
- package/dist/cli/commands/screenshot.js +282 -0
- package/dist/cli/commands/search.d.ts +5 -0
- package/dist/cli/commands/search.js +1021 -0
- package/dist/cli/commands/setup.d.ts +13 -0
- package/dist/cli/commands/setup.js +244 -0
- package/dist/cli/commands/skill.d.ts +15 -0
- package/dist/cli/commands/skill.js +195 -0
- package/dist/cli/utils.d.ts +84 -0
- package/dist/cli/utils.js +806 -0
- package/dist/cli-auth.d.ts +75 -0
- package/dist/cli-auth.js +369 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +99 -0
- package/dist/core/actions.d.ts +69 -0
- package/dist/core/actions.js +495 -0
- package/dist/core/agent.d.ts +98 -0
- package/dist/core/agent.js +558 -0
- package/dist/core/answer.d.ts +42 -0
- package/dist/core/answer.js +395 -0
- package/dist/core/application-tracker.d.ts +84 -0
- package/dist/core/application-tracker.js +184 -0
- package/dist/core/apply.d.ts +162 -0
- package/dist/core/apply.js +816 -0
- package/dist/core/auth-detection.d.ts +35 -0
- package/dist/core/auth-detection.js +358 -0
- package/dist/core/auto-extract.d.ts +82 -0
- package/dist/core/auto-extract.js +604 -0
- package/dist/core/auto-interact.d.ts +23 -0
- package/dist/core/auto-interact.js +246 -0
- package/dist/core/bm25-filter.d.ts +66 -0
- package/dist/core/bm25-filter.js +288 -0
- package/dist/core/branding.d.ts +54 -0
- package/dist/core/branding.js +234 -0
- package/dist/core/browser-fetch.d.ts +323 -0
- package/dist/core/browser-fetch.js +1600 -0
- package/dist/core/browser-pool.d.ts +91 -0
- package/dist/core/browser-pool.js +550 -0
- package/dist/core/budget.d.ts +42 -0
- package/dist/core/budget.js +324 -0
- package/dist/core/business-intel.d.ts +47 -0
- package/dist/core/business-intel.js +279 -0
- package/dist/core/cache.d.ts +13 -0
- package/dist/core/cache.js +121 -0
- package/dist/core/cf-worker-proxy.d.ts +32 -0
- package/dist/core/cf-worker-proxy.js +87 -0
- package/dist/core/challenge-detection.d.ts +26 -0
- package/dist/core/challenge-detection.js +468 -0
- package/dist/core/change-tracking.d.ts +75 -0
- package/dist/core/change-tracking.js +276 -0
- package/dist/core/chunker.d.ts +46 -0
- package/dist/core/chunker.js +249 -0
- package/dist/core/chunking.d.ts +42 -0
- package/dist/core/chunking.js +181 -0
- package/dist/core/circuit-breaker.d.ts +44 -0
- package/dist/core/circuit-breaker.js +85 -0
- package/dist/core/content-pruner.d.ts +47 -0
- package/dist/core/content-pruner.js +425 -0
- package/dist/core/cookie-cache.d.ts +60 -0
- package/dist/core/cookie-cache.js +163 -0
- package/dist/core/crawl-checkpoint.d.ts +54 -0
- package/dist/core/crawl-checkpoint.js +104 -0
- package/dist/core/crawler.d.ts +84 -0
- package/dist/core/crawler.js +349 -0
- package/dist/core/cross-verify.d.ts +27 -0
- package/dist/core/cross-verify.js +93 -0
- package/dist/core/deep-fetch.d.ts +74 -0
- package/dist/core/deep-fetch.js +405 -0
- package/dist/core/deep-research.d.ts +141 -0
- package/dist/core/deep-research.js +972 -0
- package/dist/core/design-analysis.d.ts +70 -0
- package/dist/core/design-analysis.js +490 -0
- package/dist/core/design-compare.d.ts +38 -0
- package/dist/core/design-compare.js +264 -0
- package/dist/core/diff.d.ts +61 -0
- package/dist/core/diff.js +289 -0
- package/dist/core/dns-cache.d.ts +20 -0
- package/dist/core/dns-cache.js +198 -0
- package/dist/core/documents.d.ts +23 -0
- package/dist/core/documents.js +123 -0
- package/dist/core/domain-memory.d.ts +66 -0
- package/dist/core/domain-memory.js +163 -0
- package/dist/core/domain-verify.d.ts +40 -0
- package/dist/core/domain-verify.js +379 -0
- package/dist/core/engine-ranker.d.ts +112 -0
- package/dist/core/engine-ranker.js +395 -0
- package/dist/core/extract-inline.d.ts +38 -0
- package/dist/core/extract-inline.js +215 -0
- package/dist/core/extract-listings.d.ts +38 -0
- package/dist/core/extract-listings.js +461 -0
- package/dist/core/extract.d.ts +9 -0
- package/dist/core/extract.js +139 -0
- package/dist/core/fetch-cache.d.ts +57 -0
- package/dist/core/fetch-cache.js +95 -0
- package/dist/core/fetcher.d.ts +13 -0
- package/dist/core/fetcher.js +12 -0
- package/dist/core/google-cache.d.ts +29 -0
- package/dist/core/google-cache.js +180 -0
- package/dist/core/google-serp-parser.d.ts +82 -0
- package/dist/core/google-serp-parser.js +287 -0
- package/dist/core/hotel-search.d.ts +122 -0
- package/dist/core/hotel-search.js +382 -0
- package/dist/core/http-fetch.d.ts +72 -0
- package/dist/core/http-fetch.js +820 -0
- package/dist/core/human.d.ts +175 -0
- package/dist/core/human.js +680 -0
- package/dist/core/image-caption.d.ts +44 -0
- package/dist/core/image-caption.js +271 -0
- package/dist/core/jobs.d.ts +75 -0
- package/dist/core/jobs.js +634 -0
- package/dist/core/json-ld.d.ts +15 -0
- package/dist/core/json-ld.js +617 -0
- package/dist/core/language-detect.d.ts +18 -0
- package/dist/core/language-detect.js +135 -0
- package/dist/core/links.d.ts +10 -0
- package/dist/core/links.js +44 -0
- package/dist/core/llm-extract.d.ts +71 -0
- package/dist/core/llm-extract.js +507 -0
- package/dist/core/llm-provider.d.ts +100 -0
- package/dist/core/llm-provider.js +702 -0
- package/dist/core/local-search.d.ts +60 -0
- package/dist/core/local-search.js +308 -0
- package/dist/core/logger.d.ts +28 -0
- package/dist/core/logger.js +104 -0
- package/dist/core/map.d.ts +33 -0
- package/dist/core/map.js +127 -0
- package/dist/core/markdown.d.ts +92 -0
- package/dist/core/markdown.js +809 -0
- package/dist/core/metadata.d.ts +34 -0
- package/dist/core/metadata.js +422 -0
- package/dist/core/observe.d.ts +113 -0
- package/dist/core/observe.js +395 -0
- package/dist/core/ocr.d.ts +12 -0
- package/dist/core/ocr.js +33 -0
- package/dist/core/paginate.d.ts +31 -0
- package/dist/core/paginate.js +106 -0
- package/dist/core/pdf.d.ts +8 -0
- package/dist/core/pdf.js +25 -0
- package/dist/core/peel-tls.d.ts +25 -0
- package/dist/core/peel-tls.js +220 -0
- package/dist/core/pipeline.d.ts +132 -0
- package/dist/core/pipeline.js +1666 -0
- package/dist/core/profiles.d.ts +61 -0
- package/dist/core/profiles.js +350 -0
- package/dist/core/prompt-guard.d.ts +30 -0
- package/dist/core/prompt-guard.js +119 -0
- package/dist/core/proxy-config.d.ts +90 -0
- package/dist/core/proxy-config.js +172 -0
- package/dist/core/quick-answer.d.ts +53 -0
- package/dist/core/quick-answer.js +833 -0
- package/dist/core/rate-governor.d.ts +80 -0
- package/dist/core/rate-governor.js +238 -0
- package/dist/core/readability.d.ts +57 -0
- package/dist/core/readability.js +533 -0
- package/dist/core/research.d.ts +66 -0
- package/dist/core/research.js +270 -0
- package/dist/core/retry.d.ts +60 -0
- package/dist/core/retry.js +119 -0
- package/dist/core/safe-browsing.d.ts +30 -0
- package/dist/core/safe-browsing.js +206 -0
- package/dist/core/schema-extraction.d.ts +66 -0
- package/dist/core/schema-extraction.js +352 -0
- package/dist/core/schema-postprocess.d.ts +32 -0
- package/dist/core/schema-postprocess.js +469 -0
- package/dist/core/schema-templates.d.ts +19 -0
- package/dist/core/schema-templates.js +143 -0
- package/dist/core/screenshot.d.ts +224 -0
- package/dist/core/screenshot.js +207 -0
- package/dist/core/search-engines.d.ts +25 -0
- package/dist/core/search-engines.js +182 -0
- package/dist/core/search-provider.d.ts +243 -0
- package/dist/core/search-provider.js +1629 -0
- package/dist/core/searxng-provider.d.ts +35 -0
- package/dist/core/searxng-provider.js +105 -0
- package/dist/core/selective-evidence.d.ts +151 -0
- package/dist/core/selective-evidence.js +389 -0
- package/dist/core/site-search.d.ts +44 -0
- package/dist/core/site-search.js +252 -0
- package/dist/core/sitemap.d.ts +23 -0
- package/dist/core/sitemap.js +105 -0
- package/dist/core/source-credibility.d.ts +29 -0
- package/dist/core/source-credibility.js +584 -0
- package/dist/core/source-scoring.d.ts +166 -0
- package/dist/core/source-scoring.js +396 -0
- package/dist/core/stemmer.d.ts +38 -0
- package/dist/core/stemmer.js +509 -0
- package/dist/core/strategies.d.ts +104 -0
- package/dist/core/strategies.js +1044 -0
- package/dist/core/strategy-hooks.d.ts +145 -0
- package/dist/core/strategy-hooks.js +74 -0
- package/dist/core/structured-extract.d.ts +43 -0
- package/dist/core/structured-extract.js +550 -0
- package/dist/core/summarize.d.ts +17 -0
- package/dist/core/summarize.js +78 -0
- package/dist/core/synonyms.d.ts +42 -0
- package/dist/core/synonyms.js +184 -0
- package/dist/core/system-monitor.d.ts +61 -0
- package/dist/core/system-monitor.js +133 -0
- package/dist/core/table-format.d.ts +30 -0
- package/dist/core/table-format.js +146 -0
- package/dist/core/threat-feeds.d.ts +23 -0
- package/dist/core/threat-feeds.js +104 -0
- package/dist/core/timing.d.ts +21 -0
- package/dist/core/timing.js +33 -0
- package/dist/core/transcript-export.d.ts +47 -0
- package/dist/core/transcript-export.js +107 -0
- package/dist/core/user-agents.d.ts +82 -0
- package/dist/core/user-agents.js +239 -0
- package/dist/core/vertical-search.d.ts +54 -0
- package/dist/core/vertical-search.js +158 -0
- package/dist/core/watch-manager.d.ts +175 -0
- package/dist/core/watch-manager.js +416 -0
- package/dist/core/watch.d.ts +101 -0
- package/dist/core/watch.js +389 -0
- package/dist/core/youtube.d.ts +130 -0
- package/dist/core/youtube.js +1175 -0
- package/dist/ee/challenge-re-export.d.ts +1 -0
- package/dist/ee/challenge-re-export.js +1 -0
- package/dist/ee/challenge-solver.d.ts +72 -0
- package/dist/ee/challenge-solver.js +720 -0
- package/dist/ee/domain-extractors.d.ts +8 -0
- package/dist/ee/domain-extractors.js +8 -0
- package/dist/ee/domain-intel.d.ts +16 -0
- package/dist/ee/domain-intel.js +133 -0
- package/dist/ee/extractors/allrecipes.d.ts +2 -0
- package/dist/ee/extractors/allrecipes.js +120 -0
- package/dist/ee/extractors/amazon.d.ts +2 -0
- package/dist/ee/extractors/amazon.js +78 -0
- package/dist/ee/extractors/arxiv.d.ts +2 -0
- package/dist/ee/extractors/arxiv.js +137 -0
- package/dist/ee/extractors/bestbuy.d.ts +2 -0
- package/dist/ee/extractors/bestbuy.js +78 -0
- package/dist/ee/extractors/carscom.d.ts +2 -0
- package/dist/ee/extractors/carscom.js +121 -0
- package/dist/ee/extractors/coingecko.d.ts +2 -0
- package/dist/ee/extractors/coingecko.js +134 -0
- package/dist/ee/extractors/craigslist.d.ts +2 -0
- package/dist/ee/extractors/craigslist.js +92 -0
- package/dist/ee/extractors/devto.d.ts +2 -0
- package/dist/ee/extractors/devto.js +135 -0
- package/dist/ee/extractors/ebay.d.ts +2 -0
- package/dist/ee/extractors/ebay.js +90 -0
- package/dist/ee/extractors/espn.d.ts +2 -0
- package/dist/ee/extractors/espn.js +260 -0
- package/dist/ee/extractors/etsy.d.ts +2 -0
- package/dist/ee/extractors/etsy.js +52 -0
- package/dist/ee/extractors/facebook.d.ts +2 -0
- package/dist/ee/extractors/facebook.js +46 -0
- package/dist/ee/extractors/github.d.ts +2 -0
- package/dist/ee/extractors/github.js +196 -0
- package/dist/ee/extractors/google-flights.d.ts +2 -0
- package/dist/ee/extractors/google-flights.js +176 -0
- package/dist/ee/extractors/hackernews.d.ts +2 -0
- package/dist/ee/extractors/hackernews.js +147 -0
- package/dist/ee/extractors/imdb.d.ts +2 -0
- package/dist/ee/extractors/imdb.js +172 -0
- package/dist/ee/extractors/index.d.ts +26 -0
- package/dist/ee/extractors/index.js +247 -0
- package/dist/ee/extractors/instagram.d.ts +2 -0
- package/dist/ee/extractors/instagram.js +102 -0
- package/dist/ee/extractors/kalshi.d.ts +2 -0
- package/dist/ee/extractors/kalshi.js +121 -0
- package/dist/ee/extractors/kayak-cars.d.ts +2 -0
- package/dist/ee/extractors/kayak-cars.js +270 -0
- package/dist/ee/extractors/linkedin.d.ts +2 -0
- package/dist/ee/extractors/linkedin.js +113 -0
- package/dist/ee/extractors/medium.d.ts +2 -0
- package/dist/ee/extractors/medium.js +130 -0
- package/dist/ee/extractors/news.d.ts +4 -0
- package/dist/ee/extractors/news.js +173 -0
- package/dist/ee/extractors/npm.d.ts +2 -0
- package/dist/ee/extractors/npm.js +86 -0
- package/dist/ee/extractors/pdf.d.ts +2 -0
- package/dist/ee/extractors/pdf.js +108 -0
- package/dist/ee/extractors/pinterest.d.ts +2 -0
- package/dist/ee/extractors/pinterest.js +34 -0
- package/dist/ee/extractors/polymarket.d.ts +2 -0
- package/dist/ee/extractors/polymarket.js +358 -0
- package/dist/ee/extractors/producthunt.d.ts +2 -0
- package/dist/ee/extractors/producthunt.js +88 -0
- package/dist/ee/extractors/pubmed.d.ts +2 -0
- package/dist/ee/extractors/pubmed.js +162 -0
- package/dist/ee/extractors/pypi.d.ts +2 -0
- package/dist/ee/extractors/pypi.js +80 -0
- package/dist/ee/extractors/reddit.d.ts +2 -0
- package/dist/ee/extractors/reddit.js +438 -0
- package/dist/ee/extractors/redfin.d.ts +2 -0
- package/dist/ee/extractors/redfin.js +156 -0
- package/dist/ee/extractors/semanticscholar.d.ts +2 -0
- package/dist/ee/extractors/semanticscholar.js +131 -0
- package/dist/ee/extractors/shared.d.ts +12 -0
- package/dist/ee/extractors/shared.js +76 -0
- package/dist/ee/extractors/soundcloud.d.ts +2 -0
- package/dist/ee/extractors/soundcloud.js +34 -0
- package/dist/ee/extractors/sportsbetting.d.ts +2 -0
- package/dist/ee/extractors/sportsbetting.js +37 -0
- package/dist/ee/extractors/spotify.d.ts +2 -0
- package/dist/ee/extractors/spotify.js +34 -0
- package/dist/ee/extractors/stackoverflow.d.ts +2 -0
- package/dist/ee/extractors/stackoverflow.js +61 -0
- package/dist/ee/extractors/substack.d.ts +2 -0
- package/dist/ee/extractors/substack.js +115 -0
- package/dist/ee/extractors/substackroot.d.ts +2 -0
- package/dist/ee/extractors/substackroot.js +46 -0
- package/dist/ee/extractors/tiktok.d.ts +2 -0
- package/dist/ee/extractors/tiktok.js +29 -0
- package/dist/ee/extractors/tradingview.d.ts +2 -0
- package/dist/ee/extractors/tradingview.js +182 -0
- package/dist/ee/extractors/twitch.d.ts +2 -0
- package/dist/ee/extractors/twitch.js +36 -0
- package/dist/ee/extractors/twitter.d.ts +2 -0
- package/dist/ee/extractors/twitter.js +327 -0
- package/dist/ee/extractors/types.d.ts +14 -0
- package/dist/ee/extractors/types.js +1 -0
- package/dist/ee/extractors/walmart.d.ts +2 -0
- package/dist/ee/extractors/walmart.js +50 -0
- package/dist/ee/extractors/weather.d.ts +2 -0
- package/dist/ee/extractors/weather.js +133 -0
- package/dist/ee/extractors/wikipedia.d.ts +4 -0
- package/dist/ee/extractors/wikipedia.js +235 -0
- package/dist/ee/extractors/yelp.d.ts +2 -0
- package/dist/ee/extractors/yelp.js +216 -0
- package/dist/ee/extractors/youtube.d.ts +2 -0
- package/dist/ee/extractors/youtube.js +189 -0
- package/dist/ee/extractors/zillow.d.ts +54 -0
- package/dist/ee/extractors/zillow.js +247 -0
- package/dist/ee/extractors-re-export.d.ts +1 -0
- package/dist/ee/extractors-re-export.js +1 -0
- package/dist/ee/premium-hooks.d.ts +20 -0
- package/dist/ee/premium-hooks.js +50 -0
- package/dist/ee/spa-detection.d.ts +2 -0
- package/dist/ee/spa-detection.js +2 -0
- package/dist/ee/stability.d.ts +4 -0
- package/dist/ee/stability.js +29 -0
- package/dist/ee/swr-cache.d.ts +14 -0
- package/dist/ee/swr-cache.js +34 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.js +291 -0
- package/dist/integrations/index.d.ts +2 -0
- package/dist/integrations/index.js +2 -0
- package/dist/integrations/langchain.d.ts +64 -0
- package/dist/integrations/langchain.js +115 -0
- package/dist/integrations/llamaindex.d.ts +50 -0
- package/dist/integrations/llamaindex.js +91 -0
- package/dist/mcp/handlers/act.d.ts +5 -0
- package/dist/mcp/handlers/act.js +34 -0
- package/dist/mcp/handlers/definitions.d.ts +6 -0
- package/dist/mcp/handlers/definitions.js +395 -0
- package/dist/mcp/handlers/extract.d.ts +7 -0
- package/dist/mcp/handlers/extract.js +135 -0
- package/dist/mcp/handlers/fetch.d.ts +6 -0
- package/dist/mcp/handlers/fetch.js +98 -0
- package/dist/mcp/handlers/find.d.ts +5 -0
- package/dist/mcp/handlers/find.js +137 -0
- package/dist/mcp/handlers/index.d.ts +13 -0
- package/dist/mcp/handlers/index.js +63 -0
- package/dist/mcp/handlers/legacy.d.ts +25 -0
- package/dist/mcp/handlers/legacy.js +450 -0
- package/dist/mcp/handlers/meta.d.ts +6 -0
- package/dist/mcp/handlers/meta.js +40 -0
- package/dist/mcp/handlers/monitor.d.ts +5 -0
- package/dist/mcp/handlers/monitor.js +41 -0
- package/dist/mcp/handlers/observe.d.ts +8 -0
- package/dist/mcp/handlers/observe.js +37 -0
- package/dist/mcp/handlers/read.d.ts +6 -0
- package/dist/mcp/handlers/read.js +78 -0
- package/dist/mcp/handlers/see.d.ts +5 -0
- package/dist/mcp/handlers/see.js +75 -0
- package/dist/mcp/handlers/types.d.ts +29 -0
- package/dist/mcp/handlers/types.js +28 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +108 -0
- package/dist/mcp/smart-router.d.ts +23 -0
- package/dist/mcp/smart-router.js +178 -0
- package/dist/server/app.d.ts +14 -0
- package/dist/server/app.js +632 -0
- package/dist/server/auth-store.d.ts +28 -0
- package/dist/server/auth-store.js +88 -0
- package/dist/server/bull-queues.d.ts +60 -0
- package/dist/server/bull-queues.js +90 -0
- package/dist/server/email-service.d.ts +55 -0
- package/dist/server/email-service.js +291 -0
- package/dist/server/job-queue.d.ts +100 -0
- package/dist/server/job-queue.js +145 -0
- package/dist/server/logger.d.ts +10 -0
- package/dist/server/logger.js +37 -0
- package/dist/server/middleware/audit-log.d.ts +14 -0
- package/dist/server/middleware/audit-log.js +73 -0
- package/dist/server/middleware/auth.d.ts +35 -0
- package/dist/server/middleware/auth.js +225 -0
- package/dist/server/middleware/rate-limit.d.ts +50 -0
- package/dist/server/middleware/rate-limit.js +270 -0
- package/dist/server/middleware/scope-guard.d.ts +25 -0
- package/dist/server/middleware/scope-guard.js +45 -0
- package/dist/server/middleware/url-validator.d.ts +15 -0
- package/dist/server/middleware/url-validator.js +201 -0
- package/dist/server/openapi.yaml +6418 -0
- package/dist/server/pg-auth-store.d.ts +146 -0
- package/dist/server/pg-auth-store.js +576 -0
- package/dist/server/pg-job-queue.d.ts +59 -0
- package/dist/server/pg-job-queue.js +375 -0
- package/dist/server/routes/activity.d.ts +6 -0
- package/dist/server/routes/activity.js +79 -0
- package/dist/server/routes/admin-active.d.ts +7 -0
- package/dist/server/routes/admin-active.js +120 -0
- package/dist/server/routes/admin-stats.d.ts +7 -0
- package/dist/server/routes/admin-stats.js +176 -0
- package/dist/server/routes/agent.d.ts +24 -0
- package/dist/server/routes/agent.js +480 -0
- package/dist/server/routes/answer.d.ts +5 -0
- package/dist/server/routes/answer.js +125 -0
- package/dist/server/routes/ask.d.ts +28 -0
- package/dist/server/routes/ask.js +295 -0
- package/dist/server/routes/batch.d.ts +6 -0
- package/dist/server/routes/batch.js +493 -0
- package/dist/server/routes/cache-warm.d.ts +25 -0
- package/dist/server/routes/cache-warm.js +212 -0
- package/dist/server/routes/cli-usage.d.ts +6 -0
- package/dist/server/routes/cli-usage.js +127 -0
- package/dist/server/routes/compat.d.ts +23 -0
- package/dist/server/routes/compat.js +652 -0
- package/dist/server/routes/crawl.d.ts +13 -0
- package/dist/server/routes/crawl.js +287 -0
- package/dist/server/routes/deep-fetch.d.ts +8 -0
- package/dist/server/routes/deep-fetch.js +57 -0
- package/dist/server/routes/deep-research.d.ts +11 -0
- package/dist/server/routes/deep-research.js +232 -0
- package/dist/server/routes/demo.d.ts +24 -0
- package/dist/server/routes/demo.js +517 -0
- package/dist/server/routes/do.d.ts +8 -0
- package/dist/server/routes/do.js +72 -0
- package/dist/server/routes/extract.d.ts +14 -0
- package/dist/server/routes/extract.js +325 -0
- package/dist/server/routes/feed.d.ts +15 -0
- package/dist/server/routes/feed.js +311 -0
- package/dist/server/routes/fetch-queue.d.ts +13 -0
- package/dist/server/routes/fetch-queue.js +357 -0
- package/dist/server/routes/fetch.d.ts +7 -0
- package/dist/server/routes/fetch.js +1274 -0
- package/dist/server/routes/go.d.ts +14 -0
- package/dist/server/routes/go.js +81 -0
- package/dist/server/routes/health.d.ts +11 -0
- package/dist/server/routes/health.js +141 -0
- package/dist/server/routes/jobs.d.ts +7 -0
- package/dist/server/routes/jobs.js +574 -0
- package/dist/server/routes/map.d.ts +11 -0
- package/dist/server/routes/map.js +116 -0
- package/dist/server/routes/mcp.d.ts +14 -0
- package/dist/server/routes/mcp.js +197 -0
- package/dist/server/routes/metrics.d.ts +37 -0
- package/dist/server/routes/metrics.js +149 -0
- package/dist/server/routes/oauth.d.ts +9 -0
- package/dist/server/routes/oauth.js +396 -0
- package/dist/server/routes/playground.d.ts +17 -0
- package/dist/server/routes/playground.js +283 -0
- package/dist/server/routes/reader.d.ts +18 -0
- package/dist/server/routes/reader.js +192 -0
- package/dist/server/routes/research.d.ts +14 -0
- package/dist/server/routes/research.js +482 -0
- package/dist/server/routes/screenshot.d.ts +22 -0
- package/dist/server/routes/screenshot.js +820 -0
- package/dist/server/routes/search.d.ts +6 -0
- package/dist/server/routes/search.js +874 -0
- package/dist/server/routes/session.d.ts +17 -0
- package/dist/server/routes/session.js +548 -0
- package/dist/server/routes/share.d.ts +18 -0
- package/dist/server/routes/share.js +462 -0
- package/dist/server/routes/smart-search/handlers/cars.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/cars.js +102 -0
- package/dist/server/routes/smart-search/handlers/flights.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/flights.js +72 -0
- package/dist/server/routes/smart-search/handlers/general.d.ts +13 -0
- package/dist/server/routes/smart-search/handlers/general.js +717 -0
- package/dist/server/routes/smart-search/handlers/hotels.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/hotels.js +88 -0
- package/dist/server/routes/smart-search/handlers/products.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/products.js +1309 -0
- package/dist/server/routes/smart-search/handlers/rental.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/rental.js +154 -0
- package/dist/server/routes/smart-search/handlers/restaurants.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/restaurants.js +225 -0
- package/dist/server/routes/smart-search/handlers/transit-verdict.d.ts +41 -0
- package/dist/server/routes/smart-search/handlers/transit-verdict.js +224 -0
- package/dist/server/routes/smart-search/index.d.ts +19 -0
- package/dist/server/routes/smart-search/index.js +546 -0
- package/dist/server/routes/smart-search/intent.d.ts +3 -0
- package/dist/server/routes/smart-search/intent.js +264 -0
- package/dist/server/routes/smart-search/llm.d.ts +16 -0
- package/dist/server/routes/smart-search/llm.js +70 -0
- package/dist/server/routes/smart-search/sources/reddit.d.ts +18 -0
- package/dist/server/routes/smart-search/sources/reddit.js +34 -0
- package/dist/server/routes/smart-search/sources/yelp.d.ts +25 -0
- package/dist/server/routes/smart-search/sources/yelp.js +171 -0
- package/dist/server/routes/smart-search/sources/youtube.d.ts +8 -0
- package/dist/server/routes/smart-search/sources/youtube.js +9 -0
- package/dist/server/routes/smart-search/types.d.ts +81 -0
- package/dist/server/routes/smart-search/types.js +1 -0
- package/dist/server/routes/smart-search/utils.d.ts +20 -0
- package/dist/server/routes/smart-search/utils.js +146 -0
- package/dist/server/routes/stats.d.ts +6 -0
- package/dist/server/routes/stats.js +71 -0
- package/dist/server/routes/stripe.d.ts +15 -0
- package/dist/server/routes/stripe.js +296 -0
- package/dist/server/routes/transcript-export.d.ts +10 -0
- package/dist/server/routes/transcript-export.js +178 -0
- package/dist/server/routes/usage.d.ts +9 -0
- package/dist/server/routes/usage.js +279 -0
- package/dist/server/routes/users.d.ts +8 -0
- package/dist/server/routes/users.js +1867 -0
- package/dist/server/routes/watch.d.ts +15 -0
- package/dist/server/routes/watch.js +309 -0
- package/dist/server/routes/webhooks.d.ts +26 -0
- package/dist/server/routes/webhooks.js +170 -0
- package/dist/server/routes/youtube.d.ts +6 -0
- package/dist/server/routes/youtube.js +130 -0
- package/dist/server/sentry.d.ts +14 -0
- package/dist/server/sentry.js +104 -0
- package/dist/server/types.d.ts +15 -0
- package/dist/server/types.js +7 -0
- package/dist/server/utils/response.d.ts +44 -0
- package/dist/server/utils/response.js +69 -0
- package/dist/server/utils/sse.d.ts +22 -0
- package/dist/server/utils/sse.js +38 -0
- package/dist/types.d.ts +552 -0
- package/dist/types.js +39 -0
- package/llms.txt +105 -0
- 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
|
+
}
|