pi-web-providers 0.3.0 β†’ 1.1.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/README.md CHANGED
@@ -1,43 +1,23 @@
1
1
  # 🌍 pi-web-providers
2
2
 
3
- A _meta_ web extension for [pi](https://pi.dev).
3
+ A _meta_ web extension for [pi](https://pi.dev) that routes search, content
4
+ extraction, answers, and research through configurable per-tool providers.
4
5
 
5
6
  ## Why?
6
7
 
7
- Most web extensions hard-wire a single search-and-fetch pipeline. That works
8
- until you want to swap providers, compare results, or use a capabilityβ€”like deep
9
- researchβ€”that only one backend offers.
10
-
11
- **pi-web-providers** takes a different approach: it doesn't do web work itself.
12
- Instead it dispatches every request to a **configurable set of providers**,
13
- giving you maximum flexibility and choice when it comes to consuming web results.
14
-
15
- The tool surface is **capability-based, not static**. At startup the extension
16
- inspects which providers are available and what each one supports, then registers
17
- only the tools that make sense. If your active provider offers search and
18
- content extraction but not deep research, the agent never sees a research tool.
19
- Switch to a provider that supports it and the tool appears automatically.
20
-
21
- The extension also separates **available tools** from the **active tool set**.
22
- When a session starts, it can add every available managed tool. Before each
23
- agent run, it removes tools that are no longer available but keeps any managed
24
- tools that you explicitly removed from the active set disabled. That keeps the
25
- tool prompt aligned with the tools that the agent can actually call.
8
+ Most web extensions hard-wire a single backend. **pi-web-providers** lets you
9
+ mix and match providers per tool instead, so `web_search`, `web_contents`,
10
+ `web_answer`, and `web_research` can each use a different backend or be turned
11
+ off entirely.
26
12
 
27
13
  ## ✨ Features
28
14
 
29
- - **Provider-driven tool surface** β€” tools are injected based on what the active
30
- provider actually supports, not a fixed list
31
- - **Multiple providers**: Claude, Codex, Exa, Gemini, Perplexity, Parallel,
32
- Valyu β€” each with
33
- its own SDK, strengths, and capability set
34
- - **One config command** (`/web-providers`) with a TUI that adapts to the
35
- selected provider
36
- - **Transparent fallback** β€” search falls back to Codex when no provider is
37
- explicitly enabled and the local CLI is installed and authenticated
38
- - **Per-provider tool toggles** β€” disable individual capabilities you don't need
39
- without switching providers
40
- - **Truncated output with temp-file spillover** for large results
15
+ - **Multiple providers** β€” Claude, Codex, Custom CLI, Exa, Gemini,
16
+ Perplexity, Parallel, Valyu
17
+ - **Batched search and answers** β€” run several related queries in a single
18
+ `web_search` or `web_answer` call and get grouped results back in one response
19
+ - **Async contents prefetch** β€” optionally start background `web_contents`
20
+ extraction from `web_search` results and reuse the cached pages later
41
21
 
42
22
  ## πŸ“¦ Install
43
23
 
@@ -53,272 +33,324 @@ Run:
53
33
  /web-providers
54
34
  ```
55
35
 
56
- This command edits a single global config file:
57
- `~/.pi/agent/web-providers.json`.
36
+ This edits the global config file `~/.pi/agent/web-providers.json`. The
37
+ settings UI mirrors the three sections below: tools, providers, and generic
38
+ settings.
39
+
40
+ Each tool can be routed to any compatible provider:
41
+
42
+ | Provider | search | contents | answer | research | Auth |
43
+ | -------------- | :----: | :------: | :----: | :------: | ---------------------- |
44
+ | **Claude** | βœ” | | βœ” | | Local Claude Code auth |
45
+ | **Codex** | βœ” | | | | Local Codex CLI auth |
46
+ | **Exa** | βœ” | βœ” | βœ” | βœ” | `EXA_API_KEY` |
47
+ | **Gemini** | βœ” | | βœ” | βœ” | `GOOGLE_API_KEY` |
48
+ | **Perplexity** | βœ” | | βœ” | βœ” | `PERPLEXITY_API_KEY` |
49
+ | **Parallel** | βœ” | βœ” | | | `PARALLEL_API_KEY` |
50
+ | **Valyu** | βœ” | βœ” | βœ” | βœ” | `VALYU_API_KEY` |
51
+
52
+ Advanced option: `custom-cli` is a configurable adapter provider that can route
53
+ any managed tool through a local wrapper command using a JSON stdin/stdout
54
+ contract.
55
+
56
+ See [`example-config.json`](example-config.json) for a full default
57
+ configuration.
58
+
59
+ ### Tools
60
+
61
+ Each managed tool maps to one provider id or `null` for off under the top-level
62
+ `tools` key. A tool is only exposed when it is mapped to a compatible provider
63
+ and that provider is currently available. Tool-specific settings live under
64
+ `toolSettings`; today this covers `toolSettings.search.prefetch`.
65
+
66
+ #### `web_search`
67
+
68
+ Search the public web for up to 10 queries in one call. It returns grouped
69
+ titles, URLs, and snippets for each query.
70
+
71
+ <details>
72
+ <summary><strong>Parameters and behavior</strong></summary>
73
+
74
+ | Parameter | Type | Default | Description |
75
+ | ------------ | -------- | -------- | -------------------------------------------------------------- |
76
+ | `queries` | string[] | required | One or more search queries to run (max 10) |
77
+ | `maxResults` | integer | `5` | Result count per query, clamped to `1–20` |
78
+ | `options` | object | β€” | Provider-specific search options and local `prefetch` settings |
58
79
 
59
- The flow is provider-first: pick the active provider, then configure only that
60
- provider's tool toggles and settings. Each provider view surfaces the knobs that
61
- actually applyβ€”Claude shows model/effort/turns settings; Codex shows
62
- reasoning-effort and web-search-mode toggles; Exa shows search type and
63
- text-content flags; and so on.
80
+ `web_search.options.prefetch` is local-only and not forwarded into the provider
81
+ SDK. It accepts `provider`, `maxUrls`, `ttlMs`, and `contentsOptions`, and
82
+ starts a background page-extraction workflow only when `prefetch.provider` is
83
+ set. `/web-providers` can also persist default search prefetch settings under
84
+ `toolSettings.search.prefetch`.
64
85
 
65
- ## πŸ”§ Tools
86
+ </details>
66
87
 
67
- Which of the tools below are registered depends on the capabilities of the
68
- available providers. If no provider supports a given capability, the
69
- corresponding tool is never exposed to the agent.
88
+ #### `web_contents`
70
89
 
71
- ### `web_search`
90
+ Read the main text from one or more web pages. It reuses cached pages when they
91
+ match and fetches only missing or stale URLs.
72
92
 
73
- Find likely sources on the public web and return titles, URLs, and snippets.
93
+ <details>
94
+ <summary><strong>Parameters and behavior</strong></summary>
74
95
 
75
- | Parameter | Type | Default | Description |
76
- | ------------ | ------- | -------- | ----------------------------------------------------------------------------- |
77
- | `query` | string | required | What to search for |
78
- | `maxResults` | integer | `5` | Result count, clamped to `1–20` |
79
- | `options` | object | β€” | Provider-specific search options |
80
- | `provider` | string | auto | Optional override: `claude`, `codex`, `exa`, `gemini`, `perplexity`, `parallel`, or `valyu` |
96
+ | Parameter | Type | Default | Description |
97
+ | --------- | -------- | -------- | ------------------------------------ |
98
+ | `urls` | string[] | required | One or more URLs to extract |
99
+ | `options` | object | β€” | Provider-specific extraction options |
81
100
 
82
- ### `web_contents`
101
+ `web_contents` reuses any matching cached pages already present in the local
102
+ content storeβ€”whether they came from prefetch or an earlier readβ€”and only
103
+ fetches missing or stale URLs.
83
104
 
84
- Read and extract the main contents of one or more web pages.
105
+ </details>
85
106
 
86
- | Parameter | Type | Default | Description |
87
- | ---------- | -------- | -------- | ------------------------------------------------------- |
88
- | `urls` | string[] | required | One or more URLs to extract |
89
- | `options` | object | β€” | Provider-specific extraction options |
90
- | `provider` | string | auto | Optional override among providers that support contents |
107
+ #### `web_answer`
91
108
 
92
- ### `web_answer`
109
+ Answer one or more questions using web-grounded evidence. When you ask more
110
+ than one question, the response is grouped into per-question sections.
93
111
 
94
- Answer a question using web-grounded evidence.
112
+ <details>
113
+ <summary><strong>Parameters and behavior</strong></summary>
95
114
 
96
- | Parameter | Type | Default | Description |
97
- | ---------- | ------ | -------- | ------------------------------------------------------ |
98
- | `query` | string | required | Question to answer |
99
- | `options` | object | β€” | Provider-specific answer options |
100
- | `provider` | string | auto | Optional override among providers that support answers |
115
+ | Parameter | Type | Default | Description |
116
+ | --------- | -------- | -------- | ---------------------------------------------------- |
117
+ | `queries` | string[] | required | One or more questions to answer in one call (max 10) |
118
+ | `options` | object | β€” | Provider-specific options |
101
119
 
102
- ### `web_research`
120
+ Responses are grouped into per-question sections when more than one question is
121
+ provided.
103
122
 
104
- Investigate a topic across web sources and produce a longer report.
123
+ </details>
105
124
 
106
- | Parameter | Type | Default | Description |
107
- | ---------- | ------ | -------- | ------------------------------------------------------- |
108
- | `input` | string | required | Research brief or question |
109
- | `options` | object | β€” | Provider-specific research options |
110
- | `provider` | string | auto | Optional override among providers that support research |
125
+ #### `web_research`
111
126
 
112
- `options` are provider-native and provider-specific. Equivalent concepts can
113
- use different field names across SDKs, for example Perplexity uses `country`,
114
- Exa uses `userLocation`, and Valyu uses `countryCode`. Runtime `options`
115
- override provider defaults, but managed tool inputs and tool wiring stay fixed.
127
+ Investigate a topic across web sources and produce a longer report. The
128
+ provider-specific `options` stay native to each SDK, and runtime options
129
+ override provider configuration when both are set.
116
130
 
117
- ## πŸ”Œ Providers
131
+ <details>
132
+ <summary><strong>Parameters and behavior</strong></summary>
118
133
 
119
- Every provider is a thin adapter around an official SDK. The table below
120
- summarises which capabilities each provider exposes:
134
+ | Parameter | Type | Default | Description |
135
+ | --------- | ------ | -------- | -------------------------- |
136
+ | `input` | string | required | Research brief or question |
137
+ | `options` | object | β€” | Provider-specific options |
121
138
 
122
- | Provider | search | contents | answer | research | Auth |
123
- | ------------ | :----: | :------: | :----: | :------: | ---------------------- |
124
- | **Claude** | βœ“ | | βœ“ | | Local Claude Code auth |
125
- | **Codex** | βœ“ | | | | Local Codex CLI auth |
126
- | **Exa** | βœ“ | βœ“ | βœ“ | βœ“ | `EXA_API_KEY` |
127
- | **Gemini** | βœ“ | βœ“ | βœ“ | βœ“ | `GOOGLE_API_KEY` |
128
- | **Perplexity** | βœ“ | | βœ“ | βœ“ | `PERPLEXITY_API_KEY` |
129
- | **Parallel** | βœ“ | βœ“ | | | `PARALLEL_API_KEY` |
130
- | **Valyu** | βœ“ | βœ“ | βœ“ | βœ“ | `VALYU_API_KEY` |
139
+ `options` are provider-native and provider-specific. Equivalent concepts can use
140
+ different field names across SDKsβ€”for example Perplexity uses `country`, Exa
141
+ uses `userLocation`, and Valyu uses `countryCode`. Runtime `options` override
142
+ provider-native config, but managed tool inputs and tool wiring stay fixed.
131
143
 
132
- ### Claude
144
+ </details>
145
+
146
+ <details>
147
+ <summary><strong>Timeout, retry, and delivery modes</strong></summary>
148
+
149
+ The extension accepts local control fields for robustness: `requestTimeoutMs`,
150
+ `retryCount`, and `retryDelayMs` on request/response tools, plus
151
+ `pollIntervalMs`, `timeoutMs`, `maxConsecutivePollErrors`, and `resumeId` on
152
+ `web_research` for lifecycle-based research providers. These fields are handled
153
+ by the extension and are not forwarded into the provider SDK call.
154
+
155
+ - Exa and Valyu research support polling, overall deadlines, and resume IDs
156
+ but reject `requestTimeoutMs` and do not retry non-idempotent job creation.
157
+ - Perplexity research runs in streaming foreground mode and only supports
158
+ `requestTimeoutMs`, `retryCount`, and `retryDelayMs`.
159
+
160
+ Providers deliver results in one of three modes:
161
+
162
+ - **Silent foreground** β€” no intermediate output; result returned when done.
163
+ - **Streaming foreground** β€” progress updates while running, but the result is
164
+ still only usable after the tool finishes.
165
+ - **Background research** β€” the provider runs in the background; if
166
+ interrupted, the run can be resumed later via `resumeId`.
167
+
168
+ </details>
169
+
170
+ ### Providers
171
+
172
+ The built-in providers below are thin adapters around official SDKs.
173
+
174
+ <details>
175
+ <summary><strong>Claude</strong></summary>
133
176
 
134
177
  - SDK: `@anthropic-ai/claude-agent-sdk`
135
178
  - Uses Claude Code's built-in `WebSearch` and `WebFetch` tools behind a
136
179
  structured JSON adapter
180
+ - Runs in **silent foreground** mode
137
181
  - Supports request-shaping `options` such as `model`, `thinking`, `effort`, and
138
182
  `maxTurns`
139
183
  - Great for search plus grounded answers if you already use Claude Code locally
140
184
 
141
- ### Codex
185
+ </details>
186
+
187
+ <details>
188
+ <summary><strong>Codex</strong></summary>
142
189
 
143
190
  - SDK: `@openai/codex-sdk`
144
191
  - Runs in read-only mode with web search enabled
192
+ - Runs in **silent foreground** mode
145
193
  - Supports request-shaping `web_search.options` such as `model`,
146
194
  `modelReasoningEffort`, and `webSearchMode`
147
195
  - Best if you already use the local Codex CLI and auth flow
148
196
 
149
- ### Exa
197
+ </details>
198
+
199
+ <details>
200
+ <summary><strong>Exa</strong></summary>
150
201
 
151
202
  - SDK: `exa-js`
203
+ - Search, contents, and answer run in **silent foreground** mode
204
+ - Research runs in **background research** mode and supports `resumeId`
152
205
  - Neural, keyword, hybrid, and deep-research search modes
153
206
  - Inline text-content extraction on search results
154
207
 
155
- ### Gemini
208
+ </details>
209
+
210
+ <details>
211
+ <summary><strong>Gemini</strong></summary>
156
212
 
157
213
  - SDK: `@google/genai`
158
- - Google Search grounding for answers and URL Context extraction for page contents
214
+ - Search and answer run in **silent foreground** mode
215
+ - Research runs in **background research** mode and supports `resumeId`
216
+ - Google Search grounding for answers
159
217
  - Deep-research agents via Google's Gemini API
160
218
  - Supports provider-native request options such as `model`, `config`,
161
219
  `generation_config`, and `agent_config` depending on the tool
162
220
 
163
- ### Perplexity
221
+ </details>
222
+
223
+ <details>
224
+ <summary><strong>Perplexity</strong></summary>
164
225
 
165
226
  - SDK: `@perplexity-ai/perplexity_ai`
227
+ - `web_search` and `web_answer` run in **silent foreground** mode
228
+ - `web_research` runs in **streaming foreground** mode (no `resumeId` support)
166
229
  - Uses Perplexity Search for `web_search`
167
230
  - Uses Sonar for `web_answer` and `sonar-deep-research` for `web_research`
168
231
  - Supports provider-specific `web_search.options` such as `country`,
169
232
  `search_mode`, `search_domain_filter`, and `search_recency_filter`
170
233
 
171
- ### Parallel
234
+ </details>
235
+
236
+ <details>
237
+ <summary><strong>Parallel</strong></summary>
172
238
 
173
239
  - SDK: `parallel-web`
240
+ - Runs in **silent foreground** mode
174
241
  - Agentic and one-shot search modes
175
242
  - Page content extraction with excerpt and full-content toggles
176
243
  - Supports provider-native search and extraction options from the Parallel SDK
177
244
 
178
- ### Valyu
245
+ </details>
246
+
247
+ <details>
248
+ <summary><strong>Valyu</strong></summary>
179
249
 
180
250
  - SDK: `valyu-js`
251
+ - Search, contents, and answer run in **silent foreground** mode
252
+ - Research runs in **background research** mode and supports `resumeId`
181
253
  - Web, proprietary, and news search types
182
254
  - Supports provider-native options such as `countryCode`, `responseLength`, and
183
255
  search/source filters
184
256
  - Configurable response length for answers and research
185
257
 
186
- ## πŸ“ Config Notes
187
-
188
- - `/web-providers` keeps exactly one provider active by writing `enabled: true`
189
- for the selected provider and `enabled: false` for the others
190
- - Each provider can also enable or disable its individual tools through a `tools`
191
- block
192
- - Managed tools are registered from available provider capabilities, but the
193
- active tool set can still be narrower if you removed a tool from the session
194
- - If no provider is explicitly enabled for search, the extension falls back to
195
- Codex when the local CLI is installed and authenticated, unless Codex was
196
- explicitly configured as disabled
197
- - Tools stay inactive when no provider is available for their capability, so
198
- they are not injected into the LLM prompt
199
- - Before each agent run, the extension removes newly unavailable managed tools
200
- and keeps manually pruned managed tools inactive instead of re-adding them
201
- - Secret-like values can be:
202
- - literal strings
203
- - environment variable names such as `EXA_API_KEY`
204
- - shell commands prefixed with `!`
205
-
206
- Example:
258
+ </details>
259
+
260
+ ### Custom CLI provider
261
+
262
+ The `custom-cli` provider lets you bring your own wrapper command for any
263
+ managed tool. Each capability can point at a different local command under
264
+ `providers["custom-cli"].native`.
265
+
266
+ The repo includes actual wrapper examples under
267
+ [`examples/custom-cli/wrappers/`](examples/custom-cli/wrappers/). They are
268
+ small bash scripts that use `jq` for JSON handling. Each one uses a different
269
+ backend pattern:
270
+
271
+ - `codex --search exec` for `web_search`
272
+ - Gemini API via `curl` for `web_contents`
273
+ - `claude -p` for `web_answer`
274
+ - Perplexity API via `curl` for `web_research`
275
+
276
+ <details>
277
+ <summary><strong>Configuration example</strong></summary>
278
+
279
+ Copy the example wrappers into a local `./wrappers/` directory, then configure:
207
280
 
208
281
  ```json
209
282
  {
210
- "version": 1,
283
+ "tools": {
284
+ "search": "custom-cli",
285
+ "contents": "custom-cli",
286
+ "answer": "custom-cli",
287
+ "research": "custom-cli"
288
+ },
211
289
  "providers": {
212
- "claude": {
213
- "enabled": false,
214
- "tools": {
215
- "search": true,
216
- "answer": true
217
- }
218
- },
219
- "codex": {
290
+ "custom-cli": {
220
291
  "enabled": true,
221
- "tools": {
222
- "search": true
223
- },
224
- "defaults": {
225
- "webSearchMode": "live",
226
- "networkAccessEnabled": true
227
- }
228
- },
229
- "exa": {
230
- "enabled": false,
231
- "tools": {
232
- "search": true,
233
- "contents": true,
234
- "answer": true,
235
- "research": true
236
- },
237
- "apiKey": "EXA_API_KEY",
238
- "defaults": {
239
- "type": "auto",
240
- "contents": {
241
- "text": true
242
- }
243
- }
244
- },
245
- "gemini": {
246
- "enabled": false,
247
- "tools": {
248
- "search": true,
249
- "contents": true,
250
- "answer": true,
251
- "research": true
252
- },
253
- "apiKey": "GOOGLE_API_KEY",
254
- "defaults": {
255
- "searchModel": "gemini-2.5-flash",
256
- "contentsModel": "gemini-2.5-flash",
257
- "answerModel": "gemini-2.5-flash",
258
- "researchAgent": "deep-research-pro-preview-12-2025"
259
- }
260
- },
261
- "perplexity": {
262
- "enabled": false,
263
- "tools": {
264
- "search": true,
265
- "answer": true,
266
- "research": true
267
- },
268
- "apiKey": "PERPLEXITY_API_KEY",
269
- "defaults": {
292
+ "native": {
270
293
  "search": {
271
- "country": "US"
294
+ "argv": ["bash", "./wrappers/codex-search.sh"]
295
+ },
296
+ "contents": {
297
+ "argv": ["bash", "./wrappers/gemini-contents.sh"]
272
298
  },
273
299
  "answer": {
274
- "model": "sonar"
300
+ "argv": ["bash", "./wrappers/claude-answer.sh"]
275
301
  },
276
302
  "research": {
277
- "model": "sonar-deep-research"
278
- }
279
- }
280
- },
281
- "parallel": {
282
- "enabled": false,
283
- "tools": {
284
- "search": true,
285
- "contents": true
286
- },
287
- "apiKey": "PARALLEL_API_KEY",
288
- "defaults": {
289
- "search": {
290
- "mode": "agentic"
291
- },
292
- "extract": {
293
- "excerpts": true,
294
- "full_content": false
303
+ "argv": ["bash", "./wrappers/perplexity-research.sh"]
295
304
  }
296
305
  }
297
- },
298
- "valyu": {
299
- "enabled": false,
300
- "tools": {
301
- "search": true,
302
- "contents": true,
303
- "answer": true,
304
- "research": true
305
- },
306
- "apiKey": "VALYU_API_KEY",
307
- "defaults": {
308
- "searchType": "all",
309
- "responseLength": "short"
310
- }
311
306
  }
312
307
  }
313
308
  }
314
309
  ```
315
310
 
316
- ## πŸ› οΈ Development
317
-
318
- ```bash
319
- npm run check
320
- npm test
321
- ```
311
+ Those example wrappers deliberately use different local CLIs and APIs so you
312
+ can see several wrapper styles in one setup without extra glue code.
313
+
314
+ Each capability can also set an optional `cwd` and `env` block. Use `cwd` when
315
+ one wrapper must run from a specific directory. Use `env` for per-command
316
+ variables; each value can be a literal string, an environment variable name, or
317
+ `!command`.
318
+
319
+ `web_research` runs as a foreground wrapper command, so local polling controls
320
+ (`pollIntervalMs`, `timeoutMs`, `maxConsecutivePollErrors`) and `resumeId` do
321
+ not apply to `custom-cli`.
322
+
323
+ Wrapper contract:
324
+
325
+ - `stdin`: one JSON request object with `capability` plus the per-call managed
326
+ inputs (`query`, `urls`, `input`, `maxResults`, `options`, `cwd`)
327
+ - `stdout`: one JSON response object
328
+ - `search`: `{ "results": [{ "title", "url", "snippet" }] }`
329
+ - `contents` / `answer` / `research`: `{ "text": "...", "summary"?: "...", "itemCount"?: 1, "metadata"?: {} }`
330
+ - `stderr`: optional progress lines
331
+ - exit code `0`: success
332
+ - non-zero exit code: failure
333
+
334
+ </details>
335
+
336
+ See [`examples/custom-cli/README.md`](examples/custom-cli/README.md) for a
337
+ copy-and-pasteable setup, and see
338
+ [`examples/custom-cli/wrappers/`](examples/custom-cli/wrappers/) for the actual
339
+ wrapper files.
340
+
341
+ ### Generic settings
342
+
343
+ The `genericSettings` block sets shared execution defaults that apply to all
344
+ providers unless overridden in a provider's `policy` block:
345
+
346
+ | Field | Default | Description |
347
+ | ---------------------------------- | ---------- | ---------------------------------------------- |
348
+ | `requestTimeoutMs` | `30000` | Maximum time for a single provider request |
349
+ | `retryCount` | `3` | Retries for transient failures |
350
+ | `retryDelayMs` | `2000` | Initial delay before retrying |
351
+ | `researchPollIntervalMs` | `3000` | How often to poll long-running research jobs |
352
+ | `researchTimeoutMs` | `21600000` | Overall deadline for research before returning |
353
+ | `researchMaxConsecutivePollErrors` | `3` | Consecutive poll failures before stopping |
322
354
 
323
355
  ## πŸ“„ License
324
356