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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "tools": {
3
+ "search": "codex"
4
+ }
5
+ }
@@ -0,0 +1,224 @@
1
+ # Custom wrapper examples
2
+
3
+ These examples keep the wrapper logic small. They are bash scripts that use
4
+ `jq` for JSON handling. Each wrapper uses a different backend pattern:
5
+
6
+ - `wrappers/codex-search.sh` — `codex --search exec`
7
+ - `wrappers/gemini-contents.sh` — Gemini API via `curl`
8
+ - `wrappers/claude-answer.sh` — `claude -p`
9
+ - `wrappers/perplexity-research.sh` — Perplexity API via `curl`
10
+
11
+ Each wrapper:
12
+
13
+ - reads one JSON request from `stdin`
14
+ - writes one JSON response to `stdout`
15
+ - may write progress text to `stderr`
16
+
17
+ ## Requirements
18
+
19
+ You need:
20
+
21
+ - `bash`
22
+ - `jq`
23
+ - `curl`
24
+ - `codex` on your `PATH` and authenticated locally
25
+ - `claude` on your `PATH` and authenticated locally
26
+ - `GOOGLE_API_KEY` for the Gemini example
27
+ - `PERPLEXITY_API_KEY` for the Perplexity example
28
+
29
+ ## Copy the wrappers into your project
30
+
31
+ ```bash
32
+ mkdir -p ./wrappers
33
+ cp examples/custom/wrappers/codex-search.sh ./wrappers/
34
+ cp examples/custom/wrappers/gemini-contents.sh ./wrappers/
35
+ cp examples/custom/wrappers/claude-answer.sh ./wrappers/
36
+ cp examples/custom/wrappers/perplexity-research.sh ./wrappers/
37
+ chmod +x ./wrappers/*.sh
38
+ ```
39
+
40
+ Then configure `custom` like this:
41
+
42
+ ```json
43
+ {
44
+ "tools": {
45
+ "search": "custom",
46
+ "contents": "custom",
47
+ "answer": "custom",
48
+ "research": "custom"
49
+ },
50
+ "providers": {
51
+ "custom": {
52
+ "enabled": true,
53
+ "options": {
54
+ "search": {
55
+ "argv": ["bash", "./wrappers/codex-search.sh"]
56
+ },
57
+ "contents": {
58
+ "argv": ["bash", "./wrappers/gemini-contents.sh"]
59
+ },
60
+ "answer": {
61
+ "argv": ["bash", "./wrappers/claude-answer.sh"]
62
+ },
63
+ "research": {
64
+ "argv": ["bash", "./wrappers/perplexity-research.sh"]
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ `web_research` uses the same async workflow as every other research provider:
73
+ pi starts the wrapper in the background, tracks the job locally, and writes the
74
+ final report to a file when it finishes.
75
+
76
+ ## Core command shapes
77
+
78
+ ### Search with Codex
79
+
80
+ ```bash
81
+ codex --search exec \
82
+ --skip-git-repo-check \
83
+ --sandbox read-only \
84
+ --output-schema ./schema.json \
85
+ "Search the public web and return JSON only"
86
+ ```
87
+
88
+ ### Contents with Gemini and `curl`
89
+
90
+ ```bash
91
+ curl -sS -X POST \
92
+ "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=$GOOGLE_API_KEY" \
93
+ -H "Content-Type: application/json" \
94
+ -d '{
95
+ "contents": [{"parts": [{"text": "Extract the main content from https://example.com and return JSON only"}]}],
96
+ "tools": [{"urlContext": {}}],
97
+ "generationConfig": {"responseMimeType": "application/json"}
98
+ }'
99
+ ```
100
+
101
+ ### Answers with Claude
102
+
103
+ ```bash
104
+ claude -p \
105
+ --output-format json \
106
+ --json-schema "$schema" \
107
+ --permission-mode dontAsk \
108
+ --allowedTools "WebSearch,WebFetch" \
109
+ "Answer this question using current public web information"
110
+ ```
111
+
112
+ ### Research with Perplexity and `curl`
113
+
114
+ ```bash
115
+ curl -sS https://api.perplexity.ai/chat/completions \
116
+ -H "Authorization: Bearer $PERPLEXITY_API_KEY" \
117
+ -H "Content-Type: application/json" \
118
+ -d '{
119
+ "model": "sonar-deep-research",
120
+ "stream": false,
121
+ "messages": [{"role": "user", "content": "Research this topic and return a long-form answer"}]
122
+ }'
123
+ ```
124
+
125
+ ## Try a wrapper directly
126
+
127
+ ### Search
128
+
129
+ ```bash
130
+ printf '%s' '{
131
+ "capability": "search",
132
+ "query": "latest Codex CLI release notes",
133
+ "maxResults": 5,
134
+ "options": {},
135
+ "cwd": "'"$PWD"'"
136
+ }' | bash examples/custom/wrappers/codex-search.sh
137
+ ```
138
+
139
+ ### Contents
140
+
141
+ ```bash
142
+ printf '%s' '{
143
+ "capability": "contents",
144
+ "urls": ["https://example.com"],
145
+ "options": {},
146
+ "cwd": "'"$PWD"'"
147
+ }' | bash examples/custom/wrappers/gemini-contents.sh
148
+ ```
149
+
150
+ ### Answer
151
+
152
+ ```bash
153
+ printf '%s' '{
154
+ "capability": "answer",
155
+ "query": "What changed in the latest Claude Code release?",
156
+ "options": {},
157
+ "cwd": "'"$PWD"'"
158
+ }' | bash examples/custom/wrappers/claude-answer.sh
159
+ ```
160
+
161
+ ### Research
162
+
163
+ ```bash
164
+ printf '%s' '{
165
+ "capability": "research",
166
+ "input": "Compare current local agent CLIs for web-grounded tasks.",
167
+ "options": {},
168
+ "cwd": "'"$PWD"'"
169
+ }' | bash examples/custom/wrappers/perplexity-research.sh
170
+ ```
171
+
172
+ ## Request and response contract
173
+
174
+ ### Search request
175
+
176
+ ```json
177
+ {
178
+ "capability": "search",
179
+ "query": "latest Codex CLI release notes",
180
+ "maxResults": 5,
181
+ "options": {},
182
+ "cwd": "/path/to/project"
183
+ }
184
+ ```
185
+
186
+ ### Search response
187
+
188
+ ```json
189
+ {
190
+ "results": [
191
+ {
192
+ "title": "Codex CLI docs",
193
+ "url": "https://github.com/openai/codex",
194
+ "snippet": "CLI docs, examples, and release information."
195
+ }
196
+ ]
197
+ }
198
+ ```
199
+
200
+ ### Contents response
201
+
202
+ ```json
203
+ {
204
+ "answers": [
205
+ {
206
+ "url": "https://example.com",
207
+ "content": "# Example\n\nMain page content",
208
+ "summary": "Optional short summary",
209
+ "metadata": {}
210
+ }
211
+ ]
212
+ }
213
+ ```
214
+
215
+ ### Answer and research response
216
+
217
+ ```json
218
+ {
219
+ "text": "Rendered tool output",
220
+ "summary": "Optional short summary",
221
+ "itemCount": 1,
222
+ "metadata": {}
223
+ }
224
+ ```
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ request="$(cat)"
5
+ cwd="$(jq -r '.cwd // "."' <<<"$request")"
6
+ query="$(jq -r '.query' <<<"$request")"
7
+ model="$(jq -r '.options.model // empty' <<<"$request")"
8
+
9
+ schema='{"type":"object","properties":{"text":{"type":"string"},"summary":{"type":"string"},"itemCount":{"type":"integer"},"metadata":{"type":"object"}},"required":["text","summary","itemCount","metadata"],"additionalProperties":false}'
10
+ prompt="$(
11
+ cat <<EOF
12
+ Answer this question using current public web information:
13
+ $query
14
+
15
+ Return JSON only with these fields:
16
+ - text: the full grounded answer
17
+ - summary: a one-sentence summary
18
+ - itemCount: use 1
19
+ - metadata: include a short note such as the task type
20
+
21
+ Use WebSearch and WebFetch when needed.
22
+ EOF
23
+ )"
24
+
25
+ args=(
26
+ -p
27
+ --output-format json
28
+ --json-schema "$schema"
29
+ --permission-mode dontAsk
30
+ --allowedTools "WebSearch,WebFetch"
31
+ --no-session-persistence
32
+ )
33
+
34
+ if [[ -n "$model" ]]; then
35
+ args+=(--model "$model")
36
+ fi
37
+
38
+ echo "Answering with Claude..." >&2
39
+ (
40
+ cd "$cwd"
41
+ claude "${args[@]}" "$prompt"
42
+ )
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ request="$(cat)"
5
+ cwd="$(jq -r '.cwd // "."' <<<"$request")"
6
+ query="$(jq -r '.query' <<<"$request")"
7
+ max_results="$(jq -r '.maxResults // 5' <<<"$request")"
8
+ model="$(jq -r '.options.model // empty' <<<"$request")"
9
+
10
+ schema_file="$(mktemp)"
11
+ output_file="$(mktemp)"
12
+ trap 'rm -f "$schema_file" "$output_file"' EXIT
13
+
14
+ cat >"$schema_file" <<'JSON'
15
+ {
16
+ "type": "object",
17
+ "properties": {
18
+ "results": {
19
+ "type": "array",
20
+ "items": {
21
+ "type": "object",
22
+ "properties": {
23
+ "title": { "type": "string" },
24
+ "url": { "type": "string" },
25
+ "snippet": { "type": "string" }
26
+ },
27
+ "required": ["title", "url", "snippet"],
28
+ "additionalProperties": false
29
+ }
30
+ }
31
+ },
32
+ "required": ["results"],
33
+ "additionalProperties": false
34
+ }
35
+ JSON
36
+
37
+ prompt="$(
38
+ cat <<EOF
39
+ Search the public web for: $query
40
+
41
+ Return JSON only.
42
+ Return at most $max_results results.
43
+ Each result must include:
44
+ - title
45
+ - url
46
+ - snippet
47
+
48
+ Prefer primary or official sources when possible.
49
+ EOF
50
+ )"
51
+
52
+ args=(
53
+ --search exec
54
+ --skip-git-repo-check
55
+ --sandbox read-only
56
+ --color never
57
+ --cd "$cwd"
58
+ --output-schema "$schema_file"
59
+ --output-last-message "$output_file"
60
+ )
61
+
62
+ if [[ -n "$model" ]]; then
63
+ args+=(--model "$model")
64
+ fi
65
+
66
+ echo "Searching with Codex..." >&2
67
+ codex "${args[@]}" "$prompt" >/dev/null
68
+ jq . "$output_file"
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ : "${GOOGLE_API_KEY:?GOOGLE_API_KEY is required}"
5
+
6
+ request="$(cat)"
7
+ model="$(jq -r '.options.model // "gemini-2.5-flash"' <<<"$request")"
8
+ url_count="$(jq '.urls | length' <<<"$request")"
9
+ urls="$(jq -r '.urls[]' <<<"$request")"
10
+
11
+ prompt="$(
12
+ cat <<EOF
13
+ Extract the main textual content from these URLs:
14
+ $urls
15
+
16
+ Return JSON only with this shape:
17
+ - answers: an array with exactly $url_count items
18
+ - each answer must include the input url, and either:
19
+ - content: the extracted text as a string
20
+ - or error: a short error string when extraction fails
21
+ - optionally include summary and metadata
22
+ EOF
23
+ )"
24
+
25
+ body="$(
26
+ jq -n \
27
+ --arg prompt "$prompt" \
28
+ '{
29
+ contents: [{parts: [{text: $prompt}]}],
30
+ tools: [{urlContext: {}}],
31
+ generationConfig: {responseMimeType: "application/json"}
32
+ }'
33
+ )"
34
+
35
+ echo "Fetching contents with Gemini..." >&2
36
+ response="$(curl -sS -X POST \
37
+ "https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GOOGLE_API_KEY}" \
38
+ -H "Content-Type: application/json" \
39
+ -d "$body")"
40
+
41
+ error="$(jq -r '.error.message // empty' <<<"$response")"
42
+ if [[ -n "$error" ]]; then
43
+ echo "$error" >&2
44
+ exit 1
45
+ fi
46
+
47
+ text="$(jq -r '[.candidates[]?.content.parts[]?.text // empty] | join("\n")' <<<"$response")"
48
+ json_text="$(printf '%s\n' "$text" | sed -e '1s/^```json[[:space:]]*//' -e '1s/^```[[:space:]]*//' -e '$s/```$//')"
49
+ jq . <<<"$json_text"
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ : "${PERPLEXITY_API_KEY:?PERPLEXITY_API_KEY is required}"
5
+
6
+ request="$(cat)"
7
+ input="$(jq -r '.input' <<<"$request")"
8
+ model="$(jq -r '.options.model // "sonar-deep-research"' <<<"$request")"
9
+
10
+ body="$(
11
+ jq -n \
12
+ --arg model "$model" \
13
+ --arg input "$input" \
14
+ '{
15
+ model: $model,
16
+ stream: false,
17
+ messages: [{role: "user", content: $input}]
18
+ }'
19
+ )"
20
+
21
+ echo "Researching with Perplexity..." >&2
22
+ response="$(curl -sS https://api.perplexity.ai/chat/completions \
23
+ -H "Authorization: Bearer ${PERPLEXITY_API_KEY}" \
24
+ -H "Content-Type: application/json" \
25
+ -d "$body")"
26
+
27
+ error="$(jq -r '.error.message // empty' <<<"$response")"
28
+ if [[ -n "$error" ]]; then
29
+ echo "$error" >&2
30
+ exit 1
31
+ fi
32
+
33
+ citations="$(jq '.citations // []' <<<"$response")"
34
+ count="$(jq '(.citations // []) | length' <<<"$response")"
35
+ text="$(jq -r '
36
+ (.choices[0].message.content // "No research returned.") as $text
37
+ | (.citations // []) as $citations
38
+ | if ($citations | length) == 0 then
39
+ $text
40
+ else
41
+ $text + "\n\nSources:\n" + ($citations | to_entries | map("\(.key + 1). \(.value)") | join("\n"))
42
+ end
43
+ ' <<<"$response")"
44
+
45
+ jq -n \
46
+ --arg text "$text" \
47
+ --arg summary "Research via Perplexity with $count source(s)" \
48
+ --argjson itemCount "$count" \
49
+ --argjson citations "$citations" \
50
+ '{
51
+ text: $text,
52
+ summary: $summary,
53
+ itemCount: $itemCount,
54
+ metadata: {citations: $citations}
55
+ }'
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "pi-web-providers",
3
- "version": "1.0.0",
4
- "description": "Configurable web access extension for pi with per-tool provider routing for search, contents, answers, and research across Claude, Codex, Exa, Gemini, Perplexity, Parallel, and Valyu.",
3
+ "version": "2.0.0",
4
+ "description": "Configurable web access extension for pi with per-tool provider routing for search, contents, answers, and research.",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
8
8
  "README.md",
9
- "LICENSE"
9
+ "LICENSE",
10
+ "example-config.json",
11
+ "examples"
10
12
  ],
