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.
@@ -0,0 +1,87 @@
1
+ {
2
+ "tools": {
3
+ "search": "codex",
4
+ "contents": null,
5
+ "answer": null,
6
+ "research": null
7
+ },
8
+ "genericSettings": {
9
+ "requestTimeoutMs": 30000,
10
+ "retryCount": 3,
11
+ "retryDelayMs": 2000,
12
+ "researchPollIntervalMs": 3000,
13
+ "researchTimeoutMs": 21600000,
14
+ "researchMaxConsecutivePollErrors": 3
15
+ },
16
+ "providers": {
17
+ "claude": {
18
+ "enabled": false
19
+ },
20
+ "codex": {
21
+ "enabled": true,
22
+ "native": {
23
+ "networkAccessEnabled": true,
24
+ "webSearchEnabled": true,
25
+ "webSearchMode": "live"
26
+ }
27
+ },
28
+ "custom-cli": {
29
+ "enabled": false
30
+ },
31
+ "exa": {
32
+ "enabled": false,
33
+ "apiKey": "EXA_API_KEY",
34
+ "native": {
35
+ "type": "auto",
36
+ "contents": {
37
+ "text": true
38
+ }
39
+ }
40
+ },
41
+ "gemini": {
42
+ "enabled": false,
43
+ "apiKey": "GOOGLE_API_KEY",
44
+ "native": {
45
+ "searchModel": "gemini-2.5-flash",
46
+ "answerModel": "gemini-2.5-flash",
47
+ "researchAgent": "deep-research-pro-preview-12-2025"
48
+ },
49
+ "policy": {
50
+ "researchMaxConsecutivePollErrors": 10
51
+ }
52
+ },
53
+ "perplexity": {
54
+ "enabled": false,
55
+ "apiKey": "PERPLEXITY_API_KEY",
56
+ "native": {
57
+ "answer": {
58
+ "model": "sonar"
59
+ },
60
+ "research": {
61
+ "model": "sonar-deep-research"
62
+ }
63
+ }
64
+ },
65
+ "parallel": {
66
+ "enabled": false,
67
+ "apiKey": "PARALLEL_API_KEY",
68
+ "native": {
69
+ "search": {
70
+ "mode": "agentic"
71
+ },
72
+ "extract": {
73
+ "excerpts": true,
74
+ "full_content": false
75
+ }
76
+ }
77
+ },
78
+ "valyu": {
79
+ "enabled": false,
80
+ "apiKey": "VALYU_API_KEY",
81
+ "native": {
82
+ "searchType": "all",
83
+ "responseLength": "short"
84
+ }
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,208 @@
1
+ # Custom CLI 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-cli/wrappers/codex-search.sh ./wrappers/
34
+ cp examples/custom-cli/wrappers/gemini-contents.sh ./wrappers/
35
+ cp examples/custom-cli/wrappers/claude-answer.sh ./wrappers/
36
+ cp examples/custom-cli/wrappers/perplexity-research.sh ./wrappers/
37
+ chmod +x ./wrappers/*.sh
38
+ ```
39
+
40
+ Then configure `custom-cli` like this:
41
+
42
+ ```json
43
+ {
44
+ "tools": {
45
+ "search": "custom-cli",
46
+ "contents": "custom-cli",
47
+ "answer": "custom-cli",
48
+ "research": "custom-cli"
49
+ },
50
+ "providers": {
51
+ "custom-cli": {
52
+ "enabled": true,
53
+ "native": {
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` runs as a foreground wrapper command, so polling controls and
73
+ `resumeId` do not apply to `custom-cli`.
74
+
75
+ ## Core command shapes
76
+
77
+ ### Search with Codex
78
+
79
+ ```bash
80
+ codex --search exec \
81
+ --skip-git-repo-check \
82
+ --sandbox read-only \
83
+ --output-schema ./schema.json \
84
+ "Search the public web and return JSON only"
85
+ ```
86
+
87
+ ### Contents with Gemini and `curl`
88
+
89
+ ```bash
90
+ curl -sS -X POST \
91
+ "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=$GOOGLE_API_KEY" \
92
+ -H "Content-Type: application/json" \
93
+ -d '{
94
+ "contents": [{"parts": [{"text": "Extract the main content from https://example.com and return JSON only"}]}],
95
+ "tools": [{"urlContext": {}}],
96
+ "generationConfig": {"responseMimeType": "application/json"}
97
+ }'
98
+ ```
99
+
100
+ ### Answers with Claude
101
+
102
+ ```bash
103
+ claude -p \
104
+ --output-format json \
105
+ --json-schema "$schema" \
106
+ --permission-mode dontAsk \
107
+ --allowedTools "WebSearch,WebFetch" \
108
+ "Answer this question using current public web information"
109
+ ```
110
+
111
+ ### Research with Perplexity and `curl`
112
+
113
+ ```bash
114
+ curl -sS https://api.perplexity.ai/chat/completions \
115
+ -H "Authorization: Bearer $PERPLEXITY_API_KEY" \
116
+ -H "Content-Type: application/json" \
117
+ -d '{
118
+ "model": "sonar-deep-research",
119
+ "stream": false,
120
+ "messages": [{"role": "user", "content": "Research this topic and return a long-form answer"}]
121
+ }'
122
+ ```
123
+
124
+ ## Try a wrapper directly
125
+
126
+ ### Search
127
+
128
+ ```bash
129
+ printf '%s' '{
130
+ "capability": "search",
131
+ "query": "latest Codex CLI release notes",
132
+ "maxResults": 5,
133
+ "options": {},
134
+ "cwd": "'"$PWD"'"
135
+ }' | bash examples/custom-cli/wrappers/codex-search.sh
136
+ ```
137
+
138
+ ### Contents
139
+
140
+ ```bash
141
+ printf '%s' '{
142
+ "capability": "contents",
143
+ "urls": ["https://example.com"],
144
+ "options": {},
145
+ "cwd": "'"$PWD"'"
146
+ }' | bash examples/custom-cli/wrappers/gemini-contents.sh
147
+ ```
148
+
149
+ ### Answer
150
+
151
+ ```bash
152
+ printf '%s' '{
153
+ "capability": "answer",
154
+ "query": "What changed in the latest Claude Code release?",
155
+ "options": {},
156
+ "cwd": "'"$PWD"'"
157
+ }' | bash examples/custom-cli/wrappers/claude-answer.sh
158
+ ```
159
+
160
+ ### Research
161
+
162
+ ```bash
163
+ printf '%s' '{
164
+ "capability": "research",
165
+ "input": "Compare current local agent CLIs for web-grounded tasks.",
166
+ "options": {},
167
+ "cwd": "'"$PWD"'"
168
+ }' | bash examples/custom-cli/wrappers/perplexity-research.sh
169
+ ```
170
+
171
+ ## Request and response contract
172
+
173
+ ### Search request
174
+
175
+ ```json
176
+ {
177
+ "capability": "search",
178
+ "query": "latest Codex CLI release notes",
179
+ "maxResults": 5,
180
+ "options": {},
181
+ "cwd": "/path/to/project"
182
+ }
183
+ ```
184
+
185
+ ### Search response
186
+
187
+ ```json
188
+ {
189
+ "results": [
190
+ {
191
+ "title": "Codex CLI docs",
192
+ "url": "https://github.com/openai/codex",
193
+ "snippet": "CLI docs, examples, and release information."
194
+ }
195
+ ]
196
+ }
197
+ ```
198
+
199
+ ### Contents, answer, and research response
200
+
201
+ ```json
202
+ {
203
+ "text": "Rendered tool output",
204
+ "summary": "Optional short summary",
205
+ "itemCount": 1,
206
+ "metadata": {}
207
+ }
208
+ ```
@@ -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,48 @@
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 these fields:
17
+ - text: the extracted content
18
+ - summary: a short summary
19
+ - itemCount: the number of processed URLs ($url_count)
20
+ - metadata: include the input URLs under metadata.urls
21
+ EOF
22
+ )"
23
+
24
+ body="$(
25
+ jq -n \
26
+ --arg prompt "$prompt" \
27
+ '{
28
+ contents: [{parts: [{text: $prompt}]}],
29
+ tools: [{urlContext: {}}],
30
+ generationConfig: {responseMimeType: "application/json"}
31
+ }'
32
+ )"
33
+
34
+ echo "Fetching contents with Gemini..." >&2
35
+ response="$(curl -sS -X POST \
36
+ "https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GOOGLE_API_KEY}" \
37
+ -H "Content-Type: application/json" \
38
+ -d "$body")"
39
+
40
+ error="$(jq -r '.error.message // empty' <<<"$response")"
41
+ if [[ -n "$error" ]]; then
42
+ echo "$error" >&2
43
+ exit 1
44
+ fi
45
+
46
+ text="$(jq -r '[.candidates[]?.content.parts[]?.text // empty] | join("\n")' <<<"$response")"
47
+ json_text="$(printf '%s\n' "$text" | sed -e '1s/^```json[[:space:]]*//' -e '1s/^```[[:space:]]*//' -e '$s/```$//')"
48
+ 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": "0.3.0",
4
- "description": "Configurable web access extension for pi that routes search, contents, answers, and research across Claude, Codex, Exa, Gemini, Perplexity, Parallel, and Valyu providers.",
3
+ "version": "1.1.0",
4
+ "description": "Configurable web access extension for pi with per-tool provider routing for search, contents, answers, and research across Claude, Codex, Custom CLI, Exa, Gemini, Perplexity, Parallel, and Valyu.",
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",
@@ -15,6 +17,7 @@
15
17
  "web-search",
16
18
  "claude",
17
19
  "codex",
20
+ "custom-cli",
18
21
  "exa",
19
22
  "gemini",
20
23
  "perplexity",
@@ -28,7 +31,7 @@
28
31
  "url": "git+https://github.com/mavam/pi-web-providers.git"
29
32
  },
30
33
  "engines": {
31
- "node": ">=20.0.0"
34
+ "node": ">=24.0.0"
32
35
  },
33
36
  "publishConfig": {
34
37
  "access": "public"