pi-web-providers 1.0.0 → 2.0.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
@@ -12,11 +12,11 @@ off entirely.
12
12
 
13
13
  ## ✨ Features
14
14
 
15
- - **Multiple providers** Claude, Codex, Exa, Gemini, Perplexity, Parallel,
16
- Valyu
17
- - **Batched search and answers** run several related queries in a single
15
+ - **Multiple providers**: Claude, Cloudflare, Codex, Exa, Firecrawl,
16
+ Gemini, Perplexity, Parallel, [Tavily](https://tavily.com), Valyu
17
+ - **Batched search and answers**: run several related queries in a single
18
18
  `web_search` or `web_answer` call and get grouped results back in one response
19
- - **Async contents prefetch** optionally start background `web_contents`
19
+ - **Async contents prefetch**: optionally start background `web_contents`
20
20
  extraction from `web_search` results and reuse the cached pages later
21
21
 
22
22
  ## 📦 Install
@@ -34,51 +34,72 @@ Run:
34
34
  ```
35
35
 
36
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.
37
+ settings UI mirrors the three sections below: tools, providers, and settings.
39
38
 
40
39
  Each tool can be routed to any compatible provider:
41
40
 
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
- See [`example-config.json`](example-config.json) for a full default
41
+ | Provider | search | contents | answer | research | Auth |
42
+ | -------------- | :----: | :------: | :----: | :------: | ------------------------------------------------ |
43
+ | **Claude** | ✔ | | ✔ | | Local Claude Code auth |
44
+ | **Cloudflare** | | | | | `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` |
45
+ | **Codex** | ✔ | | | | Local Codex CLI auth |
46
+ | **Exa** | ✔ || ✔ | ✔ | `EXA_API_KEY` |
47
+ | **Firecrawl** | ✔ | ✔ | | | `FIRECRAWL_API_KEY` |
48
+ | **Gemini** | | | ✔ | ✔ | `GOOGLE_API_KEY` |
49
+ | **Perplexity** | ✔ | | ✔ | ✔ | `PERPLEXITY_API_KEY` |
50
+ | **Parallel** | ✔ | ✔ | | | `PARALLEL_API_KEY` |
51
+ | **Tavily** | ✔ | ✔ | | | `TAVILY_API_KEY` |
52
+ | **Valyu** | ✔ | ✔ | ✔ | ✔ | `VALYU_API_KEY` |
53
+
54
+ Advanced option: `custom` is a configurable adapter provider that can route
55
+ any managed tool through a local wrapper command using a JSON stdin/stdout
56
+ contract.
57
+
58
+ See [`example-config.json`](example-config.json) for the minimal default
53
59
  configuration.
54
60
 
55
61
  ### Tools
56
62
 
57
- Each managed tool maps to one provider id or `null` for off under the top-level
58
- `tools` key. A tool is only exposed when it is mapped to a compatible provider
59
- and that provider is currently available. Tool-specific settings live under
60
- `toolSettings`; today this covers `toolSettings.search.prefetch`.
63
+ Each managed tool maps to one provider id under the top-level `tools` key.
64
+ Removing a tool mapping turns that tool off. A tool is only exposed when it is
65
+ mapped to a compatible provider and that provider is currently available.
66
+ Shared defaults and tool-specific settings live under `settings`; search-specific
67
+ settings live under `settings.search`, and async research uses
68
+ `settings.researchTimeoutMs`.
61
69
 
62
70
  #### `web_search`
63
71
 
64
- Find likely sources on the public web for up to 10 queries in a single call
65
- and return titles, URLs, and snippets grouped by query.
72
+ Search the public web for up to 10 queries in one call. It returns grouped
73
+ titles, URLs, and snippets for each query. Batch related queries when grouped
74
+ comparison matters; use separate sibling `web_search` calls when independent
75
+ results should arrive as soon as they are ready.
66
76
 
67
- | Parameter | Type | Default | Description |
68
- | ------------ | -------- | -------- | -------------------------------------------------------------------- |
69
- | `queries` | string[] | required | One or more search queries to run (max 10) |
70
- | `maxResults` | integer | `5` | Result count per query, clamped to `1–20` |
71
- | `options` | object | | Provider-specific search options plus local `prefetch` orchestration |
77
+ <details>
78
+ <summary><strong>Parameters and behavior</strong></summary>
79
+
80
+ | Parameter | Type | Default | Description |
81
+ | ------------ | -------- | -------- | -------------------------------------------------------------- |
82
+ | `queries` | string[] | required | One or more search queries to run (max 10) |
83
+ | `maxResults` | integer | `5` | Result count per query, clamped to `1–20` |
84
+ | `options` | object | — | Provider-specific search options and local `prefetch` settings |
72
85
 
73
86
  `web_search.options.prefetch` is local-only and not forwarded into the provider
74
87
  SDK. It accepts `provider`, `maxUrls`, `ttlMs`, and `contentsOptions`, and
75
88
  starts a background page-extraction workflow only when `prefetch.provider` is
76
89
  set. `/web-providers` can also persist default search prefetch settings under
77
- `toolSettings.search.prefetch`.
90
+ `settings.search`.
91
+
92
+ </details>
78
93
 
79
94
  #### `web_contents`
80
95
 
81
- Read and extract the main contents of one or more web pages.
96
+ Read the main text from one or more web pages. It reuses cached pages when they
97
+ match and fetches only missing or stale URLs. Batch related pages when they are
98
+ meant to be read as one bundle; use separate sibling `web_contents` calls when
99
+ each page can be acted on independently.
100
+
101
+ <details>
102
+ <summary><strong>Parameters and behavior</strong></summary>
82
103
 
83
104
  | Parameter | Type | Default | Description |
84
105
  | --------- | -------- | -------- | ------------------------------------ |
@@ -86,67 +107,61 @@ Read and extract the main contents of one or more web pages.
86
107
  | `options` | object | — | Provider-specific extraction options |
87
108
 
88
109
  `web_contents` reuses any matching cached pages already present in the local
89
- content store—whether they came from prefetch or an earlier read—and only
110
+ in-memory cache—whether they came from prefetch or an earlier read—and only
90
111
  fetches missing or stale URLs.
91
112
 
113
+ </details>
114
+
92
115
  #### `web_answer`
93
116
 
94
- Answer one or more questions using web-grounded evidence.
117
+ Answer one or more questions using web-grounded evidence. When you ask more
118
+ than one question, the response is grouped into per-question sections. Batch
119
+ related questions when the answers belong together; split them into sibling
120
+ calls when earlier independent answers can unblock the next step.
121
+
122
+ <details>
123
+ <summary><strong>Parameters and behavior</strong></summary>
95
124
 
96
125
  | Parameter | Type | Default | Description |
97
126
  | --------- | -------- | -------- | ---------------------------------------------------- |
98
127
  | `queries` | string[] | required | One or more questions to answer in one call (max 10) |
99
128
  | `options` | object | — | Provider-specific options |
100
129
 
101
- Responses are grouped into per-question sections when more than one question is provided.
130
+ Responses are grouped into per-question sections when more than one question is
131
+ provided.
132
+
133
+ </details>
102
134
 
103
135
  #### `web_research`
104
136
 
105
137
  Investigate a topic across web sources and produce a longer report.
138
+ `web_research` is always asynchronous: it starts a background run, returns a
139
+ short dispatch notice immediately, and later posts a completion message with a
140
+ saved report path.
141
+
142
+ <details>
143
+ <summary><strong>Parameters and behavior</strong></summary>
106
144
 
107
145
  | Parameter | Type | Default | Description |
108
146
  | --------- | ------ | -------- | -------------------------- |
109
147
  | `input` | string | required | Research brief or question |
110
148
  | `options` | object | — | Provider-specific options |
111
149
 
112
- `options` are provider-native and provider-specific. Equivalent concepts can use
150
+ `options` are provider-specific. Equivalent concepts can use
113
151
  different field names across SDKs—for example Perplexity uses `country`, Exa
114
152
  uses `userLocation`, and Valyu uses `countryCode`. Runtime `options` override
115
- provider-native config, but managed tool inputs and tool wiring stay fixed.
153
+ provider config, but managed tool inputs and tool wiring stay fixed.
116
154
 
117
- <details>
118
- <summary><strong>Timeout, retry, and delivery modes</strong></summary>
119
-
120
- The extension accepts local control fields for robustness: `requestTimeoutMs`,
121
- `retryCount`, and `retryDelayMs` on request/response tools, plus
122
- `pollIntervalMs`, `timeoutMs`, `maxConsecutivePollErrors`, and `resumeId` on
123
- `web_research` for lifecycle-based research providers. These fields are handled
124
- by the extension and are not forwarded into the provider SDK call.
125
-
126
- - Exa and Valyu research support polling, overall deadlines, and resume IDs
127
- but reject `requestTimeoutMs` and do not retry non-idempotent job creation.
128
- - Perplexity research runs in streaming foreground mode and only supports
129
- `requestTimeoutMs`, `retryCount`, and `retryDelayMs`.
130
-
131
- Providers deliver results in one of three modes:
132
-
133
- - **Silent foreground** — no intermediate output; result returned when done.
134
- - **Streaming foreground** — progress updates while running, but the result is
135
- still only usable after the tool finishes.
136
- - **Background research** — the provider runs in the background; if
137
- interrupted, the run can be resumed later via `resumeId`.
155
+ Unlike the other managed tools, `web_research` does not accept local timeout,
156
+ retry, polling, or resume controls. Research has one opinionated execution
157
+ style: pi starts it asynchronously, tracks it locally, and saves the final
158
+ report under `.pi/artifacts/research/`.
138
159
 
139
160
  </details>
140
161
 
141
162
  ### Providers
142
163
 
143
- Every provider is a thin adapter around an official SDK. Each provider has an
144
- `enabled` toggle that controls whether it is eligible for tool mappings.
145
- Provider config is split into `native` settings (forwarded to the SDK) and
146
- `policy` settings (local overrides that take precedence over generic settings);
147
- legacy `defaults` blocks are still accepted when reading. Secret-like values
148
- can be literal strings, environment variable names (e.g., `EXA_API_KEY`), or
149
- shell commands prefixed with `!`.
164
+ The built-in providers below are thin adapters around official SDKs.
150
165
 
151
166
  <details>
152
167
  <summary><strong>Claude</strong></summary>
@@ -154,19 +169,56 @@ shell commands prefixed with `!`.
154
169
  - SDK: `@anthropic-ai/claude-agent-sdk`
155
170
  - Uses Claude Code's built-in `WebSearch` and `WebFetch` tools behind a
156
171
  structured JSON adapter
157
- - Runs in **silent foreground** mode
158
172
  - Supports request-shaping `options` such as `model`, `thinking`, `effort`, and
159
173
  `maxTurns`
160
174
  - Great for search plus grounded answers if you already use Claude Code locally
161
175
 
162
176
  </details>
163
177
 
178
+ <details>
179
+ <summary><strong>Cloudflare</strong></summary>
180
+
181
+ - SDK: `cloudflare`
182
+ - Supports `web_contents` via Cloudflare Browser Rendering's `/markdown`
183
+ endpoint
184
+ - Good for JavaScript-heavy pages that need a real browser render before
185
+ extraction
186
+ - Supports provider-specific markdown options such as `gotoOptions`,
187
+ `waitForSelector`, `waitForTimeout`, `cacheTTL`, and request filtering
188
+
189
+ **Setup**
190
+
191
+ 1. In the Cloudflare dashboard, create an API token.
192
+ 2. Grant it this permission:
193
+ - `Account | Browser Rendering | Edit`
194
+ 3. Scope it to the account you want to use.
195
+ 4. Copy that account's **Account ID** from the Cloudflare dashboard.
196
+ 5. Configure pi with both values:
197
+
198
+ ```json
199
+ {
200
+ "tools": {
201
+ "contents": "cloudflare"
202
+ },
203
+ "providers": {
204
+ "cloudflare": {
205
+ "apiToken": "CLOUDFLARE_API_TOKEN",
206
+ "accountId": "CLOUDFLARE_ACCOUNT_ID"
207
+ }
208
+ }
209
+ }
210
+ ```
211
+
212
+ If Cloudflare returns `401 Authentication error`, the token permission, token
213
+ scope, or account ID is usually wrong.
214
+
215
+ </details>
216
+
164
217
  <details>
165
218
  <summary><strong>Codex</strong></summary>
166
219
 
167
220
  - SDK: `@openai/codex-sdk`
168
221
  - Runs in read-only mode with web search enabled
169
- - Runs in **silent foreground** mode
170
222
  - Supports request-shaping `web_search.options` such as `model`,
171
223
  `modelReasoningEffort`, and `webSearchMode`
172
224
  - Best if you already use the local Codex CLI and auth flow
@@ -177,22 +229,37 @@ shell commands prefixed with `!`.
177
229
  <summary><strong>Exa</strong></summary>
178
230
 
179
231
  - SDK: `exa-js`
180
- - Search, contents, and answer run in **silent foreground** mode
181
- - Research runs in **background research** mode and supports `resumeId`
232
+ - Supports `web_search`, `web_contents`, `web_answer`, and `web_research`
233
+ - `web_research` is exposed through pi's async research workflow
182
234
  - Neural, keyword, hybrid, and deep-research search modes
183
235
  - Inline text-content extraction on search results
184
236
 
185
237
  </details>
186
238
 
239
+ <details>
240
+ <summary><strong>Firecrawl</strong></summary>
241
+
242
+ - SDK: `@mendable/firecrawl-js`
243
+ - Supports `web_search` and `web_contents`
244
+ - Search can optionally include Firecrawl scrape-backed result enrichment
245
+ - Contents extraction uses Firecrawl scrape with markdown-first defaults
246
+ - Supports provider-specific `options.search` such as `sources`, `categories`,
247
+ `location`, `timeout`, and `scrapeOptions`
248
+ - Supports provider-specific `options.scrape` such as `formats`,
249
+ `onlyMainContent`, `waitFor`, `headers`, `location`, `mobile`, `proxy`, and
250
+ cache controls
251
+
252
+ </details>
253
+
187
254
  <details>
188
255
  <summary><strong>Gemini</strong></summary>
189
256
 
190
257
  - SDK: `@google/genai`
191
- - Search and answer run in **silent foreground** mode
192
- - Research runs in **background research** mode and supports `resumeId`
258
+ - Supports `web_search`, `web_answer`, and `web_research`
259
+ - `web_research` is exposed through pi's async research workflow
193
260
  - Google Search grounding for answers
194
261
  - Deep-research agents via Google's Gemini API
195
- - Supports provider-native request options such as `model`, `config`,
262
+ - Supports provider-specific request options such as `model`, `config`,
196
263
  `generation_config`, and `agent_config` depending on the tool
197
264
 
198
265
  </details>
@@ -201,8 +268,8 @@ shell commands prefixed with `!`.
201
268
  <summary><strong>Perplexity</strong></summary>
202
269
 
203
270
  - SDK: `@perplexity-ai/perplexity_ai`
204
- - `web_search` and `web_answer` run in **silent foreground** mode
205
- - `web_research` runs in **streaming foreground** mode (no `resumeId` support)
271
+ - Supports `web_search`, `web_answer`, and `web_research`
272
+ - `web_research` is exposed through pi's async research workflow
206
273
  - Uses Perplexity Search for `web_search`
207
274
  - Uses Sonar for `web_answer` and `sonar-deep-research` for `web_research`
208
275
  - Supports provider-specific `web_search.options` such as `country`,
@@ -214,10 +281,25 @@ shell commands prefixed with `!`.
214
281
  <summary><strong>Parallel</strong></summary>
215
282
 
216
283
  - SDK: `parallel-web`
217
- - Runs in **silent foreground** mode
218
284
  - Agentic and one-shot search modes
219
285
  - Page content extraction with excerpt and full-content toggles
220
- - Supports provider-native search and extraction options from the Parallel SDK
286
+ - Supports provider-specific search and extraction options from the Parallel SDK
287
+
288
+ </details>
289
+
290
+ <details>
291
+ <summary><strong>Tavily</strong></summary>
292
+
293
+ - SDK: `@tavily/core`
294
+ - Supports `web_search` via Tavily Search
295
+ - Supports `web_contents` via Tavily Extract
296
+ - Good for pairing LLM-oriented web search with lightweight page extraction
297
+ - Supports provider-specific `options.search` such as `searchDepth`, `topic`,
298
+ `timeRange`, `includeRawContent`, `includeDomains`, `excludeDomains`,
299
+ `country`, `exactMatch`, and `includeFavicon`
300
+ - Supports provider-specific `options.extract` such as `extractDepth`,
301
+ `format`, `includeImages`, `query`, `chunksPerSource`, and
302
+ `includeFavicon`
221
303
 
222
304
  </details>
223
305
 
@@ -225,36 +307,129 @@ shell commands prefixed with `!`.
225
307
  <summary><strong>Valyu</strong></summary>
226
308
 
227
309
  - SDK: `valyu-js`
228
- - Search, contents, and answer run in **silent foreground** mode
229
- - Research runs in **background research** mode and supports `resumeId`
310
+ - Supports `web_search`, `web_contents`, `web_answer`, and `web_research`
311
+ - `web_research` is exposed through pi's async research workflow
230
312
  - Web, proprietary, and news search types
231
- - Supports provider-native options such as `countryCode`, `responseLength`, and
313
+ - Supports provider-specific options such as `countryCode`, `responseLength`, and
232
314
  search/source filters
233
315
  - Configurable response length for answers and research
234
316
 
235
317
  </details>
236
318
 
237
- ### Generic settings
319
+ ### Custom provider
320
+
321
+ The `custom` provider lets you bring your own wrapper command for any
322
+ managed tool. Each capability can point at a different local command under
323
+ `providers["custom"].options`.
324
+
325
+ The repo includes actual wrapper examples under
326
+ [`examples/custom/wrappers/`](examples/custom/wrappers/). They are
327
+ small bash scripts that use `jq` for JSON handling. Each one uses a different
328
+ backend pattern:
329
+
330
+ - `codex --search exec` for `web_search`
331
+ - Gemini API via `curl` for `web_contents`
332
+ - `claude -p` for `web_answer`
333
+ - Perplexity API via `curl` for `web_research`
334
+
335
+ <details>
336
+ <summary><strong>Configuration example</strong></summary>
337
+
338
+ Copy the example wrappers into a local `./wrappers/` directory, then configure:
339
+
340
+ ```json
341
+ {
342
+ "tools": {
343
+ "search": "custom",
344
+ "contents": "custom",
345
+ "answer": "custom",
346
+ "research": "custom"
347
+ },
348
+ "providers": {
349
+ "custom": {
350
+ "options": {
351
+ "search": {
352
+ "argv": ["bash", "./wrappers/codex-search.sh"]
353
+ },
354
+ "contents": {
355
+ "argv": ["bash", "./wrappers/gemini-contents.sh"]
356
+ },
357
+ "answer": {
358
+ "argv": ["bash", "./wrappers/claude-answer.sh"]
359
+ },
360
+ "research": {
361
+ "argv": ["bash", "./wrappers/perplexity-research.sh"]
362
+ }
363
+ }
364
+ }
365
+ }
366
+ }
367
+ ```
368
+
369
+ Those example wrappers deliberately use different local CLIs and APIs so you
370
+ can see several wrapper styles in one setup without extra glue code.
371
+
372
+ Each capability can also set an optional `cwd` and `env` block. Use `cwd` when
373
+ one wrapper must run from a specific directory. Use `env` for per-command
374
+ variables; each value can be a literal string, an environment variable name, or
375
+ `!command`.
238
376
 
239
- The `genericSettings` block sets shared execution defaults that apply to all
240
- providers unless overridden in a provider's `policy` block:
377
+ `web_research` uses the same async workflow as every other research provider:
378
+ pi starts the wrapper in the background, tracks the job locally, and writes the
379
+ final report to a file when it finishes.
241
380
 
242
- | Field | Default | Description |
243
- | ---------------------------------- | ---------- | ---------------------------------------------- |
244
- | `requestTimeoutMs` | `30000` | Maximum time for a single provider request |
245
- | `retryCount` | `3` | Retries for transient failures |
246
- | `retryDelayMs` | `2000` | Initial delay before retrying |
247
- | `researchPollIntervalMs` | `3000` | How often to poll long-running research jobs |
248
- | `researchTimeoutMs` | `21600000` | Overall deadline for research before returning |
249
- | `researchMaxConsecutivePollErrors` | `3` | Consecutive poll failures before stopping |
381
+ Wrapper contract:
250
382
 
251
- ## 🛠️ Development
383
+ - `stdin`: one JSON request object with `capability` plus the per-call managed
384
+ inputs (`query`, `urls`, `input`, `maxResults`, `options`, `cwd`)
385
+ - `stdout`: one JSON response object
386
+ - `search`: `{ "results": [{ "title", "url", "snippet" }] }`
387
+ - `contents`: `{ "answers": [{ "url", "content"?: "...", "summary"?: unknown, "metadata"?: {}, "error"?: "..." }] }`
388
+ - `answer` / `research`: `{ "text": "...", "summary"?: "...", "itemCount"?: 1, "metadata"?: {} }`
389
+ - `stderr`: optional progress lines
390
+ - exit code `0`: success
391
+ - non-zero exit code: failure
392
+
393
+ </details>
394
+
395
+ See [`examples/custom/README.md`](examples/custom/README.md) for a
396
+ copy-and-pasteable setup, and see
397
+ [`examples/custom/wrappers/`](examples/custom/wrappers/) for the actual
398
+ wrapper files.
399
+
400
+ ### Settings
401
+
402
+ The `settings` block holds shared execution defaults that apply to all
403
+ providers unless overridden in a provider's own `settings` block:
404
+
405
+ | Field | Default | Description |
406
+ | ------------------- | --------- | ----------------------------------------------------------- |
407
+ | `requestTimeoutMs` | `30000` | Maximum time for a single provider request |
408
+ | `retryCount` | `3` | Retries for transient failures |
409
+ | `retryDelayMs` | `2000` | Initial delay before retrying |
410
+ | `researchTimeoutMs` | `1800000` | Maximum total time for an async `web_research` job (30 min) |
411
+
412
+ ## 🔎 Live smoke tests
413
+
414
+ Use the opt-in live smoke runner to validate the configured providers with the
415
+ same config-resolution and execution path the extension uses at runtime:
416
+
417
+ ```bash
418
+ npm run smoke:live
419
+ ```
420
+
421
+ Optional filters:
252
422
 
253
423
  ```bash
254
- npm run check
255
- npm test
424
+ npm run smoke:live -- --provider gemini
425
+ npm run smoke:live -- --tool contents
426
+ npm run smoke:live -- --include-research
256
427
  ```
257
428
 
429
+ The default run exercises `search`, `contents`, and `answer`. Research probes
430
+ are excluded unless you pass `--include-research`, because they are slower and
431
+ may incur higher provider cost.
432
+
258
433
  ## 📄 License
259
434
 
260
435
  [MIT](LICENSE)