pi-web-providers 0.2.0 → 0.3.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 +52 -4
- package/dist/index.js +536 -54
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -28,7 +28,8 @@ tool prompt aligned with the tools that the agent can actually call.
|
|
|
28
28
|
|
|
29
29
|
- **Provider-driven tool surface** — tools are injected based on what the active
|
|
30
30
|
provider actually supports, not a fixed list
|
|
31
|
-
- **
|
|
31
|
+
- **Multiple providers**: Claude, Codex, Exa, Gemini, Perplexity, Parallel,
|
|
32
|
+
Valyu — each with
|
|
32
33
|
its own SDK, strengths, and capability set
|
|
33
34
|
- **One config command** (`/web-providers`) with a TUI that adapts to the
|
|
34
35
|
selected provider
|
|
@@ -75,7 +76,8 @@ Find likely sources on the public web and return titles, URLs, and snippets.
|
|
|
75
76
|
| ------------ | ------- | -------- | ----------------------------------------------------------------------------- |
|
|
76
77
|
| `query` | string | required | What to search for |
|
|
77
78
|
| `maxResults` | integer | `5` | Result count, clamped to `1–20` |
|
|
78
|
-
| `
|
|
79
|
+
| `options` | object | — | Provider-specific search options |
|
|
80
|
+
| `provider` | string | auto | Optional override: `claude`, `codex`, `exa`, `gemini`, `perplexity`, `parallel`, or `valyu` |
|
|
79
81
|
|
|
80
82
|
### `web_contents`
|
|
81
83
|
|
|
@@ -107,6 +109,11 @@ Investigate a topic across web sources and produce a longer report.
|
|
|
107
109
|
| `options` | object | — | Provider-specific research options |
|
|
108
110
|
| `provider` | string | auto | Optional override among providers that support research |
|
|
109
111
|
|
|
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.
|
|
116
|
+
|
|
110
117
|
## 🔌 Providers
|
|
111
118
|
|
|
112
119
|
Every provider is a thin adapter around an official SDK. The table below
|
|
@@ -117,7 +124,8 @@ summarises which capabilities each provider exposes:
|
|
|
117
124
|
| **Claude** | ✓ | | ✓ | | Local Claude Code auth |
|
|
118
125
|
| **Codex** | ✓ | | | | Local Codex CLI auth |
|
|
119
126
|
| **Exa** | ✓ | ✓ | ✓ | ✓ | `EXA_API_KEY` |
|
|
120
|
-
| **Gemini** | ✓ |
|
|
127
|
+
| **Gemini** | ✓ | ✓ | ✓ | ✓ | `GOOGLE_API_KEY` |
|
|
128
|
+
| **Perplexity** | ✓ | | ✓ | ✓ | `PERPLEXITY_API_KEY` |
|
|
121
129
|
| **Parallel** | ✓ | ✓ | | | `PARALLEL_API_KEY` |
|
|
122
130
|
| **Valyu** | ✓ | ✓ | ✓ | ✓ | `VALYU_API_KEY` |
|
|
123
131
|
|
|
@@ -126,12 +134,16 @@ summarises which capabilities each provider exposes:
|
|
|
126
134
|
- SDK: `@anthropic-ai/claude-agent-sdk`
|
|
127
135
|
- Uses Claude Code's built-in `WebSearch` and `WebFetch` tools behind a
|
|
128
136
|
structured JSON adapter
|
|
137
|
+
- Supports request-shaping `options` such as `model`, `thinking`, `effort`, and
|
|
138
|
+
`maxTurns`
|
|
129
139
|
- Great for search plus grounded answers if you already use Claude Code locally
|
|
130
140
|
|
|
131
141
|
### Codex
|
|
132
142
|
|
|
133
143
|
- SDK: `@openai/codex-sdk`
|
|
134
144
|
- Runs in read-only mode with web search enabled
|
|
145
|
+
- Supports request-shaping `web_search.options` such as `model`,
|
|
146
|
+
`modelReasoningEffort`, and `webSearchMode`
|
|
135
147
|
- Best if you already use the local Codex CLI and auth flow
|
|
136
148
|
|
|
137
149
|
### Exa
|
|
@@ -143,18 +155,32 @@ summarises which capabilities each provider exposes:
|
|
|
143
155
|
### Gemini
|
|
144
156
|
|
|
145
157
|
- SDK: `@google/genai`
|
|
146
|
-
-
|
|
158
|
+
- Google Search grounding for answers and URL Context extraction for page contents
|
|
159
|
+
- Deep-research agents via Google's Gemini API
|
|
160
|
+
- Supports provider-native request options such as `model`, `config`,
|
|
161
|
+
`generation_config`, and `agent_config` depending on the tool
|
|
162
|
+
|
|
163
|
+
### Perplexity
|
|
164
|
+
|
|
165
|
+
- SDK: `@perplexity-ai/perplexity_ai`
|
|
166
|
+
- Uses Perplexity Search for `web_search`
|
|
167
|
+
- Uses Sonar for `web_answer` and `sonar-deep-research` for `web_research`
|
|
168
|
+
- Supports provider-specific `web_search.options` such as `country`,
|
|
169
|
+
`search_mode`, `search_domain_filter`, and `search_recency_filter`
|
|
147
170
|
|
|
148
171
|
### Parallel
|
|
149
172
|
|
|
150
173
|
- SDK: `parallel-web`
|
|
151
174
|
- Agentic and one-shot search modes
|
|
152
175
|
- Page content extraction with excerpt and full-content toggles
|
|
176
|
+
- Supports provider-native search and extraction options from the Parallel SDK
|
|
153
177
|
|
|
154
178
|
### Valyu
|
|
155
179
|
|
|
156
180
|
- SDK: `valyu-js`
|
|
157
181
|
- Web, proprietary, and news search types
|
|
182
|
+
- Supports provider-native options such as `countryCode`, `responseLength`, and
|
|
183
|
+
search/source filters
|
|
158
184
|
- Configurable response length for answers and research
|
|
159
185
|
|
|
160
186
|
## 📝 Config Notes
|
|
@@ -220,16 +246,38 @@ Example:
|
|
|
220
246
|
"enabled": false,
|
|
221
247
|
"tools": {
|
|
222
248
|
"search": true,
|
|
249
|
+
"contents": true,
|
|
223
250
|
"answer": true,
|
|
224
251
|
"research": true
|
|
225
252
|
},
|
|
226
253
|
"apiKey": "GOOGLE_API_KEY",
|
|
227
254
|
"defaults": {
|
|
228
255
|
"searchModel": "gemini-2.5-flash",
|
|
256
|
+
"contentsModel": "gemini-2.5-flash",
|
|
229
257
|
"answerModel": "gemini-2.5-flash",
|
|
230
258
|
"researchAgent": "deep-research-pro-preview-12-2025"
|
|
231
259
|
}
|
|
232
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": {
|
|
270
|
+
"search": {
|
|
271
|
+
"country": "US"
|
|
272
|
+
},
|
|
273
|
+
"answer": {
|
|
274
|
+
"model": "sonar"
|
|
275
|
+
},
|
|
276
|
+
"research": {
|
|
277
|
+
"model": "sonar-deep-research"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
},
|
|
233
281
|
"parallel": {
|
|
234
282
|
"enabled": false,
|
|
235
283
|
"tools": {
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,8 @@ var PROVIDER_TOOLS = {
|
|
|
32
32
|
claude: ["search", "answer"],
|
|
33
33
|
codex: ["search"],
|
|
34
34
|
exa: ["search", "contents", "answer", "research"],
|
|
35
|
-
gemini: ["search", "answer", "research"],
|
|
35
|
+
gemini: ["search", "contents", "answer", "research"],
|
|
36
|
+
perplexity: ["search", "answer", "research"],
|
|
36
37
|
parallel: ["search", "contents"],
|
|
37
38
|
valyu: ["search", "contents", "answer", "research"]
|
|
38
39
|
};
|
|
@@ -195,6 +196,12 @@ function normalizeConfig(raw, source) {
|
|
|
195
196
|
source
|
|
196
197
|
);
|
|
197
198
|
}
|
|
199
|
+
if (raw.providers.perplexity !== void 0) {
|
|
200
|
+
config.providers.perplexity = normalizePerplexityProvider(
|
|
201
|
+
raw.providers.perplexity,
|
|
202
|
+
source
|
|
203
|
+
);
|
|
204
|
+
}
|
|
198
205
|
if (raw.providers.parallel !== void 0) {
|
|
199
206
|
config.providers.parallel = normalizeParallelProvider(
|
|
200
207
|
raw.providers.parallel,
|
|
@@ -208,7 +215,7 @@ function normalizeConfig(raw, source) {
|
|
|
208
215
|
);
|
|
209
216
|
}
|
|
210
217
|
const unknownProviders = Object.keys(raw.providers).filter(
|
|
211
|
-
(key) => key !== "claude" && key !== "codex" && key !== "exa" && key !== "gemini" && key !== "parallel" && key !== "valyu"
|
|
218
|
+
(key) => key !== "claude" && key !== "codex" && key !== "exa" && key !== "gemini" && key !== "perplexity" && key !== "parallel" && key !== "valyu"
|
|
212
219
|
);
|
|
213
220
|
if (unknownProviders.length > 0) {
|
|
214
221
|
throw new Error(
|
|
@@ -435,6 +442,11 @@ function normalizeGeminiProvider(raw, source) {
|
|
|
435
442
|
source,
|
|
436
443
|
"providers.gemini.defaults.searchModel"
|
|
437
444
|
),
|
|
445
|
+
contentsModel: parseOptionalString(
|
|
446
|
+
defaults.contentsModel,
|
|
447
|
+
source,
|
|
448
|
+
"providers.gemini.defaults.contentsModel"
|
|
449
|
+
),
|
|
438
450
|
answerModel: parseOptionalString(
|
|
439
451
|
defaults.answerModel,
|
|
440
452
|
source,
|
|
@@ -448,6 +460,54 @@ function normalizeGeminiProvider(raw, source) {
|
|
|
448
460
|
}
|
|
449
461
|
};
|
|
450
462
|
}
|
|
463
|
+
function normalizePerplexityProvider(raw, source) {
|
|
464
|
+
const provider = parseProviderObject(raw, source, "perplexity");
|
|
465
|
+
const defaults = parseOptionalJsonObject(
|
|
466
|
+
provider.defaults,
|
|
467
|
+
source,
|
|
468
|
+
"providers.perplexity.defaults"
|
|
469
|
+
);
|
|
470
|
+
return {
|
|
471
|
+
enabled: parseOptionalBoolean(
|
|
472
|
+
provider.enabled,
|
|
473
|
+
source,
|
|
474
|
+
"providers.perplexity.enabled"
|
|
475
|
+
),
|
|
476
|
+
tools: parseOptionalProviderTools(
|
|
477
|
+
"perplexity",
|
|
478
|
+
provider.tools,
|
|
479
|
+
source,
|
|
480
|
+
"providers.perplexity.tools"
|
|
481
|
+
),
|
|
482
|
+
apiKey: parseOptionalString(
|
|
483
|
+
provider.apiKey,
|
|
484
|
+
source,
|
|
485
|
+
"providers.perplexity.apiKey"
|
|
486
|
+
),
|
|
487
|
+
baseUrl: parseOptionalString(
|
|
488
|
+
provider.baseUrl,
|
|
489
|
+
source,
|
|
490
|
+
"providers.perplexity.baseUrl"
|
|
491
|
+
),
|
|
492
|
+
defaults: defaults === void 0 ? void 0 : {
|
|
493
|
+
search: parseOptionalJsonObject(
|
|
494
|
+
defaults.search,
|
|
495
|
+
source,
|
|
496
|
+
"providers.perplexity.defaults.search"
|
|
497
|
+
),
|
|
498
|
+
answer: parseOptionalJsonObject(
|
|
499
|
+
defaults.answer,
|
|
500
|
+
source,
|
|
501
|
+
"providers.perplexity.defaults.answer"
|
|
502
|
+
),
|
|
503
|
+
research: parseOptionalJsonObject(
|
|
504
|
+
defaults.research,
|
|
505
|
+
source,
|
|
506
|
+
"providers.perplexity.defaults.research"
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
}
|
|
451
511
|
function normalizeParallelProvider(raw, source) {
|
|
452
512
|
const provider = parseProviderObject(raw, source, "parallel");
|
|
453
513
|
const defaults = parseOptionalJsonObject(
|
|
@@ -698,7 +758,7 @@ var ClaudeProvider = class {
|
|
|
698
758
|
}
|
|
699
759
|
return { available: true, summary: "enabled" };
|
|
700
760
|
}
|
|
701
|
-
async search(queryText, maxResults, config, context) {
|
|
761
|
+
async search(queryText, maxResults, options, config, context) {
|
|
702
762
|
const output = parseClaudeSearchOutput(
|
|
703
763
|
await this.runStructuredQuery({
|
|
704
764
|
prompt: [
|
|
@@ -715,7 +775,8 @@ var ClaudeProvider = class {
|
|
|
715
775
|
schema: SEARCH_OUTPUT_SCHEMA,
|
|
716
776
|
tools: ["WebSearch"],
|
|
717
777
|
config,
|
|
718
|
-
context
|
|
778
|
+
context,
|
|
779
|
+
options
|
|
719
780
|
})
|
|
720
781
|
);
|
|
721
782
|
return {
|
|
@@ -738,13 +799,13 @@ var ClaudeProvider = class {
|
|
|
738
799
|
"Keep the answer concise but informative.",
|
|
739
800
|
"Only cite sources you actually used.",
|
|
740
801
|
"",
|
|
741
|
-
`User query: ${queryText}
|
|
742
|
-
|
|
743
|
-
].filter(Boolean).join("\n"),
|
|
802
|
+
`User query: ${queryText}`
|
|
803
|
+
].join("\n"),
|
|
744
804
|
schema: ANSWER_OUTPUT_SCHEMA,
|
|
745
805
|
tools: ["WebSearch", "WebFetch"],
|
|
746
806
|
config,
|
|
747
|
-
context
|
|
807
|
+
context,
|
|
808
|
+
options
|
|
748
809
|
})
|
|
749
810
|
);
|
|
750
811
|
const lines = [];
|
|
@@ -769,7 +830,8 @@ var ClaudeProvider = class {
|
|
|
769
830
|
schema,
|
|
770
831
|
tools,
|
|
771
832
|
config,
|
|
772
|
-
context
|
|
833
|
+
context,
|
|
834
|
+
options
|
|
773
835
|
}) {
|
|
774
836
|
const abortController = new AbortController();
|
|
775
837
|
if (context.signal?.aborted) {
|
|
@@ -785,9 +847,7 @@ var ClaudeProvider = class {
|
|
|
785
847
|
abortController,
|
|
786
848
|
allowedTools: tools,
|
|
787
849
|
cwd: context.cwd,
|
|
788
|
-
|
|
789
|
-
maxTurns: config.defaults?.maxTurns,
|
|
790
|
-
model: config.defaults?.model,
|
|
850
|
+
...getClaudeRuntimeOptions(config, options),
|
|
791
851
|
outputFormat: {
|
|
792
852
|
type: "json_schema",
|
|
793
853
|
schema
|
|
@@ -939,6 +999,40 @@ function parseStructuredOutput(result) {
|
|
|
939
999
|
return JSON.parse(match[0]);
|
|
940
1000
|
}
|
|
941
1001
|
}
|
|
1002
|
+
function getClaudeRuntimeOptions(config, options) {
|
|
1003
|
+
const model = readNonEmptyString(options?.model) ?? config.defaults?.model;
|
|
1004
|
+
const effort = readEnum(options?.effort, ["low", "medium", "high", "max"]);
|
|
1005
|
+
const maxTurns = readPositiveInteger(options?.maxTurns);
|
|
1006
|
+
const maxThinkingTokens = readNonNegativeInteger(options?.maxThinkingTokens);
|
|
1007
|
+
const maxBudgetUsd = readPositiveNumber(options?.maxBudgetUsd);
|
|
1008
|
+
const thinking = isPlainObject2(options?.thinking) ? options?.thinking : void 0;
|
|
1009
|
+
return {
|
|
1010
|
+
...model ? { model } : {},
|
|
1011
|
+
...effort ?? config.defaults?.effort ? { effort: effort ?? config.defaults?.effort } : {},
|
|
1012
|
+
...maxTurns ?? config.defaults?.maxTurns ? { maxTurns: maxTurns ?? config.defaults?.maxTurns } : {},
|
|
1013
|
+
...maxThinkingTokens !== void 0 ? { maxThinkingTokens } : {},
|
|
1014
|
+
...maxBudgetUsd !== void 0 ? { maxBudgetUsd } : {},
|
|
1015
|
+
...thinking ? { thinking } : {}
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
function readNonEmptyString(value) {
|
|
1019
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
1020
|
+
}
|
|
1021
|
+
function readPositiveInteger(value) {
|
|
1022
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
1023
|
+
}
|
|
1024
|
+
function readNonNegativeInteger(value) {
|
|
1025
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1026
|
+
}
|
|
1027
|
+
function readPositiveNumber(value) {
|
|
1028
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1029
|
+
}
|
|
1030
|
+
function readEnum(value, values) {
|
|
1031
|
+
return typeof value === "string" && values.includes(value) ? value : void 0;
|
|
1032
|
+
}
|
|
1033
|
+
function isPlainObject2(value) {
|
|
1034
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1035
|
+
}
|
|
942
1036
|
function parseClaudeSearchOutput(value) {
|
|
943
1037
|
const sources = readArray(value, "sources").map((entry) => ({
|
|
944
1038
|
title: readString(entry, "title"),
|
|
@@ -1042,7 +1136,7 @@ var CodexProvider = class {
|
|
|
1042
1136
|
}
|
|
1043
1137
|
return { available: true, summary: "enabled" };
|
|
1044
1138
|
}
|
|
1045
|
-
async search(query2, maxResults, config, context) {
|
|
1139
|
+
async search(query2, maxResults, options, config, context) {
|
|
1046
1140
|
const codex = new Codex({
|
|
1047
1141
|
codexPathOverride: config.codexPath,
|
|
1048
1142
|
baseUrl: config.baseUrl,
|
|
@@ -1050,18 +1144,9 @@ var CodexProvider = class {
|
|
|
1050
1144
|
config: config.config,
|
|
1051
1145
|
env: resolveEnvMap(config.env)
|
|
1052
1146
|
});
|
|
1053
|
-
const thread = codex.startThread(
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
model: config.defaults?.model,
|
|
1057
|
-
modelReasoningEffort: config.defaults?.modelReasoningEffort,
|
|
1058
|
-
networkAccessEnabled: config.defaults?.networkAccessEnabled ?? true,
|
|
1059
|
-
sandboxMode: "read-only",
|
|
1060
|
-
skipGitRepoCheck: true,
|
|
1061
|
-
webSearchEnabled: config.defaults?.webSearchEnabled ?? true,
|
|
1062
|
-
webSearchMode: config.defaults?.webSearchMode ?? "live",
|
|
1063
|
-
workingDirectory: context.cwd
|
|
1064
|
-
});
|
|
1147
|
+
const thread = codex.startThread(
|
|
1148
|
+
buildCodexSearchThreadOptions(config, context.cwd, options)
|
|
1149
|
+
);
|
|
1065
1150
|
const prompt = [
|
|
1066
1151
|
"You are performing web research for another coding agent.",
|
|
1067
1152
|
"Search the public web and return only a JSON object matching the provided schema.",
|
|
@@ -1098,6 +1183,50 @@ var CodexProvider = class {
|
|
|
1098
1183
|
};
|
|
1099
1184
|
}
|
|
1100
1185
|
};
|
|
1186
|
+
function buildCodexSearchThreadOptions(config, cwd, options) {
|
|
1187
|
+
const runtimeOptions = getCodexSearchRuntimeOptions(options);
|
|
1188
|
+
return {
|
|
1189
|
+
additionalDirectories: config.defaults?.additionalDirectories,
|
|
1190
|
+
approvalPolicy: "never",
|
|
1191
|
+
model: runtimeOptions.model ?? config.defaults?.model,
|
|
1192
|
+
modelReasoningEffort: runtimeOptions.modelReasoningEffort ?? config.defaults?.modelReasoningEffort,
|
|
1193
|
+
networkAccessEnabled: config.defaults?.networkAccessEnabled ?? true,
|
|
1194
|
+
sandboxMode: "read-only",
|
|
1195
|
+
skipGitRepoCheck: true,
|
|
1196
|
+
webSearchEnabled: config.defaults?.webSearchEnabled ?? true,
|
|
1197
|
+
webSearchMode: runtimeOptions.webSearchMode ?? config.defaults?.webSearchMode ?? "live",
|
|
1198
|
+
workingDirectory: cwd
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function getCodexSearchRuntimeOptions(options) {
|
|
1202
|
+
if (!options) {
|
|
1203
|
+
return {};
|
|
1204
|
+
}
|
|
1205
|
+
const model = readNonEmptyString2(options.model);
|
|
1206
|
+
const modelReasoningEffort = readEnum2(options.modelReasoningEffort, [
|
|
1207
|
+
"minimal",
|
|
1208
|
+
"low",
|
|
1209
|
+
"medium",
|
|
1210
|
+
"high",
|
|
1211
|
+
"xhigh"
|
|
1212
|
+
]);
|
|
1213
|
+
const webSearchMode = readEnum2(options.webSearchMode, [
|
|
1214
|
+
"disabled",
|
|
1215
|
+
"cached",
|
|
1216
|
+
"live"
|
|
1217
|
+
]);
|
|
1218
|
+
return {
|
|
1219
|
+
...model ? { model } : {},
|
|
1220
|
+
...modelReasoningEffort ? { modelReasoningEffort } : {},
|
|
1221
|
+
...webSearchMode ? { webSearchMode } : {}
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
function readNonEmptyString2(value) {
|
|
1225
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
1226
|
+
}
|
|
1227
|
+
function readEnum2(value, values) {
|
|
1228
|
+
return typeof value === "string" && values.includes(value) ? value : void 0;
|
|
1229
|
+
}
|
|
1101
1230
|
function hasCodexCredentials(config) {
|
|
1102
1231
|
if (hasConfiguredReference(config.apiKey)) {
|
|
1103
1232
|
return true;
|
|
@@ -1188,7 +1317,7 @@ var ExaProvider = class {
|
|
|
1188
1317
|
}
|
|
1189
1318
|
return { available: true, summary: "enabled" };
|
|
1190
1319
|
}
|
|
1191
|
-
async search(query2, maxResults, config, context) {
|
|
1320
|
+
async search(query2, maxResults, searchOptions, config, context) {
|
|
1192
1321
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
1193
1322
|
if (!apiKey) {
|
|
1194
1323
|
throw new Error("Exa is missing an API key.");
|
|
@@ -1196,6 +1325,7 @@ var ExaProvider = class {
|
|
|
1196
1325
|
const client = new Exa(apiKey, config.baseUrl);
|
|
1197
1326
|
const options = {
|
|
1198
1327
|
...asJsonObject(config.defaults),
|
|
1328
|
+
...searchOptions ?? {},
|
|
1199
1329
|
numResults: maxResults
|
|
1200
1330
|
};
|
|
1201
1331
|
context.onProgress?.(`Searching Exa for: ${query2}`);
|
|
@@ -1304,6 +1434,7 @@ var ExaProvider = class {
|
|
|
1304
1434
|
// src/providers/gemini.ts
|
|
1305
1435
|
import { GoogleGenAI } from "@google/genai";
|
|
1306
1436
|
var DEFAULT_SEARCH_MODEL = "gemini-2.5-flash";
|
|
1437
|
+
var DEFAULT_CONTENTS_MODEL = "gemini-2.5-flash";
|
|
1307
1438
|
var DEFAULT_ANSWER_MODEL = "gemini-2.5-flash";
|
|
1308
1439
|
var DEFAULT_RESEARCH_AGENT = "deep-research-pro-preview-12-2025";
|
|
1309
1440
|
var DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
@@ -1316,12 +1447,14 @@ var GeminiProvider = class {
|
|
|
1316
1447
|
enabled: false,
|
|
1317
1448
|
tools: {
|
|
1318
1449
|
search: true,
|
|
1450
|
+
contents: true,
|
|
1319
1451
|
answer: true,
|
|
1320
1452
|
research: true
|
|
1321
1453
|
},
|
|
1322
1454
|
apiKey: "GOOGLE_API_KEY",
|
|
1323
1455
|
defaults: {
|
|
1324
1456
|
searchModel: DEFAULT_SEARCH_MODEL,
|
|
1457
|
+
contentsModel: DEFAULT_CONTENTS_MODEL,
|
|
1325
1458
|
answerModel: DEFAULT_ANSWER_MODEL,
|
|
1326
1459
|
researchAgent: DEFAULT_RESEARCH_AGENT
|
|
1327
1460
|
}
|
|
@@ -1340,11 +1473,15 @@ var GeminiProvider = class {
|
|
|
1340
1473
|
}
|
|
1341
1474
|
return { available: true, summary: "enabled" };
|
|
1342
1475
|
}
|
|
1343
|
-
async search(query2, maxResults, config, context) {
|
|
1476
|
+
async search(query2, maxResults, options, config, context) {
|
|
1344
1477
|
const ai = this.createClient(config);
|
|
1345
|
-
const
|
|
1478
|
+
const request = buildGeminiSearchRequest(
|
|
1479
|
+
query2,
|
|
1480
|
+
config.defaults?.searchModel ?? DEFAULT_SEARCH_MODEL,
|
|
1481
|
+
options
|
|
1482
|
+
);
|
|
1346
1483
|
context.onProgress?.(`Searching Gemini for: ${query2}`);
|
|
1347
|
-
const interaction = await createSearchInteraction(ai,
|
|
1484
|
+
const interaction = await createSearchInteraction(ai, request);
|
|
1348
1485
|
const results = await Promise.all(
|
|
1349
1486
|
extractGoogleSearchResults(interaction.outputs).slice(0, maxResults).map(async (result) => {
|
|
1350
1487
|
const resolvedUrl = await resolveGoogleSearchUrl(result.url);
|
|
@@ -1360,17 +1497,66 @@ var GeminiProvider = class {
|
|
|
1360
1497
|
results
|
|
1361
1498
|
};
|
|
1362
1499
|
}
|
|
1500
|
+
async contents(urls, options, config, context) {
|
|
1501
|
+
const ai = this.createClient(config);
|
|
1502
|
+
context.onProgress?.(
|
|
1503
|
+
`Fetching contents from Gemini for ${urls.length} URL(s)`
|
|
1504
|
+
);
|
|
1505
|
+
const urlList = urls.map((url) => `- ${url}`).join("\n");
|
|
1506
|
+
const request = buildGeminiGenerateContentRequest({
|
|
1507
|
+
defaultModel: config.defaults?.contentsModel ?? DEFAULT_CONTENTS_MODEL,
|
|
1508
|
+
prompt: `Extract the main textual content from each of the following URLs. For each URL, return the page title followed by the cleaned body text. Preserve the original structure (headings, paragraphs, lists) but remove navigation, ads, and boilerplate.
|
|
1509
|
+
|
|
1510
|
+
${urlList}`,
|
|
1511
|
+
options,
|
|
1512
|
+
toolConfig: { urlContext: {} }
|
|
1513
|
+
});
|
|
1514
|
+
const response = await ai.models.generateContent({
|
|
1515
|
+
model: request.model,
|
|
1516
|
+
contents: [request.contents],
|
|
1517
|
+
config: request.config
|
|
1518
|
+
});
|
|
1519
|
+
const text = response.text?.trim() || "";
|
|
1520
|
+
const metadata = extractUrlContextMetadata(response.candidates);
|
|
1521
|
+
const lines = [];
|
|
1522
|
+
if (text) {
|
|
1523
|
+
lines.push(text);
|
|
1524
|
+
}
|
|
1525
|
+
if (metadata.length > 0) {
|
|
1526
|
+
const failures = metadata.filter(
|
|
1527
|
+
(entry) => entry.status !== "URL_RETRIEVAL_STATUS_SUCCESS" && entry.status !== void 0
|
|
1528
|
+
);
|
|
1529
|
+
if (failures.length > 0) {
|
|
1530
|
+
lines.push("");
|
|
1531
|
+
lines.push("Retrieval issues:");
|
|
1532
|
+
for (const failure of failures) {
|
|
1533
|
+
lines.push(`- ${failure.url}: ${failure.status}`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
const successCount = metadata.filter(
|
|
1538
|
+
(entry) => entry.status === "URL_RETRIEVAL_STATUS_SUCCESS" || entry.status === void 0
|
|
1539
|
+
).length;
|
|
1540
|
+
return {
|
|
1541
|
+
provider: this.id,
|
|
1542
|
+
text: lines.join("\n").trimEnd() || "No contents extracted.",
|
|
1543
|
+
summary: `${successCount} of ${urls.length} URL(s) extracted via Gemini`,
|
|
1544
|
+
itemCount: successCount
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1363
1547
|
async answer(query2, options, config, context) {
|
|
1364
1548
|
const ai = this.createClient(config);
|
|
1365
|
-
const
|
|
1549
|
+
const request = buildGeminiGenerateContentRequest({
|
|
1550
|
+
defaultModel: config.defaults?.answerModel ?? DEFAULT_ANSWER_MODEL,
|
|
1551
|
+
prompt: query2,
|
|
1552
|
+
options,
|
|
1553
|
+
toolConfig: { googleSearch: {} }
|
|
1554
|
+
});
|
|
1366
1555
|
context.onProgress?.(`Getting Gemini answer for: ${query2}`);
|
|
1367
1556
|
const response = await ai.models.generateContent({
|
|
1368
|
-
model,
|
|
1369
|
-
contents:
|
|
1370
|
-
config:
|
|
1371
|
-
...options ?? {},
|
|
1372
|
-
tools: [{ googleSearch: {} }]
|
|
1373
|
-
}
|
|
1557
|
+
model: request.model,
|
|
1558
|
+
contents: request.contents,
|
|
1559
|
+
config: request.config
|
|
1374
1560
|
});
|
|
1375
1561
|
const lines = [];
|
|
1376
1562
|
lines.push(response.text?.trim() || "No answer returned.");
|
|
@@ -1398,7 +1584,9 @@ var GeminiProvider = class {
|
|
|
1398
1584
|
const ai = this.createClient(config);
|
|
1399
1585
|
const agent = config.defaults?.researchAgent ?? DEFAULT_RESEARCH_AGENT;
|
|
1400
1586
|
const pollIntervalMs = getPollInterval(options);
|
|
1401
|
-
const requestOptions =
|
|
1587
|
+
const requestOptions = getGeminiResearchRequestOptions(
|
|
1588
|
+
stripPollIntervalOption(options)
|
|
1589
|
+
);
|
|
1402
1590
|
const startedAt = Date.now();
|
|
1403
1591
|
let lastStatus;
|
|
1404
1592
|
context.onProgress?.("Starting Gemini deep research");
|
|
@@ -1503,6 +1691,31 @@ function extractGroundingSources(chunks) {
|
|
|
1503
1691
|
}
|
|
1504
1692
|
return sources;
|
|
1505
1693
|
}
|
|
1694
|
+
function extractUrlContextMetadata(candidates) {
|
|
1695
|
+
const results = [];
|
|
1696
|
+
if (!Array.isArray(candidates)) {
|
|
1697
|
+
return results;
|
|
1698
|
+
}
|
|
1699
|
+
for (const candidate of candidates) {
|
|
1700
|
+
if (typeof candidate !== "object" || candidate === null) {
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
const metadata = candidate.urlContextMetadata;
|
|
1704
|
+
if (!metadata?.urlMetadata || !Array.isArray(metadata.urlMetadata)) {
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1707
|
+
for (const entry of metadata.urlMetadata) {
|
|
1708
|
+
if (typeof entry !== "object" || entry === null) {
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
results.push({
|
|
1712
|
+
url: typeof entry.retrievedUrl === "string" ? entry.retrievedUrl : "unknown",
|
|
1713
|
+
status: typeof entry.urlRetrievalStatus === "string" ? entry.urlRetrievalStatus : void 0
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
return results;
|
|
1718
|
+
}
|
|
1506
1719
|
function formatInteractionOutputs(outputs) {
|
|
1507
1720
|
const lines = [];
|
|
1508
1721
|
if (!Array.isArray(outputs)) {
|
|
@@ -1549,24 +1762,31 @@ function isGoogleGroundingRedirect(url) {
|
|
|
1549
1762
|
return false;
|
|
1550
1763
|
}
|
|
1551
1764
|
}
|
|
1552
|
-
async function createSearchInteraction(ai,
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
tools: [{ type: "google_search" }]
|
|
1557
|
-
};
|
|
1558
|
-
try {
|
|
1559
|
-
return await ai.interactions.create({
|
|
1560
|
-
...request,
|
|
1765
|
+
async function createSearchInteraction(ai, request) {
|
|
1766
|
+
const forcedRequest = {
|
|
1767
|
+
...request,
|
|
1768
|
+
...request.generation_config ? {
|
|
1561
1769
|
generation_config: {
|
|
1770
|
+
...request.generation_config,
|
|
1562
1771
|
tool_choice: "any"
|
|
1563
1772
|
}
|
|
1564
|
-
}
|
|
1773
|
+
} : {
|
|
1774
|
+
generation_config: {
|
|
1775
|
+
tool_choice: "any"
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
try {
|
|
1780
|
+
return await ai.interactions.create(forcedRequest);
|
|
1565
1781
|
} catch (error) {
|
|
1566
1782
|
if (!isBuiltInToolChoiceError(error)) {
|
|
1567
1783
|
throw error;
|
|
1568
1784
|
}
|
|
1569
|
-
|
|
1785
|
+
const fallbackGenerationConfig = stripToolChoice(request.generation_config);
|
|
1786
|
+
return ai.interactions.create({
|
|
1787
|
+
...request,
|
|
1788
|
+
...fallbackGenerationConfig ? { generation_config: fallbackGenerationConfig } : {}
|
|
1789
|
+
});
|
|
1570
1790
|
}
|
|
1571
1791
|
}
|
|
1572
1792
|
function isBuiltInToolChoiceError(error) {
|
|
@@ -1639,6 +1859,244 @@ function stripPollIntervalOption(options) {
|
|
|
1639
1859
|
const { pollIntervalMs: _ignored, ...rest } = options;
|
|
1640
1860
|
return rest;
|
|
1641
1861
|
}
|
|
1862
|
+
function buildGeminiSearchRequest(query2, defaultModel, options) {
|
|
1863
|
+
return {
|
|
1864
|
+
model: readNonEmptyString3(options?.model) ?? defaultModel,
|
|
1865
|
+
input: query2,
|
|
1866
|
+
tools: [{ type: "google_search" }],
|
|
1867
|
+
...isPlainObject3(options?.generation_config) ? { generation_config: options.generation_config } : {}
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
function buildGeminiGenerateContentRequest({
|
|
1871
|
+
defaultModel,
|
|
1872
|
+
prompt,
|
|
1873
|
+
options,
|
|
1874
|
+
toolConfig
|
|
1875
|
+
}) {
|
|
1876
|
+
const requestOptions = isPlainObject3(options) ? options : {};
|
|
1877
|
+
const explicitConfig = isPlainObject3(requestOptions.config) ? requestOptions.config : {};
|
|
1878
|
+
return {
|
|
1879
|
+
model: readNonEmptyString3(requestOptions.model) ?? defaultModel,
|
|
1880
|
+
contents: prompt,
|
|
1881
|
+
config: {
|
|
1882
|
+
...explicitConfig,
|
|
1883
|
+
tools: [toolConfig]
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
function getGeminiResearchRequestOptions(options) {
|
|
1888
|
+
if (!isPlainObject3(options)) {
|
|
1889
|
+
return {};
|
|
1890
|
+
}
|
|
1891
|
+
return { ...options };
|
|
1892
|
+
}
|
|
1893
|
+
function stripToolChoice(generationConfig) {
|
|
1894
|
+
if (!generationConfig || !Object.hasOwn(generationConfig, "tool_choice")) {
|
|
1895
|
+
return generationConfig;
|
|
1896
|
+
}
|
|
1897
|
+
const { tool_choice: _ignored, ...rest } = generationConfig;
|
|
1898
|
+
return Object.keys(rest).length > 0 ? rest : void 0;
|
|
1899
|
+
}
|
|
1900
|
+
function isPlainObject3(value) {
|
|
1901
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1902
|
+
}
|
|
1903
|
+
function readNonEmptyString3(value) {
|
|
1904
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
// src/providers/perplexity.ts
|
|
1908
|
+
import Perplexity from "@perplexity-ai/perplexity_ai";
|
|
1909
|
+
var DEFAULT_ANSWER_MODEL2 = "sonar";
|
|
1910
|
+
var DEFAULT_RESEARCH_MODEL = "sonar-deep-research";
|
|
1911
|
+
var PerplexityProvider = class {
|
|
1912
|
+
id = "perplexity";
|
|
1913
|
+
label = "Perplexity";
|
|
1914
|
+
docsUrl = "https://docs.perplexity.ai/docs/sdk/overview.md";
|
|
1915
|
+
createTemplate() {
|
|
1916
|
+
return {
|
|
1917
|
+
enabled: false,
|
|
1918
|
+
tools: {
|
|
1919
|
+
search: true,
|
|
1920
|
+
answer: true,
|
|
1921
|
+
research: true
|
|
1922
|
+
},
|
|
1923
|
+
apiKey: "PERPLEXITY_API_KEY",
|
|
1924
|
+
defaults: {
|
|
1925
|
+
answer: {
|
|
1926
|
+
model: DEFAULT_ANSWER_MODEL2
|
|
1927
|
+
},
|
|
1928
|
+
research: {
|
|
1929
|
+
model: DEFAULT_RESEARCH_MODEL
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
getStatus(config) {
|
|
1935
|
+
if (!config) {
|
|
1936
|
+
return { available: false, summary: "not configured" };
|
|
1937
|
+
}
|
|
1938
|
+
if (config.enabled === false) {
|
|
1939
|
+
return { available: false, summary: "disabled" };
|
|
1940
|
+
}
|
|
1941
|
+
const apiKey = resolveConfigValue(config.apiKey);
|
|
1942
|
+
if (!apiKey) {
|
|
1943
|
+
return { available: false, summary: "missing apiKey" };
|
|
1944
|
+
}
|
|
1945
|
+
return { available: true, summary: "enabled" };
|
|
1946
|
+
}
|
|
1947
|
+
async search(query2, maxResults, options, config, context) {
|
|
1948
|
+
const client = this.createClient(config);
|
|
1949
|
+
const request = {
|
|
1950
|
+
...asJsonObject(config.defaults?.search),
|
|
1951
|
+
...options ?? {},
|
|
1952
|
+
query: query2,
|
|
1953
|
+
max_results: maxResults
|
|
1954
|
+
};
|
|
1955
|
+
context.onProgress?.(`Searching Perplexity for: ${query2}`);
|
|
1956
|
+
const response = await client.search.create(
|
|
1957
|
+
request,
|
|
1958
|
+
buildRequestOptions(context)
|
|
1959
|
+
);
|
|
1960
|
+
return {
|
|
1961
|
+
provider: this.id,
|
|
1962
|
+
results: response.results.slice(0, maxResults).map((result) => ({
|
|
1963
|
+
title: result.title,
|
|
1964
|
+
url: result.url,
|
|
1965
|
+
snippet: trimSnippet(result.snippet),
|
|
1966
|
+
metadata: result.date || result.last_updated ? {
|
|
1967
|
+
...result.date ? { date: result.date } : {},
|
|
1968
|
+
...result.last_updated ? { last_updated: result.last_updated } : {}
|
|
1969
|
+
} : void 0
|
|
1970
|
+
}))
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
async answer(query2, options, config, context) {
|
|
1974
|
+
context.onProgress?.(`Getting Perplexity answer for: ${query2}`);
|
|
1975
|
+
return this.runChatTool(
|
|
1976
|
+
query2,
|
|
1977
|
+
options,
|
|
1978
|
+
config,
|
|
1979
|
+
context,
|
|
1980
|
+
DEFAULT_ANSWER_MODEL2,
|
|
1981
|
+
"Answer"
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
async research(input, options, config, context) {
|
|
1985
|
+
context.onProgress?.("Starting Perplexity research");
|
|
1986
|
+
return this.runChatTool(
|
|
1987
|
+
input,
|
|
1988
|
+
options,
|
|
1989
|
+
config,
|
|
1990
|
+
context,
|
|
1991
|
+
DEFAULT_RESEARCH_MODEL,
|
|
1992
|
+
"Research",
|
|
1993
|
+
true
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
async runChatTool(input, options, config, context, fallbackModel, label, isResearch = false) {
|
|
1997
|
+
const client = this.createClient(config);
|
|
1998
|
+
const defaults = isResearch ? config.defaults?.research : config.defaults?.answer;
|
|
1999
|
+
const request = {
|
|
2000
|
+
...asJsonObject(defaults),
|
|
2001
|
+
...options ?? {},
|
|
2002
|
+
messages: [{ role: "user", content: input }],
|
|
2003
|
+
model: resolveModel(
|
|
2004
|
+
(options ?? {}).model,
|
|
2005
|
+
asJsonObject(defaults).model,
|
|
2006
|
+
fallbackModel
|
|
2007
|
+
) ?? fallbackModel,
|
|
2008
|
+
stream: false
|
|
2009
|
+
};
|
|
2010
|
+
const response = await client.chat.completions.create(
|
|
2011
|
+
request,
|
|
2012
|
+
buildRequestOptions(context)
|
|
2013
|
+
);
|
|
2014
|
+
const content = extractMessageText(response.choices[0]?.message?.content);
|
|
2015
|
+
const sources = dedupeSources(extractSources(response));
|
|
2016
|
+
const lines = [];
|
|
2017
|
+
lines.push(content || `No ${label.toLowerCase()} returned.`);
|
|
2018
|
+
if (sources.length > 0) {
|
|
2019
|
+
lines.push("");
|
|
2020
|
+
lines.push("Sources:");
|
|
2021
|
+
for (const [index, source] of sources.entries()) {
|
|
2022
|
+
lines.push(`${index + 1}. ${source.title}`);
|
|
2023
|
+
lines.push(` ${source.url}`);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
return {
|
|
2027
|
+
provider: this.id,
|
|
2028
|
+
text: lines.join("\n").trimEnd(),
|
|
2029
|
+
summary: `${label} via Perplexity with ${sources.length} source(s)`,
|
|
2030
|
+
itemCount: sources.length
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
createClient(config) {
|
|
2034
|
+
const apiKey = resolveConfigValue(config.apiKey);
|
|
2035
|
+
if (!apiKey) {
|
|
2036
|
+
throw new Error("Perplexity is missing an API key.");
|
|
2037
|
+
}
|
|
2038
|
+
return new Perplexity({
|
|
2039
|
+
apiKey,
|
|
2040
|
+
baseURL: resolveConfigValue(config.baseUrl)
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
};
|
|
2044
|
+
function resolveModel(optionModel, defaultModel, fallbackModel) {
|
|
2045
|
+
if (typeof optionModel === "string" && optionModel.trim().length > 0) {
|
|
2046
|
+
return optionModel;
|
|
2047
|
+
}
|
|
2048
|
+
if (typeof defaultModel === "string" && defaultModel.trim().length > 0) {
|
|
2049
|
+
return defaultModel;
|
|
2050
|
+
}
|
|
2051
|
+
return fallbackModel;
|
|
2052
|
+
}
|
|
2053
|
+
function extractMessageText(content) {
|
|
2054
|
+
if (typeof content === "string") {
|
|
2055
|
+
return content.trim();
|
|
2056
|
+
}
|
|
2057
|
+
if (!Array.isArray(content)) {
|
|
2058
|
+
return "";
|
|
2059
|
+
}
|
|
2060
|
+
return content.flatMap((chunk) => {
|
|
2061
|
+
if (typeof chunk === "object" && chunk !== null && "type" in chunk && chunk.type === "text" && "text" in chunk && typeof chunk.text === "string") {
|
|
2062
|
+
return [chunk.text.trim()];
|
|
2063
|
+
}
|
|
2064
|
+
return [];
|
|
2065
|
+
}).filter((text) => text.length > 0).join("\n\n").trim();
|
|
2066
|
+
}
|
|
2067
|
+
function dedupeSources(sources) {
|
|
2068
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2069
|
+
const unique = [];
|
|
2070
|
+
for (const source of sources) {
|
|
2071
|
+
const title = source.title.trim() || source.url.trim() || "Untitled";
|
|
2072
|
+
const url = source.url.trim();
|
|
2073
|
+
if (!url) continue;
|
|
2074
|
+
const key = `${title.toLowerCase()}::${url.toLowerCase()}`;
|
|
2075
|
+
if (seen.has(key)) continue;
|
|
2076
|
+
seen.add(key);
|
|
2077
|
+
unique.push({ title, url });
|
|
2078
|
+
}
|
|
2079
|
+
return unique;
|
|
2080
|
+
}
|
|
2081
|
+
function extractSources(response) {
|
|
2082
|
+
const searchResults = response.search_results?.flatMap((result) => {
|
|
2083
|
+
const url = result.url?.trim() ?? "";
|
|
2084
|
+
if (!url) {
|
|
2085
|
+
return [];
|
|
2086
|
+
}
|
|
2087
|
+
return [{ title: result.title?.trim() ?? url, url }];
|
|
2088
|
+
}) ?? [];
|
|
2089
|
+
if (searchResults.length > 0) {
|
|
2090
|
+
return searchResults;
|
|
2091
|
+
}
|
|
2092
|
+
return response.citations?.flatMap((citation) => {
|
|
2093
|
+
const url = citation?.trim() ?? "";
|
|
2094
|
+
return url ? [{ title: url, url }] : [];
|
|
2095
|
+
}) ?? [];
|
|
2096
|
+
}
|
|
2097
|
+
function buildRequestOptions(context) {
|
|
2098
|
+
return context.signal ? { signal: context.signal } : void 0;
|
|
2099
|
+
}
|
|
1642
2100
|
|
|
1643
2101
|
// src/providers/parallel.ts
|
|
1644
2102
|
import Parallel from "parallel-web";
|
|
@@ -1678,12 +2136,13 @@ var ParallelProvider = class {
|
|
|
1678
2136
|
}
|
|
1679
2137
|
return { available: true, summary: "enabled" };
|
|
1680
2138
|
}
|
|
1681
|
-
async search(query2, maxResults, config, context) {
|
|
2139
|
+
async search(query2, maxResults, options, config, context) {
|
|
1682
2140
|
const client = this.createClient(config);
|
|
1683
2141
|
const defaults = asJsonObject(config.defaults?.search);
|
|
1684
2142
|
context.onProgress?.(`Searching Parallel for: ${query2}`);
|
|
1685
2143
|
const response = await client.beta.search({
|
|
1686
2144
|
...defaults,
|
|
2145
|
+
...options ?? {},
|
|
1687
2146
|
objective: query2,
|
|
1688
2147
|
max_results: maxResults
|
|
1689
2148
|
});
|
|
@@ -1781,7 +2240,7 @@ var ValyuProvider = class {
|
|
|
1781
2240
|
}
|
|
1782
2241
|
return { available: true, summary: "enabled" };
|
|
1783
2242
|
}
|
|
1784
|
-
async search(query2, maxResults, config, context) {
|
|
2243
|
+
async search(query2, maxResults, searchOptions, config, context) {
|
|
1785
2244
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
1786
2245
|
if (!apiKey) {
|
|
1787
2246
|
throw new Error("Valyu is missing an API key.");
|
|
@@ -1789,6 +2248,7 @@ var ValyuProvider = class {
|
|
|
1789
2248
|
const client = new Valyu(apiKey, config.baseUrl);
|
|
1790
2249
|
const options = {
|
|
1791
2250
|
...asJsonObject(config.defaults),
|
|
2251
|
+
...searchOptions ?? {},
|
|
1792
2252
|
maxNumResults: maxResults
|
|
1793
2253
|
};
|
|
1794
2254
|
context.onProgress?.(`Searching Valyu for: ${query2}`);
|
|
@@ -1938,6 +2398,7 @@ var PROVIDERS = [
|
|
|
1938
2398
|
new CodexProvider(),
|
|
1939
2399
|
new ExaProvider(),
|
|
1940
2400
|
new GeminiProvider(),
|
|
2401
|
+
new PerplexityProvider(),
|
|
1941
2402
|
new ParallelProvider(),
|
|
1942
2403
|
new ValyuProvider()
|
|
1943
2404
|
];
|
|
@@ -1946,8 +2407,9 @@ var PROVIDER_MAP = {
|
|
|
1946
2407
|
codex: PROVIDERS[1],
|
|
1947
2408
|
exa: PROVIDERS[2],
|
|
1948
2409
|
gemini: PROVIDERS[3],
|
|
1949
|
-
|
|
1950
|
-
|
|
2410
|
+
perplexity: PROVIDERS[4],
|
|
2411
|
+
parallel: PROVIDERS[5],
|
|
2412
|
+
valyu: PROVIDERS[6]
|
|
1951
2413
|
};
|
|
1952
2414
|
|
|
1953
2415
|
// src/provider-resolution.ts
|
|
@@ -2025,6 +2487,7 @@ var PROVIDER_IDS = [
|
|
|
2025
2487
|
"codex",
|
|
2026
2488
|
"exa",
|
|
2027
2489
|
"gemini",
|
|
2490
|
+
"perplexity",
|
|
2028
2491
|
"parallel",
|
|
2029
2492
|
"valyu"
|
|
2030
2493
|
];
|
|
@@ -2093,6 +2556,7 @@ function registerWebSearchTool(pi, providerIds) {
|
|
|
2093
2556
|
description: `Maximum number of results to return (default: ${DEFAULT_MAX_RESULTS})`
|
|
2094
2557
|
})
|
|
2095
2558
|
),
|
|
2559
|
+
options: jsonOptionsSchema("Provider-specific search options."),
|
|
2096
2560
|
provider: providerEnum(
|
|
2097
2561
|
visibleProviderIds,
|
|
2098
2562
|
"Provider override. If omitted, uses the active configured provider or falls back to Codex for search when it is not explicitly disabled."
|
|
@@ -2109,6 +2573,7 @@ function registerWebSearchTool(pi, providerIds) {
|
|
|
2109
2573
|
const response = await provider.search(
|
|
2110
2574
|
params.query,
|
|
2111
2575
|
maxResults,
|
|
2576
|
+
normalizeOptions(params.options),
|
|
2112
2577
|
providerConfig,
|
|
2113
2578
|
{
|
|
2114
2579
|
cwd: ctx.cwd,
|
|
@@ -2671,6 +3136,11 @@ function buildProviderMenuOptions(providerId) {
|
|
|
2671
3136
|
"Search model",
|
|
2672
3137
|
"Model used for Gemini search interactions."
|
|
2673
3138
|
);
|
|
3139
|
+
pushText(
|
|
3140
|
+
"geminiContentsModel",
|
|
3141
|
+
"Contents model",
|
|
3142
|
+
"Model used for Gemini URL content extraction via URL Context."
|
|
3143
|
+
);
|
|
2674
3144
|
pushText(
|
|
2675
3145
|
"geminiAnswerModel",
|
|
2676
3146
|
"Answer model",
|
|
@@ -2683,6 +3153,9 @@ function buildProviderMenuOptions(providerId) {
|
|
|
2683
3153
|
);
|
|
2684
3154
|
return options;
|
|
2685
3155
|
}
|
|
3156
|
+
if (providerId === "perplexity") {
|
|
3157
|
+
return options;
|
|
3158
|
+
}
|
|
2686
3159
|
if (providerId === "parallel") {
|
|
2687
3160
|
pushValues(
|
|
2688
3161
|
"parallelSearchMode",
|
|
@@ -2862,7 +3335,7 @@ var WebProvidersSettingsView = class {
|
|
|
2862
3335
|
) : key === "model" || key === "additionalDirectories" ? getCodexTextSettingValue(
|
|
2863
3336
|
providerConfig,
|
|
2864
3337
|
key
|
|
2865
|
-
) : key === "geminiSearchModel" || key === "geminiAnswerModel" || key === "geminiResearchAgent" ? getGeminiTextSettingValue(
|
|
3338
|
+
) : key === "geminiSearchModel" || key === "geminiContentsModel" || key === "geminiAnswerModel" || key === "geminiResearchAgent" ? getGeminiTextSettingValue(
|
|
2866
3339
|
providerConfig,
|
|
2867
3340
|
key
|
|
2868
3341
|
) : getProviderStringValue(
|
|
@@ -3002,7 +3475,7 @@ var WebProvidersSettingsView = class {
|
|
|
3002
3475
|
id
|
|
3003
3476
|
);
|
|
3004
3477
|
}
|
|
3005
|
-
if (id === "geminiSearchModel" || id === "geminiAnswerModel" || id === "geminiResearchAgent") {
|
|
3478
|
+
if (id === "geminiSearchModel" || id === "geminiContentsModel" || id === "geminiAnswerModel" || id === "geminiResearchAgent") {
|
|
3006
3479
|
return getGeminiTextSettingValue(
|
|
3007
3480
|
providerConfig,
|
|
3008
3481
|
id
|
|
@@ -3271,6 +3744,7 @@ function getGeminiTextSettingValue(config, key) {
|
|
|
3271
3744
|
const defaults = config?.defaults;
|
|
3272
3745
|
if (!defaults) return void 0;
|
|
3273
3746
|
if (key === "geminiSearchModel") return defaults.searchModel;
|
|
3747
|
+
if (key === "geminiContentsModel") return defaults.contentsModel;
|
|
3274
3748
|
if (key === "geminiAnswerModel") return defaults.answerModel;
|
|
3275
3749
|
return defaults.researchAgent;
|
|
3276
3750
|
}
|
|
@@ -3442,6 +3916,14 @@ function applyGeminiSettingChange(target, key, value) {
|
|
|
3442
3916
|
);
|
|
3443
3917
|
cleanupGeminiDefaults(target);
|
|
3444
3918
|
return true;
|
|
3919
|
+
case "geminiContentsModel":
|
|
3920
|
+
assignOptionalString(
|
|
3921
|
+
target.defaults,
|
|
3922
|
+
"contentsModel",
|
|
3923
|
+
value
|
|
3924
|
+
);
|
|
3925
|
+
cleanupGeminiDefaults(target);
|
|
3926
|
+
return true;
|
|
3445
3927
|
case "geminiAnswerModel":
|
|
3446
3928
|
assignOptionalString(
|
|
3447
3929
|
target.defaults,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-providers",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Configurable web access extension for pi that routes search, contents, answers, and research across Claude, Codex, Exa, Gemini, Parallel, and Valyu 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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"codex",
|
|
18
18
|
"exa",
|
|
19
19
|
"gemini",
|
|
20
|
+
"perplexity",
|
|
20
21
|
"parallel",
|
|
21
22
|
"valyu"
|
|
22
23
|
],
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
]
|
|
39
40
|
},
|
|
40
41
|
"scripts": {
|
|
41
|
-
"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:exa-js --external:parallel-web --external:valyu-js",
|
|
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",
|
|
42
43
|
"prepare": "npm run build",
|
|
43
44
|
"prepack": "npm run build",
|
|
44
45
|
"check": "tsc --noEmit",
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
"@anthropic-ai/claude-agent-sdk": "^0.2.71",
|
|
52
53
|
"@google/genai": "^1.44.0",
|
|
53
54
|
"@openai/codex-sdk": "^0.111.0",
|
|
55
|
+
"@perplexity-ai/perplexity_ai": "^0.26.1",
|
|
54
56
|
"exa-js": "^2.7.0",
|
|
55
57
|
"parallel-web": "^0.3.1",
|
|
56
58
|
"valyu-js": "^2.5.9",
|