pi-web-providers 0.1.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 +118 -41
- package/dist/index.js +1669 -324
- package/package.json +9 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join as join4 } from "node:path";
|
|
2
5
|
import {
|
|
3
6
|
DEFAULT_MAX_BYTES,
|
|
4
7
|
DEFAULT_MAX_LINES,
|
|
@@ -6,33 +9,31 @@ import {
|
|
|
6
9
|
keyHint,
|
|
7
10
|
truncateHead
|
|
8
11
|
} from "@mariozechner/pi-coding-agent";
|
|
9
|
-
import { StringEnum } from "@mariozechner/pi-ai";
|
|
10
12
|
import {
|
|
11
13
|
Editor,
|
|
12
|
-
Key,
|
|
13
|
-
Text,
|
|
14
14
|
getEditorKeybindings,
|
|
15
|
+
Key,
|
|
15
16
|
matchesKey,
|
|
17
|
+
Text,
|
|
16
18
|
truncateToWidth,
|
|
17
19
|
visibleWidth,
|
|
18
20
|
wrapTextWithAnsi
|
|
19
21
|
} from "@mariozechner/pi-tui";
|
|
20
22
|
import { Type } from "@sinclair/typebox";
|
|
21
|
-
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
22
|
-
import { tmpdir } from "node:os";
|
|
23
|
-
import { join as join2 } from "node:path";
|
|
24
23
|
|
|
25
24
|
// src/config.ts
|
|
26
|
-
import { getAgentDir } from "@mariozechner/pi-coding-agent";
|
|
27
25
|
import { execSync } from "node:child_process";
|
|
28
26
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
29
27
|
import { dirname, join } from "node:path";
|
|
28
|
+
import { getAgentDir } from "@mariozechner/pi-coding-agent";
|
|
30
29
|
|
|
31
30
|
// src/provider-tools.ts
|
|
32
31
|
var PROVIDER_TOOLS = {
|
|
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
|
};
|
|
@@ -76,6 +77,7 @@ var LEGACY_TOOL_ALIASES = {
|
|
|
76
77
|
};
|
|
77
78
|
var CONFIG_FILE_NAME = "web-providers.json";
|
|
78
79
|
var VERSION = 1;
|
|
80
|
+
var commandValueCache = /* @__PURE__ */ new Map();
|
|
79
81
|
function getConfigPath() {
|
|
80
82
|
return join(getAgentDir(), CONFIG_FILE_NAME);
|
|
81
83
|
}
|
|
@@ -115,11 +117,26 @@ function serializeConfig(config) {
|
|
|
115
117
|
function resolveConfigValue(reference) {
|
|
116
118
|
if (!reference) return void 0;
|
|
117
119
|
if (reference.startsWith("!")) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
const cached = commandValueCache.get(reference);
|
|
121
|
+
if (cached) {
|
|
122
|
+
if (cached.errorMessage) {
|
|
123
|
+
throw new Error(cached.errorMessage);
|
|
124
|
+
}
|
|
125
|
+
return cached.value;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const output = execSync(reference.slice(1), {
|
|
129
|
+
encoding: "utf-8",
|
|
130
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
131
|
+
}).trim();
|
|
132
|
+
const value = output.length > 0 ? output : void 0;
|
|
133
|
+
commandValueCache.set(reference, { value });
|
|
134
|
+
return value;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const errorMessage = error.message;
|
|
137
|
+
commandValueCache.set(reference, { errorMessage });
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
123
140
|
}
|
|
124
141
|
const envValue = process.env[reference];
|
|
125
142
|
if (envValue !== void 0) {
|
|
@@ -158,6 +175,12 @@ function normalizeConfig(raw, source) {
|
|
|
158
175
|
throw new Error(`'providers' in ${source} must be a JSON object.`);
|
|
159
176
|
}
|
|
160
177
|
config.providers = {};
|
|
178
|
+
if (raw.providers.claude !== void 0) {
|
|
179
|
+
config.providers.claude = normalizeClaudeProvider(
|
|
180
|
+
raw.providers.claude,
|
|
181
|
+
source
|
|
182
|
+
);
|
|
183
|
+
}
|
|
161
184
|
if (raw.providers.codex !== void 0) {
|
|
162
185
|
config.providers.codex = normalizeCodexProvider(
|
|
163
186
|
raw.providers.codex,
|
|
@@ -173,6 +196,12 @@ function normalizeConfig(raw, source) {
|
|
|
173
196
|
source
|
|
174
197
|
);
|
|
175
198
|
}
|
|
199
|
+
if (raw.providers.perplexity !== void 0) {
|
|
200
|
+
config.providers.perplexity = normalizePerplexityProvider(
|
|
201
|
+
raw.providers.perplexity,
|
|
202
|
+
source
|
|
203
|
+
);
|
|
204
|
+
}
|
|
176
205
|
if (raw.providers.parallel !== void 0) {
|
|
177
206
|
config.providers.parallel = normalizeParallelProvider(
|
|
178
207
|
raw.providers.parallel,
|
|
@@ -186,7 +215,7 @@ function normalizeConfig(raw, source) {
|
|
|
186
215
|
);
|
|
187
216
|
}
|
|
188
217
|
const unknownProviders = Object.keys(raw.providers).filter(
|
|
189
|
-
(key) => 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"
|
|
190
219
|
);
|
|
191
220
|
if (unknownProviders.length > 0) {
|
|
192
221
|
throw new Error(
|
|
@@ -196,6 +225,50 @@ function normalizeConfig(raw, source) {
|
|
|
196
225
|
}
|
|
197
226
|
return config;
|
|
198
227
|
}
|
|
228
|
+
function normalizeClaudeProvider(raw, source) {
|
|
229
|
+
const provider = parseProviderObject(raw, source, "claude");
|
|
230
|
+
const defaults = parseOptionalJsonObject(
|
|
231
|
+
provider.defaults,
|
|
232
|
+
source,
|
|
233
|
+
"providers.claude.defaults"
|
|
234
|
+
);
|
|
235
|
+
return {
|
|
236
|
+
enabled: parseOptionalBoolean(
|
|
237
|
+
provider.enabled,
|
|
238
|
+
source,
|
|
239
|
+
"providers.claude.enabled"
|
|
240
|
+
),
|
|
241
|
+
tools: parseOptionalProviderTools(
|
|
242
|
+
"claude",
|
|
243
|
+
provider.tools,
|
|
244
|
+
source,
|
|
245
|
+
"providers.claude.tools"
|
|
246
|
+
),
|
|
247
|
+
pathToClaudeCodeExecutable: parseOptionalString(
|
|
248
|
+
provider.pathToClaudeCodeExecutable,
|
|
249
|
+
source,
|
|
250
|
+
"providers.claude.pathToClaudeCodeExecutable"
|
|
251
|
+
),
|
|
252
|
+
defaults: defaults === void 0 ? void 0 : {
|
|
253
|
+
model: parseOptionalString(
|
|
254
|
+
defaults.model,
|
|
255
|
+
source,
|
|
256
|
+
"providers.claude.defaults.model"
|
|
257
|
+
),
|
|
258
|
+
effort: parseOptionalLiteral(
|
|
259
|
+
defaults.effort,
|
|
260
|
+
source,
|
|
261
|
+
"providers.claude.defaults.effort",
|
|
262
|
+
["low", "medium", "high", "max"]
|
|
263
|
+
),
|
|
264
|
+
maxTurns: parseOptionalInteger(
|
|
265
|
+
defaults.maxTurns,
|
|
266
|
+
source,
|
|
267
|
+
"providers.claude.defaults.maxTurns"
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
199
272
|
function normalizeCodexProvider(raw, source) {
|
|
200
273
|
const provider = parseProviderObject(raw, source, "codex");
|
|
201
274
|
const defaults = parseOptionalJsonObject(
|
|
@@ -369,6 +442,11 @@ function normalizeGeminiProvider(raw, source) {
|
|
|
369
442
|
source,
|
|
370
443
|
"providers.gemini.defaults.searchModel"
|
|
371
444
|
),
|
|
445
|
+
contentsModel: parseOptionalString(
|
|
446
|
+
defaults.contentsModel,
|
|
447
|
+
source,
|
|
448
|
+
"providers.gemini.defaults.contentsModel"
|
|
449
|
+
),
|
|
372
450
|
answerModel: parseOptionalString(
|
|
373
451
|
defaults.answerModel,
|
|
374
452
|
source,
|
|
@@ -382,6 +460,54 @@ function normalizeGeminiProvider(raw, source) {
|
|
|
382
460
|
}
|
|
383
461
|
};
|
|
384
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
|
+
}
|
|
385
511
|
function normalizeParallelProvider(raw, source) {
|
|
386
512
|
const provider = parseProviderObject(raw, source, "parallel");
|
|
387
513
|
const defaults = parseOptionalJsonObject(
|
|
@@ -450,18 +576,14 @@ function parseOptionalProviderTools(providerId, value, source, field) {
|
|
|
450
576
|
continue;
|
|
451
577
|
}
|
|
452
578
|
if (!supportsProviderTool(providerId, normalizedKey)) {
|
|
453
|
-
throw new Error(
|
|
454
|
-
`Unknown tools for ${providerId} in ${source}: ${key}.`
|
|
455
|
-
);
|
|
579
|
+
throw new Error(`Unknown tools for ${providerId} in ${source}: ${key}.`);
|
|
456
580
|
}
|
|
457
581
|
parsed[normalizedKey] = parseBoolean(entry, source, `${field}.${key}`);
|
|
458
582
|
}
|
|
459
|
-
const unknownTools = Object.keys(value).filter(
|
|
460
|
-
(toolId)
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
);
|
|
583
|
+
const unknownTools = Object.keys(value).filter((toolId) => {
|
|
584
|
+
const normalizedKey = normalizeProviderToolKey(providerId, toolId);
|
|
585
|
+
return normalizedKey !== null && !PROVIDER_TOOLS[providerId].includes(normalizedKey);
|
|
586
|
+
});
|
|
465
587
|
if (unknownTools.length > 0) {
|
|
466
588
|
throw new Error(
|
|
467
589
|
`Unknown tools for ${providerId} in ${source}: ${unknownTools.join(", ")}.`
|
|
@@ -522,6 +644,13 @@ function parseString(value, source, field) {
|
|
|
522
644
|
}
|
|
523
645
|
return value;
|
|
524
646
|
}
|
|
647
|
+
function parseOptionalInteger(value, source, field) {
|
|
648
|
+
if (value === void 0) return void 0;
|
|
649
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
650
|
+
throw new Error(`'${field}' in ${source} must be a positive integer.`);
|
|
651
|
+
}
|
|
652
|
+
return value;
|
|
653
|
+
}
|
|
525
654
|
function parseOptionalLiteral(value, source, field, allowed) {
|
|
526
655
|
if (value === void 0) return void 0;
|
|
527
656
|
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
@@ -535,8 +664,14 @@ function isPlainObject(value) {
|
|
|
535
664
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
536
665
|
}
|
|
537
666
|
|
|
538
|
-
// src/providers/
|
|
539
|
-
import {
|
|
667
|
+
// src/providers/claude.ts
|
|
668
|
+
import { execFileSync } from "node:child_process";
|
|
669
|
+
import { existsSync } from "node:fs";
|
|
670
|
+
import { createRequire } from "node:module";
|
|
671
|
+
import { dirname as dirname2, extname, join as join2 } from "node:path";
|
|
672
|
+
import {
|
|
673
|
+
query
|
|
674
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
540
675
|
|
|
541
676
|
// src/providers/shared.ts
|
|
542
677
|
function trimSnippet(input, maxLength = 300) {
|
|
@@ -551,8 +686,9 @@ function formatJson(value) {
|
|
|
551
686
|
return JSON.stringify(value, null, 2);
|
|
552
687
|
}
|
|
553
688
|
|
|
554
|
-
// src/providers/
|
|
555
|
-
var
|
|
689
|
+
// src/providers/claude.ts
|
|
690
|
+
var require2 = createRequire(import.meta.url);
|
|
691
|
+
var SEARCH_OUTPUT_SCHEMA = {
|
|
556
692
|
type: "object",
|
|
557
693
|
additionalProperties: false,
|
|
558
694
|
properties: {
|
|
@@ -572,20 +708,36 @@ var OUTPUT_SCHEMA = {
|
|
|
572
708
|
},
|
|
573
709
|
required: ["sources"]
|
|
574
710
|
};
|
|
575
|
-
var
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
711
|
+
var ANSWER_OUTPUT_SCHEMA = {
|
|
712
|
+
type: "object",
|
|
713
|
+
additionalProperties: false,
|
|
714
|
+
properties: {
|
|
715
|
+
answer: { type: "string" },
|
|
716
|
+
sources: {
|
|
717
|
+
type: "array",
|
|
718
|
+
items: {
|
|
719
|
+
type: "object",
|
|
720
|
+
additionalProperties: false,
|
|
721
|
+
properties: {
|
|
722
|
+
title: { type: "string" },
|
|
723
|
+
url: { type: "string" }
|
|
724
|
+
},
|
|
725
|
+
required: ["title", "url"]
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
required: ["answer", "sources"]
|
|
730
|
+
};
|
|
731
|
+
var ClaudeProvider = class {
|
|
732
|
+
id = "claude";
|
|
733
|
+
label = "Claude";
|
|
734
|
+
docsUrl = "https://github.com/anthropics/claude-agent-sdk-typescript";
|
|
579
735
|
createTemplate() {
|
|
580
736
|
return {
|
|
581
|
-
enabled:
|
|
737
|
+
enabled: false,
|
|
582
738
|
tools: {
|
|
583
|
-
search: true
|
|
584
|
-
|
|
585
|
-
defaults: {
|
|
586
|
-
networkAccessEnabled: true,
|
|
587
|
-
webSearchEnabled: true,
|
|
588
|
-
webSearchMode: "live"
|
|
739
|
+
search: true,
|
|
740
|
+
answer: true
|
|
589
741
|
}
|
|
590
742
|
};
|
|
591
743
|
}
|
|
@@ -596,103 +748,555 @@ var CodexProvider = class {
|
|
|
596
748
|
if (config.enabled === false) {
|
|
597
749
|
return { available: false, summary: "disabled" };
|
|
598
750
|
}
|
|
751
|
+
const executablePath = resolveClaudeExecutablePath(config);
|
|
752
|
+
if (executablePath && !existsSync(executablePath)) {
|
|
753
|
+
return { available: false, summary: "missing Claude Code executable" };
|
|
754
|
+
}
|
|
755
|
+
const authStatus = getClaudeAuthStatus(executablePath);
|
|
756
|
+
if (!authStatus.loggedIn) {
|
|
757
|
+
return { available: false, summary: "missing Claude auth" };
|
|
758
|
+
}
|
|
599
759
|
return { available: true, summary: "enabled" };
|
|
600
760
|
}
|
|
601
|
-
async search(
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
"You are performing web research for another coding agent.",
|
|
623
|
-
"Search the public web and return only a JSON object matching the provided schema.",
|
|
624
|
-
"Do not include markdown fences or extra commentary.",
|
|
625
|
-
`Return at most ${maxResults} sources.`,
|
|
626
|
-
"Prefer primary or official sources when they are available.",
|
|
627
|
-
"Each snippet should be short and specific.",
|
|
628
|
-
"",
|
|
629
|
-
`User query: ${query}`
|
|
630
|
-
].join("\n");
|
|
631
|
-
const streamed = await thread.runStreamed(prompt, {
|
|
632
|
-
outputSchema: OUTPUT_SCHEMA,
|
|
633
|
-
signal: context.signal
|
|
634
|
-
});
|
|
635
|
-
let finalResponse = "";
|
|
636
|
-
const seenQueries = /* @__PURE__ */ new Set();
|
|
637
|
-
for await (const event of streamed.events) {
|
|
638
|
-
handleProgressEvent(event, seenQueries, context.onProgress);
|
|
639
|
-
if (event.type === "item.completed" && event.item.type === "agent_message") {
|
|
640
|
-
finalResponse = event.item.text;
|
|
641
|
-
}
|
|
642
|
-
if (event.type === "turn.failed") {
|
|
643
|
-
throw new Error(event.error.message);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
const parsed = parseOutput(finalResponse);
|
|
761
|
+
async search(queryText, maxResults, options, config, context) {
|
|
762
|
+
const output = parseClaudeSearchOutput(
|
|
763
|
+
await this.runStructuredQuery({
|
|
764
|
+
prompt: [
|
|
765
|
+
"You are performing web research for another coding agent.",
|
|
766
|
+
"Use the WebSearch tool to search the public web.",
|
|
767
|
+
"Return only a JSON object matching the provided schema.",
|
|
768
|
+
"Do not include markdown fences or extra commentary.",
|
|
769
|
+
`Return at most ${maxResults} sources.`,
|
|
770
|
+
"Each snippet should be short, factual, and specific to the result.",
|
|
771
|
+
"Prefer primary or official sources when they are available.",
|
|
772
|
+
"",
|
|
773
|
+
`User query: ${queryText}`
|
|
774
|
+
].join("\n"),
|
|
775
|
+
schema: SEARCH_OUTPUT_SCHEMA,
|
|
776
|
+
tools: ["WebSearch"],
|
|
777
|
+
config,
|
|
778
|
+
context,
|
|
779
|
+
options
|
|
780
|
+
})
|
|
781
|
+
);
|
|
647
782
|
return {
|
|
648
783
|
provider: this.id,
|
|
649
|
-
results:
|
|
784
|
+
results: output.sources.slice(0, maxResults).map((source) => ({
|
|
650
785
|
title: source.title.trim(),
|
|
651
786
|
url: source.url.trim(),
|
|
652
787
|
snippet: trimSnippet(source.snippet)
|
|
653
788
|
}))
|
|
654
789
|
};
|
|
655
790
|
}
|
|
791
|
+
async answer(queryText, options, config, context) {
|
|
792
|
+
const output = parseClaudeAnswerOutput(
|
|
793
|
+
await this.runStructuredQuery({
|
|
794
|
+
prompt: [
|
|
795
|
+
"Answer the user's question using current public web information.",
|
|
796
|
+
"Use WebSearch to find relevant sources and WebFetch when you need to verify important details.",
|
|
797
|
+
"Return only a JSON object matching the provided schema.",
|
|
798
|
+
"Do not include markdown fences or extra commentary.",
|
|
799
|
+
"Keep the answer concise but informative.",
|
|
800
|
+
"Only cite sources you actually used.",
|
|
801
|
+
"",
|
|
802
|
+
`User query: ${queryText}`
|
|
803
|
+
].join("\n"),
|
|
804
|
+
schema: ANSWER_OUTPUT_SCHEMA,
|
|
805
|
+
tools: ["WebSearch", "WebFetch"],
|
|
806
|
+
config,
|
|
807
|
+
context,
|
|
808
|
+
options
|
|
809
|
+
})
|
|
810
|
+
);
|
|
811
|
+
const lines = [];
|
|
812
|
+
lines.push(output.answer.trim() || "No answer returned.");
|
|
813
|
+
if (output.sources.length > 0) {
|
|
814
|
+
lines.push("");
|
|
815
|
+
lines.push("Sources:");
|
|
816
|
+
for (const [index, source] of output.sources.entries()) {
|
|
817
|
+
lines.push(`${index + 1}. ${source.title}`);
|
|
818
|
+
lines.push(` ${source.url}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
provider: this.id,
|
|
823
|
+
text: lines.join("\n").trimEnd(),
|
|
824
|
+
summary: `Answer via Claude with ${output.sources.length} source(s)`,
|
|
825
|
+
itemCount: output.sources.length
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
async runStructuredQuery({
|
|
829
|
+
prompt,
|
|
830
|
+
schema,
|
|
831
|
+
tools,
|
|
832
|
+
config,
|
|
833
|
+
context,
|
|
834
|
+
options
|
|
835
|
+
}) {
|
|
836
|
+
const abortController = new AbortController();
|
|
837
|
+
if (context.signal?.aborted) {
|
|
838
|
+
abortController.abort(context.signal.reason);
|
|
839
|
+
}
|
|
840
|
+
const onAbort = () => {
|
|
841
|
+
abortController.abort(context.signal?.reason);
|
|
842
|
+
};
|
|
843
|
+
context.signal?.addEventListener("abort", onAbort, { once: true });
|
|
844
|
+
const stream = query({
|
|
845
|
+
prompt,
|
|
846
|
+
options: {
|
|
847
|
+
abortController,
|
|
848
|
+
allowedTools: tools,
|
|
849
|
+
cwd: context.cwd,
|
|
850
|
+
...getClaudeRuntimeOptions(config, options),
|
|
851
|
+
outputFormat: {
|
|
852
|
+
type: "json_schema",
|
|
853
|
+
schema
|
|
854
|
+
},
|
|
855
|
+
pathToClaudeCodeExecutable: config.pathToClaudeCodeExecutable,
|
|
856
|
+
persistSession: false,
|
|
857
|
+
permissionMode: "dontAsk",
|
|
858
|
+
systemPrompt: {
|
|
859
|
+
type: "preset",
|
|
860
|
+
preset: "claude_code",
|
|
861
|
+
append: "Use only the provided web tools. Always produce output that matches the requested JSON schema exactly."
|
|
862
|
+
},
|
|
863
|
+
tools
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
const seenToolUseIds = /* @__PURE__ */ new Set();
|
|
867
|
+
let finalResult;
|
|
868
|
+
try {
|
|
869
|
+
for await (const message of stream) {
|
|
870
|
+
handleProgressMessage(message, seenToolUseIds, context.onProgress);
|
|
871
|
+
if (message.type === "result") {
|
|
872
|
+
finalResult = message;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
} finally {
|
|
876
|
+
context.signal?.removeEventListener("abort", onAbort);
|
|
877
|
+
stream.close();
|
|
878
|
+
}
|
|
879
|
+
if (!finalResult) {
|
|
880
|
+
throw new Error("Claude returned no result.");
|
|
881
|
+
}
|
|
882
|
+
if (finalResult.subtype !== "success") {
|
|
883
|
+
throw new Error(
|
|
884
|
+
finalResult.errors.join("\n") || `Claude query failed (${finalResult.subtype}).`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
return parseStructuredOutput(finalResult);
|
|
888
|
+
}
|
|
656
889
|
};
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
890
|
+
var CLAUDE_AUTH_CACHE_TTL_MS = 5e3;
|
|
891
|
+
var defaultClaudeExecutablePath;
|
|
892
|
+
var claudeAuthStatusCache = /* @__PURE__ */ new Map();
|
|
893
|
+
function resolveClaudeExecutablePath(config) {
|
|
894
|
+
if (config.pathToClaudeCodeExecutable) {
|
|
895
|
+
return config.pathToClaudeCodeExecutable;
|
|
662
896
|
}
|
|
897
|
+
if (defaultClaudeExecutablePath !== void 0) {
|
|
898
|
+
return defaultClaudeExecutablePath;
|
|
899
|
+
}
|
|
900
|
+
try {
|
|
901
|
+
const sdkEntryPath = require2.resolve("@anthropic-ai/claude-agent-sdk");
|
|
902
|
+
defaultClaudeExecutablePath = join2(dirname2(sdkEntryPath), "cli.js");
|
|
903
|
+
} catch {
|
|
904
|
+
defaultClaudeExecutablePath = void 0;
|
|
905
|
+
}
|
|
906
|
+
return defaultClaudeExecutablePath;
|
|
663
907
|
}
|
|
664
|
-
function
|
|
665
|
-
if (!
|
|
666
|
-
|
|
908
|
+
function getClaudeAuthStatus(executablePath) {
|
|
909
|
+
if (!executablePath) {
|
|
910
|
+
return { loggedIn: false };
|
|
911
|
+
}
|
|
912
|
+
const cachedStatus = claudeAuthStatusCache.get(executablePath);
|
|
913
|
+
if (cachedStatus && Date.now() - cachedStatus.checkedAt < CLAUDE_AUTH_CACHE_TTL_MS) {
|
|
914
|
+
return { loggedIn: cachedStatus.loggedIn };
|
|
667
915
|
}
|
|
916
|
+
const [command, ...args] = getClaudeAuthCommand(executablePath);
|
|
668
917
|
try {
|
|
669
|
-
|
|
918
|
+
const stdout = execFileSync(command, args, {
|
|
919
|
+
encoding: "utf8",
|
|
920
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
921
|
+
});
|
|
922
|
+
return cacheClaudeAuthStatus(executablePath, parseClaudeAuthStatus(stdout));
|
|
923
|
+
} catch (error) {
|
|
924
|
+
const stdout = getExecOutput(
|
|
925
|
+
error.stdout
|
|
926
|
+
);
|
|
927
|
+
if (stdout) {
|
|
928
|
+
return cacheClaudeAuthStatus(
|
|
929
|
+
executablePath,
|
|
930
|
+
parseClaudeAuthStatus(stdout)
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
return cacheClaudeAuthStatus(executablePath, { loggedIn: false });
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
function cacheClaudeAuthStatus(executablePath, status) {
|
|
937
|
+
claudeAuthStatusCache.set(executablePath, {
|
|
938
|
+
...status,
|
|
939
|
+
checkedAt: Date.now()
|
|
940
|
+
});
|
|
941
|
+
return status;
|
|
942
|
+
}
|
|
943
|
+
function getClaudeAuthCommand(executablePath) {
|
|
944
|
+
const extension = extname(executablePath);
|
|
945
|
+
if (extension === ".js" || extension === ".cjs" || extension === ".mjs") {
|
|
946
|
+
return [process.execPath, executablePath, "auth", "status", "--json"];
|
|
947
|
+
}
|
|
948
|
+
return [executablePath, "auth", "status", "--json"];
|
|
949
|
+
}
|
|
950
|
+
function getExecOutput(output) {
|
|
951
|
+
if (typeof output === "string") {
|
|
952
|
+
return output;
|
|
953
|
+
}
|
|
954
|
+
if (Buffer.isBuffer(output)) {
|
|
955
|
+
return output.toString("utf8");
|
|
956
|
+
}
|
|
957
|
+
return "";
|
|
958
|
+
}
|
|
959
|
+
function parseClaudeAuthStatus(raw) {
|
|
960
|
+
try {
|
|
961
|
+
const parsed = JSON.parse(raw);
|
|
962
|
+
return { loggedIn: parsed.loggedIn === true };
|
|
670
963
|
} catch {
|
|
671
|
-
|
|
964
|
+
return { loggedIn: false };
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function handleProgressMessage(message, seenToolUseIds, onProgress) {
|
|
968
|
+
if (!onProgress || message.type !== "tool_progress") {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (seenToolUseIds.has(message.tool_use_id)) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
seenToolUseIds.add(message.tool_use_id);
|
|
975
|
+
onProgress(`Claude ${formatToolName(message.tool_name)}`);
|
|
976
|
+
}
|
|
977
|
+
function formatToolName(toolName) {
|
|
978
|
+
if (toolName === "WebSearch") return "web search";
|
|
979
|
+
if (toolName === "WebFetch") return "web fetch";
|
|
980
|
+
return toolName;
|
|
981
|
+
}
|
|
982
|
+
function parseStructuredOutput(result) {
|
|
983
|
+
if (result.subtype !== "success") {
|
|
984
|
+
throw new Error("Claude query did not succeed.");
|
|
985
|
+
}
|
|
986
|
+
if (result.structured_output !== void 0) {
|
|
987
|
+
return result.structured_output;
|
|
988
|
+
}
|
|
989
|
+
if (!result.result.trim()) {
|
|
990
|
+
throw new Error("Claude returned an empty response.");
|
|
991
|
+
}
|
|
992
|
+
try {
|
|
993
|
+
return JSON.parse(result.result);
|
|
994
|
+
} catch {
|
|
995
|
+
const match = result.result.match(/\{[\s\S]*\}/);
|
|
672
996
|
if (!match) {
|
|
673
|
-
throw new Error("
|
|
997
|
+
throw new Error("Claude returned invalid JSON output.");
|
|
674
998
|
}
|
|
675
999
|
return JSON.parse(match[0]);
|
|
676
1000
|
}
|
|
677
1001
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
+
}
|
|
1036
|
+
function parseClaudeSearchOutput(value) {
|
|
1037
|
+
const sources = readArray(value, "sources").map((entry) => ({
|
|
1038
|
+
title: readString(entry, "title"),
|
|
1039
|
+
url: readString(entry, "url"),
|
|
1040
|
+
snippet: readString(entry, "snippet")
|
|
1041
|
+
}));
|
|
1042
|
+
return { sources };
|
|
1043
|
+
}
|
|
1044
|
+
function parseClaudeAnswerOutput(value) {
|
|
1045
|
+
return {
|
|
1046
|
+
answer: readString(value, "answer"),
|
|
1047
|
+
sources: readArray(value, "sources").map((entry) => ({
|
|
1048
|
+
title: readString(entry, "title"),
|
|
1049
|
+
url: readString(entry, "url")
|
|
1050
|
+
}))
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
function readArray(value, key) {
|
|
1054
|
+
if (typeof value !== "object" || value === null || !(key in value)) {
|
|
1055
|
+
throw new Error(`Claude output is missing '${key}'.`);
|
|
1056
|
+
}
|
|
1057
|
+
const entry = value[key];
|
|
1058
|
+
if (!Array.isArray(entry)) {
|
|
1059
|
+
throw new Error(`Claude output field '${key}' must be an array.`);
|
|
1060
|
+
}
|
|
1061
|
+
return entry;
|
|
1062
|
+
}
|
|
1063
|
+
function readString(value, key) {
|
|
1064
|
+
if (typeof value !== "object" || value === null || !(key in value)) {
|
|
1065
|
+
throw new Error(`Claude output is missing '${key}'.`);
|
|
1066
|
+
}
|
|
1067
|
+
const entry = value[key];
|
|
1068
|
+
if (typeof entry !== "string") {
|
|
1069
|
+
throw new Error(`Claude output field '${key}' must be a string.`);
|
|
1070
|
+
}
|
|
1071
|
+
return entry;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// src/providers/codex.ts
|
|
1075
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
1076
|
+
import { homedir } from "node:os";
|
|
1077
|
+
import { join as join3 } from "node:path";
|
|
1078
|
+
import { Codex } from "@openai/codex-sdk";
|
|
1079
|
+
var OUTPUT_SCHEMA = {
|
|
1080
|
+
type: "object",
|
|
1081
|
+
additionalProperties: false,
|
|
1082
|
+
properties: {
|
|
1083
|
+
sources: {
|
|
1084
|
+
type: "array",
|
|
1085
|
+
items: {
|
|
1086
|
+
type: "object",
|
|
1087
|
+
additionalProperties: false,
|
|
1088
|
+
properties: {
|
|
1089
|
+
title: { type: "string" },
|
|
1090
|
+
url: { type: "string" },
|
|
1091
|
+
snippet: { type: "string" }
|
|
1092
|
+
},
|
|
1093
|
+
required: ["title", "url", "snippet"]
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
},
|
|
1097
|
+
required: ["sources"]
|
|
1098
|
+
};
|
|
1099
|
+
var CodexProvider = class {
|
|
1100
|
+
id = "codex";
|
|
1101
|
+
label = "Codex";
|
|
1102
|
+
docsUrl = "https://github.com/openai/codex/tree/main/sdk/typescript";
|
|
1103
|
+
createTemplate() {
|
|
1104
|
+
return {
|
|
1105
|
+
enabled: true,
|
|
1106
|
+
tools: {
|
|
1107
|
+
search: true
|
|
1108
|
+
},
|
|
1109
|
+
defaults: {
|
|
1110
|
+
networkAccessEnabled: true,
|
|
1111
|
+
webSearchEnabled: true,
|
|
1112
|
+
webSearchMode: "live"
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
getStatus(config, _cwd) {
|
|
1117
|
+
if (!config) {
|
|
1118
|
+
return { available: false, summary: "not configured" };
|
|
1119
|
+
}
|
|
1120
|
+
if (config.enabled === false) {
|
|
1121
|
+
return { available: false, summary: "disabled" };
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
new Codex({
|
|
1125
|
+
codexPathOverride: config.codexPath,
|
|
1126
|
+
config: config.config
|
|
1127
|
+
});
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
return {
|
|
1130
|
+
available: false,
|
|
1131
|
+
summary: error.message
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
if (!hasCodexCredentials(config)) {
|
|
1135
|
+
return { available: false, summary: "missing Codex auth" };
|
|
1136
|
+
}
|
|
1137
|
+
return { available: true, summary: "enabled" };
|
|
1138
|
+
}
|
|
1139
|
+
async search(query2, maxResults, options, config, context) {
|
|
1140
|
+
const codex = new Codex({
|
|
1141
|
+
codexPathOverride: config.codexPath,
|
|
1142
|
+
baseUrl: config.baseUrl,
|
|
1143
|
+
apiKey: resolveConfigValue(config.apiKey),
|
|
1144
|
+
config: config.config,
|
|
1145
|
+
env: resolveEnvMap(config.env)
|
|
1146
|
+
});
|
|
1147
|
+
const thread = codex.startThread(
|
|
1148
|
+
buildCodexSearchThreadOptions(config, context.cwd, options)
|
|
1149
|
+
);
|
|
1150
|
+
const prompt = [
|
|
1151
|
+
"You are performing web research for another coding agent.",
|
|
1152
|
+
"Search the public web and return only a JSON object matching the provided schema.",
|
|
1153
|
+
"Do not include markdown fences or extra commentary.",
|
|
1154
|
+
`Return at most ${maxResults} sources.`,
|
|
1155
|
+
"Prefer primary or official sources when they are available.",
|
|
1156
|
+
"Each snippet should be short and specific.",
|
|
1157
|
+
"",
|
|
1158
|
+
`User query: ${query2}`
|
|
1159
|
+
].join("\n");
|
|
1160
|
+
const streamed = await thread.runStreamed(prompt, {
|
|
1161
|
+
outputSchema: OUTPUT_SCHEMA,
|
|
1162
|
+
signal: context.signal
|
|
1163
|
+
});
|
|
1164
|
+
let finalResponse = "";
|
|
1165
|
+
const seenQueries = /* @__PURE__ */ new Set();
|
|
1166
|
+
for await (const event of streamed.events) {
|
|
1167
|
+
handleProgressEvent(event, seenQueries, context.onProgress);
|
|
1168
|
+
if (event.type === "item.completed" && event.item.type === "agent_message") {
|
|
1169
|
+
finalResponse = event.item.text;
|
|
1170
|
+
}
|
|
1171
|
+
if (event.type === "turn.failed") {
|
|
1172
|
+
throw new Error(event.error.message);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
const parsed = parseOutput(finalResponse);
|
|
1176
|
+
return {
|
|
1177
|
+
provider: this.id,
|
|
1178
|
+
results: parsed.sources.slice(0, maxResults).map((source) => ({
|
|
1179
|
+
title: source.title.trim(),
|
|
1180
|
+
url: source.url.trim(),
|
|
1181
|
+
snippet: trimSnippet(source.snippet)
|
|
1182
|
+
}))
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
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
|
+
}
|
|
1230
|
+
function hasCodexCredentials(config) {
|
|
1231
|
+
if (hasConfiguredReference(config.apiKey)) {
|
|
1232
|
+
return true;
|
|
1233
|
+
}
|
|
1234
|
+
if (hasConfiguredReference(config.env?.CODEX_API_KEY) || hasConfiguredReference(config.env?.OPENAI_API_KEY)) {
|
|
1235
|
+
return true;
|
|
1236
|
+
}
|
|
1237
|
+
if (!config.env) {
|
|
1238
|
+
const inheritedKey = process.env.CODEX_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
1239
|
+
if (typeof inheritedKey === "string" && inheritedKey.trim().length > 0) {
|
|
1240
|
+
return true;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return existsSync2(join3(homedir(), ".codex", "auth.json"));
|
|
1244
|
+
}
|
|
1245
|
+
function hasConfiguredReference(reference) {
|
|
1246
|
+
if (!reference) {
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
if (reference.startsWith("!")) {
|
|
1250
|
+
return reference.slice(1).trim().length > 0;
|
|
1251
|
+
}
|
|
1252
|
+
const envValue = process.env[reference];
|
|
1253
|
+
if (typeof envValue === "string") {
|
|
1254
|
+
return envValue.trim().length > 0;
|
|
1255
|
+
}
|
|
1256
|
+
if (/^[A-Z][A-Z0-9_]*$/.test(reference)) {
|
|
1257
|
+
return false;
|
|
1258
|
+
}
|
|
1259
|
+
return reference.trim().length > 0;
|
|
1260
|
+
}
|
|
1261
|
+
function handleProgressEvent(event, seenQueries, onProgress) {
|
|
1262
|
+
if (!onProgress) return;
|
|
1263
|
+
if (event.type === "item.completed" && event.item.type === "web_search" && !seenQueries.has(event.item.query)) {
|
|
1264
|
+
seenQueries.add(event.item.query);
|
|
1265
|
+
onProgress(`Codex web search ${seenQueries.size}: ${event.item.query}`);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
function parseOutput(raw) {
|
|
1269
|
+
if (!raw.trim()) {
|
|
1270
|
+
throw new Error("Codex returned an empty response.");
|
|
1271
|
+
}
|
|
1272
|
+
try {
|
|
1273
|
+
return JSON.parse(raw);
|
|
1274
|
+
} catch {
|
|
1275
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
1276
|
+
if (!match) {
|
|
1277
|
+
throw new Error("Codex returned invalid JSON output.");
|
|
1278
|
+
}
|
|
1279
|
+
return JSON.parse(match[0]);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// src/providers/exa.ts
|
|
1284
|
+
import { Exa } from "exa-js";
|
|
1285
|
+
var ExaProvider = class {
|
|
1286
|
+
id = "exa";
|
|
1287
|
+
label = "Exa";
|
|
1288
|
+
docsUrl = "https://exa.ai/docs/sdks/typescript-sdk-specification";
|
|
1289
|
+
createTemplate() {
|
|
1290
|
+
return {
|
|
1291
|
+
enabled: false,
|
|
1292
|
+
tools: {
|
|
1293
|
+
search: true,
|
|
1294
|
+
contents: true,
|
|
1295
|
+
answer: true,
|
|
1296
|
+
research: true
|
|
1297
|
+
},
|
|
1298
|
+
apiKey: "EXA_API_KEY",
|
|
1299
|
+
defaults: {
|
|
696
1300
|
type: "auto",
|
|
697
1301
|
contents: {
|
|
698
1302
|
text: true
|
|
@@ -713,7 +1317,7 @@ var ExaProvider = class {
|
|
|
713
1317
|
}
|
|
714
1318
|
return { available: true, summary: "enabled" };
|
|
715
1319
|
}
|
|
716
|
-
async search(
|
|
1320
|
+
async search(query2, maxResults, searchOptions, config, context) {
|
|
717
1321
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
718
1322
|
if (!apiKey) {
|
|
719
1323
|
throw new Error("Exa is missing an API key.");
|
|
@@ -721,10 +1325,11 @@ var ExaProvider = class {
|
|
|
721
1325
|
const client = new Exa(apiKey, config.baseUrl);
|
|
722
1326
|
const options = {
|
|
723
1327
|
...asJsonObject(config.defaults),
|
|
1328
|
+
...searchOptions ?? {},
|
|
724
1329
|
numResults: maxResults
|
|
725
1330
|
};
|
|
726
|
-
context.onProgress?.(`Searching Exa for: ${
|
|
727
|
-
const response = await client.search(
|
|
1331
|
+
context.onProgress?.(`Searching Exa for: ${query2}`);
|
|
1332
|
+
const response = await client.search(query2, options);
|
|
728
1333
|
return {
|
|
729
1334
|
provider: this.id,
|
|
730
1335
|
results: (response.results ?? []).slice(0, maxResults).map((result) => ({
|
|
@@ -743,11 +1348,15 @@ var ExaProvider = class {
|
|
|
743
1348
|
throw new Error("Exa is missing an API key.");
|
|
744
1349
|
}
|
|
745
1350
|
const client = new Exa(apiKey, config.baseUrl);
|
|
746
|
-
context.onProgress?.(
|
|
1351
|
+
context.onProgress?.(
|
|
1352
|
+
`Fetching contents from Exa for ${urls.length} URL(s)`
|
|
1353
|
+
);
|
|
747
1354
|
const response = await client.getContents(urls, options);
|
|
748
1355
|
const lines = [];
|
|
749
1356
|
for (const [index, result] of (response.results ?? []).entries()) {
|
|
750
|
-
lines.push(
|
|
1357
|
+
lines.push(
|
|
1358
|
+
`${index + 1}. ${String(result.title ?? result.url ?? "Untitled")}`
|
|
1359
|
+
);
|
|
751
1360
|
lines.push(` ${String(result.url ?? "")}`);
|
|
752
1361
|
const summary = typeof result.summary === "string" ? result.summary : result.summary ? formatJson(result.summary) : void 0;
|
|
753
1362
|
const text = typeof result.text === "string" ? result.text : Array.isArray(result.highlights) ? result.highlights.join(" ") : "";
|
|
@@ -764,14 +1373,14 @@ var ExaProvider = class {
|
|
|
764
1373
|
itemCount: response.results?.length ?? 0
|
|
765
1374
|
};
|
|
766
1375
|
}
|
|
767
|
-
async answer(
|
|
1376
|
+
async answer(query2, options, config, context) {
|
|
768
1377
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
769
1378
|
if (!apiKey) {
|
|
770
1379
|
throw new Error("Exa is missing an API key.");
|
|
771
1380
|
}
|
|
772
1381
|
const client = new Exa(apiKey, config.baseUrl);
|
|
773
|
-
context.onProgress?.(`Getting Exa answer for: ${
|
|
774
|
-
const response = await client.answer(
|
|
1382
|
+
context.onProgress?.(`Getting Exa answer for: ${query2}`);
|
|
1383
|
+
const response = await client.answer(query2, options);
|
|
775
1384
|
const lines = [];
|
|
776
1385
|
lines.push(
|
|
777
1386
|
typeof response.answer === "string" ? response.answer : formatJson(response.answer)
|
|
@@ -781,7 +1390,9 @@ var ExaProvider = class {
|
|
|
781
1390
|
lines.push("");
|
|
782
1391
|
lines.push("Sources:");
|
|
783
1392
|
for (const [index, citation] of citations.entries()) {
|
|
784
|
-
lines.push(
|
|
1393
|
+
lines.push(
|
|
1394
|
+
`${index + 1}. ${String(citation.title ?? citation.url ?? "Untitled")}`
|
|
1395
|
+
);
|
|
785
1396
|
lines.push(` ${String(citation.url ?? "")}`);
|
|
786
1397
|
}
|
|
787
1398
|
}
|
|
@@ -823,6 +1434,7 @@ var ExaProvider = class {
|
|
|
823
1434
|
// src/providers/gemini.ts
|
|
824
1435
|
import { GoogleGenAI } from "@google/genai";
|
|
825
1436
|
var DEFAULT_SEARCH_MODEL = "gemini-2.5-flash";
|
|
1437
|
+
var DEFAULT_CONTENTS_MODEL = "gemini-2.5-flash";
|
|
826
1438
|
var DEFAULT_ANSWER_MODEL = "gemini-2.5-flash";
|
|
827
1439
|
var DEFAULT_RESEARCH_AGENT = "deep-research-pro-preview-12-2025";
|
|
828
1440
|
var DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
@@ -835,12 +1447,14 @@ var GeminiProvider = class {
|
|
|
835
1447
|
enabled: false,
|
|
836
1448
|
tools: {
|
|
837
1449
|
search: true,
|
|
1450
|
+
contents: true,
|
|
838
1451
|
answer: true,
|
|
839
1452
|
research: true
|
|
840
1453
|
},
|
|
841
1454
|
apiKey: "GOOGLE_API_KEY",
|
|
842
1455
|
defaults: {
|
|
843
1456
|
searchModel: DEFAULT_SEARCH_MODEL,
|
|
1457
|
+
contentsModel: DEFAULT_CONTENTS_MODEL,
|
|
844
1458
|
answerModel: DEFAULT_ANSWER_MODEL,
|
|
845
1459
|
researchAgent: DEFAULT_RESEARCH_AGENT
|
|
846
1460
|
}
|
|
@@ -859,39 +1473,90 @@ var GeminiProvider = class {
|
|
|
859
1473
|
}
|
|
860
1474
|
return { available: true, summary: "enabled" };
|
|
861
1475
|
}
|
|
862
|
-
async search(
|
|
1476
|
+
async search(query2, maxResults, options, config, context) {
|
|
863
1477
|
const ai = this.createClient(config);
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1478
|
+
const request = buildGeminiSearchRequest(
|
|
1479
|
+
query2,
|
|
1480
|
+
config.defaults?.searchModel ?? DEFAULT_SEARCH_MODEL,
|
|
1481
|
+
options
|
|
1482
|
+
);
|
|
1483
|
+
context.onProgress?.(`Searching Gemini for: ${query2}`);
|
|
1484
|
+
const interaction = await createSearchInteraction(ai, request);
|
|
1485
|
+
const results = await Promise.all(
|
|
1486
|
+
extractGoogleSearchResults(interaction.outputs).slice(0, maxResults).map(async (result) => {
|
|
1487
|
+
const resolvedUrl = await resolveGoogleSearchUrl(result.url);
|
|
1488
|
+
return {
|
|
1489
|
+
title: result.title ?? resolvedUrl ?? result.url ?? "Untitled",
|
|
1490
|
+
url: resolvedUrl ?? result.url ?? "",
|
|
1491
|
+
snippet: ""
|
|
1492
|
+
};
|
|
1493
|
+
})
|
|
1494
|
+
);
|
|
879
1495
|
return {
|
|
880
1496
|
provider: this.id,
|
|
881
1497
|
results
|
|
882
1498
|
};
|
|
883
1499
|
}
|
|
884
|
-
async
|
|
1500
|
+
async contents(urls, options, config, context) {
|
|
885
1501
|
const ai = this.createClient(config);
|
|
886
|
-
|
|
887
|
-
|
|
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
|
+
});
|
|
888
1514
|
const response = await ai.models.generateContent({
|
|
889
|
-
model,
|
|
890
|
-
contents:
|
|
891
|
-
config:
|
|
892
|
-
|
|
893
|
-
|
|
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
|
+
}
|
|
894
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
|
+
}
|
|
1547
|
+
async answer(query2, options, config, context) {
|
|
1548
|
+
const ai = this.createClient(config);
|
|
1549
|
+
const request = buildGeminiGenerateContentRequest({
|
|
1550
|
+
defaultModel: config.defaults?.answerModel ?? DEFAULT_ANSWER_MODEL,
|
|
1551
|
+
prompt: query2,
|
|
1552
|
+
options,
|
|
1553
|
+
toolConfig: { googleSearch: {} }
|
|
1554
|
+
});
|
|
1555
|
+
context.onProgress?.(`Getting Gemini answer for: ${query2}`);
|
|
1556
|
+
const response = await ai.models.generateContent({
|
|
1557
|
+
model: request.model,
|
|
1558
|
+
contents: request.contents,
|
|
1559
|
+
config: request.config
|
|
895
1560
|
});
|
|
896
1561
|
const lines = [];
|
|
897
1562
|
lines.push(response.text?.trim() || "No answer returned.");
|
|
@@ -903,7 +1568,9 @@ var GeminiProvider = class {
|
|
|
903
1568
|
lines.push("Sources:");
|
|
904
1569
|
for (const [index, source] of sources.entries()) {
|
|
905
1570
|
lines.push(`${index + 1}. ${source.title}`);
|
|
906
|
-
|
|
1571
|
+
if (source.url) {
|
|
1572
|
+
lines.push(` ${source.url}`);
|
|
1573
|
+
}
|
|
907
1574
|
}
|
|
908
1575
|
}
|
|
909
1576
|
return {
|
|
@@ -917,7 +1584,11 @@ var GeminiProvider = class {
|
|
|
917
1584
|
const ai = this.createClient(config);
|
|
918
1585
|
const agent = config.defaults?.researchAgent ?? DEFAULT_RESEARCH_AGENT;
|
|
919
1586
|
const pollIntervalMs = getPollInterval(options);
|
|
920
|
-
const requestOptions =
|
|
1587
|
+
const requestOptions = getGeminiResearchRequestOptions(
|
|
1588
|
+
stripPollIntervalOption(options)
|
|
1589
|
+
);
|
|
1590
|
+
const startedAt = Date.now();
|
|
1591
|
+
let lastStatus;
|
|
921
1592
|
context.onProgress?.("Starting Gemini deep research");
|
|
922
1593
|
const initialInteraction = await ai.interactions.create({
|
|
923
1594
|
...requestOptions,
|
|
@@ -931,7 +1602,13 @@ var GeminiProvider = class {
|
|
|
931
1602
|
throw new Error("Gemini research aborted.");
|
|
932
1603
|
}
|
|
933
1604
|
const interaction = await ai.interactions.get(initialInteraction.id);
|
|
934
|
-
|
|
1605
|
+
const now = Date.now();
|
|
1606
|
+
if (interaction.status !== lastStatus) {
|
|
1607
|
+
context.onProgress?.(
|
|
1608
|
+
`Gemini research status: ${interaction.status} (${formatElapsed(now - startedAt)} elapsed)`
|
|
1609
|
+
);
|
|
1610
|
+
lastStatus = interaction.status;
|
|
1611
|
+
}
|
|
935
1612
|
if (interaction.status === "completed") {
|
|
936
1613
|
const text = formatInteractionOutputs(interaction.outputs);
|
|
937
1614
|
return {
|
|
@@ -943,112 +1620,482 @@ var GeminiProvider = class {
|
|
|
943
1620
|
if (interaction.status === "failed" || interaction.status === "cancelled") {
|
|
944
1621
|
throw new Error(`Gemini research ${interaction.status}.`);
|
|
945
1622
|
}
|
|
946
|
-
await sleep(pollIntervalMs, context.signal);
|
|
1623
|
+
await sleep(pollIntervalMs, context.signal);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
createClient(config) {
|
|
1627
|
+
const apiKey = resolveConfigValue(config.apiKey);
|
|
1628
|
+
if (!apiKey) {
|
|
1629
|
+
throw new Error("Gemini is missing an API key.");
|
|
1630
|
+
}
|
|
1631
|
+
return new GoogleGenAI({
|
|
1632
|
+
apiKey,
|
|
1633
|
+
apiVersion: config.defaults?.apiVersion
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
function extractGoogleSearchResults(outputs) {
|
|
1638
|
+
const results = [];
|
|
1639
|
+
if (!Array.isArray(outputs)) {
|
|
1640
|
+
return results;
|
|
1641
|
+
}
|
|
1642
|
+
for (const output of outputs) {
|
|
1643
|
+
if (typeof output !== "object" || output === null) {
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
const content = output;
|
|
1647
|
+
if (content.type !== "google_search_result") {
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
const items = Array.isArray(content.result) ? content.result : [];
|
|
1651
|
+
for (const item of items) {
|
|
1652
|
+
if (typeof item !== "object" || item === null) {
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
const record = item;
|
|
1656
|
+
results.push({
|
|
1657
|
+
title: typeof record.title === "string" ? record.title : void 0,
|
|
1658
|
+
url: typeof record.url === "string" ? record.url : void 0,
|
|
1659
|
+
rendered_content: typeof record.rendered_content === "string" ? record.rendered_content : void 0
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return results;
|
|
1664
|
+
}
|
|
1665
|
+
function extractGroundingSources(chunks) {
|
|
1666
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1667
|
+
const sources = [];
|
|
1668
|
+
const maxSources = 5;
|
|
1669
|
+
if (!Array.isArray(chunks)) {
|
|
1670
|
+
return sources;
|
|
1671
|
+
}
|
|
1672
|
+
for (const chunk of chunks) {
|
|
1673
|
+
const web = typeof chunk === "object" && chunk !== null && "web" in chunk && typeof chunk.web === "object" && chunk.web !== null ? chunk.web : void 0;
|
|
1674
|
+
if (!web) continue;
|
|
1675
|
+
const rawUrl = typeof web.uri === "string" ? web.uri : "";
|
|
1676
|
+
const title = formatGroundingSourceTitle(
|
|
1677
|
+
typeof web.title === "string" ? web.title : rawUrl,
|
|
1678
|
+
rawUrl
|
|
1679
|
+
);
|
|
1680
|
+
const url = formatGroundingSourceUrl(rawUrl);
|
|
1681
|
+
const key = [title.toLowerCase(), url.toLowerCase()].join("::");
|
|
1682
|
+
if (seen.has(key)) continue;
|
|
1683
|
+
seen.add(key);
|
|
1684
|
+
sources.push({
|
|
1685
|
+
title,
|
|
1686
|
+
url
|
|
1687
|
+
});
|
|
1688
|
+
if (sources.length >= maxSources) {
|
|
1689
|
+
break;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
return sources;
|
|
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
|
+
}
|
|
1719
|
+
function formatInteractionOutputs(outputs) {
|
|
1720
|
+
const lines = [];
|
|
1721
|
+
if (!Array.isArray(outputs)) {
|
|
1722
|
+
return "";
|
|
1723
|
+
}
|
|
1724
|
+
for (const output of outputs) {
|
|
1725
|
+
if (typeof output === "object" && output !== null && "type" in output && output.type === "text" && "text" in output && typeof output.text === "string") {
|
|
1726
|
+
const text = output.text.trim();
|
|
1727
|
+
if (text) {
|
|
1728
|
+
lines.push(text);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
return lines.join("\n\n").trim();
|
|
1733
|
+
}
|
|
1734
|
+
function formatGroundingSourceTitle(title, url) {
|
|
1735
|
+
const trimmedTitle = title?.trim();
|
|
1736
|
+
if (trimmedTitle) {
|
|
1737
|
+
return trimmedTitle;
|
|
1738
|
+
}
|
|
1739
|
+
if (url) {
|
|
1740
|
+
try {
|
|
1741
|
+
return new URL(url).hostname;
|
|
1742
|
+
} catch {
|
|
1743
|
+
return url;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return "Untitled";
|
|
1747
|
+
}
|
|
1748
|
+
function formatGroundingSourceUrl(url) {
|
|
1749
|
+
if (!url) {
|
|
1750
|
+
return "";
|
|
1751
|
+
}
|
|
1752
|
+
if (isGoogleGroundingRedirect(url)) {
|
|
1753
|
+
return "";
|
|
1754
|
+
}
|
|
1755
|
+
return url;
|
|
1756
|
+
}
|
|
1757
|
+
function isGoogleGroundingRedirect(url) {
|
|
1758
|
+
try {
|
|
1759
|
+
const parsed = new URL(url);
|
|
1760
|
+
return parsed.hostname === "vertexaisearch.cloud.google.com" && parsed.pathname.startsWith("/grounding-api-redirect/");
|
|
1761
|
+
} catch {
|
|
1762
|
+
return false;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
async function createSearchInteraction(ai, request) {
|
|
1766
|
+
const forcedRequest = {
|
|
1767
|
+
...request,
|
|
1768
|
+
...request.generation_config ? {
|
|
1769
|
+
generation_config: {
|
|
1770
|
+
...request.generation_config,
|
|
1771
|
+
tool_choice: "any"
|
|
1772
|
+
}
|
|
1773
|
+
} : {
|
|
1774
|
+
generation_config: {
|
|
1775
|
+
tool_choice: "any"
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
try {
|
|
1780
|
+
return await ai.interactions.create(forcedRequest);
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
if (!isBuiltInToolChoiceError(error)) {
|
|
1783
|
+
throw error;
|
|
1784
|
+
}
|
|
1785
|
+
const fallbackGenerationConfig = stripToolChoice(request.generation_config);
|
|
1786
|
+
return ai.interactions.create({
|
|
1787
|
+
...request,
|
|
1788
|
+
...fallbackGenerationConfig ? { generation_config: fallbackGenerationConfig } : {}
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function isBuiltInToolChoiceError(error) {
|
|
1793
|
+
if (error instanceof Error) {
|
|
1794
|
+
return error.message.includes(
|
|
1795
|
+
"Function calling config is set without function_declarations"
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
1799
|
+
return error.message.includes(
|
|
1800
|
+
"Function calling config is set without function_declarations"
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
return false;
|
|
1804
|
+
}
|
|
1805
|
+
async function resolveGoogleSearchUrl(url) {
|
|
1806
|
+
if (!url) {
|
|
1807
|
+
return void 0;
|
|
1808
|
+
}
|
|
1809
|
+
if (!isGoogleGroundingRedirect(url)) {
|
|
1810
|
+
return url;
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
const response = await fetch(url, {
|
|
1814
|
+
method: "HEAD",
|
|
1815
|
+
redirect: "manual"
|
|
1816
|
+
});
|
|
1817
|
+
return response.headers.get("location") || url;
|
|
1818
|
+
} catch {
|
|
1819
|
+
return url;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
async function sleep(ms, signal) {
|
|
1823
|
+
if (signal?.aborted) {
|
|
1824
|
+
throw new Error("Operation aborted.");
|
|
1825
|
+
}
|
|
1826
|
+
await new Promise((resolve, reject) => {
|
|
1827
|
+
const timer = setTimeout(() => {
|
|
1828
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1829
|
+
resolve();
|
|
1830
|
+
}, ms);
|
|
1831
|
+
const onAbort = () => {
|
|
1832
|
+
clearTimeout(timer);
|
|
1833
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1834
|
+
reject(new Error("Operation aborted."));
|
|
1835
|
+
};
|
|
1836
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
function formatElapsed(ms) {
|
|
1840
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
1841
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
1842
|
+
const seconds = totalSeconds % 60;
|
|
1843
|
+
if (minutes > 0) {
|
|
1844
|
+
return `${minutes}m ${seconds}s`;
|
|
1845
|
+
}
|
|
1846
|
+
return `${totalSeconds}s`;
|
|
1847
|
+
}
|
|
1848
|
+
function getPollInterval(options) {
|
|
1849
|
+
const raw = options?.pollIntervalMs;
|
|
1850
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw >= 1e3) {
|
|
1851
|
+
return Math.trunc(raw);
|
|
1852
|
+
}
|
|
1853
|
+
return DEFAULT_POLL_INTERVAL_MS;
|
|
1854
|
+
}
|
|
1855
|
+
function stripPollIntervalOption(options) {
|
|
1856
|
+
if (!options || !Object.hasOwn(options, "pollIntervalMs")) {
|
|
1857
|
+
return options;
|
|
1858
|
+
}
|
|
1859
|
+
const { pollIntervalMs: _ignored, ...rest } = options;
|
|
1860
|
+
return rest;
|
|
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
|
+
}
|
|
947
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
|
+
};
|
|
948
2032
|
}
|
|
949
2033
|
createClient(config) {
|
|
950
2034
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
951
2035
|
if (!apiKey) {
|
|
952
|
-
throw new Error("
|
|
2036
|
+
throw new Error("Perplexity is missing an API key.");
|
|
953
2037
|
}
|
|
954
|
-
return new
|
|
2038
|
+
return new Perplexity({
|
|
955
2039
|
apiKey,
|
|
956
|
-
|
|
2040
|
+
baseURL: resolveConfigValue(config.baseUrl)
|
|
957
2041
|
});
|
|
958
2042
|
}
|
|
959
2043
|
};
|
|
960
|
-
function
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
return results;
|
|
2044
|
+
function resolveModel(optionModel, defaultModel, fallbackModel) {
|
|
2045
|
+
if (typeof optionModel === "string" && optionModel.trim().length > 0) {
|
|
2046
|
+
return optionModel;
|
|
964
2047
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
continue;
|
|
968
|
-
}
|
|
969
|
-
const content = output;
|
|
970
|
-
if (content.type !== "google_search_result") {
|
|
971
|
-
continue;
|
|
972
|
-
}
|
|
973
|
-
const items = Array.isArray(content.result) ? content.result : [];
|
|
974
|
-
for (const item of items) {
|
|
975
|
-
if (typeof item !== "object" || item === null) {
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
const record = item;
|
|
979
|
-
results.push({
|
|
980
|
-
title: typeof record.title === "string" ? record.title : void 0,
|
|
981
|
-
url: typeof record.url === "string" ? record.url : void 0,
|
|
982
|
-
rendered_content: typeof record.rendered_content === "string" ? record.rendered_content : void 0
|
|
983
|
-
});
|
|
984
|
-
}
|
|
2048
|
+
if (typeof defaultModel === "string" && defaultModel.trim().length > 0) {
|
|
2049
|
+
return defaultModel;
|
|
985
2050
|
}
|
|
986
|
-
return
|
|
2051
|
+
return fallbackModel;
|
|
987
2052
|
}
|
|
988
|
-
function
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
if (!Array.isArray(chunks)) {
|
|
992
|
-
return sources;
|
|
993
|
-
}
|
|
994
|
-
for (const chunk of chunks) {
|
|
995
|
-
const web = typeof chunk === "object" && chunk !== null && "web" in chunk && typeof chunk.web === "object" && chunk.web !== null ? chunk.web : void 0;
|
|
996
|
-
if (!web) continue;
|
|
997
|
-
const url = typeof web.uri === "string" ? web.uri : void 0;
|
|
998
|
-
if (!url || seen.has(url)) continue;
|
|
999
|
-
seen.add(url);
|
|
1000
|
-
sources.push({
|
|
1001
|
-
title: typeof web.title === "string" ? web.title : url,
|
|
1002
|
-
url
|
|
1003
|
-
});
|
|
2053
|
+
function extractMessageText(content) {
|
|
2054
|
+
if (typeof content === "string") {
|
|
2055
|
+
return content.trim();
|
|
1004
2056
|
}
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1007
|
-
function formatInteractionOutputs(outputs) {
|
|
1008
|
-
const lines = [];
|
|
1009
|
-
if (!Array.isArray(outputs)) {
|
|
2057
|
+
if (!Array.isArray(content)) {
|
|
1010
2058
|
return "";
|
|
1011
2059
|
}
|
|
1012
|
-
|
|
1013
|
-
if (typeof
|
|
1014
|
-
|
|
1015
|
-
if (text) {
|
|
1016
|
-
lines.push(text);
|
|
1017
|
-
}
|
|
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()];
|
|
1018
2063
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
2064
|
+
return [];
|
|
2065
|
+
}).filter((text) => text.length > 0).join("\n\n").trim();
|
|
1021
2066
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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 });
|
|
1025
2078
|
}
|
|
1026
|
-
|
|
1027
|
-
const timer = setTimeout(() => {
|
|
1028
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1029
|
-
resolve();
|
|
1030
|
-
}, ms);
|
|
1031
|
-
const onAbort = () => {
|
|
1032
|
-
clearTimeout(timer);
|
|
1033
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1034
|
-
reject(new Error("Operation aborted."));
|
|
1035
|
-
};
|
|
1036
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1037
|
-
});
|
|
2079
|
+
return unique;
|
|
1038
2080
|
}
|
|
1039
|
-
function
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
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;
|
|
1043
2091
|
}
|
|
1044
|
-
return
|
|
2092
|
+
return response.citations?.flatMap((citation) => {
|
|
2093
|
+
const url = citation?.trim() ?? "";
|
|
2094
|
+
return url ? [{ title: url, url }] : [];
|
|
2095
|
+
}) ?? [];
|
|
1045
2096
|
}
|
|
1046
|
-
function
|
|
1047
|
-
|
|
1048
|
-
return options;
|
|
1049
|
-
}
|
|
1050
|
-
const { pollIntervalMs: _ignored, ...rest } = options;
|
|
1051
|
-
return rest;
|
|
2097
|
+
function buildRequestOptions(context) {
|
|
2098
|
+
return context.signal ? { signal: context.signal } : void 0;
|
|
1052
2099
|
}
|
|
1053
2100
|
|
|
1054
2101
|
// src/providers/parallel.ts
|
|
@@ -1089,13 +2136,14 @@ var ParallelProvider = class {
|
|
|
1089
2136
|
}
|
|
1090
2137
|
return { available: true, summary: "enabled" };
|
|
1091
2138
|
}
|
|
1092
|
-
async search(
|
|
2139
|
+
async search(query2, maxResults, options, config, context) {
|
|
1093
2140
|
const client = this.createClient(config);
|
|
1094
2141
|
const defaults = asJsonObject(config.defaults?.search);
|
|
1095
|
-
context.onProgress?.(`Searching Parallel for: ${
|
|
2142
|
+
context.onProgress?.(`Searching Parallel for: ${query2}`);
|
|
1096
2143
|
const response = await client.beta.search({
|
|
1097
2144
|
...defaults,
|
|
1098
|
-
|
|
2145
|
+
...options ?? {},
|
|
2146
|
+
objective: query2,
|
|
1099
2147
|
max_results: maxResults
|
|
1100
2148
|
});
|
|
1101
2149
|
return {
|
|
@@ -1192,7 +2240,7 @@ var ValyuProvider = class {
|
|
|
1192
2240
|
}
|
|
1193
2241
|
return { available: true, summary: "enabled" };
|
|
1194
2242
|
}
|
|
1195
|
-
async search(
|
|
2243
|
+
async search(query2, maxResults, searchOptions, config, context) {
|
|
1196
2244
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
1197
2245
|
if (!apiKey) {
|
|
1198
2246
|
throw new Error("Valyu is missing an API key.");
|
|
@@ -1200,10 +2248,11 @@ var ValyuProvider = class {
|
|
|
1200
2248
|
const client = new Valyu(apiKey, config.baseUrl);
|
|
1201
2249
|
const options = {
|
|
1202
2250
|
...asJsonObject(config.defaults),
|
|
2251
|
+
...searchOptions ?? {},
|
|
1203
2252
|
maxNumResults: maxResults
|
|
1204
2253
|
};
|
|
1205
|
-
context.onProgress?.(`Searching Valyu for: ${
|
|
1206
|
-
const response = await client.search(
|
|
2254
|
+
context.onProgress?.(`Searching Valyu for: ${query2}`);
|
|
2255
|
+
const response = await client.search(query2, options);
|
|
1207
2256
|
if (!response.success) {
|
|
1208
2257
|
throw new Error(response.error || "Valyu search failed.");
|
|
1209
2258
|
}
|
|
@@ -1225,7 +2274,9 @@ var ValyuProvider = class {
|
|
|
1225
2274
|
throw new Error("Valyu is missing an API key.");
|
|
1226
2275
|
}
|
|
1227
2276
|
const client = new Valyu(apiKey, config.baseUrl);
|
|
1228
|
-
context.onProgress?.(
|
|
2277
|
+
context.onProgress?.(
|
|
2278
|
+
`Fetching contents from Valyu for ${urls.length} URL(s)`
|
|
2279
|
+
);
|
|
1229
2280
|
const response = await client.contents(urls, options);
|
|
1230
2281
|
const finalResponse = "jobId" in response ? await client.waitForJob(response.jobId, {
|
|
1231
2282
|
onProgress: (status) => context.onProgress?.(
|
|
@@ -1257,14 +2308,14 @@ var ValyuProvider = class {
|
|
|
1257
2308
|
itemCount: results.length
|
|
1258
2309
|
};
|
|
1259
2310
|
}
|
|
1260
|
-
async answer(
|
|
2311
|
+
async answer(query2, options, config, context) {
|
|
1261
2312
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
1262
2313
|
if (!apiKey) {
|
|
1263
2314
|
throw new Error("Valyu is missing an API key.");
|
|
1264
2315
|
}
|
|
1265
2316
|
const client = new Valyu(apiKey, config.baseUrl);
|
|
1266
|
-
context.onProgress?.(`Getting Valyu answer for: ${
|
|
1267
|
-
const response = await client.answer(
|
|
2317
|
+
context.onProgress?.(`Getting Valyu answer for: ${query2}`);
|
|
2318
|
+
const response = await client.answer(query2, {
|
|
1268
2319
|
...options ?? {},
|
|
1269
2320
|
streaming: false
|
|
1270
2321
|
});
|
|
@@ -1343,45 +2394,57 @@ var ValyuProvider = class {
|
|
|
1343
2394
|
|
|
1344
2395
|
// src/providers/index.ts
|
|
1345
2396
|
var PROVIDERS = [
|
|
2397
|
+
new ClaudeProvider(),
|
|
1346
2398
|
new CodexProvider(),
|
|
1347
2399
|
new ExaProvider(),
|
|
1348
2400
|
new GeminiProvider(),
|
|
2401
|
+
new PerplexityProvider(),
|
|
1349
2402
|
new ParallelProvider(),
|
|
1350
2403
|
new ValyuProvider()
|
|
1351
2404
|
];
|
|
1352
2405
|
var PROVIDER_MAP = {
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
2406
|
+
claude: PROVIDERS[0],
|
|
2407
|
+
codex: PROVIDERS[1],
|
|
2408
|
+
exa: PROVIDERS[2],
|
|
2409
|
+
gemini: PROVIDERS[3],
|
|
2410
|
+
perplexity: PROVIDERS[4],
|
|
2411
|
+
parallel: PROVIDERS[5],
|
|
2412
|
+
valyu: PROVIDERS[6]
|
|
1358
2413
|
};
|
|
1359
2414
|
|
|
1360
2415
|
// src/provider-resolution.ts
|
|
2416
|
+
var IMPLICIT_PROVIDER_FALLBACKS = ["codex"];
|
|
1361
2417
|
function resolveProviderChoice(config, explicit, cwd) {
|
|
1362
2418
|
return resolveProviderForCapability(config, explicit, cwd, "search");
|
|
1363
2419
|
}
|
|
2420
|
+
function getEffectiveProviderConfig(config, providerId) {
|
|
2421
|
+
const configured = config.providers?.[providerId];
|
|
2422
|
+
if (configured) {
|
|
2423
|
+
return configured;
|
|
2424
|
+
}
|
|
2425
|
+
if (IMPLICIT_PROVIDER_FALLBACKS.includes(providerId)) {
|
|
2426
|
+
return {
|
|
2427
|
+
...PROVIDER_MAP[providerId].createTemplate(),
|
|
2428
|
+
enabled: true
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
return void 0;
|
|
2432
|
+
}
|
|
1364
2433
|
function resolveProviderForCapability(config, explicit, cwd, capability) {
|
|
1365
2434
|
if (explicit) {
|
|
1366
2435
|
const provider = PROVIDER_MAP[explicit];
|
|
2436
|
+
const providerConfig = getEffectiveProviderConfig(config, explicit);
|
|
1367
2437
|
if (typeof provider[capability] !== "function") {
|
|
1368
2438
|
throw new Error(
|
|
1369
2439
|
`Provider '${explicit}' does not support '${capability}'.`
|
|
1370
2440
|
);
|
|
1371
2441
|
}
|
|
1372
|
-
if (!isProviderToolEnabled(
|
|
1373
|
-
explicit,
|
|
1374
|
-
config.providers?.[explicit],
|
|
1375
|
-
capability
|
|
1376
|
-
)) {
|
|
2442
|
+
if (!isProviderToolEnabled(explicit, providerConfig, capability)) {
|
|
1377
2443
|
throw new Error(
|
|
1378
2444
|
`Provider '${explicit}' has '${capability}' disabled in config.`
|
|
1379
2445
|
);
|
|
1380
2446
|
}
|
|
1381
|
-
const status = provider.getStatus(
|
|
1382
|
-
config.providers?.[explicit],
|
|
1383
|
-
cwd
|
|
1384
|
-
);
|
|
2447
|
+
const status = provider.getStatus(providerConfig, cwd);
|
|
1385
2448
|
if (!status.available) {
|
|
1386
2449
|
throw new Error(
|
|
1387
2450
|
`Provider '${explicit}' is not available: ${status.summary}.`
|
|
@@ -1403,19 +2466,14 @@ function resolveProviderForCapability(config, explicit, cwd, capability) {
|
|
|
1403
2466
|
const status = provider.getStatus(providerConfig, cwd);
|
|
1404
2467
|
if (status.available) return provider;
|
|
1405
2468
|
}
|
|
1406
|
-
for (const
|
|
2469
|
+
for (const providerId of IMPLICIT_PROVIDER_FALLBACKS) {
|
|
2470
|
+
const provider = PROVIDER_MAP[providerId];
|
|
1407
2471
|
if (typeof provider[capability] !== "function") continue;
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
config.providers?.[provider.id],
|
|
1411
|
-
capability
|
|
1412
|
-
)) {
|
|
2472
|
+
const providerConfig = getEffectiveProviderConfig(config, provider.id);
|
|
2473
|
+
if (!isProviderToolEnabled(provider.id, providerConfig, capability)) {
|
|
1413
2474
|
continue;
|
|
1414
2475
|
}
|
|
1415
|
-
const status = provider.getStatus(
|
|
1416
|
-
config.providers?.[provider.id],
|
|
1417
|
-
cwd
|
|
1418
|
-
);
|
|
2476
|
+
const status = provider.getStatus(providerConfig, cwd);
|
|
1419
2477
|
if (status.available) return provider;
|
|
1420
2478
|
}
|
|
1421
2479
|
throw new Error(
|
|
@@ -1424,16 +2482,32 @@ function resolveProviderForCapability(config, explicit, cwd, capability) {
|
|
|
1424
2482
|
}
|
|
1425
2483
|
|
|
1426
2484
|
// src/types.ts
|
|
1427
|
-
var PROVIDER_IDS = [
|
|
2485
|
+
var PROVIDER_IDS = [
|
|
2486
|
+
"claude",
|
|
2487
|
+
"codex",
|
|
2488
|
+
"exa",
|
|
2489
|
+
"gemini",
|
|
2490
|
+
"perplexity",
|
|
2491
|
+
"parallel",
|
|
2492
|
+
"valyu"
|
|
2493
|
+
];
|
|
1428
2494
|
|
|
1429
2495
|
// src/index.ts
|
|
1430
2496
|
var DEFAULT_MAX_RESULTS = 5;
|
|
1431
2497
|
var MAX_ALLOWED_RESULTS = 20;
|
|
2498
|
+
var RESEARCH_HEARTBEAT_MS = 15e3;
|
|
2499
|
+
var CAPABILITY_TOOL_NAMES = {
|
|
2500
|
+
search: "web_search",
|
|
2501
|
+
contents: "web_contents",
|
|
2502
|
+
answer: "web_answer",
|
|
2503
|
+
research: "web_research"
|
|
2504
|
+
};
|
|
2505
|
+
var MANAGED_TOOL_NAMES = Object.values(CAPABILITY_TOOL_NAMES);
|
|
2506
|
+
var PROVIDER_OVERRIDE_GUIDELINES = [
|
|
2507
|
+
"Do not set provider unless the user asks for one."
|
|
2508
|
+
];
|
|
1432
2509
|
function webProvidersExtension(pi) {
|
|
1433
|
-
|
|
1434
|
-
registerWebContentsTool(pi);
|
|
1435
|
-
registerWebAnswerTool(pi);
|
|
1436
|
-
registerWebResearchTool(pi);
|
|
2510
|
+
registerManagedTools(pi);
|
|
1437
2511
|
pi.registerCommand("web-providers", {
|
|
1438
2512
|
description: "Configure web search providers",
|
|
1439
2513
|
handler: async (_args, ctx) => {
|
|
@@ -1441,15 +2515,38 @@ function webProvidersExtension(pi) {
|
|
|
1441
2515
|
ctx.ui.notify("web-providers requires interactive mode", "error");
|
|
1442
2516
|
return;
|
|
1443
2517
|
}
|
|
1444
|
-
await runWebProvidersConfig(ctx);
|
|
2518
|
+
await runWebProvidersConfig(pi, ctx);
|
|
1445
2519
|
}
|
|
1446
2520
|
});
|
|
2521
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
2522
|
+
await refreshManagedTools(pi, ctx.cwd, { addAvailable: true });
|
|
2523
|
+
});
|
|
2524
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
2525
|
+
await refreshManagedTools(pi, ctx.cwd, { addAvailable: false });
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
function registerManagedTools(pi, providerIdsByCapability = {}) {
|
|
2529
|
+
registerWebSearchTool(pi, providerIdsByCapability.search ?? PROVIDER_IDS);
|
|
2530
|
+
registerWebContentsTool(
|
|
2531
|
+
pi,
|
|
2532
|
+
providerIdsByCapability.contents ?? getProviderIdsForCapability("contents")
|
|
2533
|
+
);
|
|
2534
|
+
registerWebAnswerTool(
|
|
2535
|
+
pi,
|
|
2536
|
+
providerIdsByCapability.answer ?? getProviderIdsForCapability("answer")
|
|
2537
|
+
);
|
|
2538
|
+
registerWebResearchTool(
|
|
2539
|
+
pi,
|
|
2540
|
+
providerIdsByCapability.research ?? getProviderIdsForCapability("research")
|
|
2541
|
+
);
|
|
1447
2542
|
}
|
|
1448
|
-
function registerWebSearchTool(pi) {
|
|
2543
|
+
function registerWebSearchTool(pi, providerIds) {
|
|
2544
|
+
const visibleProviderIds = providerIds.length > 0 ? providerIds : PROVIDER_IDS;
|
|
1449
2545
|
pi.registerTool({
|
|
1450
2546
|
name: "web_search",
|
|
1451
2547
|
label: "Web Search",
|
|
1452
|
-
description: `
|
|
2548
|
+
description: `Find likely sources on the public web and return titles, URLs, and snippets. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)} when needed.`,
|
|
2549
|
+
promptGuidelines: PROVIDER_OVERRIDE_GUIDELINES,
|
|
1453
2550
|
parameters: Type.Object({
|
|
1454
2551
|
query: Type.String({ description: "What to search for on the web" }),
|
|
1455
2552
|
maxResults: Type.Optional(
|
|
@@ -1459,23 +2556,24 @@ function registerWebSearchTool(pi) {
|
|
|
1459
2556
|
description: `Maximum number of results to return (default: ${DEFAULT_MAX_RESULTS})`
|
|
1460
2557
|
})
|
|
1461
2558
|
),
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
2559
|
+
options: jsonOptionsSchema("Provider-specific search options."),
|
|
2560
|
+
provider: providerEnum(
|
|
2561
|
+
visibleProviderIds,
|
|
2562
|
+
"Provider override. If omitted, uses the active configured provider or falls back to Codex for search when it is not explicitly disabled."
|
|
1466
2563
|
)
|
|
1467
2564
|
}),
|
|
1468
2565
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1469
2566
|
const config = await loadConfig();
|
|
1470
2567
|
const provider = resolveProviderChoice(config, params.provider, ctx.cwd);
|
|
1471
2568
|
const maxResults = clampResults(params.maxResults);
|
|
1472
|
-
const providerConfig = config
|
|
2569
|
+
const providerConfig = getEffectiveProviderConfig(config, provider.id);
|
|
1473
2570
|
if (!providerConfig) {
|
|
1474
2571
|
throw new Error(`Provider '${provider.id}' is not configured.`);
|
|
1475
2572
|
}
|
|
1476
2573
|
const response = await provider.search(
|
|
1477
2574
|
params.query,
|
|
1478
2575
|
maxResults,
|
|
2576
|
+
normalizeOptions(params.options),
|
|
1479
2577
|
providerConfig,
|
|
1480
2578
|
{
|
|
1481
2579
|
cwd: ctx.cwd,
|
|
@@ -1524,13 +2622,12 @@ function registerWebSearchTool(pi) {
|
|
|
1524
2622
|
}
|
|
1525
2623
|
});
|
|
1526
2624
|
}
|
|
1527
|
-
function registerWebContentsTool(pi) {
|
|
1528
|
-
const providerIds = getProviderIdsForCapability("contents");
|
|
2625
|
+
function registerWebContentsTool(pi, providerIds) {
|
|
1529
2626
|
if (providerIds.length === 0) return;
|
|
1530
2627
|
pi.registerTool({
|
|
1531
2628
|
name: "web_contents",
|
|
1532
2629
|
label: "Web Contents",
|
|
1533
|
-
description: "
|
|
2630
|
+
description: "Read and extract the main contents of one or more web pages.",
|
|
1534
2631
|
parameters: Type.Object({
|
|
1535
2632
|
urls: Type.Array(Type.String({ minLength: 1 }), {
|
|
1536
2633
|
minItems: 1,
|
|
@@ -1542,6 +2639,7 @@ function registerWebContentsTool(pi) {
|
|
|
1542
2639
|
"Provider override. If omitted, uses the active configured provider that supports web contents."
|
|
1543
2640
|
)
|
|
1544
2641
|
}),
|
|
2642
|
+
promptGuidelines: PROVIDER_OVERRIDE_GUIDELINES,
|
|
1545
2643
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1546
2644
|
return executeProviderTool({
|
|
1547
2645
|
capability: "contents",
|
|
@@ -1579,13 +2677,12 @@ function registerWebContentsTool(pi) {
|
|
|
1579
2677
|
}
|
|
1580
2678
|
});
|
|
1581
2679
|
}
|
|
1582
|
-
function registerWebAnswerTool(pi) {
|
|
1583
|
-
const providerIds = getProviderIdsForCapability("answer");
|
|
2680
|
+
function registerWebAnswerTool(pi, providerIds) {
|
|
1584
2681
|
if (providerIds.length === 0) return;
|
|
1585
2682
|
pi.registerTool({
|
|
1586
2683
|
name: "web_answer",
|
|
1587
2684
|
label: "Web Answer",
|
|
1588
|
-
description: "
|
|
2685
|
+
description: "Answer a question using web-grounded evidence.",
|
|
1589
2686
|
parameters: Type.Object({
|
|
1590
2687
|
query: Type.String({ description: "Question to answer" }),
|
|
1591
2688
|
options: jsonOptionsSchema("Provider-specific answer options."),
|
|
@@ -1594,6 +2691,7 @@ function registerWebAnswerTool(pi) {
|
|
|
1594
2691
|
"Provider override. If omitted, uses the active configured provider that supports web answers."
|
|
1595
2692
|
)
|
|
1596
2693
|
}),
|
|
2694
|
+
promptGuidelines: PROVIDER_OVERRIDE_GUIDELINES,
|
|
1597
2695
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1598
2696
|
return executeProviderTool({
|
|
1599
2697
|
capability: "answer",
|
|
@@ -1613,8 +2711,10 @@ function registerWebAnswerTool(pi) {
|
|
|
1613
2711
|
renderCall(args, theme) {
|
|
1614
2712
|
return renderToolCallHeader(
|
|
1615
2713
|
"web_answer",
|
|
1616
|
-
|
|
1617
|
-
[
|
|
2714
|
+
formatQuotedPreview(String(args.query ?? "")),
|
|
2715
|
+
[
|
|
2716
|
+
`provider=${String(args.provider ?? "auto")}`
|
|
2717
|
+
],
|
|
1618
2718
|
theme
|
|
1619
2719
|
);
|
|
1620
2720
|
},
|
|
@@ -1629,13 +2729,12 @@ function registerWebAnswerTool(pi) {
|
|
|
1629
2729
|
}
|
|
1630
2730
|
});
|
|
1631
2731
|
}
|
|
1632
|
-
function registerWebResearchTool(pi) {
|
|
1633
|
-
const providerIds = getProviderIdsForCapability("research");
|
|
2732
|
+
function registerWebResearchTool(pi, providerIds) {
|
|
1634
2733
|
if (providerIds.length === 0) return;
|
|
1635
2734
|
pi.registerTool({
|
|
1636
2735
|
name: "web_research",
|
|
1637
2736
|
label: "Web Research",
|
|
1638
|
-
description: "
|
|
2737
|
+
description: "Investigate a topic across web sources and produce a longer report.",
|
|
1639
2738
|
parameters: Type.Object({
|
|
1640
2739
|
input: Type.String({ description: "Research brief or question" }),
|
|
1641
2740
|
options: jsonOptionsSchema("Provider-specific research options."),
|
|
@@ -1644,6 +2743,7 @@ function registerWebResearchTool(pi) {
|
|
|
1644
2743
|
"Provider override. If omitted, uses the active configured provider that supports research."
|
|
1645
2744
|
)
|
|
1646
2745
|
}),
|
|
2746
|
+
promptGuidelines: PROVIDER_OVERRIDE_GUIDELINES,
|
|
1647
2747
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1648
2748
|
return executeProviderTool({
|
|
1649
2749
|
capability: "research",
|
|
@@ -1663,8 +2763,10 @@ function registerWebResearchTool(pi) {
|
|
|
1663
2763
|
renderCall(args, theme) {
|
|
1664
2764
|
return renderToolCallHeader(
|
|
1665
2765
|
"web_research",
|
|
1666
|
-
|
|
1667
|
-
[
|
|
2766
|
+
formatQuotedPreview(String(args.input ?? "")),
|
|
2767
|
+
[
|
|
2768
|
+
`provider=${String(args.provider ?? "auto")}`
|
|
2769
|
+
],
|
|
1668
2770
|
theme
|
|
1669
2771
|
);
|
|
1670
2772
|
},
|
|
@@ -1679,7 +2781,7 @@ function registerWebResearchTool(pi) {
|
|
|
1679
2781
|
}
|
|
1680
2782
|
});
|
|
1681
2783
|
}
|
|
1682
|
-
async function runWebProvidersConfig(ctx) {
|
|
2784
|
+
async function runWebProvidersConfig(pi, ctx) {
|
|
1683
2785
|
const config = await loadConfig();
|
|
1684
2786
|
const activeProvider = await getPreferredProvider(ctx.cwd);
|
|
1685
2787
|
await ctx.ui.custom(
|
|
@@ -1692,6 +2794,60 @@ async function runWebProvidersConfig(ctx) {
|
|
|
1692
2794
|
activeProvider
|
|
1693
2795
|
)
|
|
1694
2796
|
);
|
|
2797
|
+
await refreshManagedTools(pi, ctx.cwd, { addAvailable: true });
|
|
2798
|
+
}
|
|
2799
|
+
function getAvailableProviderIdsForCapability(config, cwd, capability) {
|
|
2800
|
+
const providerIds = [];
|
|
2801
|
+
for (const providerId of getProviderIdsForCapability(capability)) {
|
|
2802
|
+
try {
|
|
2803
|
+
resolveProviderForCapability(config, providerId, cwd, capability);
|
|
2804
|
+
providerIds.push(providerId);
|
|
2805
|
+
} catch {
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
return providerIds;
|
|
2809
|
+
}
|
|
2810
|
+
function getAvailableManagedToolNames(config, cwd) {
|
|
2811
|
+
return Object.keys(CAPABILITY_TOOL_NAMES).filter(
|
|
2812
|
+
(capability) => getAvailableProviderIdsForCapability(config, cwd, capability).length > 0
|
|
2813
|
+
).map((capability) => CAPABILITY_TOOL_NAMES[capability]);
|
|
2814
|
+
}
|
|
2815
|
+
function getSyncedActiveTools(config, cwd, activeToolNames, options) {
|
|
2816
|
+
const availableToolNames = new Set(getAvailableManagedToolNames(config, cwd));
|
|
2817
|
+
const nextActiveTools = new Set(activeToolNames);
|
|
2818
|
+
for (const toolName of MANAGED_TOOL_NAMES) {
|
|
2819
|
+
if (availableToolNames.has(toolName)) {
|
|
2820
|
+
if (options.addAvailable) {
|
|
2821
|
+
nextActiveTools.add(toolName);
|
|
2822
|
+
}
|
|
2823
|
+
continue;
|
|
2824
|
+
}
|
|
2825
|
+
nextActiveTools.delete(toolName);
|
|
2826
|
+
}
|
|
2827
|
+
return nextActiveTools;
|
|
2828
|
+
}
|
|
2829
|
+
async function refreshManagedTools(pi, cwd, options) {
|
|
2830
|
+
const config = await loadConfig();
|
|
2831
|
+
const nextActiveTools = getSyncedActiveTools(
|
|
2832
|
+
config,
|
|
2833
|
+
cwd,
|
|
2834
|
+
pi.getActiveTools(),
|
|
2835
|
+
options
|
|
2836
|
+
);
|
|
2837
|
+
registerManagedTools(pi, {
|
|
2838
|
+
search: getAvailableProviderIdsForCapability(config, cwd, "search"),
|
|
2839
|
+
contents: getAvailableProviderIdsForCapability(config, cwd, "contents"),
|
|
2840
|
+
answer: getAvailableProviderIdsForCapability(config, cwd, "answer"),
|
|
2841
|
+
research: getAvailableProviderIdsForCapability(config, cwd, "research")
|
|
2842
|
+
});
|
|
2843
|
+
await syncManagedToolAvailability(pi, nextActiveTools);
|
|
2844
|
+
}
|
|
2845
|
+
async function syncManagedToolAvailability(pi, nextActiveTools) {
|
|
2846
|
+
const activeTools = pi.getActiveTools();
|
|
2847
|
+
const changed = activeTools.length !== nextActiveTools.size || activeTools.some((toolName) => !nextActiveTools.has(toolName));
|
|
2848
|
+
if (changed) {
|
|
2849
|
+
pi.setActiveTools(Array.from(nextActiveTools));
|
|
2850
|
+
}
|
|
1695
2851
|
}
|
|
1696
2852
|
function getProviderIdsForCapability(capability) {
|
|
1697
2853
|
return PROVIDERS.filter(
|
|
@@ -1703,15 +2859,21 @@ function providerEnum(providerIds, description) {
|
|
|
1703
2859
|
return Type.Optional(Type.Literal(providerIds[0], { description }));
|
|
1704
2860
|
}
|
|
1705
2861
|
return Type.Optional(
|
|
1706
|
-
Type.Union(
|
|
2862
|
+
Type.Union(
|
|
2863
|
+
providerIds.map((id) => Type.Literal(id)),
|
|
2864
|
+
{ description }
|
|
2865
|
+
)
|
|
1707
2866
|
);
|
|
1708
2867
|
}
|
|
1709
2868
|
function jsonOptionsSchema(description) {
|
|
1710
2869
|
return Type.Optional(
|
|
1711
|
-
Type.Object(
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
2870
|
+
Type.Object(
|
|
2871
|
+
{},
|
|
2872
|
+
{
|
|
2873
|
+
additionalProperties: true,
|
|
2874
|
+
description
|
|
2875
|
+
}
|
|
2876
|
+
)
|
|
1715
2877
|
);
|
|
1716
2878
|
}
|
|
1717
2879
|
async function executeProviderTool({
|
|
@@ -1729,36 +2891,74 @@ async function executeProviderTool({
|
|
|
1729
2891
|
ctx.cwd,
|
|
1730
2892
|
capability
|
|
1731
2893
|
);
|
|
1732
|
-
const providerConfig = config
|
|
2894
|
+
const providerConfig = getEffectiveProviderConfig(config, provider.id);
|
|
1733
2895
|
if (!providerConfig) {
|
|
1734
2896
|
throw new Error(`Provider '${provider.id}' is not configured.`);
|
|
1735
2897
|
}
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2898
|
+
const progress = createToolProgressReporter(
|
|
2899
|
+
capability,
|
|
2900
|
+
provider.id,
|
|
2901
|
+
onUpdate
|
|
2902
|
+
);
|
|
2903
|
+
let response;
|
|
2904
|
+
try {
|
|
2905
|
+
response = await invoke(provider, providerConfig, {
|
|
1740
2906
|
cwd: ctx.cwd,
|
|
1741
2907
|
signal: signal ?? void 0,
|
|
1742
|
-
onProgress:
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
);
|
|
2908
|
+
onProgress: progress.report
|
|
2909
|
+
});
|
|
2910
|
+
} finally {
|
|
2911
|
+
progress.stop();
|
|
2912
|
+
}
|
|
1748
2913
|
const details = {
|
|
1749
2914
|
tool: `web_${capability}`,
|
|
1750
2915
|
provider: response.provider,
|
|
1751
2916
|
summary: response.summary,
|
|
1752
2917
|
itemCount: response.itemCount
|
|
1753
2918
|
};
|
|
2919
|
+
const text = await truncateAndSave(response.text, capability);
|
|
1754
2920
|
return {
|
|
1755
|
-
content: [{ type: "text", text
|
|
2921
|
+
content: [{ type: "text", text }],
|
|
1756
2922
|
details
|
|
1757
2923
|
};
|
|
1758
2924
|
}
|
|
1759
2925
|
function normalizeOptions(value) {
|
|
1760
2926
|
return isJsonObject(value) ? value : void 0;
|
|
1761
2927
|
}
|
|
2928
|
+
function createToolProgressReporter(capability, providerId, onUpdate) {
|
|
2929
|
+
if (!onUpdate) {
|
|
2930
|
+
return { report: void 0, stop: () => {
|
|
2931
|
+
} };
|
|
2932
|
+
}
|
|
2933
|
+
const emit = (message) => onUpdate({
|
|
2934
|
+
content: [{ type: "text", text: message }],
|
|
2935
|
+
details: {}
|
|
2936
|
+
});
|
|
2937
|
+
const startedAt = Date.now();
|
|
2938
|
+
let lastUpdateAt = startedAt;
|
|
2939
|
+
let timer;
|
|
2940
|
+
if (capability === "research") {
|
|
2941
|
+
timer = setInterval(() => {
|
|
2942
|
+
if (Date.now() - lastUpdateAt < RESEARCH_HEARTBEAT_MS) {
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
const elapsed = formatElapsed2(Date.now() - startedAt);
|
|
2946
|
+
emit(`web_research still running via ${providerId} (${elapsed} elapsed)`);
|
|
2947
|
+
lastUpdateAt = Date.now();
|
|
2948
|
+
}, RESEARCH_HEARTBEAT_MS);
|
|
2949
|
+
}
|
|
2950
|
+
return {
|
|
2951
|
+
report: (message) => {
|
|
2952
|
+
lastUpdateAt = Date.now();
|
|
2953
|
+
emit(message);
|
|
2954
|
+
},
|
|
2955
|
+
stop: () => {
|
|
2956
|
+
if (timer) {
|
|
2957
|
+
clearInterval(timer);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
1762
2962
|
function renderToolCallHeader(toolName, primary, details, theme) {
|
|
1763
2963
|
return {
|
|
1764
2964
|
invalidate() {
|
|
@@ -1829,6 +3029,30 @@ function buildProviderMenuOptions(providerId) {
|
|
|
1829
3029
|
values
|
|
1830
3030
|
});
|
|
1831
3031
|
};
|
|
3032
|
+
if (providerId === "claude") {
|
|
3033
|
+
pushText(
|
|
3034
|
+
"model",
|
|
3035
|
+
"Model",
|
|
3036
|
+
"Optional Claude model override. Leave empty to use the local default."
|
|
3037
|
+
);
|
|
3038
|
+
pushValues(
|
|
3039
|
+
"claudeEffort",
|
|
3040
|
+
"Effort",
|
|
3041
|
+
"How much effort Claude should use. 'default' uses the SDK default.",
|
|
3042
|
+
["default", "low", "medium", "high", "max"]
|
|
3043
|
+
);
|
|
3044
|
+
pushText(
|
|
3045
|
+
"claudeMaxTurns",
|
|
3046
|
+
"Max turns",
|
|
3047
|
+
"Optional maximum number of Claude turns. Leave empty to use the SDK default."
|
|
3048
|
+
);
|
|
3049
|
+
pushText(
|
|
3050
|
+
"claudePathToExecutable",
|
|
3051
|
+
"Executable path",
|
|
3052
|
+
"Optional path to the Claude Code executable. Leave empty to use the bundled/default executable."
|
|
3053
|
+
);
|
|
3054
|
+
return options;
|
|
3055
|
+
}
|
|
1832
3056
|
if (providerId === "codex") {
|
|
1833
3057
|
pushText(
|
|
1834
3058
|
"model",
|
|
@@ -1912,6 +3136,11 @@ function buildProviderMenuOptions(providerId) {
|
|
|
1912
3136
|
"Search model",
|
|
1913
3137
|
"Model used for Gemini search interactions."
|
|
1914
3138
|
);
|
|
3139
|
+
pushText(
|
|
3140
|
+
"geminiContentsModel",
|
|
3141
|
+
"Contents model",
|
|
3142
|
+
"Model used for Gemini URL content extraction via URL Context."
|
|
3143
|
+
);
|
|
1915
3144
|
pushText(
|
|
1916
3145
|
"geminiAnswerModel",
|
|
1917
3146
|
"Answer model",
|
|
@@ -1924,6 +3153,9 @@ function buildProviderMenuOptions(providerId) {
|
|
|
1924
3153
|
);
|
|
1925
3154
|
return options;
|
|
1926
3155
|
}
|
|
3156
|
+
if (providerId === "perplexity") {
|
|
3157
|
+
return options;
|
|
3158
|
+
}
|
|
1927
3159
|
if (providerId === "parallel") {
|
|
1928
3160
|
pushValues(
|
|
1929
3161
|
"parallelSearchMode",
|
|
@@ -1983,17 +3215,24 @@ var WebProvidersSettingsView = class {
|
|
|
1983
3215
|
}
|
|
1984
3216
|
const lines = [];
|
|
1985
3217
|
const providerItems = this.buildProviderSectionItems();
|
|
1986
|
-
lines.push(
|
|
3218
|
+
lines.push(
|
|
3219
|
+
...this.renderSection(width, "Provider", "provider", providerItems)
|
|
3220
|
+
);
|
|
1987
3221
|
lines.push("");
|
|
1988
3222
|
const toolItems = this.buildToolSectionItems();
|
|
1989
3223
|
lines.push(...this.renderSection(width, "Tools", "tools", toolItems));
|
|
1990
3224
|
lines.push("");
|
|
1991
3225
|
const configItems = this.buildConfigSectionItems();
|
|
1992
|
-
lines.push(
|
|
3226
|
+
lines.push(
|
|
3227
|
+
...this.renderSection(width, "Provider config", "config", configItems)
|
|
3228
|
+
);
|
|
1993
3229
|
const selected = this.getSelectedEntry();
|
|
1994
3230
|
if (selected) {
|
|
1995
3231
|
lines.push("");
|
|
1996
|
-
for (const line of wrapTextWithAnsi(
|
|
3232
|
+
for (const line of wrapTextWithAnsi(
|
|
3233
|
+
selected.description,
|
|
3234
|
+
Math.max(10, width - 2)
|
|
3235
|
+
)) {
|
|
1997
3236
|
lines.push(truncateToWidth(this.theme.fg("dim", line), width));
|
|
1998
3237
|
}
|
|
1999
3238
|
}
|
|
@@ -2090,10 +3329,13 @@ var WebProvidersSettingsView = class {
|
|
|
2090
3329
|
}
|
|
2091
3330
|
if (option.kind === "text") {
|
|
2092
3331
|
const key = option.key;
|
|
2093
|
-
const currentValue = key === "model" || key === "
|
|
3332
|
+
const currentValue = this.activeProvider === "claude" && (key === "model" || key === "claudePathToExecutable" || key === "claudeMaxTurns") ? getClaudeTextSettingValue(
|
|
2094
3333
|
providerConfig,
|
|
2095
3334
|
key
|
|
2096
|
-
) : key === "
|
|
3335
|
+
) : key === "model" || key === "additionalDirectories" ? getCodexTextSettingValue(
|
|
3336
|
+
providerConfig,
|
|
3337
|
+
key
|
|
3338
|
+
) : key === "geminiSearchModel" || key === "geminiContentsModel" || key === "geminiAnswerModel" || key === "geminiResearchAgent" ? getGeminiTextSettingValue(
|
|
2097
3339
|
providerConfig,
|
|
2098
3340
|
key
|
|
2099
3341
|
) : getProviderStringValue(
|
|
@@ -2132,7 +3374,7 @@ var WebProvidersSettingsView = class {
|
|
|
2132
3374
|
"tools",
|
|
2133
3375
|
"config"
|
|
2134
3376
|
];
|
|
2135
|
-
|
|
3377
|
+
const index = sections.indexOf(this.activeSection);
|
|
2136
3378
|
for (let offset = 1; offset <= sections.length; offset++) {
|
|
2137
3379
|
const next = sections[(index + offset * direction + sections.length) % sections.length];
|
|
2138
3380
|
if (this.getSectionEntries(next).length > 0) {
|
|
@@ -2221,13 +3463,19 @@ var WebProvidersSettingsView = class {
|
|
|
2221
3463
|
if (id === "apiKey" || id === "baseUrl") {
|
|
2222
3464
|
return getProviderStringValue(providerConfig, id);
|
|
2223
3465
|
}
|
|
3466
|
+
if (this.activeProvider === "claude" && (id === "model" || id === "claudePathToExecutable" || id === "claudeMaxTurns")) {
|
|
3467
|
+
return getClaudeTextSettingValue(
|
|
3468
|
+
providerConfig,
|
|
3469
|
+
id
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
2224
3472
|
if (id === "model" || id === "additionalDirectories") {
|
|
2225
3473
|
return getCodexTextSettingValue(
|
|
2226
3474
|
providerConfig,
|
|
2227
3475
|
id
|
|
2228
3476
|
);
|
|
2229
3477
|
}
|
|
2230
|
-
if (id === "geminiSearchModel" || id === "geminiAnswerModel" || id === "geminiResearchAgent") {
|
|
3478
|
+
if (id === "geminiSearchModel" || id === "geminiContentsModel" || id === "geminiAnswerModel" || id === "geminiResearchAgent") {
|
|
2231
3479
|
return getGeminiTextSettingValue(
|
|
2232
3480
|
providerConfig,
|
|
2233
3481
|
id
|
|
@@ -2268,6 +3516,11 @@ var WebProvidersSettingsView = class {
|
|
|
2268
3516
|
}
|
|
2269
3517
|
if (id === "apiKey" || id === "baseUrl") {
|
|
2270
3518
|
assignOptionalString(providerConfig, id, value);
|
|
3519
|
+
} else if (this.activeProvider === "claude" && applyClaudeSettingChange(
|
|
3520
|
+
providerConfig,
|
|
3521
|
+
id,
|
|
3522
|
+
value
|
|
3523
|
+
)) {
|
|
2271
3524
|
} else if (this.activeProvider === "codex" && applyCodexSettingChange(
|
|
2272
3525
|
providerConfig,
|
|
2273
3526
|
id,
|
|
@@ -2408,6 +3661,12 @@ function getProviderStringValue(config, key) {
|
|
|
2408
3661
|
return typeof value === "string" ? value : void 0;
|
|
2409
3662
|
}
|
|
2410
3663
|
function getProviderChoiceValue(providerId, config, key) {
|
|
3664
|
+
if (providerId === "claude") {
|
|
3665
|
+
const defaults = config?.defaults;
|
|
3666
|
+
if (key === "claudeEffort") {
|
|
3667
|
+
return typeof defaults?.effort === "string" ? defaults.effort : "default";
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
2411
3670
|
if (providerId === "codex") {
|
|
2412
3671
|
const defaults = config?.defaults;
|
|
2413
3672
|
if (key === "networkAccessEnabled" || key === "webSearchEnabled") {
|
|
@@ -2462,6 +3721,17 @@ function getProviderChoiceValue(providerId, config, key) {
|
|
|
2462
3721
|
}
|
|
2463
3722
|
throw new Error(`Unsupported choice setting '${key}' for '${providerId}'.`);
|
|
2464
3723
|
}
|
|
3724
|
+
function getClaudeTextSettingValue(config, key) {
|
|
3725
|
+
if (key === "claudePathToExecutable") {
|
|
3726
|
+
return config?.pathToClaudeCodeExecutable;
|
|
3727
|
+
}
|
|
3728
|
+
const defaults = config?.defaults;
|
|
3729
|
+
if (!defaults) return void 0;
|
|
3730
|
+
if (key === "claudeMaxTurns") {
|
|
3731
|
+
return typeof defaults.maxTurns === "number" ? String(defaults.maxTurns) : void 0;
|
|
3732
|
+
}
|
|
3733
|
+
return defaults.model;
|
|
3734
|
+
}
|
|
2465
3735
|
function getCodexTextSettingValue(config, key) {
|
|
2466
3736
|
const defaults = config?.defaults;
|
|
2467
3737
|
if (!defaults) return void 0;
|
|
@@ -2474,6 +3744,7 @@ function getGeminiTextSettingValue(config, key) {
|
|
|
2474
3744
|
const defaults = config?.defaults;
|
|
2475
3745
|
if (!defaults) return void 0;
|
|
2476
3746
|
if (key === "geminiSearchModel") return defaults.searchModel;
|
|
3747
|
+
if (key === "geminiContentsModel") return defaults.contentsModel;
|
|
2477
3748
|
if (key === "geminiAnswerModel") return defaults.answerModel;
|
|
2478
3749
|
return defaults.researchAgent;
|
|
2479
3750
|
}
|
|
@@ -2485,6 +3756,51 @@ function assignOptionalString(target, key, value) {
|
|
|
2485
3756
|
target[key] = trimmed;
|
|
2486
3757
|
}
|
|
2487
3758
|
}
|
|
3759
|
+
function applyClaudeSettingChange(target, key, value) {
|
|
3760
|
+
target.defaults ??= {};
|
|
3761
|
+
switch (key) {
|
|
3762
|
+
case "model":
|
|
3763
|
+
assignOptionalString(
|
|
3764
|
+
target.defaults,
|
|
3765
|
+
"model",
|
|
3766
|
+
value
|
|
3767
|
+
);
|
|
3768
|
+
cleanupClaudeDefaults(target);
|
|
3769
|
+
return true;
|
|
3770
|
+
case "claudePathToExecutable":
|
|
3771
|
+
assignOptionalString(
|
|
3772
|
+
target,
|
|
3773
|
+
"pathToClaudeCodeExecutable",
|
|
3774
|
+
value
|
|
3775
|
+
);
|
|
3776
|
+
cleanupClaudeDefaults(target);
|
|
3777
|
+
return true;
|
|
3778
|
+
case "claudeMaxTurns": {
|
|
3779
|
+
const trimmed = value.trim();
|
|
3780
|
+
if (!trimmed) {
|
|
3781
|
+
delete target.defaults.maxTurns;
|
|
3782
|
+
} else {
|
|
3783
|
+
const parsed = Number(trimmed);
|
|
3784
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
3785
|
+
throw new Error("Claude max turns must be a positive integer.");
|
|
3786
|
+
}
|
|
3787
|
+
target.defaults.maxTurns = parsed;
|
|
3788
|
+
}
|
|
3789
|
+
cleanupClaudeDefaults(target);
|
|
3790
|
+
return true;
|
|
3791
|
+
}
|
|
3792
|
+
case "claudeEffort":
|
|
3793
|
+
if (value === "default") {
|
|
3794
|
+
delete target.defaults.effort;
|
|
3795
|
+
} else {
|
|
3796
|
+
target.defaults.effort = value;
|
|
3797
|
+
}
|
|
3798
|
+
cleanupClaudeDefaults(target);
|
|
3799
|
+
return true;
|
|
3800
|
+
default:
|
|
3801
|
+
return false;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
2488
3804
|
function applyCodexSettingChange(target, key, value) {
|
|
2489
3805
|
target.defaults ??= {};
|
|
2490
3806
|
switch (key) {
|
|
@@ -2600,6 +3916,14 @@ function applyGeminiSettingChange(target, key, value) {
|
|
|
2600
3916
|
);
|
|
2601
3917
|
cleanupGeminiDefaults(target);
|
|
2602
3918
|
return true;
|
|
3919
|
+
case "geminiContentsModel":
|
|
3920
|
+
assignOptionalString(
|
|
3921
|
+
target.defaults,
|
|
3922
|
+
"contentsModel",
|
|
3923
|
+
value
|
|
3924
|
+
);
|
|
3925
|
+
cleanupGeminiDefaults(target);
|
|
3926
|
+
return true;
|
|
2603
3927
|
case "geminiAnswerModel":
|
|
2604
3928
|
assignOptionalString(
|
|
2605
3929
|
target.defaults,
|
|
@@ -2653,6 +3977,11 @@ function applyParallelSettingChange(target, key, value) {
|
|
|
2653
3977
|
return false;
|
|
2654
3978
|
}
|
|
2655
3979
|
}
|
|
3980
|
+
function cleanupClaudeDefaults(target) {
|
|
3981
|
+
if (target.defaults && Object.keys(target.defaults).length === 0) {
|
|
3982
|
+
delete target.defaults;
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
2656
3985
|
function cleanupCodexDefaults(target) {
|
|
2657
3986
|
if (target.defaults && Object.keys(target.defaults).length === 0) {
|
|
2658
3987
|
delete target.defaults;
|
|
@@ -2699,9 +4028,9 @@ function renderCallHeader(params, theme) {
|
|
|
2699
4028
|
},
|
|
2700
4029
|
render(width) {
|
|
2701
4030
|
let header = theme.fg("toolTitle", theme.bold("web_search"));
|
|
2702
|
-
const
|
|
2703
|
-
if (
|
|
2704
|
-
header += ` ${theme.fg("accent",
|
|
4031
|
+
const query2 = cleanSingleLine(String(params.query ?? "")).trim();
|
|
4032
|
+
if (query2.length > 0) {
|
|
4033
|
+
header += ` ${theme.fg("accent", formatQuotedPreview(query2))} `;
|
|
2705
4034
|
}
|
|
2706
4035
|
const lines = [];
|
|
2707
4036
|
const headerLine = truncateToWidth(header.trimEnd(), width);
|
|
@@ -2758,6 +4087,18 @@ function getExpandHint() {
|
|
|
2758
4087
|
function cleanSingleLine(text) {
|
|
2759
4088
|
return text.replace(/\s+/g, " ").trim();
|
|
2760
4089
|
}
|
|
4090
|
+
function formatQuotedPreview(text, maxLength = 80) {
|
|
4091
|
+
return `"${truncateInline(cleanSingleLine(text), maxLength)}"`;
|
|
4092
|
+
}
|
|
4093
|
+
function formatElapsed2(ms) {
|
|
4094
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
4095
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
4096
|
+
const seconds = totalSeconds % 60;
|
|
4097
|
+
if (minutes > 0) {
|
|
4098
|
+
return `${minutes}m ${seconds}s`;
|
|
4099
|
+
}
|
|
4100
|
+
return `${totalSeconds}s`;
|
|
4101
|
+
}
|
|
2761
4102
|
function formatSearchResponse(response) {
|
|
2762
4103
|
if (response.results.length === 0) {
|
|
2763
4104
|
return "No results found.";
|
|
@@ -2779,9 +4120,9 @@ async function truncateAndSave(text, prefix) {
|
|
|
2779
4120
|
maxBytes: DEFAULT_MAX_BYTES
|
|
2780
4121
|
});
|
|
2781
4122
|
if (!truncation.truncated) return truncation.content;
|
|
2782
|
-
const dir =
|
|
4123
|
+
const dir = join4(tmpdir(), `pi-web-providers-${prefix}-${Date.now()}`);
|
|
2783
4124
|
await mkdir2(dir, { recursive: true });
|
|
2784
|
-
const fullPath =
|
|
4125
|
+
const fullPath = join4(dir, "output.txt");
|
|
2785
4126
|
await writeFile2(fullPath, text, "utf-8");
|
|
2786
4127
|
return truncation.content + `
|
|
2787
4128
|
|
|
@@ -2792,7 +4133,11 @@ function truncateInline(text, maxLength) {
|
|
|
2792
4133
|
return `${text.slice(0, maxLength - 1)}\u2026`;
|
|
2793
4134
|
}
|
|
2794
4135
|
var __test__ = {
|
|
4136
|
+
executeProviderTool,
|
|
2795
4137
|
extractTextContent,
|
|
4138
|
+
getAvailableManagedToolNames,
|
|
4139
|
+
getAvailableProviderIdsForCapability,
|
|
4140
|
+
getSyncedActiveTools,
|
|
2796
4141
|
renderCallHeader,
|
|
2797
4142
|
renderCollapsedSearchSummary
|
|
2798
4143
|
};
|