pi-web-providers 0.1.0 → 0.2.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 +69 -40
- package/dist/index.js +1010 -147
- package/package.json +7 -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,30 +9,27 @@ 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
35
|
gemini: ["search", "answer", "research"],
|
|
@@ -76,6 +76,7 @@ var LEGACY_TOOL_ALIASES = {
|
|
|
76
76
|
};
|
|
77
77
|
var CONFIG_FILE_NAME = "web-providers.json";
|
|
78
78
|
var VERSION = 1;
|
|
79
|
+
var commandValueCache = /* @__PURE__ */ new Map();
|
|
79
80
|
function getConfigPath() {
|
|
80
81
|
return join(getAgentDir(), CONFIG_FILE_NAME);
|
|
81
82
|
}
|
|
@@ -115,11 +116,26 @@ function serializeConfig(config) {
|
|
|
115
116
|
function resolveConfigValue(reference) {
|
|
116
117
|
if (!reference) return void 0;
|
|
117
118
|
if (reference.startsWith("!")) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
const cached = commandValueCache.get(reference);
|
|
120
|
+
if (cached) {
|
|
121
|
+
if (cached.errorMessage) {
|
|
122
|
+
throw new Error(cached.errorMessage);
|
|
123
|
+
}
|
|
124
|
+
return cached.value;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const output = execSync(reference.slice(1), {
|
|
128
|
+
encoding: "utf-8",
|
|
129
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
130
|
+
}).trim();
|
|
131
|
+
const value = output.length > 0 ? output : void 0;
|
|
132
|
+
commandValueCache.set(reference, { value });
|
|
133
|
+
return value;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const errorMessage = error.message;
|
|
136
|
+
commandValueCache.set(reference, { errorMessage });
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
123
139
|
}
|
|
124
140
|
const envValue = process.env[reference];
|
|
125
141
|
if (envValue !== void 0) {
|
|
@@ -158,6 +174,12 @@ function normalizeConfig(raw, source) {
|
|
|
158
174
|
throw new Error(`'providers' in ${source} must be a JSON object.`);
|
|
159
175
|
}
|
|
160
176
|
config.providers = {};
|
|
177
|
+
if (raw.providers.claude !== void 0) {
|
|
178
|
+
config.providers.claude = normalizeClaudeProvider(
|
|
179
|
+
raw.providers.claude,
|
|
180
|
+
source
|
|
181
|
+
);
|
|
182
|
+
}
|
|
161
183
|
if (raw.providers.codex !== void 0) {
|
|
162
184
|
config.providers.codex = normalizeCodexProvider(
|
|
163
185
|
raw.providers.codex,
|
|
@@ -186,7 +208,7 @@ function normalizeConfig(raw, source) {
|
|
|
186
208
|
);
|
|
187
209
|
}
|
|
188
210
|
const unknownProviders = Object.keys(raw.providers).filter(
|
|
189
|
-
(key) => key !== "codex" && key !== "exa" && key !== "gemini" && key !== "parallel" && key !== "valyu"
|
|
211
|
+
(key) => key !== "claude" && key !== "codex" && key !== "exa" && key !== "gemini" && key !== "parallel" && key !== "valyu"
|
|
190
212
|
);
|
|
191
213
|
if (unknownProviders.length > 0) {
|
|
192
214
|
throw new Error(
|
|
@@ -196,6 +218,50 @@ function normalizeConfig(raw, source) {
|
|
|
196
218
|
}
|
|
197
219
|
return config;
|
|
198
220
|
}
|
|
221
|
+
function normalizeClaudeProvider(raw, source) {
|
|
222
|
+
const provider = parseProviderObject(raw, source, "claude");
|
|
223
|
+
const defaults = parseOptionalJsonObject(
|
|
224
|
+
provider.defaults,
|
|
225
|
+
source,
|
|
226
|
+
"providers.claude.defaults"
|
|
227
|
+
);
|
|
228
|
+
return {
|
|
229
|
+
enabled: parseOptionalBoolean(
|
|
230
|
+
provider.enabled,
|
|
231
|
+
source,
|
|
232
|
+
"providers.claude.enabled"
|
|
233
|
+
),
|
|
234
|
+
tools: parseOptionalProviderTools(
|
|
235
|
+
"claude",
|
|
236
|
+
provider.tools,
|
|
237
|
+
source,
|
|
238
|
+
"providers.claude.tools"
|
|
239
|
+
),
|
|
240
|
+
pathToClaudeCodeExecutable: parseOptionalString(
|
|
241
|
+
provider.pathToClaudeCodeExecutable,
|
|
242
|
+
source,
|
|
243
|
+
"providers.claude.pathToClaudeCodeExecutable"
|
|
244
|
+
),
|
|
245
|
+
defaults: defaults === void 0 ? void 0 : {
|
|
246
|
+
model: parseOptionalString(
|
|
247
|
+
defaults.model,
|
|
248
|
+
source,
|
|
249
|
+
"providers.claude.defaults.model"
|
|
250
|
+
),
|
|
251
|
+
effort: parseOptionalLiteral(
|
|
252
|
+
defaults.effort,
|
|
253
|
+
source,
|
|
254
|
+
"providers.claude.defaults.effort",
|
|
255
|
+
["low", "medium", "high", "max"]
|
|
256
|
+
),
|
|
257
|
+
maxTurns: parseOptionalInteger(
|
|
258
|
+
defaults.maxTurns,
|
|
259
|
+
source,
|
|
260
|
+
"providers.claude.defaults.maxTurns"
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
199
265
|
function normalizeCodexProvider(raw, source) {
|
|
200
266
|
const provider = parseProviderObject(raw, source, "codex");
|
|
201
267
|
const defaults = parseOptionalJsonObject(
|
|
@@ -450,18 +516,14 @@ function parseOptionalProviderTools(providerId, value, source, field) {
|
|
|
450
516
|
continue;
|
|
451
517
|
}
|
|
452
518
|
if (!supportsProviderTool(providerId, normalizedKey)) {
|
|
453
|
-
throw new Error(
|
|
454
|
-
`Unknown tools for ${providerId} in ${source}: ${key}.`
|
|
455
|
-
);
|
|
519
|
+
throw new Error(`Unknown tools for ${providerId} in ${source}: ${key}.`);
|
|
456
520
|
}
|
|
457
521
|
parsed[normalizedKey] = parseBoolean(entry, source, `${field}.${key}`);
|
|
458
522
|
}
|
|
459
|
-
const unknownTools = Object.keys(value).filter(
|
|
460
|
-
(toolId)
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
);
|
|
523
|
+
const unknownTools = Object.keys(value).filter((toolId) => {
|
|
524
|
+
const normalizedKey = normalizeProviderToolKey(providerId, toolId);
|
|
525
|
+
return normalizedKey !== null && !PROVIDER_TOOLS[providerId].includes(normalizedKey);
|
|
526
|
+
});
|
|
465
527
|
if (unknownTools.length > 0) {
|
|
466
528
|
throw new Error(
|
|
467
529
|
`Unknown tools for ${providerId} in ${source}: ${unknownTools.join(", ")}.`
|
|
@@ -522,6 +584,13 @@ function parseString(value, source, field) {
|
|
|
522
584
|
}
|
|
523
585
|
return value;
|
|
524
586
|
}
|
|
587
|
+
function parseOptionalInteger(value, source, field) {
|
|
588
|
+
if (value === void 0) return void 0;
|
|
589
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
590
|
+
throw new Error(`'${field}' in ${source} must be a positive integer.`);
|
|
591
|
+
}
|
|
592
|
+
return value;
|
|
593
|
+
}
|
|
525
594
|
function parseOptionalLiteral(value, source, field, allowed) {
|
|
526
595
|
if (value === void 0) return void 0;
|
|
527
596
|
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
@@ -535,8 +604,14 @@ function isPlainObject(value) {
|
|
|
535
604
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
536
605
|
}
|
|
537
606
|
|
|
538
|
-
// src/providers/
|
|
539
|
-
import {
|
|
607
|
+
// src/providers/claude.ts
|
|
608
|
+
import { execFileSync } from "node:child_process";
|
|
609
|
+
import { existsSync } from "node:fs";
|
|
610
|
+
import { createRequire } from "node:module";
|
|
611
|
+
import { dirname as dirname2, extname, join as join2 } from "node:path";
|
|
612
|
+
import {
|
|
613
|
+
query
|
|
614
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
540
615
|
|
|
541
616
|
// src/providers/shared.ts
|
|
542
617
|
function trimSnippet(input, maxLength = 300) {
|
|
@@ -551,7 +626,362 @@ function formatJson(value) {
|
|
|
551
626
|
return JSON.stringify(value, null, 2);
|
|
552
627
|
}
|
|
553
628
|
|
|
629
|
+
// src/providers/claude.ts
|
|
630
|
+
var require2 = createRequire(import.meta.url);
|
|
631
|
+
var SEARCH_OUTPUT_SCHEMA = {
|
|
632
|
+
type: "object",
|
|
633
|
+
additionalProperties: false,
|
|
634
|
+
properties: {
|
|
635
|
+
sources: {
|
|
636
|
+
type: "array",
|
|
637
|
+
items: {
|
|
638
|
+
type: "object",
|
|
639
|
+
additionalProperties: false,
|
|
640
|
+
properties: {
|
|
641
|
+
title: { type: "string" },
|
|
642
|
+
url: { type: "string" },
|
|
643
|
+
snippet: { type: "string" }
|
|
644
|
+
},
|
|
645
|
+
required: ["title", "url", "snippet"]
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
required: ["sources"]
|
|
650
|
+
};
|
|
651
|
+
var ANSWER_OUTPUT_SCHEMA = {
|
|
652
|
+
type: "object",
|
|
653
|
+
additionalProperties: false,
|
|
654
|
+
properties: {
|
|
655
|
+
answer: { type: "string" },
|
|
656
|
+
sources: {
|
|
657
|
+
type: "array",
|
|
658
|
+
items: {
|
|
659
|
+
type: "object",
|
|
660
|
+
additionalProperties: false,
|
|
661
|
+
properties: {
|
|
662
|
+
title: { type: "string" },
|
|
663
|
+
url: { type: "string" }
|
|
664
|
+
},
|
|
665
|
+
required: ["title", "url"]
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
required: ["answer", "sources"]
|
|
670
|
+
};
|
|
671
|
+
var ClaudeProvider = class {
|
|
672
|
+
id = "claude";
|
|
673
|
+
label = "Claude";
|
|
674
|
+
docsUrl = "https://github.com/anthropics/claude-agent-sdk-typescript";
|
|
675
|
+
createTemplate() {
|
|
676
|
+
return {
|
|
677
|
+
enabled: false,
|
|
678
|
+
tools: {
|
|
679
|
+
search: true,
|
|
680
|
+
answer: true
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
getStatus(config, _cwd) {
|
|
685
|
+
if (!config) {
|
|
686
|
+
return { available: false, summary: "not configured" };
|
|
687
|
+
}
|
|
688
|
+
if (config.enabled === false) {
|
|
689
|
+
return { available: false, summary: "disabled" };
|
|
690
|
+
}
|
|
691
|
+
const executablePath = resolveClaudeExecutablePath(config);
|
|
692
|
+
if (executablePath && !existsSync(executablePath)) {
|
|
693
|
+
return { available: false, summary: "missing Claude Code executable" };
|
|
694
|
+
}
|
|
695
|
+
const authStatus = getClaudeAuthStatus(executablePath);
|
|
696
|
+
if (!authStatus.loggedIn) {
|
|
697
|
+
return { available: false, summary: "missing Claude auth" };
|
|
698
|
+
}
|
|
699
|
+
return { available: true, summary: "enabled" };
|
|
700
|
+
}
|
|
701
|
+
async search(queryText, maxResults, config, context) {
|
|
702
|
+
const output = parseClaudeSearchOutput(
|
|
703
|
+
await this.runStructuredQuery({
|
|
704
|
+
prompt: [
|
|
705
|
+
"You are performing web research for another coding agent.",
|
|
706
|
+
"Use the WebSearch tool to search the public web.",
|
|
707
|
+
"Return only a JSON object matching the provided schema.",
|
|
708
|
+
"Do not include markdown fences or extra commentary.",
|
|
709
|
+
`Return at most ${maxResults} sources.`,
|
|
710
|
+
"Each snippet should be short, factual, and specific to the result.",
|
|
711
|
+
"Prefer primary or official sources when they are available.",
|
|
712
|
+
"",
|
|
713
|
+
`User query: ${queryText}`
|
|
714
|
+
].join("\n"),
|
|
715
|
+
schema: SEARCH_OUTPUT_SCHEMA,
|
|
716
|
+
tools: ["WebSearch"],
|
|
717
|
+
config,
|
|
718
|
+
context
|
|
719
|
+
})
|
|
720
|
+
);
|
|
721
|
+
return {
|
|
722
|
+
provider: this.id,
|
|
723
|
+
results: output.sources.slice(0, maxResults).map((source) => ({
|
|
724
|
+
title: source.title.trim(),
|
|
725
|
+
url: source.url.trim(),
|
|
726
|
+
snippet: trimSnippet(source.snippet)
|
|
727
|
+
}))
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
async answer(queryText, options, config, context) {
|
|
731
|
+
const output = parseClaudeAnswerOutput(
|
|
732
|
+
await this.runStructuredQuery({
|
|
733
|
+
prompt: [
|
|
734
|
+
"Answer the user's question using current public web information.",
|
|
735
|
+
"Use WebSearch to find relevant sources and WebFetch when you need to verify important details.",
|
|
736
|
+
"Return only a JSON object matching the provided schema.",
|
|
737
|
+
"Do not include markdown fences or extra commentary.",
|
|
738
|
+
"Keep the answer concise but informative.",
|
|
739
|
+
"Only cite sources you actually used.",
|
|
740
|
+
"",
|
|
741
|
+
`User query: ${queryText}`,
|
|
742
|
+
options ? `Additional options: ${JSON.stringify(options)}` : ""
|
|
743
|
+
].filter(Boolean).join("\n"),
|
|
744
|
+
schema: ANSWER_OUTPUT_SCHEMA,
|
|
745
|
+
tools: ["WebSearch", "WebFetch"],
|
|
746
|
+
config,
|
|
747
|
+
context
|
|
748
|
+
})
|
|
749
|
+
);
|
|
750
|
+
const lines = [];
|
|
751
|
+
lines.push(output.answer.trim() || "No answer returned.");
|
|
752
|
+
if (output.sources.length > 0) {
|
|
753
|
+
lines.push("");
|
|
754
|
+
lines.push("Sources:");
|
|
755
|
+
for (const [index, source] of output.sources.entries()) {
|
|
756
|
+
lines.push(`${index + 1}. ${source.title}`);
|
|
757
|
+
lines.push(` ${source.url}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
provider: this.id,
|
|
762
|
+
text: lines.join("\n").trimEnd(),
|
|
763
|
+
summary: `Answer via Claude with ${output.sources.length} source(s)`,
|
|
764
|
+
itemCount: output.sources.length
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
async runStructuredQuery({
|
|
768
|
+
prompt,
|
|
769
|
+
schema,
|
|
770
|
+
tools,
|
|
771
|
+
config,
|
|
772
|
+
context
|
|
773
|
+
}) {
|
|
774
|
+
const abortController = new AbortController();
|
|
775
|
+
if (context.signal?.aborted) {
|
|
776
|
+
abortController.abort(context.signal.reason);
|
|
777
|
+
}
|
|
778
|
+
const onAbort = () => {
|
|
779
|
+
abortController.abort(context.signal?.reason);
|
|
780
|
+
};
|
|
781
|
+
context.signal?.addEventListener("abort", onAbort, { once: true });
|
|
782
|
+
const stream = query({
|
|
783
|
+
prompt,
|
|
784
|
+
options: {
|
|
785
|
+
abortController,
|
|
786
|
+
allowedTools: tools,
|
|
787
|
+
cwd: context.cwd,
|
|
788
|
+
effort: config.defaults?.effort,
|
|
789
|
+
maxTurns: config.defaults?.maxTurns,
|
|
790
|
+
model: config.defaults?.model,
|
|
791
|
+
outputFormat: {
|
|
792
|
+
type: "json_schema",
|
|
793
|
+
schema
|
|
794
|
+
},
|
|
795
|
+
pathToClaudeCodeExecutable: config.pathToClaudeCodeExecutable,
|
|
796
|
+
persistSession: false,
|
|
797
|
+
permissionMode: "dontAsk",
|
|
798
|
+
systemPrompt: {
|
|
799
|
+
type: "preset",
|
|
800
|
+
preset: "claude_code",
|
|
801
|
+
append: "Use only the provided web tools. Always produce output that matches the requested JSON schema exactly."
|
|
802
|
+
},
|
|
803
|
+
tools
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
const seenToolUseIds = /* @__PURE__ */ new Set();
|
|
807
|
+
let finalResult;
|
|
808
|
+
try {
|
|
809
|
+
for await (const message of stream) {
|
|
810
|
+
handleProgressMessage(message, seenToolUseIds, context.onProgress);
|
|
811
|
+
if (message.type === "result") {
|
|
812
|
+
finalResult = message;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
} finally {
|
|
816
|
+
context.signal?.removeEventListener("abort", onAbort);
|
|
817
|
+
stream.close();
|
|
818
|
+
}
|
|
819
|
+
if (!finalResult) {
|
|
820
|
+
throw new Error("Claude returned no result.");
|
|
821
|
+
}
|
|
822
|
+
if (finalResult.subtype !== "success") {
|
|
823
|
+
throw new Error(
|
|
824
|
+
finalResult.errors.join("\n") || `Claude query failed (${finalResult.subtype}).`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
return parseStructuredOutput(finalResult);
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
var CLAUDE_AUTH_CACHE_TTL_MS = 5e3;
|
|
831
|
+
var defaultClaudeExecutablePath;
|
|
832
|
+
var claudeAuthStatusCache = /* @__PURE__ */ new Map();
|
|
833
|
+
function resolveClaudeExecutablePath(config) {
|
|
834
|
+
if (config.pathToClaudeCodeExecutable) {
|
|
835
|
+
return config.pathToClaudeCodeExecutable;
|
|
836
|
+
}
|
|
837
|
+
if (defaultClaudeExecutablePath !== void 0) {
|
|
838
|
+
return defaultClaudeExecutablePath;
|
|
839
|
+
}
|
|
840
|
+
try {
|
|
841
|
+
const sdkEntryPath = require2.resolve("@anthropic-ai/claude-agent-sdk");
|
|
842
|
+
defaultClaudeExecutablePath = join2(dirname2(sdkEntryPath), "cli.js");
|
|
843
|
+
} catch {
|
|
844
|
+
defaultClaudeExecutablePath = void 0;
|
|
845
|
+
}
|
|
846
|
+
return defaultClaudeExecutablePath;
|
|
847
|
+
}
|
|
848
|
+
function getClaudeAuthStatus(executablePath) {
|
|
849
|
+
if (!executablePath) {
|
|
850
|
+
return { loggedIn: false };
|
|
851
|
+
}
|
|
852
|
+
const cachedStatus = claudeAuthStatusCache.get(executablePath);
|
|
853
|
+
if (cachedStatus && Date.now() - cachedStatus.checkedAt < CLAUDE_AUTH_CACHE_TTL_MS) {
|
|
854
|
+
return { loggedIn: cachedStatus.loggedIn };
|
|
855
|
+
}
|
|
856
|
+
const [command, ...args] = getClaudeAuthCommand(executablePath);
|
|
857
|
+
try {
|
|
858
|
+
const stdout = execFileSync(command, args, {
|
|
859
|
+
encoding: "utf8",
|
|
860
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
861
|
+
});
|
|
862
|
+
return cacheClaudeAuthStatus(executablePath, parseClaudeAuthStatus(stdout));
|
|
863
|
+
} catch (error) {
|
|
864
|
+
const stdout = getExecOutput(
|
|
865
|
+
error.stdout
|
|
866
|
+
);
|
|
867
|
+
if (stdout) {
|
|
868
|
+
return cacheClaudeAuthStatus(
|
|
869
|
+
executablePath,
|
|
870
|
+
parseClaudeAuthStatus(stdout)
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
return cacheClaudeAuthStatus(executablePath, { loggedIn: false });
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function cacheClaudeAuthStatus(executablePath, status) {
|
|
877
|
+
claudeAuthStatusCache.set(executablePath, {
|
|
878
|
+
...status,
|
|
879
|
+
checkedAt: Date.now()
|
|
880
|
+
});
|
|
881
|
+
return status;
|
|
882
|
+
}
|
|
883
|
+
function getClaudeAuthCommand(executablePath) {
|
|
884
|
+
const extension = extname(executablePath);
|
|
885
|
+
if (extension === ".js" || extension === ".cjs" || extension === ".mjs") {
|
|
886
|
+
return [process.execPath, executablePath, "auth", "status", "--json"];
|
|
887
|
+
}
|
|
888
|
+
return [executablePath, "auth", "status", "--json"];
|
|
889
|
+
}
|
|
890
|
+
function getExecOutput(output) {
|
|
891
|
+
if (typeof output === "string") {
|
|
892
|
+
return output;
|
|
893
|
+
}
|
|
894
|
+
if (Buffer.isBuffer(output)) {
|
|
895
|
+
return output.toString("utf8");
|
|
896
|
+
}
|
|
897
|
+
return "";
|
|
898
|
+
}
|
|
899
|
+
function parseClaudeAuthStatus(raw) {
|
|
900
|
+
try {
|
|
901
|
+
const parsed = JSON.parse(raw);
|
|
902
|
+
return { loggedIn: parsed.loggedIn === true };
|
|
903
|
+
} catch {
|
|
904
|
+
return { loggedIn: false };
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function handleProgressMessage(message, seenToolUseIds, onProgress) {
|
|
908
|
+
if (!onProgress || message.type !== "tool_progress") {
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (seenToolUseIds.has(message.tool_use_id)) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
seenToolUseIds.add(message.tool_use_id);
|
|
915
|
+
onProgress(`Claude ${formatToolName(message.tool_name)}`);
|
|
916
|
+
}
|
|
917
|
+
function formatToolName(toolName) {
|
|
918
|
+
if (toolName === "WebSearch") return "web search";
|
|
919
|
+
if (toolName === "WebFetch") return "web fetch";
|
|
920
|
+
return toolName;
|
|
921
|
+
}
|
|
922
|
+
function parseStructuredOutput(result) {
|
|
923
|
+
if (result.subtype !== "success") {
|
|
924
|
+
throw new Error("Claude query did not succeed.");
|
|
925
|
+
}
|
|
926
|
+
if (result.structured_output !== void 0) {
|
|
927
|
+
return result.structured_output;
|
|
928
|
+
}
|
|
929
|
+
if (!result.result.trim()) {
|
|
930
|
+
throw new Error("Claude returned an empty response.");
|
|
931
|
+
}
|
|
932
|
+
try {
|
|
933
|
+
return JSON.parse(result.result);
|
|
934
|
+
} catch {
|
|
935
|
+
const match = result.result.match(/\{[\s\S]*\}/);
|
|
936
|
+
if (!match) {
|
|
937
|
+
throw new Error("Claude returned invalid JSON output.");
|
|
938
|
+
}
|
|
939
|
+
return JSON.parse(match[0]);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function parseClaudeSearchOutput(value) {
|
|
943
|
+
const sources = readArray(value, "sources").map((entry) => ({
|
|
944
|
+
title: readString(entry, "title"),
|
|
945
|
+
url: readString(entry, "url"),
|
|
946
|
+
snippet: readString(entry, "snippet")
|
|
947
|
+
}));
|
|
948
|
+
return { sources };
|
|
949
|
+
}
|
|
950
|
+
function parseClaudeAnswerOutput(value) {
|
|
951
|
+
return {
|
|
952
|
+
answer: readString(value, "answer"),
|
|
953
|
+
sources: readArray(value, "sources").map((entry) => ({
|
|
954
|
+
title: readString(entry, "title"),
|
|
955
|
+
url: readString(entry, "url")
|
|
956
|
+
}))
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
function readArray(value, key) {
|
|
960
|
+
if (typeof value !== "object" || value === null || !(key in value)) {
|
|
961
|
+
throw new Error(`Claude output is missing '${key}'.`);
|
|
962
|
+
}
|
|
963
|
+
const entry = value[key];
|
|
964
|
+
if (!Array.isArray(entry)) {
|
|
965
|
+
throw new Error(`Claude output field '${key}' must be an array.`);
|
|
966
|
+
}
|
|
967
|
+
return entry;
|
|
968
|
+
}
|
|
969
|
+
function readString(value, key) {
|
|
970
|
+
if (typeof value !== "object" || value === null || !(key in value)) {
|
|
971
|
+
throw new Error(`Claude output is missing '${key}'.`);
|
|
972
|
+
}
|
|
973
|
+
const entry = value[key];
|
|
974
|
+
if (typeof entry !== "string") {
|
|
975
|
+
throw new Error(`Claude output field '${key}' must be a string.`);
|
|
976
|
+
}
|
|
977
|
+
return entry;
|
|
978
|
+
}
|
|
979
|
+
|
|
554
980
|
// src/providers/codex.ts
|
|
981
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
982
|
+
import { homedir } from "node:os";
|
|
983
|
+
import { join as join3 } from "node:path";
|
|
984
|
+
import { Codex } from "@openai/codex-sdk";
|
|
555
985
|
var OUTPUT_SCHEMA = {
|
|
556
986
|
type: "object",
|
|
557
987
|
additionalProperties: false,
|
|
@@ -596,9 +1026,23 @@ var CodexProvider = class {
|
|
|
596
1026
|
if (config.enabled === false) {
|
|
597
1027
|
return { available: false, summary: "disabled" };
|
|
598
1028
|
}
|
|
1029
|
+
try {
|
|
1030
|
+
new Codex({
|
|
1031
|
+
codexPathOverride: config.codexPath,
|
|
1032
|
+
config: config.config
|
|
1033
|
+
});
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
return {
|
|
1036
|
+
available: false,
|
|
1037
|
+
summary: error.message
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
if (!hasCodexCredentials(config)) {
|
|
1041
|
+
return { available: false, summary: "missing Codex auth" };
|
|
1042
|
+
}
|
|
599
1043
|
return { available: true, summary: "enabled" };
|
|
600
1044
|
}
|
|
601
|
-
async search(
|
|
1045
|
+
async search(query2, maxResults, config, context) {
|
|
602
1046
|
const codex = new Codex({
|
|
603
1047
|
codexPathOverride: config.codexPath,
|
|
604
1048
|
baseUrl: config.baseUrl,
|
|
@@ -626,7 +1070,7 @@ var CodexProvider = class {
|
|
|
626
1070
|
"Prefer primary or official sources when they are available.",
|
|
627
1071
|
"Each snippet should be short and specific.",
|
|
628
1072
|
"",
|
|
629
|
-
`User query: ${
|
|
1073
|
+
`User query: ${query2}`
|
|
630
1074
|
].join("\n");
|
|
631
1075
|
const streamed = await thread.runStreamed(prompt, {
|
|
632
1076
|
outputSchema: OUTPUT_SCHEMA,
|
|
@@ -654,6 +1098,37 @@ var CodexProvider = class {
|
|
|
654
1098
|
};
|
|
655
1099
|
}
|
|
656
1100
|
};
|
|
1101
|
+
function hasCodexCredentials(config) {
|
|
1102
|
+
if (hasConfiguredReference(config.apiKey)) {
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
if (hasConfiguredReference(config.env?.CODEX_API_KEY) || hasConfiguredReference(config.env?.OPENAI_API_KEY)) {
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
if (!config.env) {
|
|
1109
|
+
const inheritedKey = process.env.CODEX_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
1110
|
+
if (typeof inheritedKey === "string" && inheritedKey.trim().length > 0) {
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return existsSync2(join3(homedir(), ".codex", "auth.json"));
|
|
1115
|
+
}
|
|
1116
|
+
function hasConfiguredReference(reference) {
|
|
1117
|
+
if (!reference) {
|
|
1118
|
+
return false;
|
|
1119
|
+
}
|
|
1120
|
+
if (reference.startsWith("!")) {
|
|
1121
|
+
return reference.slice(1).trim().length > 0;
|
|
1122
|
+
}
|
|
1123
|
+
const envValue = process.env[reference];
|
|
1124
|
+
if (typeof envValue === "string") {
|
|
1125
|
+
return envValue.trim().length > 0;
|
|
1126
|
+
}
|
|
1127
|
+
if (/^[A-Z][A-Z0-9_]*$/.test(reference)) {
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1130
|
+
return reference.trim().length > 0;
|
|
1131
|
+
}
|
|
657
1132
|
function handleProgressEvent(event, seenQueries, onProgress) {
|
|
658
1133
|
if (!onProgress) return;
|
|
659
1134
|
if (event.type === "item.completed" && event.item.type === "web_search" && !seenQueries.has(event.item.query)) {
|
|
@@ -713,7 +1188,7 @@ var ExaProvider = class {
|
|
|
713
1188
|
}
|
|
714
1189
|
return { available: true, summary: "enabled" };
|
|
715
1190
|
}
|
|
716
|
-
async search(
|
|
1191
|
+
async search(query2, maxResults, config, context) {
|
|
717
1192
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
718
1193
|
if (!apiKey) {
|
|
719
1194
|
throw new Error("Exa is missing an API key.");
|
|
@@ -723,8 +1198,8 @@ var ExaProvider = class {
|
|
|
723
1198
|
...asJsonObject(config.defaults),
|
|
724
1199
|
numResults: maxResults
|
|
725
1200
|
};
|
|
726
|
-
context.onProgress?.(`Searching Exa for: ${
|
|
727
|
-
const response = await client.search(
|
|
1201
|
+
context.onProgress?.(`Searching Exa for: ${query2}`);
|
|
1202
|
+
const response = await client.search(query2, options);
|
|
728
1203
|
return {
|
|
729
1204
|
provider: this.id,
|
|
730
1205
|
results: (response.results ?? []).slice(0, maxResults).map((result) => ({
|
|
@@ -743,11 +1218,15 @@ var ExaProvider = class {
|
|
|
743
1218
|
throw new Error("Exa is missing an API key.");
|
|
744
1219
|
}
|
|
745
1220
|
const client = new Exa(apiKey, config.baseUrl);
|
|
746
|
-
context.onProgress?.(
|
|
1221
|
+
context.onProgress?.(
|
|
1222
|
+
`Fetching contents from Exa for ${urls.length} URL(s)`
|
|
1223
|
+
);
|
|
747
1224
|
const response = await client.getContents(urls, options);
|
|
748
1225
|
const lines = [];
|
|
749
1226
|
for (const [index, result] of (response.results ?? []).entries()) {
|
|
750
|
-
lines.push(
|
|
1227
|
+
lines.push(
|
|
1228
|
+
`${index + 1}. ${String(result.title ?? result.url ?? "Untitled")}`
|
|
1229
|
+
);
|
|
751
1230
|
lines.push(` ${String(result.url ?? "")}`);
|
|
752
1231
|
const summary = typeof result.summary === "string" ? result.summary : result.summary ? formatJson(result.summary) : void 0;
|
|
753
1232
|
const text = typeof result.text === "string" ? result.text : Array.isArray(result.highlights) ? result.highlights.join(" ") : "";
|
|
@@ -764,14 +1243,14 @@ var ExaProvider = class {
|
|
|
764
1243
|
itemCount: response.results?.length ?? 0
|
|
765
1244
|
};
|
|
766
1245
|
}
|
|
767
|
-
async answer(
|
|
1246
|
+
async answer(query2, options, config, context) {
|
|
768
1247
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
769
1248
|
if (!apiKey) {
|
|
770
1249
|
throw new Error("Exa is missing an API key.");
|
|
771
1250
|
}
|
|
772
1251
|
const client = new Exa(apiKey, config.baseUrl);
|
|
773
|
-
context.onProgress?.(`Getting Exa answer for: ${
|
|
774
|
-
const response = await client.answer(
|
|
1252
|
+
context.onProgress?.(`Getting Exa answer for: ${query2}`);
|
|
1253
|
+
const response = await client.answer(query2, options);
|
|
775
1254
|
const lines = [];
|
|
776
1255
|
lines.push(
|
|
777
1256
|
typeof response.answer === "string" ? response.answer : formatJson(response.answer)
|
|
@@ -781,7 +1260,9 @@ var ExaProvider = class {
|
|
|
781
1260
|
lines.push("");
|
|
782
1261
|
lines.push("Sources:");
|
|
783
1262
|
for (const [index, citation] of citations.entries()) {
|
|
784
|
-
lines.push(
|
|
1263
|
+
lines.push(
|
|
1264
|
+
`${index + 1}. ${String(citation.title ?? citation.url ?? "Untitled")}`
|
|
1265
|
+
);
|
|
785
1266
|
lines.push(` ${String(citation.url ?? "")}`);
|
|
786
1267
|
}
|
|
787
1268
|
}
|
|
@@ -859,35 +1340,33 @@ var GeminiProvider = class {
|
|
|
859
1340
|
}
|
|
860
1341
|
return { available: true, summary: "enabled" };
|
|
861
1342
|
}
|
|
862
|
-
async search(
|
|
1343
|
+
async search(query2, maxResults, config, context) {
|
|
863
1344
|
const ai = this.createClient(config);
|
|
864
1345
|
const model = config.defaults?.searchModel ?? DEFAULT_SEARCH_MODEL;
|
|
865
|
-
context.onProgress?.(`Searching Gemini for: ${
|
|
866
|
-
const interaction = await ai
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
snippet: trimSnippet(result.rendered_content ?? "")
|
|
878
|
-
}));
|
|
1346
|
+
context.onProgress?.(`Searching Gemini for: ${query2}`);
|
|
1347
|
+
const interaction = await createSearchInteraction(ai, model, query2);
|
|
1348
|
+
const results = await Promise.all(
|
|
1349
|
+
extractGoogleSearchResults(interaction.outputs).slice(0, maxResults).map(async (result) => {
|
|
1350
|
+
const resolvedUrl = await resolveGoogleSearchUrl(result.url);
|
|
1351
|
+
return {
|
|
1352
|
+
title: result.title ?? resolvedUrl ?? result.url ?? "Untitled",
|
|
1353
|
+
url: resolvedUrl ?? result.url ?? "",
|
|
1354
|
+
snippet: ""
|
|
1355
|
+
};
|
|
1356
|
+
})
|
|
1357
|
+
);
|
|
879
1358
|
return {
|
|
880
1359
|
provider: this.id,
|
|
881
1360
|
results
|
|
882
1361
|
};
|
|
883
1362
|
}
|
|
884
|
-
async answer(
|
|
1363
|
+
async answer(query2, options, config, context) {
|
|
885
1364
|
const ai = this.createClient(config);
|
|
886
1365
|
const model = config.defaults?.answerModel ?? DEFAULT_ANSWER_MODEL;
|
|
887
|
-
context.onProgress?.(`Getting Gemini answer for: ${
|
|
1366
|
+
context.onProgress?.(`Getting Gemini answer for: ${query2}`);
|
|
888
1367
|
const response = await ai.models.generateContent({
|
|
889
1368
|
model,
|
|
890
|
-
contents:
|
|
1369
|
+
contents: query2,
|
|
891
1370
|
config: {
|
|
892
1371
|
...options ?? {},
|
|
893
1372
|
tools: [{ googleSearch: {} }]
|
|
@@ -903,7 +1382,9 @@ var GeminiProvider = class {
|
|
|
903
1382
|
lines.push("Sources:");
|
|
904
1383
|
for (const [index, source] of sources.entries()) {
|
|
905
1384
|
lines.push(`${index + 1}. ${source.title}`);
|
|
906
|
-
|
|
1385
|
+
if (source.url) {
|
|
1386
|
+
lines.push(` ${source.url}`);
|
|
1387
|
+
}
|
|
907
1388
|
}
|
|
908
1389
|
}
|
|
909
1390
|
return {
|
|
@@ -918,6 +1399,8 @@ var GeminiProvider = class {
|
|
|
918
1399
|
const agent = config.defaults?.researchAgent ?? DEFAULT_RESEARCH_AGENT;
|
|
919
1400
|
const pollIntervalMs = getPollInterval(options);
|
|
920
1401
|
const requestOptions = stripPollIntervalOption(options);
|
|
1402
|
+
const startedAt = Date.now();
|
|
1403
|
+
let lastStatus;
|
|
921
1404
|
context.onProgress?.("Starting Gemini deep research");
|
|
922
1405
|
const initialInteraction = await ai.interactions.create({
|
|
923
1406
|
...requestOptions,
|
|
@@ -931,7 +1414,13 @@ var GeminiProvider = class {
|
|
|
931
1414
|
throw new Error("Gemini research aborted.");
|
|
932
1415
|
}
|
|
933
1416
|
const interaction = await ai.interactions.get(initialInteraction.id);
|
|
934
|
-
|
|
1417
|
+
const now = Date.now();
|
|
1418
|
+
if (interaction.status !== lastStatus) {
|
|
1419
|
+
context.onProgress?.(
|
|
1420
|
+
`Gemini research status: ${interaction.status} (${formatElapsed(now - startedAt)} elapsed)`
|
|
1421
|
+
);
|
|
1422
|
+
lastStatus = interaction.status;
|
|
1423
|
+
}
|
|
935
1424
|
if (interaction.status === "completed") {
|
|
936
1425
|
const text = formatInteractionOutputs(interaction.outputs);
|
|
937
1426
|
return {
|
|
@@ -988,19 +1477,29 @@ function extractGoogleSearchResults(outputs) {
|
|
|
988
1477
|
function extractGroundingSources(chunks) {
|
|
989
1478
|
const seen = /* @__PURE__ */ new Set();
|
|
990
1479
|
const sources = [];
|
|
1480
|
+
const maxSources = 5;
|
|
991
1481
|
if (!Array.isArray(chunks)) {
|
|
992
1482
|
return sources;
|
|
993
1483
|
}
|
|
994
1484
|
for (const chunk of chunks) {
|
|
995
1485
|
const web = typeof chunk === "object" && chunk !== null && "web" in chunk && typeof chunk.web === "object" && chunk.web !== null ? chunk.web : void 0;
|
|
996
1486
|
if (!web) continue;
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1487
|
+
const rawUrl = typeof web.uri === "string" ? web.uri : "";
|
|
1488
|
+
const title = formatGroundingSourceTitle(
|
|
1489
|
+
typeof web.title === "string" ? web.title : rawUrl,
|
|
1490
|
+
rawUrl
|
|
1491
|
+
);
|
|
1492
|
+
const url = formatGroundingSourceUrl(rawUrl);
|
|
1493
|
+
const key = [title.toLowerCase(), url.toLowerCase()].join("::");
|
|
1494
|
+
if (seen.has(key)) continue;
|
|
1495
|
+
seen.add(key);
|
|
1000
1496
|
sources.push({
|
|
1001
|
-
title
|
|
1497
|
+
title,
|
|
1002
1498
|
url
|
|
1003
1499
|
});
|
|
1500
|
+
if (sources.length >= maxSources) {
|
|
1501
|
+
break;
|
|
1502
|
+
}
|
|
1004
1503
|
}
|
|
1005
1504
|
return sources;
|
|
1006
1505
|
}
|
|
@@ -1019,6 +1518,87 @@ function formatInteractionOutputs(outputs) {
|
|
|
1019
1518
|
}
|
|
1020
1519
|
return lines.join("\n\n").trim();
|
|
1021
1520
|
}
|
|
1521
|
+
function formatGroundingSourceTitle(title, url) {
|
|
1522
|
+
const trimmedTitle = title?.trim();
|
|
1523
|
+
if (trimmedTitle) {
|
|
1524
|
+
return trimmedTitle;
|
|
1525
|
+
}
|
|
1526
|
+
if (url) {
|
|
1527
|
+
try {
|
|
1528
|
+
return new URL(url).hostname;
|
|
1529
|
+
} catch {
|
|
1530
|
+
return url;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return "Untitled";
|
|
1534
|
+
}
|
|
1535
|
+
function formatGroundingSourceUrl(url) {
|
|
1536
|
+
if (!url) {
|
|
1537
|
+
return "";
|
|
1538
|
+
}
|
|
1539
|
+
if (isGoogleGroundingRedirect(url)) {
|
|
1540
|
+
return "";
|
|
1541
|
+
}
|
|
1542
|
+
return url;
|
|
1543
|
+
}
|
|
1544
|
+
function isGoogleGroundingRedirect(url) {
|
|
1545
|
+
try {
|
|
1546
|
+
const parsed = new URL(url);
|
|
1547
|
+
return parsed.hostname === "vertexaisearch.cloud.google.com" && parsed.pathname.startsWith("/grounding-api-redirect/");
|
|
1548
|
+
} catch {
|
|
1549
|
+
return false;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
async function createSearchInteraction(ai, model, query2) {
|
|
1553
|
+
const request = {
|
|
1554
|
+
model,
|
|
1555
|
+
input: query2,
|
|
1556
|
+
tools: [{ type: "google_search" }]
|
|
1557
|
+
};
|
|
1558
|
+
try {
|
|
1559
|
+
return await ai.interactions.create({
|
|
1560
|
+
...request,
|
|
1561
|
+
generation_config: {
|
|
1562
|
+
tool_choice: "any"
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
if (!isBuiltInToolChoiceError(error)) {
|
|
1567
|
+
throw error;
|
|
1568
|
+
}
|
|
1569
|
+
return ai.interactions.create(request);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
function isBuiltInToolChoiceError(error) {
|
|
1573
|
+
if (error instanceof Error) {
|
|
1574
|
+
return error.message.includes(
|
|
1575
|
+
"Function calling config is set without function_declarations"
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
|
|
1579
|
+
return error.message.includes(
|
|
1580
|
+
"Function calling config is set without function_declarations"
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
async function resolveGoogleSearchUrl(url) {
|
|
1586
|
+
if (!url) {
|
|
1587
|
+
return void 0;
|
|
1588
|
+
}
|
|
1589
|
+
if (!isGoogleGroundingRedirect(url)) {
|
|
1590
|
+
return url;
|
|
1591
|
+
}
|
|
1592
|
+
try {
|
|
1593
|
+
const response = await fetch(url, {
|
|
1594
|
+
method: "HEAD",
|
|
1595
|
+
redirect: "manual"
|
|
1596
|
+
});
|
|
1597
|
+
return response.headers.get("location") || url;
|
|
1598
|
+
} catch {
|
|
1599
|
+
return url;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1022
1602
|
async function sleep(ms, signal) {
|
|
1023
1603
|
if (signal?.aborted) {
|
|
1024
1604
|
throw new Error("Operation aborted.");
|
|
@@ -1036,6 +1616,15 @@ async function sleep(ms, signal) {
|
|
|
1036
1616
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1037
1617
|
});
|
|
1038
1618
|
}
|
|
1619
|
+
function formatElapsed(ms) {
|
|
1620
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
1621
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
1622
|
+
const seconds = totalSeconds % 60;
|
|
1623
|
+
if (minutes > 0) {
|
|
1624
|
+
return `${minutes}m ${seconds}s`;
|
|
1625
|
+
}
|
|
1626
|
+
return `${totalSeconds}s`;
|
|
1627
|
+
}
|
|
1039
1628
|
function getPollInterval(options) {
|
|
1040
1629
|
const raw = options?.pollIntervalMs;
|
|
1041
1630
|
if (typeof raw === "number" && Number.isFinite(raw) && raw >= 1e3) {
|
|
@@ -1089,13 +1678,13 @@ var ParallelProvider = class {
|
|
|
1089
1678
|
}
|
|
1090
1679
|
return { available: true, summary: "enabled" };
|
|
1091
1680
|
}
|
|
1092
|
-
async search(
|
|
1681
|
+
async search(query2, maxResults, config, context) {
|
|
1093
1682
|
const client = this.createClient(config);
|
|
1094
1683
|
const defaults = asJsonObject(config.defaults?.search);
|
|
1095
|
-
context.onProgress?.(`Searching Parallel for: ${
|
|
1684
|
+
context.onProgress?.(`Searching Parallel for: ${query2}`);
|
|
1096
1685
|
const response = await client.beta.search({
|
|
1097
1686
|
...defaults,
|
|
1098
|
-
objective:
|
|
1687
|
+
objective: query2,
|
|
1099
1688
|
max_results: maxResults
|
|
1100
1689
|
});
|
|
1101
1690
|
return {
|
|
@@ -1192,7 +1781,7 @@ var ValyuProvider = class {
|
|
|
1192
1781
|
}
|
|
1193
1782
|
return { available: true, summary: "enabled" };
|
|
1194
1783
|
}
|
|
1195
|
-
async search(
|
|
1784
|
+
async search(query2, maxResults, config, context) {
|
|
1196
1785
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
1197
1786
|
if (!apiKey) {
|
|
1198
1787
|
throw new Error("Valyu is missing an API key.");
|
|
@@ -1202,8 +1791,8 @@ var ValyuProvider = class {
|
|
|
1202
1791
|
...asJsonObject(config.defaults),
|
|
1203
1792
|
maxNumResults: maxResults
|
|
1204
1793
|
};
|
|
1205
|
-
context.onProgress?.(`Searching Valyu for: ${
|
|
1206
|
-
const response = await client.search(
|
|
1794
|
+
context.onProgress?.(`Searching Valyu for: ${query2}`);
|
|
1795
|
+
const response = await client.search(query2, options);
|
|
1207
1796
|
if (!response.success) {
|
|
1208
1797
|
throw new Error(response.error || "Valyu search failed.");
|
|
1209
1798
|
}
|
|
@@ -1225,7 +1814,9 @@ var ValyuProvider = class {
|
|
|
1225
1814
|
throw new Error("Valyu is missing an API key.");
|
|
1226
1815
|
}
|
|
1227
1816
|
const client = new Valyu(apiKey, config.baseUrl);
|
|
1228
|
-
context.onProgress?.(
|
|
1817
|
+
context.onProgress?.(
|
|
1818
|
+
`Fetching contents from Valyu for ${urls.length} URL(s)`
|
|
1819
|
+
);
|
|
1229
1820
|
const response = await client.contents(urls, options);
|
|
1230
1821
|
const finalResponse = "jobId" in response ? await client.waitForJob(response.jobId, {
|
|
1231
1822
|
onProgress: (status) => context.onProgress?.(
|
|
@@ -1257,14 +1848,14 @@ var ValyuProvider = class {
|
|
|
1257
1848
|
itemCount: results.length
|
|
1258
1849
|
};
|
|
1259
1850
|
}
|
|
1260
|
-
async answer(
|
|
1851
|
+
async answer(query2, options, config, context) {
|
|
1261
1852
|
const apiKey = resolveConfigValue(config.apiKey);
|
|
1262
1853
|
if (!apiKey) {
|
|
1263
1854
|
throw new Error("Valyu is missing an API key.");
|
|
1264
1855
|
}
|
|
1265
1856
|
const client = new Valyu(apiKey, config.baseUrl);
|
|
1266
|
-
context.onProgress?.(`Getting Valyu answer for: ${
|
|
1267
|
-
const response = await client.answer(
|
|
1857
|
+
context.onProgress?.(`Getting Valyu answer for: ${query2}`);
|
|
1858
|
+
const response = await client.answer(query2, {
|
|
1268
1859
|
...options ?? {},
|
|
1269
1860
|
streaming: false
|
|
1270
1861
|
});
|
|
@@ -1343,6 +1934,7 @@ var ValyuProvider = class {
|
|
|
1343
1934
|
|
|
1344
1935
|
// src/providers/index.ts
|
|
1345
1936
|
var PROVIDERS = [
|
|
1937
|
+
new ClaudeProvider(),
|
|
1346
1938
|
new CodexProvider(),
|
|
1347
1939
|
new ExaProvider(),
|
|
1348
1940
|
new GeminiProvider(),
|
|
@@ -1350,38 +1942,47 @@ var PROVIDERS = [
|
|
|
1350
1942
|
new ValyuProvider()
|
|
1351
1943
|
];
|
|
1352
1944
|
var PROVIDER_MAP = {
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1945
|
+
claude: PROVIDERS[0],
|
|
1946
|
+
codex: PROVIDERS[1],
|
|
1947
|
+
exa: PROVIDERS[2],
|
|
1948
|
+
gemini: PROVIDERS[3],
|
|
1949
|
+
parallel: PROVIDERS[4],
|
|
1950
|
+
valyu: PROVIDERS[5]
|
|
1358
1951
|
};
|
|
1359
1952
|
|
|
1360
1953
|
// src/provider-resolution.ts
|
|
1954
|
+
var IMPLICIT_PROVIDER_FALLBACKS = ["codex"];
|
|
1361
1955
|
function resolveProviderChoice(config, explicit, cwd) {
|
|
1362
1956
|
return resolveProviderForCapability(config, explicit, cwd, "search");
|
|
1363
1957
|
}
|
|
1958
|
+
function getEffectiveProviderConfig(config, providerId) {
|
|
1959
|
+
const configured = config.providers?.[providerId];
|
|
1960
|
+
if (configured) {
|
|
1961
|
+
return configured;
|
|
1962
|
+
}
|
|
1963
|
+
if (IMPLICIT_PROVIDER_FALLBACKS.includes(providerId)) {
|
|
1964
|
+
return {
|
|
1965
|
+
...PROVIDER_MAP[providerId].createTemplate(),
|
|
1966
|
+
enabled: true
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
return void 0;
|
|
1970
|
+
}
|
|
1364
1971
|
function resolveProviderForCapability(config, explicit, cwd, capability) {
|
|
1365
1972
|
if (explicit) {
|
|
1366
1973
|
const provider = PROVIDER_MAP[explicit];
|
|
1974
|
+
const providerConfig = getEffectiveProviderConfig(config, explicit);
|
|
1367
1975
|
if (typeof provider[capability] !== "function") {
|
|
1368
1976
|
throw new Error(
|
|
1369
1977
|
`Provider '${explicit}' does not support '${capability}'.`
|
|
1370
1978
|
);
|
|
1371
1979
|
}
|
|
1372
|
-
if (!isProviderToolEnabled(
|
|
1373
|
-
explicit,
|
|
1374
|
-
config.providers?.[explicit],
|
|
1375
|
-
capability
|
|
1376
|
-
)) {
|
|
1980
|
+
if (!isProviderToolEnabled(explicit, providerConfig, capability)) {
|
|
1377
1981
|
throw new Error(
|
|
1378
1982
|
`Provider '${explicit}' has '${capability}' disabled in config.`
|
|
1379
1983
|
);
|
|
1380
1984
|
}
|
|
1381
|
-
const status = provider.getStatus(
|
|
1382
|
-
config.providers?.[explicit],
|
|
1383
|
-
cwd
|
|
1384
|
-
);
|
|
1985
|
+
const status = provider.getStatus(providerConfig, cwd);
|
|
1385
1986
|
if (!status.available) {
|
|
1386
1987
|
throw new Error(
|
|
1387
1988
|
`Provider '${explicit}' is not available: ${status.summary}.`
|
|
@@ -1403,19 +2004,14 @@ function resolveProviderForCapability(config, explicit, cwd, capability) {
|
|
|
1403
2004
|
const status = provider.getStatus(providerConfig, cwd);
|
|
1404
2005
|
if (status.available) return provider;
|
|
1405
2006
|
}
|
|
1406
|
-
for (const
|
|
2007
|
+
for (const providerId of IMPLICIT_PROVIDER_FALLBACKS) {
|
|
2008
|
+
const provider = PROVIDER_MAP[providerId];
|
|
1407
2009
|
if (typeof provider[capability] !== "function") continue;
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
config.providers?.[provider.id],
|
|
1411
|
-
capability
|
|
1412
|
-
)) {
|
|
2010
|
+
const providerConfig = getEffectiveProviderConfig(config, provider.id);
|
|
2011
|
+
if (!isProviderToolEnabled(provider.id, providerConfig, capability)) {
|
|
1413
2012
|
continue;
|
|
1414
2013
|
}
|
|
1415
|
-
const status = provider.getStatus(
|
|
1416
|
-
config.providers?.[provider.id],
|
|
1417
|
-
cwd
|
|
1418
|
-
);
|
|
2014
|
+
const status = provider.getStatus(providerConfig, cwd);
|
|
1419
2015
|
if (status.available) return provider;
|
|
1420
2016
|
}
|
|
1421
2017
|
throw new Error(
|
|
@@ -1424,16 +2020,31 @@ function resolveProviderForCapability(config, explicit, cwd, capability) {
|
|
|
1424
2020
|
}
|
|
1425
2021
|
|
|
1426
2022
|
// src/types.ts
|
|
1427
|
-
var PROVIDER_IDS = [
|
|
2023
|
+
var PROVIDER_IDS = [
|
|
2024
|
+
"claude",
|
|
2025
|
+
"codex",
|
|
2026
|
+
"exa",
|
|
2027
|
+
"gemini",
|
|
2028
|
+
"parallel",
|
|
2029
|
+
"valyu"
|
|
2030
|
+
];
|
|
1428
2031
|
|
|
1429
2032
|
// src/index.ts
|
|
1430
2033
|
var DEFAULT_MAX_RESULTS = 5;
|
|
1431
2034
|
var MAX_ALLOWED_RESULTS = 20;
|
|
2035
|
+
var RESEARCH_HEARTBEAT_MS = 15e3;
|
|
2036
|
+
var CAPABILITY_TOOL_NAMES = {
|
|
2037
|
+
search: "web_search",
|
|
2038
|
+
contents: "web_contents",
|
|
2039
|
+
answer: "web_answer",
|
|
2040
|
+
research: "web_research"
|
|
2041
|
+
};
|
|
2042
|
+
var MANAGED_TOOL_NAMES = Object.values(CAPABILITY_TOOL_NAMES);
|
|
2043
|
+
var PROVIDER_OVERRIDE_GUIDELINES = [
|
|
2044
|
+
"Do not set provider unless the user asks for one."
|
|
2045
|
+
];
|
|
1432
2046
|
function webProvidersExtension(pi) {
|
|
1433
|
-
|
|
1434
|
-
registerWebContentsTool(pi);
|
|
1435
|
-
registerWebAnswerTool(pi);
|
|
1436
|
-
registerWebResearchTool(pi);
|
|
2047
|
+
registerManagedTools(pi);
|
|
1437
2048
|
pi.registerCommand("web-providers", {
|
|
1438
2049
|
description: "Configure web search providers",
|
|
1439
2050
|
handler: async (_args, ctx) => {
|
|
@@ -1441,15 +2052,38 @@ function webProvidersExtension(pi) {
|
|
|
1441
2052
|
ctx.ui.notify("web-providers requires interactive mode", "error");
|
|
1442
2053
|
return;
|
|
1443
2054
|
}
|
|
1444
|
-
await runWebProvidersConfig(ctx);
|
|
2055
|
+
await runWebProvidersConfig(pi, ctx);
|
|
1445
2056
|
}
|
|
1446
2057
|
});
|
|
2058
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
2059
|
+
await refreshManagedTools(pi, ctx.cwd, { addAvailable: true });
|
|
2060
|
+
});
|
|
2061
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
2062
|
+
await refreshManagedTools(pi, ctx.cwd, { addAvailable: false });
|
|
2063
|
+
});
|
|
1447
2064
|
}
|
|
1448
|
-
function
|
|
2065
|
+
function registerManagedTools(pi, providerIdsByCapability = {}) {
|
|
2066
|
+
registerWebSearchTool(pi, providerIdsByCapability.search ?? PROVIDER_IDS);
|
|
2067
|
+
registerWebContentsTool(
|
|
2068
|
+
pi,
|
|
2069
|
+
providerIdsByCapability.contents ?? getProviderIdsForCapability("contents")
|
|
2070
|
+
);
|
|
2071
|
+
registerWebAnswerTool(
|
|
2072
|
+
pi,
|
|
2073
|
+
providerIdsByCapability.answer ?? getProviderIdsForCapability("answer")
|
|
2074
|
+
);
|
|
2075
|
+
registerWebResearchTool(
|
|
2076
|
+
pi,
|
|
2077
|
+
providerIdsByCapability.research ?? getProviderIdsForCapability("research")
|
|
2078
|
+
);
|
|
2079
|
+
}
|
|
2080
|
+
function registerWebSearchTool(pi, providerIds) {
|
|
2081
|
+
const visibleProviderIds = providerIds.length > 0 ? providerIds : PROVIDER_IDS;
|
|
1449
2082
|
pi.registerTool({
|
|
1450
2083
|
name: "web_search",
|
|
1451
2084
|
label: "Web Search",
|
|
1452
|
-
description: `
|
|
2085
|
+
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.`,
|
|
2086
|
+
promptGuidelines: PROVIDER_OVERRIDE_GUIDELINES,
|
|
1453
2087
|
parameters: Type.Object({
|
|
1454
2088
|
query: Type.String({ description: "What to search for on the web" }),
|
|
1455
2089
|
maxResults: Type.Optional(
|
|
@@ -1459,17 +2093,16 @@ function registerWebSearchTool(pi) {
|
|
|
1459
2093
|
description: `Maximum number of results to return (default: ${DEFAULT_MAX_RESULTS})`
|
|
1460
2094
|
})
|
|
1461
2095
|
),
|
|
1462
|
-
provider:
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
})
|
|
2096
|
+
provider: providerEnum(
|
|
2097
|
+
visibleProviderIds,
|
|
2098
|
+
"Provider override. If omitted, uses the active configured provider or falls back to Codex for search when it is not explicitly disabled."
|
|
1466
2099
|
)
|
|
1467
2100
|
}),
|
|
1468
2101
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1469
2102
|
const config = await loadConfig();
|
|
1470
2103
|
const provider = resolveProviderChoice(config, params.provider, ctx.cwd);
|
|
1471
2104
|
const maxResults = clampResults(params.maxResults);
|
|
1472
|
-
const providerConfig = config
|
|
2105
|
+
const providerConfig = getEffectiveProviderConfig(config, provider.id);
|
|
1473
2106
|
if (!providerConfig) {
|
|
1474
2107
|
throw new Error(`Provider '${provider.id}' is not configured.`);
|
|
1475
2108
|
}
|
|
@@ -1524,13 +2157,12 @@ function registerWebSearchTool(pi) {
|
|
|
1524
2157
|
}
|
|
1525
2158
|
});
|
|
1526
2159
|
}
|
|
1527
|
-
function registerWebContentsTool(pi) {
|
|
1528
|
-
const providerIds = getProviderIdsForCapability("contents");
|
|
2160
|
+
function registerWebContentsTool(pi, providerIds) {
|
|
1529
2161
|
if (providerIds.length === 0) return;
|
|
1530
2162
|
pi.registerTool({
|
|
1531
2163
|
name: "web_contents",
|
|
1532
2164
|
label: "Web Contents",
|
|
1533
|
-
description: "
|
|
2165
|
+
description: "Read and extract the main contents of one or more web pages.",
|
|
1534
2166
|
parameters: Type.Object({
|
|
1535
2167
|
urls: Type.Array(Type.String({ minLength: 1 }), {
|
|
1536
2168
|
minItems: 1,
|
|
@@ -1542,6 +2174,7 @@ function registerWebContentsTool(pi) {
|
|
|
1542
2174
|
"Provider override. If omitted, uses the active configured provider that supports web contents."
|
|
1543
2175
|
)
|
|
1544
2176
|
}),
|
|
2177
|
+
promptGuidelines: PROVIDER_OVERRIDE_GUIDELINES,
|
|
1545
2178
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1546
2179
|
return executeProviderTool({
|
|
1547
2180
|
capability: "contents",
|
|
@@ -1579,13 +2212,12 @@ function registerWebContentsTool(pi) {
|
|
|
1579
2212
|
}
|
|
1580
2213
|
});
|
|
1581
2214
|
}
|
|
1582
|
-
function registerWebAnswerTool(pi) {
|
|
1583
|
-
const providerIds = getProviderIdsForCapability("answer");
|
|
2215
|
+
function registerWebAnswerTool(pi, providerIds) {
|
|
1584
2216
|
if (providerIds.length === 0) return;
|
|
1585
2217
|
pi.registerTool({
|
|
1586
2218
|
name: "web_answer",
|
|
1587
2219
|
label: "Web Answer",
|
|
1588
|
-
description: "
|
|
2220
|
+
description: "Answer a question using web-grounded evidence.",
|
|
1589
2221
|
parameters: Type.Object({
|
|
1590
2222
|
query: Type.String({ description: "Question to answer" }),
|
|
1591
2223
|
options: jsonOptionsSchema("Provider-specific answer options."),
|
|
@@ -1594,6 +2226,7 @@ function registerWebAnswerTool(pi) {
|
|
|
1594
2226
|
"Provider override. If omitted, uses the active configured provider that supports web answers."
|
|
1595
2227
|
)
|
|
1596
2228
|
}),
|
|
2229
|
+
promptGuidelines: PROVIDER_OVERRIDE_GUIDELINES,
|
|
1597
2230
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1598
2231
|
return executeProviderTool({
|
|
1599
2232
|
capability: "answer",
|
|
@@ -1613,8 +2246,10 @@ function registerWebAnswerTool(pi) {
|
|
|
1613
2246
|
renderCall(args, theme) {
|
|
1614
2247
|
return renderToolCallHeader(
|
|
1615
2248
|
"web_answer",
|
|
1616
|
-
|
|
1617
|
-
[
|
|
2249
|
+
formatQuotedPreview(String(args.query ?? "")),
|
|
2250
|
+
[
|
|
2251
|
+
`provider=${String(args.provider ?? "auto")}`
|
|
2252
|
+
],
|
|
1618
2253
|
theme
|
|
1619
2254
|
);
|
|
1620
2255
|
},
|
|
@@ -1629,13 +2264,12 @@ function registerWebAnswerTool(pi) {
|
|
|
1629
2264
|
}
|
|
1630
2265
|
});
|
|
1631
2266
|
}
|
|
1632
|
-
function registerWebResearchTool(pi) {
|
|
1633
|
-
const providerIds = getProviderIdsForCapability("research");
|
|
2267
|
+
function registerWebResearchTool(pi, providerIds) {
|
|
1634
2268
|
if (providerIds.length === 0) return;
|
|
1635
2269
|
pi.registerTool({
|
|
1636
2270
|
name: "web_research",
|
|
1637
2271
|
label: "Web Research",
|
|
1638
|
-
description: "
|
|
2272
|
+
description: "Investigate a topic across web sources and produce a longer report.",
|
|
1639
2273
|
parameters: Type.Object({
|
|
1640
2274
|
input: Type.String({ description: "Research brief or question" }),
|
|
1641
2275
|
options: jsonOptionsSchema("Provider-specific research options."),
|
|
@@ -1644,6 +2278,7 @@ function registerWebResearchTool(pi) {
|
|
|
1644
2278
|
"Provider override. If omitted, uses the active configured provider that supports research."
|
|
1645
2279
|
)
|
|
1646
2280
|
}),
|
|
2281
|
+
promptGuidelines: PROVIDER_OVERRIDE_GUIDELINES,
|
|
1647
2282
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1648
2283
|
return executeProviderTool({
|
|
1649
2284
|
capability: "research",
|
|
@@ -1663,8 +2298,10 @@ function registerWebResearchTool(pi) {
|
|
|
1663
2298
|
renderCall(args, theme) {
|
|
1664
2299
|
return renderToolCallHeader(
|
|
1665
2300
|
"web_research",
|
|
1666
|
-
|
|
1667
|
-
[
|
|
2301
|
+
formatQuotedPreview(String(args.input ?? "")),
|
|
2302
|
+
[
|
|
2303
|
+
`provider=${String(args.provider ?? "auto")}`
|
|
2304
|
+
],
|
|
1668
2305
|
theme
|
|
1669
2306
|
);
|
|
1670
2307
|
},
|
|
@@ -1679,7 +2316,7 @@ function registerWebResearchTool(pi) {
|
|
|
1679
2316
|
}
|
|
1680
2317
|
});
|
|
1681
2318
|
}
|
|
1682
|
-
async function runWebProvidersConfig(ctx) {
|
|
2319
|
+
async function runWebProvidersConfig(pi, ctx) {
|
|
1683
2320
|
const config = await loadConfig();
|
|
1684
2321
|
const activeProvider = await getPreferredProvider(ctx.cwd);
|
|
1685
2322
|
await ctx.ui.custom(
|
|
@@ -1692,6 +2329,60 @@ async function runWebProvidersConfig(ctx) {
|
|
|
1692
2329
|
activeProvider
|
|
1693
2330
|
)
|
|
1694
2331
|
);
|
|
2332
|
+
await refreshManagedTools(pi, ctx.cwd, { addAvailable: true });
|
|
2333
|
+
}
|
|
2334
|
+
function getAvailableProviderIdsForCapability(config, cwd, capability) {
|
|
2335
|
+
const providerIds = [];
|
|
2336
|
+
for (const providerId of getProviderIdsForCapability(capability)) {
|
|
2337
|
+
try {
|
|
2338
|
+
resolveProviderForCapability(config, providerId, cwd, capability);
|
|
2339
|
+
providerIds.push(providerId);
|
|
2340
|
+
} catch {
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
return providerIds;
|
|
2344
|
+
}
|
|
2345
|
+
function getAvailableManagedToolNames(config, cwd) {
|
|
2346
|
+
return Object.keys(CAPABILITY_TOOL_NAMES).filter(
|
|
2347
|
+
(capability) => getAvailableProviderIdsForCapability(config, cwd, capability).length > 0
|
|
2348
|
+
).map((capability) => CAPABILITY_TOOL_NAMES[capability]);
|
|
2349
|
+
}
|
|
2350
|
+
function getSyncedActiveTools(config, cwd, activeToolNames, options) {
|
|
2351
|
+
const availableToolNames = new Set(getAvailableManagedToolNames(config, cwd));
|
|
2352
|
+
const nextActiveTools = new Set(activeToolNames);
|
|
2353
|
+
for (const toolName of MANAGED_TOOL_NAMES) {
|
|
2354
|
+
if (availableToolNames.has(toolName)) {
|
|
2355
|
+
if (options.addAvailable) {
|
|
2356
|
+
nextActiveTools.add(toolName);
|
|
2357
|
+
}
|
|
2358
|
+
continue;
|
|
2359
|
+
}
|
|
2360
|
+
nextActiveTools.delete(toolName);
|
|
2361
|
+
}
|
|
2362
|
+
return nextActiveTools;
|
|
2363
|
+
}
|
|
2364
|
+
async function refreshManagedTools(pi, cwd, options) {
|
|
2365
|
+
const config = await loadConfig();
|
|
2366
|
+
const nextActiveTools = getSyncedActiveTools(
|
|
2367
|
+
config,
|
|
2368
|
+
cwd,
|
|
2369
|
+
pi.getActiveTools(),
|
|
2370
|
+
options
|
|
2371
|
+
);
|
|
2372
|
+
registerManagedTools(pi, {
|
|
2373
|
+
search: getAvailableProviderIdsForCapability(config, cwd, "search"),
|
|
2374
|
+
contents: getAvailableProviderIdsForCapability(config, cwd, "contents"),
|
|
2375
|
+
answer: getAvailableProviderIdsForCapability(config, cwd, "answer"),
|
|
2376
|
+
research: getAvailableProviderIdsForCapability(config, cwd, "research")
|
|
2377
|
+
});
|
|
2378
|
+
await syncManagedToolAvailability(pi, nextActiveTools);
|
|
2379
|
+
}
|
|
2380
|
+
async function syncManagedToolAvailability(pi, nextActiveTools) {
|
|
2381
|
+
const activeTools = pi.getActiveTools();
|
|
2382
|
+
const changed = activeTools.length !== nextActiveTools.size || activeTools.some((toolName) => !nextActiveTools.has(toolName));
|
|
2383
|
+
if (changed) {
|
|
2384
|
+
pi.setActiveTools(Array.from(nextActiveTools));
|
|
2385
|
+
}
|
|
1695
2386
|
}
|
|
1696
2387
|
function getProviderIdsForCapability(capability) {
|
|
1697
2388
|
return PROVIDERS.filter(
|
|
@@ -1703,15 +2394,21 @@ function providerEnum(providerIds, description) {
|
|
|
1703
2394
|
return Type.Optional(Type.Literal(providerIds[0], { description }));
|
|
1704
2395
|
}
|
|
1705
2396
|
return Type.Optional(
|
|
1706
|
-
Type.Union(
|
|
2397
|
+
Type.Union(
|
|
2398
|
+
providerIds.map((id) => Type.Literal(id)),
|
|
2399
|
+
{ description }
|
|
2400
|
+
)
|
|
1707
2401
|
);
|
|
1708
2402
|
}
|
|
1709
2403
|
function jsonOptionsSchema(description) {
|
|
1710
2404
|
return Type.Optional(
|
|
1711
|
-
Type.Object(
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
2405
|
+
Type.Object(
|
|
2406
|
+
{},
|
|
2407
|
+
{
|
|
2408
|
+
additionalProperties: true,
|
|
2409
|
+
description
|
|
2410
|
+
}
|
|
2411
|
+
)
|
|
1715
2412
|
);
|
|
1716
2413
|
}
|
|
1717
2414
|
async function executeProviderTool({
|
|
@@ -1729,36 +2426,74 @@ async function executeProviderTool({
|
|
|
1729
2426
|
ctx.cwd,
|
|
1730
2427
|
capability
|
|
1731
2428
|
);
|
|
1732
|
-
const providerConfig = config
|
|
2429
|
+
const providerConfig = getEffectiveProviderConfig(config, provider.id);
|
|
1733
2430
|
if (!providerConfig) {
|
|
1734
2431
|
throw new Error(`Provider '${provider.id}' is not configured.`);
|
|
1735
2432
|
}
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2433
|
+
const progress = createToolProgressReporter(
|
|
2434
|
+
capability,
|
|
2435
|
+
provider.id,
|
|
2436
|
+
onUpdate
|
|
2437
|
+
);
|
|
2438
|
+
let response;
|
|
2439
|
+
try {
|
|
2440
|
+
response = await invoke(provider, providerConfig, {
|
|
1740
2441
|
cwd: ctx.cwd,
|
|
1741
2442
|
signal: signal ?? void 0,
|
|
1742
|
-
onProgress:
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
);
|
|
2443
|
+
onProgress: progress.report
|
|
2444
|
+
});
|
|
2445
|
+
} finally {
|
|
2446
|
+
progress.stop();
|
|
2447
|
+
}
|
|
1748
2448
|
const details = {
|
|
1749
2449
|
tool: `web_${capability}`,
|
|
1750
2450
|
provider: response.provider,
|
|
1751
2451
|
summary: response.summary,
|
|
1752
2452
|
itemCount: response.itemCount
|
|
1753
2453
|
};
|
|
2454
|
+
const text = await truncateAndSave(response.text, capability);
|
|
1754
2455
|
return {
|
|
1755
|
-
content: [{ type: "text", text
|
|
2456
|
+
content: [{ type: "text", text }],
|
|
1756
2457
|
details
|
|
1757
2458
|
};
|
|
1758
2459
|
}
|
|
1759
2460
|
function normalizeOptions(value) {
|
|
1760
2461
|
return isJsonObject(value) ? value : void 0;
|
|
1761
2462
|
}
|
|
2463
|
+
function createToolProgressReporter(capability, providerId, onUpdate) {
|
|
2464
|
+
if (!onUpdate) {
|
|
2465
|
+
return { report: void 0, stop: () => {
|
|
2466
|
+
} };
|
|
2467
|
+
}
|
|
2468
|
+
const emit = (message) => onUpdate({
|
|
2469
|
+
content: [{ type: "text", text: message }],
|
|
2470
|
+
details: {}
|
|
2471
|
+
});
|
|
2472
|
+
const startedAt = Date.now();
|
|
2473
|
+
let lastUpdateAt = startedAt;
|
|
2474
|
+
let timer;
|
|
2475
|
+
if (capability === "research") {
|
|
2476
|
+
timer = setInterval(() => {
|
|
2477
|
+
if (Date.now() - lastUpdateAt < RESEARCH_HEARTBEAT_MS) {
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
const elapsed = formatElapsed2(Date.now() - startedAt);
|
|
2481
|
+
emit(`web_research still running via ${providerId} (${elapsed} elapsed)`);
|
|
2482
|
+
lastUpdateAt = Date.now();
|
|
2483
|
+
}, RESEARCH_HEARTBEAT_MS);
|
|
2484
|
+
}
|
|
2485
|
+
return {
|
|
2486
|
+
report: (message) => {
|
|
2487
|
+
lastUpdateAt = Date.now();
|
|
2488
|
+
emit(message);
|
|
2489
|
+
},
|
|
2490
|
+
stop: () => {
|
|
2491
|
+
if (timer) {
|
|
2492
|
+
clearInterval(timer);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
1762
2497
|
function renderToolCallHeader(toolName, primary, details, theme) {
|
|
1763
2498
|
return {
|
|
1764
2499
|
invalidate() {
|
|
@@ -1829,6 +2564,30 @@ function buildProviderMenuOptions(providerId) {
|
|
|
1829
2564
|
values
|
|
1830
2565
|
});
|
|
1831
2566
|
};
|
|
2567
|
+
if (providerId === "claude") {
|
|
2568
|
+
pushText(
|
|
2569
|
+
"model",
|
|
2570
|
+
"Model",
|
|
2571
|
+
"Optional Claude model override. Leave empty to use the local default."
|
|
2572
|
+
);
|
|
2573
|
+
pushValues(
|
|
2574
|
+
"claudeEffort",
|
|
2575
|
+
"Effort",
|
|
2576
|
+
"How much effort Claude should use. 'default' uses the SDK default.",
|
|
2577
|
+
["default", "low", "medium", "high", "max"]
|
|
2578
|
+
);
|
|
2579
|
+
pushText(
|
|
2580
|
+
"claudeMaxTurns",
|
|
2581
|
+
"Max turns",
|
|
2582
|
+
"Optional maximum number of Claude turns. Leave empty to use the SDK default."
|
|
2583
|
+
);
|
|
2584
|
+
pushText(
|
|
2585
|
+
"claudePathToExecutable",
|
|
2586
|
+
"Executable path",
|
|
2587
|
+
"Optional path to the Claude Code executable. Leave empty to use the bundled/default executable."
|
|
2588
|
+
);
|
|
2589
|
+
return options;
|
|
2590
|
+
}
|
|
1832
2591
|
if (providerId === "codex") {
|
|
1833
2592
|
pushText(
|
|
1834
2593
|
"model",
|
|
@@ -1983,17 +2742,24 @@ var WebProvidersSettingsView = class {
|
|
|
1983
2742
|
}
|
|
1984
2743
|
const lines = [];
|
|
1985
2744
|
const providerItems = this.buildProviderSectionItems();
|
|
1986
|
-
lines.push(
|
|
2745
|
+
lines.push(
|
|
2746
|
+
...this.renderSection(width, "Provider", "provider", providerItems)
|
|
2747
|
+
);
|
|
1987
2748
|
lines.push("");
|
|
1988
2749
|
const toolItems = this.buildToolSectionItems();
|
|
1989
2750
|
lines.push(...this.renderSection(width, "Tools", "tools", toolItems));
|
|
1990
2751
|
lines.push("");
|
|
1991
2752
|
const configItems = this.buildConfigSectionItems();
|
|
1992
|
-
lines.push(
|
|
2753
|
+
lines.push(
|
|
2754
|
+
...this.renderSection(width, "Provider config", "config", configItems)
|
|
2755
|
+
);
|
|
1993
2756
|
const selected = this.getSelectedEntry();
|
|
1994
2757
|
if (selected) {
|
|
1995
2758
|
lines.push("");
|
|
1996
|
-
for (const line of wrapTextWithAnsi(
|
|
2759
|
+
for (const line of wrapTextWithAnsi(
|
|
2760
|
+
selected.description,
|
|
2761
|
+
Math.max(10, width - 2)
|
|
2762
|
+
)) {
|
|
1997
2763
|
lines.push(truncateToWidth(this.theme.fg("dim", line), width));
|
|
1998
2764
|
}
|
|
1999
2765
|
}
|
|
@@ -2090,7 +2856,10 @@ var WebProvidersSettingsView = class {
|
|
|
2090
2856
|
}
|
|
2091
2857
|
if (option.kind === "text") {
|
|
2092
2858
|
const key = option.key;
|
|
2093
|
-
const currentValue = key === "model" || key === "
|
|
2859
|
+
const currentValue = this.activeProvider === "claude" && (key === "model" || key === "claudePathToExecutable" || key === "claudeMaxTurns") ? getClaudeTextSettingValue(
|
|
2860
|
+
providerConfig,
|
|
2861
|
+
key
|
|
2862
|
+
) : key === "model" || key === "additionalDirectories" ? getCodexTextSettingValue(
|
|
2094
2863
|
providerConfig,
|
|
2095
2864
|
key
|
|
2096
2865
|
) : key === "geminiSearchModel" || key === "geminiAnswerModel" || key === "geminiResearchAgent" ? getGeminiTextSettingValue(
|
|
@@ -2132,7 +2901,7 @@ var WebProvidersSettingsView = class {
|
|
|
2132
2901
|
"tools",
|
|
2133
2902
|
"config"
|
|
2134
2903
|
];
|
|
2135
|
-
|
|
2904
|
+
const index = sections.indexOf(this.activeSection);
|
|
2136
2905
|
for (let offset = 1; offset <= sections.length; offset++) {
|
|
2137
2906
|
const next = sections[(index + offset * direction + sections.length) % sections.length];
|
|
2138
2907
|
if (this.getSectionEntries(next).length > 0) {
|
|
@@ -2221,6 +2990,12 @@ var WebProvidersSettingsView = class {
|
|
|
2221
2990
|
if (id === "apiKey" || id === "baseUrl") {
|
|
2222
2991
|
return getProviderStringValue(providerConfig, id);
|
|
2223
2992
|
}
|
|
2993
|
+
if (this.activeProvider === "claude" && (id === "model" || id === "claudePathToExecutable" || id === "claudeMaxTurns")) {
|
|
2994
|
+
return getClaudeTextSettingValue(
|
|
2995
|
+
providerConfig,
|
|
2996
|
+
id
|
|
2997
|
+
);
|
|
2998
|
+
}
|
|
2224
2999
|
if (id === "model" || id === "additionalDirectories") {
|
|
2225
3000
|
return getCodexTextSettingValue(
|
|
2226
3001
|
providerConfig,
|
|
@@ -2268,6 +3043,11 @@ var WebProvidersSettingsView = class {
|
|
|
2268
3043
|
}
|
|
2269
3044
|
if (id === "apiKey" || id === "baseUrl") {
|
|
2270
3045
|
assignOptionalString(providerConfig, id, value);
|
|
3046
|
+
} else if (this.activeProvider === "claude" && applyClaudeSettingChange(
|
|
3047
|
+
providerConfig,
|
|
3048
|
+
id,
|
|
3049
|
+
value
|
|
3050
|
+
)) {
|
|
2271
3051
|
} else if (this.activeProvider === "codex" && applyCodexSettingChange(
|
|
2272
3052
|
providerConfig,
|
|
2273
3053
|
id,
|
|
@@ -2408,6 +3188,12 @@ function getProviderStringValue(config, key) {
|
|
|
2408
3188
|
return typeof value === "string" ? value : void 0;
|
|
2409
3189
|
}
|
|
2410
3190
|
function getProviderChoiceValue(providerId, config, key) {
|
|
3191
|
+
if (providerId === "claude") {
|
|
3192
|
+
const defaults = config?.defaults;
|
|
3193
|
+
if (key === "claudeEffort") {
|
|
3194
|
+
return typeof defaults?.effort === "string" ? defaults.effort : "default";
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
2411
3197
|
if (providerId === "codex") {
|
|
2412
3198
|
const defaults = config?.defaults;
|
|
2413
3199
|
if (key === "networkAccessEnabled" || key === "webSearchEnabled") {
|
|
@@ -2462,6 +3248,17 @@ function getProviderChoiceValue(providerId, config, key) {
|
|
|
2462
3248
|
}
|
|
2463
3249
|
throw new Error(`Unsupported choice setting '${key}' for '${providerId}'.`);
|
|
2464
3250
|
}
|
|
3251
|
+
function getClaudeTextSettingValue(config, key) {
|
|
3252
|
+
if (key === "claudePathToExecutable") {
|
|
3253
|
+
return config?.pathToClaudeCodeExecutable;
|
|
3254
|
+
}
|
|
3255
|
+
const defaults = config?.defaults;
|
|
3256
|
+
if (!defaults) return void 0;
|
|
3257
|
+
if (key === "claudeMaxTurns") {
|
|
3258
|
+
return typeof defaults.maxTurns === "number" ? String(defaults.maxTurns) : void 0;
|
|
3259
|
+
}
|
|
3260
|
+
return defaults.model;
|
|
3261
|
+
}
|
|
2465
3262
|
function getCodexTextSettingValue(config, key) {
|
|
2466
3263
|
const defaults = config?.defaults;
|
|
2467
3264
|
if (!defaults) return void 0;
|
|
@@ -2485,6 +3282,51 @@ function assignOptionalString(target, key, value) {
|
|
|
2485
3282
|
target[key] = trimmed;
|
|
2486
3283
|
}
|
|
2487
3284
|
}
|
|
3285
|
+
function applyClaudeSettingChange(target, key, value) {
|
|
3286
|
+
target.defaults ??= {};
|
|
3287
|
+
switch (key) {
|
|
3288
|
+
case "model":
|
|
3289
|
+
assignOptionalString(
|
|
3290
|
+
target.defaults,
|
|
3291
|
+
"model",
|
|
3292
|
+
value
|
|
3293
|
+
);
|
|
3294
|
+
cleanupClaudeDefaults(target);
|
|
3295
|
+
return true;
|
|
3296
|
+
case "claudePathToExecutable":
|
|
3297
|
+
assignOptionalString(
|
|
3298
|
+
target,
|
|
3299
|
+
"pathToClaudeCodeExecutable",
|
|
3300
|
+
value
|
|
3301
|
+
);
|
|
3302
|
+
cleanupClaudeDefaults(target);
|
|
3303
|
+
return true;
|
|
3304
|
+
case "claudeMaxTurns": {
|
|
3305
|
+
const trimmed = value.trim();
|
|
3306
|
+
if (!trimmed) {
|
|
3307
|
+
delete target.defaults.maxTurns;
|
|
3308
|
+
} else {
|
|
3309
|
+
const parsed = Number(trimmed);
|
|
3310
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
3311
|
+
throw new Error("Claude max turns must be a positive integer.");
|
|
3312
|
+
}
|
|
3313
|
+
target.defaults.maxTurns = parsed;
|
|
3314
|
+
}
|
|
3315
|
+
cleanupClaudeDefaults(target);
|
|
3316
|
+
return true;
|
|
3317
|
+
}
|
|
3318
|
+
case "claudeEffort":
|
|
3319
|
+
if (value === "default") {
|
|
3320
|
+
delete target.defaults.effort;
|
|
3321
|
+
} else {
|
|
3322
|
+
target.defaults.effort = value;
|
|
3323
|
+
}
|
|
3324
|
+
cleanupClaudeDefaults(target);
|
|
3325
|
+
return true;
|
|
3326
|
+
default:
|
|
3327
|
+
return false;
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
2488
3330
|
function applyCodexSettingChange(target, key, value) {
|
|
2489
3331
|
target.defaults ??= {};
|
|
2490
3332
|
switch (key) {
|
|
@@ -2653,6 +3495,11 @@ function applyParallelSettingChange(target, key, value) {
|
|
|
2653
3495
|
return false;
|
|
2654
3496
|
}
|
|
2655
3497
|
}
|
|
3498
|
+
function cleanupClaudeDefaults(target) {
|
|
3499
|
+
if (target.defaults && Object.keys(target.defaults).length === 0) {
|
|
3500
|
+
delete target.defaults;
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
2656
3503
|
function cleanupCodexDefaults(target) {
|
|
2657
3504
|
if (target.defaults && Object.keys(target.defaults).length === 0) {
|
|
2658
3505
|
delete target.defaults;
|
|
@@ -2699,9 +3546,9 @@ function renderCallHeader(params, theme) {
|
|
|
2699
3546
|
},
|
|
2700
3547
|
render(width) {
|
|
2701
3548
|
let header = theme.fg("toolTitle", theme.bold("web_search"));
|
|
2702
|
-
const
|
|
2703
|
-
if (
|
|
2704
|
-
header += ` ${theme.fg("accent",
|
|
3549
|
+
const query2 = cleanSingleLine(String(params.query ?? "")).trim();
|
|
3550
|
+
if (query2.length > 0) {
|
|
3551
|
+
header += ` ${theme.fg("accent", formatQuotedPreview(query2))} `;
|
|
2705
3552
|
}
|
|
2706
3553
|
const lines = [];
|
|
2707
3554
|
const headerLine = truncateToWidth(header.trimEnd(), width);
|
|
@@ -2758,6 +3605,18 @@ function getExpandHint() {
|
|
|
2758
3605
|
function cleanSingleLine(text) {
|
|
2759
3606
|
return text.replace(/\s+/g, " ").trim();
|
|
2760
3607
|
}
|
|
3608
|
+
function formatQuotedPreview(text, maxLength = 80) {
|
|
3609
|
+
return `"${truncateInline(cleanSingleLine(text), maxLength)}"`;
|
|
3610
|
+
}
|
|
3611
|
+
function formatElapsed2(ms) {
|
|
3612
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
3613
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
3614
|
+
const seconds = totalSeconds % 60;
|
|
3615
|
+
if (minutes > 0) {
|
|
3616
|
+
return `${minutes}m ${seconds}s`;
|
|
3617
|
+
}
|
|
3618
|
+
return `${totalSeconds}s`;
|
|
3619
|
+
}
|
|
2761
3620
|
function formatSearchResponse(response) {
|
|
2762
3621
|
if (response.results.length === 0) {
|
|
2763
3622
|
return "No results found.";
|
|
@@ -2779,9 +3638,9 @@ async function truncateAndSave(text, prefix) {
|
|
|
2779
3638
|
maxBytes: DEFAULT_MAX_BYTES
|
|
2780
3639
|
});
|
|
2781
3640
|
if (!truncation.truncated) return truncation.content;
|
|
2782
|
-
const dir =
|
|
3641
|
+
const dir = join4(tmpdir(), `pi-web-providers-${prefix}-${Date.now()}`);
|
|
2783
3642
|
await mkdir2(dir, { recursive: true });
|
|
2784
|
-
const fullPath =
|
|
3643
|
+
const fullPath = join4(dir, "output.txt");
|
|
2785
3644
|
await writeFile2(fullPath, text, "utf-8");
|
|
2786
3645
|
return truncation.content + `
|
|
2787
3646
|
|
|
@@ -2792,7 +3651,11 @@ function truncateInline(text, maxLength) {
|
|
|
2792
3651
|
return `${text.slice(0, maxLength - 1)}\u2026`;
|
|
2793
3652
|
}
|
|
2794
3653
|
var __test__ = {
|
|
3654
|
+
executeProviderTool,
|
|
2795
3655
|
extractTextContent,
|
|
3656
|
+
getAvailableManagedToolNames,
|
|
3657
|
+
getAvailableProviderIdsForCapability,
|
|
3658
|
+
getSyncedActiveTools,
|
|
2796
3659
|
renderCallHeader,
|
|
2797
3660
|
renderCollapsedSearchSummary
|
|
2798
3661
|
};
|