npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@workglow/web-search

v0.6.7

Published

Web-search task for Workglow with pluggable providers (Brave, Tavily, SearXNG; grounded providers ship in vendor packages).

Readme

@workglow/web-search

WebSearchTask for Workglow, with pluggable search providers.

Server-side only

Every commercial search API authenticates with a request header, which forces a CORS preflight that none of them answer — so a browser cannot call one directly. Even where it could, the API key would be readable by any visitor.

The browser build therefore registers the task (so a builder UI can render and validate the node) but no providers. Execution happens on a server.

Registering providers

Importing this package registers the task class and nothing else. Which providers exist — and so which one "auto" may bill you for — is the host's decision, stated with a call:

import { registerBuiltInWebSearchProviders } from "@workglow/web-search";

registerBuiltInWebSearchProviders(); // brave, tavily, and searxng when a base URL is set

Nothing registers itself on import, which is what lets a vendor adapter pull this package in for registerWebSearchProvider without quietly adding three providers ahead of the one the host asked for.

Providers

| provider | auth | answer | content | domain filter | date filter | | ------------ | ---------------------- | ------ | ------- | ------------------------ | ----------- | | brave | X-Subscription-Token | no | no | via site: | yes | | tavily | bearer token | yes | yes | native | yes | | searxng | none (self-hosted) | no | no | via site: | no | | anthropic | vendor SDK | yes | no | native, one list | no | | openai | vendor SDK | yes | no | native, include only | no | | openrouter | vendor SDK | yes | no | native | no | | gemini | vendor SDK | yes | no | none | yes |

The four grounded providers ship in their vendor packages and must be registered explicitly — importing the subpath registers nothing:

import { registerAnthropicWebSearchProvider } from "@workglow/anthropic/web-search";
import { registerOpenAiWebSearchProvider } from "@workglow/openai/web-search";
import { registerOpenRouterWebSearchProvider } from "@workglow/openrouter/web-search";
import { registerGeminiWebSearchProvider } from "@workglow/google-gemini/web-search";

Their capability profiles genuinely differ — OpenAI can restrict to domains but not away from them, Anthropic takes either list but never both in one request, and Gemini is the only one that filters by date while filtering by no domain at all. That is why excludeDomainFilter is separable from domainFilter, and why exclusiveDomainDirections exists: auto routing must score a request a provider cannot honor as a gap and move on, rather than land on it and throw. SearXNG needs WEB_SEARCH_SEARXNG_URL (or an explicit base URL) and an instance with format=json enabled.

Usage

import { registerBuiltInWebSearchProviders, WebSearchTask } from "@workglow/web-search";

registerBuiltInWebSearchProviders();

const out = await new WebSearchTask().run({
  query: "transformer architecture",
  provider: "tavily",
  includeAnswer: true,
  includeDomains: ["arxiv.org"],
  credential_key: "tavily-api-key",
});

provider is required and has no default — which provider serves a request determines its cost, rate limit and quality. Pass "auto" to let the registry pick one that can serve every option you asked for; the provider that ran is always reported on the provider output port.

A pinned provider that cannot honor an option throws rather than quietly rerouting or ignoring the option.

Credentials

credential_key names one credential-store entry and belongs with a pinned provider, where the vendor receiving it is settled before the run. With "auto" it is refused: routing chooses the vendor at run time, so an unnamed key would be sent wherever routing landed.

Name the key per provider instead, and "auto" stays usable:

const out = await new WebSearchTask().run({
  query: "transformer architecture",
  provider: "auto",
  includeAnswer: true,
  credential_keys: { tavily: "tavily-api-key", brave: "brave-api-key" },
});

The key sent is the one named for the provider that actually runs, so a key issued for one vendor cannot reach another; routing prefers a provider a key is named for, and a provider none was named for is simply searched unauthenticated.

A name in credential_keys that matches no registered provider is refused, not ignored — a typo or a missing register…() call otherwise surfaces as an authentication error from whichever provider ran instead. So is a name matching a grounded provider: those authenticate through their own vendor client, which never sees this key. Give them theirs at registration (registerAnthropicWebSearchProvider({ apiKey })) or in the vendor's own environment variable.

Adding a provider

Implement IWebSearchProvider and register it:

import { registerWebSearchProvider } from "@workglow/web-search";

registerWebSearchProvider(myProvider);

Declare capabilities honestly — domainFilter: "query-operator" means the task rewrites the query with site:, and dateFilter: false means a date-bounded request is refused rather than approximated by dropping results. Set acceptsCredentialKey to true only if request.credentialKey actually reaches the request; a provider holding its own client sets false, and a credential key named for it is then refused rather than dropped.

publishedDate is an ISO-8601 string or absent. Run a provider's own value through toIsoPublishedDate — several engines report display text ("3 days ago") that a downstream date comparison turns into an Invalid Date.

License

Apache 2.0 - See LICENSE for details.