pi-web-search
A Pi extension that adds web_search and web_fetch tools backed mostly by
direct REST APIs. Exa and Z-AI may use their MCP-compatible HTTP endpoints internally as fallbacks,
but the extension does not require Pi-level MCP integration or an MCP SDK. No DuckDuckGo scraping.
Providers
| Provider | Backend | Filters | Env var |
|---|---|---|---|
| Exa | /answer + /search REST, fallback to Exa MCP-over-HTTP |
recency, domain, content | optional (EXA_API_KEY) |
| Perplexity | chat/completions (sonar) |
recency, domain | PERPLEXITY_API_KEY |
| Z-AI (GLM) | /paas/v4/web_search, fallback to Z-AI MCP-over-HTTP on 1113/balance errors |
recency, domain | ZAI_API_KEY |
| xAI (Grok) | /v1/responses + web_search tool |
— | XAI_API_KEY |
| OpenAI | /v1/responses + web_search tool |
— | OPENAI_API_KEY |
Anthropic and Gemini are planned for a future release.
Install
pi install npm:pi-web-search
Then configure at least one API key (see Configuration).
Configuration
API keys are resolved with environment variable > config file precedence.
Primary config file: ~/.pi/agent/web-search.json
Legacy fallback (read-only compatibility): ~/.pi/web-search.json
{
"providerOrder": ["zai", "exa", "perplexity", "xai", "openai"],
"exaApiKey": "abb8ecf3-...",
"perplexityApiKey": "pplx-...",
"zaiApiKey": "...",
"xaiApiKey": "xai-...",
"openaiApiKey": "sk-...",
"xaiModel": "grok-3",
"openaiModel": "gpt-4o",
"enabled": true
}
Equivalent environment variables (EXA_API_KEY, PERPLEXITY_API_KEY, ZAI_API_KEY,
XAI_API_KEY, OPENAI_API_KEY) override the config file.
Notes:
EXA_API_KEYis optional because Exa can fall back to its MCP-over-HTTP endpoint.ZAI_API_KEYis still required. The extension may fall back from the REST search API to the Z-AI MCP-over-HTTP endpoint when the REST API reports insufficient balance / no resource package.
providerOrder
providerOrder defines the global fallback preference used when the tool call does not force a specific provider.
Example:
{
"providerOrder": ["zai", "exa", "openai"]
}
Rules:
- entries must be one of:
exa,perplexity,zai,xai,openai - duplicates are ignored
- partial lists are allowed; missing providers are appended in default order
So the example above becomes the effective order:
zai → exa → openai → perplexity → xai
Provider auto-selection
Provider resolution is internal and stateful per session. Each auto search computes an effective order:
- start from
providerOrder - keep only providers with credentials
- demote providers that have failed (
sortByHealth) - drop providers currently in cooldown
The search then tries each eligible provider in turn and falls back to the next one on failure.
Default effective order:
Exa → Perplexity → Z-AI → xAI → OpenAI
If no provider is usable, the tool returns a clear setup message. Exa remains usable without a key via its MCP-over-HTTP fallback.
Failure handling (cooldown + demotion)
Runtime health is kept in memory only and never written to your config file.
When a provider fails, it is recorded with a category-based cooldown:
| Failure | Cooldown |
|---|---|
billing/quota (1113, insufficient balance, no resource package) |
30 min |
rate-limited (429) |
5 min |
auth (401/403, bad key) |
30 min |
| other 4xx | 5 min |
| 5xx / network / timeout | 60 s |
Behavior:
- a provider in cooldown is skipped until its window expires
- a provider that fails repeatedly is demoted toward the back of the effective order
- a successful call resets that provider's health
- aborts/cancellations are never counted as failures
session_startclears all runtime health for a clean slate
Tools
web_search
web_search({ query: "Odin programming language latest release" })
// batch + filters
web_search({
queries: ["query one", "query two"],
numResults: 10,
recencyFilter: "week", // "day" | "week" | "month" | "year"
domainFilter: ["github.com"], // prefix with - to exclude
})
| Parameter | Description |
|---|---|
query / queries |
Single query or batch (run in parallel) |
numResults |
Results per query (default 5; Z-AI max 50) |
recencyFilter |
day, week, month, year (Exa, Perplexity, Z-AI) |
domainFilter |
Whitelist domains; prefix - to exclude (Exa, Perplexity, Z-AI) |
includeContent |
Request inline page content where supported (Exa) |
Returns a synthesized answer (where the provider produces one) plus a numbered Sources list. Z-AI is a pure search engine, so it returns a source list with snippets.
web_fetch
web_fetch({ url: "https://example.com/docs" })
Parameters:
| Parameter | Description |
|---|---|
url |
Absolute URL to fetch |
Behavior:
- backend is resolved internally
- if
zaiis the first usable provider inproviderOrder, use Z-AI Web Reader (POST /api/paas/v4/reader) - if the Z-AI REST reader reports
1113/ insufficient-balance errors, fall back internally to the Z-AI Web Reader MCP-over-HTTP endpoint - otherwise use local HTTP + lightweight HTML stripping / JSON pretty-print
Output is truncated to ~50 KB / 2000 lines.
/search command
| Command | Action |
|---|---|
/search |
Interactive provider-order picker (moves one provider to the front) |
Provider configuration is stored in ~/.pi/agent/web-search.json.
Testing
npm test
This runs node:test directly on the TypeScript test files via Node's strip-types mode.
Development
This is a TypeScript-only package — Pi loads index.ts directly via jiti, so there is no
build step. To type-check during development, point the compiler at Pi's installed packages:
# from the package directory
mkdir -p node_modules/@earendil-works node_modules/@types
ln -s "$(npm root -g)/@earendil-works/pi-coding-agent" node_modules/@earendil-works/pi-coding-agent
ln -s "$(npm root -g)/@earendil-works/pi-ai" node_modules/@earendil-works/pi-ai
ln -s "$(npm root -g)/@earendil-works/pi-tui" node_modules/@earendil-works/pi-tui
ln -s "$(npm root -g)/typebox" node_modules/typebox
ln -s "$(npm root -g)/@types/node" node_modules/@types/node
npx tsc --noEmit -p tsconfig.json
For local testing, symlink the package into Pi's extensions dir and run /reload:
ln -s "$PWD" ~/.pi/agent/extensions/pi-web-search
Structure
index.ts # tool + command registration, events, status bar
search.ts # provider resolution, execution, response formatting
config.ts # ~/.pi/agent/web-search.json load/save + legacy fallback
fetch.ts # local HTTP fetch + Z-AI Web Reader backend
types.ts # shared types
providers/
exa.ts # /answer + /search
perplexity.ts # chat/completions (sonar)
zai.ts # /paas/v4/web_search (search-prime)
xai.ts # /v1/responses + web_search tool
openai.ts # /v1/responses + web_search tool
License
MIT — see LICENSE.