11
13
  "keywords": [
12
14
  "pi-package",
@@ -14,11 +16,15 @@
14
16
  "coding-agent",
15
17
  "web-search",
16
18
  "claude",
19
+ "cloudflare",
17
20
  "codex",
21
+ "custom",
18
22
  "exa",
23
+ "firecrawl",
19
24
  "gemini",
20
25
  "perplexity",
21
26
  "parallel",
27
+ "tavily",
22
28
  "valyu"
23
29
  ],
24
30
  "author": "mavam",
@@ -39,20 +45,24 @@
39
45
  ]
40
46
  },
41
47
  "scripts": {
42
- "build": "rm -rf dist && esbuild src/index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --external:@mariozechner/pi-coding-agent --external:@mariozechner/pi-ai --external:@mariozechner/pi-tui --external:@sinclair/typebox --external:@anthropic-ai/claude-agent-sdk --external:@google/genai --external:@openai/codex-sdk --external:@perplexity-ai/perplexity_ai --external:exa-js --external:parallel-web --external:valyu-js",
48
+ "build": "rm -rf dist && esbuild src/index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --external:@mariozechner/pi-coding-agent --external:@mariozechner/pi-ai --external:@mariozechner/pi-tui --external:@sinclair/typebox --external:@anthropic-ai/claude-agent-sdk --external:@google/genai --external:@mendable/firecrawl-js --external:@openai/codex-sdk --external:@perplexity-ai/perplexity_ai --external:@tavily/core --external:cloudflare --external:exa-js --external:parallel-web --external:valyu-js",
43
49
  "prepare": "npm run build",
44
50
  "prepack": "npm run build",
45
51
  "check": "tsc --noEmit",
46
52
  "format": "biome format --write .",
47
53
  "format:check": "biome format .",
54
+ "smoke:live": "npm run build && node scripts/live-smoke.mjs",
48
55
  "test": "vitest run",
49
56
  "test:watch": "vitest"
50
57
  },
51
58
  "dependencies": {
52
59
  "@anthropic-ai/claude-agent-sdk": "^0.2.71",
53
60
  "@google/genai": "^1.44.0",
61
+ "@mendable/firecrawl-js": "^4.18.1",
54
62
  "@openai/codex-sdk": "^0.111.0",
55
63
  "@perplexity-ai/perplexity_ai": "^0.26.1",
64
+ "@tavily/core": "^0.7.2",
65
+ "cloudflare": "^5.2.0",
56
66
  "exa-js": "^2.7.0",
57
67
  "parallel-web": "^0.3.1",
58
68
  "valyu-js": "^2.5.9",