pi-native-search

Pi extension that adds web_search and web_fetch tools using each provider's native search backend (ZAI MCP, Anthropic, Google, OpenAI, xAI, Claude Code subscription via claude-bridge), with DuckDuckGo as a universal fallback.

Package details

extension

Install pi-native-search from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-native-search
Package
pi-native-search
Version
0.1.0
Published
May 2, 2026
Downloads
not available
Author
salem-malibary
License
MIT
Types
extension
Size
46.9 KB
Dependencies
0 dependencies · 3 peers
Pi manifest JSON
{
  "extensions": [
    "./extensions/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-native-search

npm version license

A pi extension that adds web_search and web_fetch tools, routing each call through the active provider's own native search backend when available, and falling back to DuckDuckGo HTML scraping otherwise.

The headline feature: when you're using your Claude Code subscription via pi-claude-bridge, search and fetch are delegated to Claude Code's actual WebSearch and WebFetch tools — the same ones you get in Zed, Claude Desktop, or the Claude Code CLI. No separate API key required.

Why

Pi ships with provider plumbing but no built-in search. Most extensions either (a) hard-code DuckDuckGo, or (b) require you to wire up your own paid search API. This extension uses what each provider already gives you for free.

Provider Native backend Auth source
claude-bridge (Claude Code subscription) Claude Code's WebSearch / WebFetch (via @anthropic-ai/claude-agent-sdk) Your claude CLI login
zai (GLM) ZAI MCP web_search_prime (included in Coding Plans, not the separate paid Web Search API) ZAI_API_KEY
anthropic web_search_20250305 server tool ANTHROPIC_API_KEY
google (Gemini) google_search grounding tool GEMINI_API_KEY
openai Responses API web_search tool OPENAI_API_KEY
xai (Grok) Responses API web_search tool XAI_API_KEY
All other providers DuckDuckGo HTML fallback none

web_fetch uses the same routing — currently only claude-bridge has a native backend; everything else uses a built-in HTTP fetcher.

Install

pi install npm:pi-native-search

The extension auto-detects your active provider via pi's ctx.model.provider and picks the right backend on every call.

Usage

Once installed, two tools become available to the model:

  • web_search { query } — searches the web and returns ranked results.
  • web_fetch { url } — fetches and returns a page's text content (truncated to 50 KB / 2000 lines).

The model decides when to use them; you don't need to do anything else. To configure or inspect the extension, use the /search slash command:

/search           # open the settings panel (configured providers only)
/search providers # show ALL providers and their capabilities
/search config    # print current config (active provider, native vs. ddg, etc.)
/search on        # enable both tools
/search off       # disable the extension entirely

The bottom status bar shows the active backend per call, e.g.:

search[search:native:cc-sdk,fetch:cc-sdk]    # claude-bridge route
search[search:native:mcp,fetch]               # ZAI route
search[search:ddg,fetch]                      # DDG fallback

Configuration persists in ~/.pi/agent/search-config.json.

How it works

The dispatcher in doSearch (and the web_fetch handler) reads the active provider from the extension context and selects a backend:

async function doSearch(query, provider, model, baseUrl, signal) {
  const cap = PROVIDERS[provider];
  if (cap?.nativeSearch && hasAuth) {
    switch (provider) {
      case "zai":           return zaiSearch(query, apiKey, signal);
      case "google":        return googleSearch(query, model, apiKey, signal);
      case "openai":        return openaiSearch(query, model, apiKey, signal);
      case "xai":           return xaiSearch(query, model, apiKey, signal);
      case "anthropic":     return anthropicSearch(query, model, apiKey, baseUrl, signal);
      case "claude-bridge": return claudeBridgeSearch(query, signal);
    }
  }
  return ddgSearch(query, signal); // fallback
}

If the native call throws, the result is silently swapped for the DDG fallback with a > Native failed (...) prefix so you can see what went wrong without losing the search result.

claude-bridge specifics

When the active provider is claude-bridge, the extension dynamically locates @anthropic-ai/claude-agent-sdk (shipped inside pi-claude-bridge's own node_modules) and spawns a one-shot query() with allowedTools: ["WebSearch"] (or ["WebFetch"]). The result text is captured and returned as the tool output. This means:

  • No extra dependency to install — reuses what pi-claude-bridge already brought in.
  • Auth comes from your claude CLI login, not an API key.
  • It uses your subscription, not API credits.

Adding a new provider

The extension is structured so that adding a backend is a self-contained change. To add provider foo:

1. Add an entry to the PROVIDERS map at the top of extensions/index.ts:

foo: {
  name: "Foo Provider",
  nativeSearch: true,
  nativeFetch: false,
  envKey: "FOO_API_KEY",
},

2. Implement the search function:

async function fooSearch(
  query: string,
  model: string,
  apiKey: string,
  signal?: AbortSignal,
): Promise<string> {
  const res = await fetch("https://api.foo.com/search", {
    method: "POST",
    signal,
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify({ query, model }),
  });
  if (!res.ok)
    throw new Error(`Foo ${res.status}: ${(await res.text()).slice(0, 200)}`);
  const data = (await res.json()) as any;
  // Format as numbered list with title, url, snippet
  return data.results
    .map(
      (r: any, i: number) =>
        `${i + 1}. **${r.title}**\n   ${r.url}\n   ${r.snippet}`,
    )
    .join("\n\n");
}

3. Add a case to doSearch:

case "foo":
  return { text: await fooSearch(query, model, apiKey, signal) };

That's it. The settings UI, status line, and fallback handling all pick it up automatically from the PROVIDERS map.

If the provider doesn't use a standard Bearer API key (e.g. OAuth, MCP session, or an SDK that handles auth itself like claude-bridge), see claudeBridgeSearch for how to special-case the auth check in hasCredentials and the hasAuth gate in doSearch.

Development

git clone https://github.com/smalibary/pi-native-search.git
cd pi-native-search
# Edit extensions/index.ts, then test by symlinking into pi:
cp extensions/index.ts ~/.pi/agent/extensions/pi-native-search/index.ts
# In pi: /reload

The extension is a single TypeScript file (extensions/index.ts) that pi loads via tsx at runtime — no build step required.

License

MIT — see LICENSE. PRs welcome, especially for new provider backends.

Acknowledgements

  • pi by Mario Zechner — the host TUI agent
  • pi-claude-bridge by Eli Dickinson — provides the Claude Agent SDK that claude-bridge mode reuses