@oh-my-pi/pi-coding-agent 11.0.3 → 11.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/CHANGELOG.md +199 -49
- package/README.md +1 -1
- package/docs/config-usage.md +3 -4
- package/docs/sdk.md +6 -5
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +1 -1
- package/package.json +19 -11
- package/src/cli/args.ts +11 -94
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/oclif-help.ts +26 -0
- package/src/cli/web-search-cli.ts +148 -0
- package/src/cli.ts +8 -2
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +41 -0
- package/src/commands/index/index.ts +136 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/plugin.ts +70 -0
- package/src/commands/setup.ts +39 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +50 -0
- package/src/commit/agentic/index.ts +3 -2
- package/src/commit/agentic/tools/analyze-file.ts +1 -3
- package/src/commit/git/errors.ts +4 -6
- package/src/commit/pipeline.ts +3 -2
- package/src/config/keybindings.ts +1 -3
- package/src/config/model-registry.ts +89 -162
- package/src/config/settings-schema.ts +10 -0
- package/src/config.ts +202 -132
- package/src/exa/mcp-client.ts +8 -41
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/extensions/loader.ts +7 -10
- package/src/extensibility/extensions/runner.ts +5 -15
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/runner.ts +6 -9
- package/src/index.ts +0 -1
- package/src/ipy/kernel.ts +10 -22
- package/src/lsp/clients/biome-client.ts +4 -7
- package/src/lsp/clients/lsp-linter-client.ts +4 -6
- package/src/lsp/index.ts +5 -4
- package/src/lsp/utils.ts +18 -0
- package/src/main.ts +86 -181
- package/src/mcp/json-rpc.ts +2 -2
- package/src/mcp/transports/http.ts +12 -49
- package/src/modes/components/armin.ts +1 -3
- package/src/modes/components/assistant-message.ts +4 -4
- package/src/modes/components/bash-execution.ts +5 -3
- package/src/modes/components/branch-summary-message.ts +1 -3
- package/src/modes/components/compaction-summary-message.ts +1 -3
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/extensions/extension-dashboard.ts +10 -16
- package/src/modes/components/extensions/extension-list.ts +5 -5
- package/src/modes/components/footer.ts +1 -4
- package/src/modes/components/hook-editor.ts +7 -32
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/plugin-settings.ts +16 -20
- package/src/modes/components/python-execution.ts +5 -5
- package/src/modes/components/session-selector.ts +6 -7
- package/src/modes/components/settings-defs.ts +49 -40
- package/src/modes/components/settings-selector.ts +8 -17
- package/src/modes/components/skill-message.ts +1 -3
- package/src/modes/components/status-line-segment-editor.ts +1 -3
- package/src/modes/components/status-line.ts +1 -3
- package/src/modes/components/todo-reminder.ts +5 -7
- package/src/modes/components/tree-selector.ts +10 -12
- package/src/modes/components/ttsr-notification.ts +1 -3
- package/src/modes/components/user-message-selector.ts +2 -4
- package/src/modes/components/welcome.ts +6 -18
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +7 -34
- package/src/modes/controllers/selector-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +27 -1
- package/src/modes/rpc/rpc-client.ts +2 -5
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/theme/theme.ts +2 -6
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +6 -1
- package/src/patch/index.ts +1 -4
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/frontmatter.md +2 -1
- package/src/prompts/agents/init.md +1 -0
- package/src/prompts/agents/plan.md +1 -0
- package/src/prompts/agents/reviewer.md +1 -0
- package/src/prompts/system/subagent-submit-reminder.md +2 -0
- package/src/prompts/system/subagent-system-prompt.md +2 -0
- package/src/prompts/system/subagent-user-prompt.md +8 -0
- package/src/prompts/system/system-prompt.md +5 -3
- package/src/prompts/system/web-search.md +6 -4
- package/src/prompts/tools/task.md +216 -163
- package/src/sdk.ts +11 -110
- package/src/session/agent-session.ts +117 -83
- package/src/session/auth-storage.ts +10 -51
- package/src/session/messages.ts +17 -3
- package/src/session/session-manager.ts +30 -30
- package/src/session/streaming-output.ts +1 -1
- package/src/ssh/ssh-executor.ts +6 -3
- package/src/task/agents.ts +2 -0
- package/src/task/discovery.ts +1 -1
- package/src/task/executor.ts +5 -10
- package/src/task/index.ts +43 -23
- package/src/task/render.ts +67 -64
- package/src/task/template.ts +17 -34
- package/src/task/types.ts +49 -22
- package/src/tools/ask.ts +1 -3
- package/src/tools/bash.ts +1 -4
- package/src/tools/browser.ts +5 -7
- package/src/tools/exit-plan-mode.ts +1 -4
- package/src/tools/fetch.ts +1 -3
- package/src/tools/find.ts +4 -3
- package/src/tools/gemini-image.ts +24 -55
- package/src/tools/grep.ts +4 -4
- package/src/tools/index.ts +12 -14
- package/src/tools/notebook.ts +1 -5
- package/src/tools/python.ts +4 -3
- package/src/tools/read.ts +2 -4
- package/src/tools/render-utils.ts +23 -0
- package/src/tools/ssh.ts +8 -12
- package/src/tools/todo-write.ts +1 -4
- package/src/tools/tool-errors.ts +1 -4
- package/src/tools/write.ts +1 -3
- package/src/utils/external-editor.ts +59 -0
- package/src/utils/file-mentions.ts +39 -1
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +4 -4
- package/src/web/search/auth.ts +3 -33
- package/src/web/search/index.ts +73 -139
- package/src/web/search/provider.ts +58 -0
- package/src/web/search/providers/anthropic.ts +53 -14
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/codex.ts +38 -16
- package/src/web/search/providers/exa.ts +30 -6
- package/src/web/search/providers/gemini.ts +56 -20
- package/src/web/search/providers/jina.ts +28 -5
- package/src/web/search/providers/perplexity.ts +103 -36
- package/src/web/search/render.ts +84 -74
- package/src/web/search/types.ts +285 -59
- package/src/migrations.ts +0 -175
- package/src/session/storage-migration.ts +0 -173
package/src/web/search/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified Web Search Tool
|
|
3
3
|
*
|
|
4
|
-
* Single tool supporting Anthropic, Perplexity, Exa,
|
|
5
|
-
* provider-specific parameters exposed conditionally.
|
|
4
|
+
* Single tool supporting Anthropic, Perplexity, Exa, Jina, Gemini, and Codex
|
|
5
|
+
* providers with provider-specific parameters exposed conditionally.
|
|
6
6
|
*
|
|
7
7
|
* When EXA_API_KEY is available, additional specialized tools are exposed:
|
|
8
8
|
* - web_search_deep: Natural language web search with synthesized results
|
|
@@ -24,20 +24,16 @@ import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { ty
|
|
|
24
24
|
import webSearchDescription from "../../prompts/tools/web-search.md" with { type: "text" };
|
|
25
25
|
import type { ToolSession } from "../../tools";
|
|
26
26
|
import { formatAge } from "../../tools/render-utils";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { findApiKey as findPerplexityKey, searchPerplexity } from "./providers/perplexity";
|
|
32
|
-
import { renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./render";
|
|
33
|
-
import type { WebSearchProvider, WebSearchResponse } from "./types";
|
|
34
|
-
import { WebSearchProviderError } from "./types";
|
|
27
|
+
import { getSearchProvider, resolveProviderChain, type SearchProvider } from "./provider";
|
|
28
|
+
import { renderSearchCall, renderSearchResult, type SearchRenderDetails } from "./render";
|
|
29
|
+
import type { SearchResponse } from "./types";
|
|
30
|
+
import { SearchProviderError } from "./types";
|
|
35
31
|
|
|
36
32
|
/** Web search parameters schema */
|
|
37
33
|
export const webSearchSchema = Type.Object({
|
|
38
34
|
query: Type.String({ description: "Search query" }),
|
|
39
35
|
provider: Type.Optional(
|
|
40
|
-
StringEnum(["auto", "exa", "jina", "anthropic", "perplexity"], {
|
|
36
|
+
StringEnum(["auto", "exa", "jina", "anthropic", "perplexity", "gemini", "codex"], {
|
|
41
37
|
description: "Search provider (default: auto)",
|
|
42
38
|
}),
|
|
43
39
|
),
|
|
@@ -49,86 +45,35 @@ export const webSearchSchema = Type.Object({
|
|
|
49
45
|
limit: Type.Optional(Type.Number({ description: "Max results to return" })),
|
|
50
46
|
});
|
|
51
47
|
|
|
52
|
-
export type
|
|
48
|
+
export type SearchParams = {
|
|
53
49
|
query: string;
|
|
54
|
-
provider?: "auto" | "exa" | "jina" | "anthropic" | "perplexity";
|
|
50
|
+
provider?: "auto" | "exa" | "jina" | "anthropic" | "perplexity" | "gemini" | "codex";
|
|
55
51
|
recency?: "day" | "week" | "month" | "year";
|
|
56
52
|
limit?: number;
|
|
53
|
+
/** Maximum output tokens. Defaults to 4096. */
|
|
54
|
+
max_tokens?: number;
|
|
55
|
+
/** Sampling temperature (0–1). Lower = more focused/factual. Defaults to 0.2. */
|
|
56
|
+
temperature?: number;
|
|
57
|
+
/** Number of search results to retrieve. Defaults to 10. */
|
|
58
|
+
num_search_results?: number;
|
|
57
59
|
};
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/** Set the preferred web search provider from settings */
|
|
63
|
-
export function setPreferredWebSearchProvider(provider: WebSearchProvider | "auto"): void {
|
|
64
|
-
preferredProvider = provider;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** Determine which providers are configured (priority order) */
|
|
68
|
-
async function getAvailableProviders(): Promise<WebSearchProvider[]> {
|
|
69
|
-
const providers: WebSearchProvider[] = [];
|
|
70
|
-
|
|
71
|
-
const exaKey = await findExaKey();
|
|
72
|
-
if (exaKey) providers.push("exa");
|
|
73
|
-
|
|
74
|
-
const jinaKey = await findJinaKey();
|
|
75
|
-
if (jinaKey) providers.push("jina");
|
|
76
|
-
|
|
77
|
-
const perplexityKey = await findPerplexityKey();
|
|
78
|
-
if (perplexityKey) providers.push("perplexity");
|
|
79
|
-
|
|
80
|
-
const anthropicAuth = await findAnthropicAuth();
|
|
81
|
-
if (anthropicAuth) providers.push("anthropic");
|
|
82
|
-
|
|
83
|
-
return providers;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function formatProviderLabel(provider: WebSearchProvider): string {
|
|
87
|
-
switch (provider) {
|
|
88
|
-
case "exa":
|
|
89
|
-
return "Exa";
|
|
90
|
-
case "jina":
|
|
91
|
-
return "Jina";
|
|
92
|
-
case "perplexity":
|
|
93
|
-
return "Perplexity";
|
|
94
|
-
case "anthropic":
|
|
95
|
-
return "Anthropic";
|
|
96
|
-
default:
|
|
97
|
-
return provider;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function formatProviderList(providers: WebSearchProvider[]): string {
|
|
102
|
-
return providers.map(provider => formatProviderLabel(provider)).join(", ");
|
|
61
|
+
function formatProviderList(providers: SearchProvider[]): string {
|
|
62
|
+
return providers.map(provider => provider.label).join(", ");
|
|
103
63
|
}
|
|
104
64
|
|
|
105
|
-
function formatProviderError(error: unknown, provider:
|
|
106
|
-
if (error instanceof
|
|
65
|
+
function formatProviderError(error: unknown, provider: SearchProvider): string {
|
|
66
|
+
if (error instanceof SearchProviderError) {
|
|
107
67
|
if (error.provider === "anthropic" && error.status === 404) {
|
|
108
68
|
return "Anthropic web search returned 404 (model or endpoint not found).";
|
|
109
69
|
}
|
|
110
70
|
if (error.status === 401 || error.status === 403) {
|
|
111
|
-
return `${
|
|
71
|
+
return `${getSearchProvider(error.provider).label} authorization failed (${error.status}). Check API key or base URL.`;
|
|
112
72
|
}
|
|
113
73
|
return error.message;
|
|
114
74
|
}
|
|
115
75
|
if (error instanceof Error) return error.message;
|
|
116
|
-
return `Unknown error from ${
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async function resolveProviderChain(
|
|
120
|
-
requestedProvider?: WebSearchProvider | "auto",
|
|
121
|
-
): Promise<{ providers: WebSearchProvider[]; allowFallback: boolean }> {
|
|
122
|
-
if (requestedProvider && requestedProvider !== "auto") {
|
|
123
|
-
return { providers: [requestedProvider], allowFallback: false };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (preferredProvider !== "auto") {
|
|
127
|
-
return { providers: [preferredProvider], allowFallback: false };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const providers = await getAvailableProviders();
|
|
131
|
-
return { providers, allowFallback: true };
|
|
76
|
+
return `Unknown error from ${provider.label}`;
|
|
132
77
|
}
|
|
133
78
|
|
|
134
79
|
/** Truncate text for tool output */
|
|
@@ -142,7 +87,7 @@ function formatCount(label: string, count: number): string {
|
|
|
142
87
|
}
|
|
143
88
|
|
|
144
89
|
/** Format response for LLM consumption */
|
|
145
|
-
function formatForLLM(response:
|
|
90
|
+
function formatForLLM(response: SearchResponse): string {
|
|
146
91
|
const parts: string[] = [];
|
|
147
92
|
|
|
148
93
|
parts.push("## Answer");
|
|
@@ -216,18 +161,17 @@ function formatForLLM(response: WebSearchResponse): string {
|
|
|
216
161
|
}
|
|
217
162
|
|
|
218
163
|
/** Execute web search */
|
|
219
|
-
async function
|
|
164
|
+
async function executeSearch(
|
|
220
165
|
_toolCallId: string,
|
|
221
|
-
params:
|
|
222
|
-
): Promise<{ content: Array<{ type: "text"; text: string }>; details:
|
|
223
|
-
const
|
|
166
|
+
params: SearchParams,
|
|
167
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
168
|
+
const providers = await resolveProviderChain(params.provider);
|
|
224
169
|
|
|
225
170
|
if (providers.length === 0) {
|
|
226
171
|
const message = "No web search provider configured.";
|
|
227
|
-
const fallbackProvider = preferredProvider === "auto" ? "anthropic" : preferredProvider;
|
|
228
172
|
return {
|
|
229
173
|
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
230
|
-
details: { response: { provider:
|
|
174
|
+
details: { response: { provider: "none", sources: [] }, error: message },
|
|
231
175
|
};
|
|
232
176
|
}
|
|
233
177
|
|
|
@@ -237,31 +181,15 @@ async function executeWebSearch(
|
|
|
237
181
|
for (const provider of providers) {
|
|
238
182
|
lastProvider = provider;
|
|
239
183
|
try {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
num_results: params.limit,
|
|
250
|
-
});
|
|
251
|
-
} else if (provider === "anthropic") {
|
|
252
|
-
response = await searchAnthropic({
|
|
253
|
-
query: params.query,
|
|
254
|
-
system_prompt: webSearchSystemPrompt,
|
|
255
|
-
num_results: params.limit,
|
|
256
|
-
});
|
|
257
|
-
} else {
|
|
258
|
-
response = await searchPerplexity({
|
|
259
|
-
query: params.query,
|
|
260
|
-
system_prompt: webSearchSystemPrompt,
|
|
261
|
-
search_recency_filter: params.recency,
|
|
262
|
-
num_results: params.limit,
|
|
263
|
-
});
|
|
264
|
-
}
|
|
184
|
+
const response = await provider.search({
|
|
185
|
+
query: params.query.replace(/202\d/g, String(new Date().getFullYear())), // LUL
|
|
186
|
+
limit: params.limit,
|
|
187
|
+
recency: params.recency,
|
|
188
|
+
systemPrompt: webSearchSystemPrompt,
|
|
189
|
+
maxOutputTokens: params.max_tokens,
|
|
190
|
+
numSearchResults: params.num_search_results,
|
|
191
|
+
temperature: params.temperature,
|
|
192
|
+
});
|
|
265
193
|
|
|
266
194
|
const text = formatForLLM(response);
|
|
267
195
|
|
|
@@ -271,29 +199,37 @@ async function executeWebSearch(
|
|
|
271
199
|
};
|
|
272
200
|
} catch (error) {
|
|
273
201
|
lastError = error;
|
|
274
|
-
if (!allowFallback) break;
|
|
275
202
|
}
|
|
276
203
|
}
|
|
277
204
|
|
|
278
205
|
const baseMessage = formatProviderError(lastError, lastProvider);
|
|
279
206
|
const message =
|
|
280
|
-
|
|
207
|
+
providers.length > 1
|
|
281
208
|
? `All web search providers failed (${formatProviderList(providers)}). Last error: ${baseMessage}`
|
|
282
209
|
: baseMessage;
|
|
283
210
|
|
|
284
211
|
return {
|
|
285
212
|
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
286
|
-
details: { response: { provider: lastProvider, sources: [] }, error: message },
|
|
213
|
+
details: { response: { provider: lastProvider.id, sources: [] }, error: message },
|
|
287
214
|
};
|
|
288
215
|
}
|
|
289
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Execute a web search query for CLI/testing workflows.
|
|
219
|
+
*/
|
|
220
|
+
export async function runSearchQuery(
|
|
221
|
+
params: SearchParams,
|
|
222
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
223
|
+
return executeSearch("cli-web-search", params);
|
|
224
|
+
}
|
|
225
|
+
|
|
290
226
|
/**
|
|
291
227
|
* Web search tool implementation.
|
|
292
228
|
*
|
|
293
|
-
* Supports Anthropic, Perplexity, Exa, and
|
|
229
|
+
* Supports Anthropic, Perplexity, Exa, Jina, Gemini, and Codex providers with automatic fallback.
|
|
294
230
|
* Session is accepted for interface consistency but not used.
|
|
295
231
|
*/
|
|
296
|
-
export class
|
|
232
|
+
export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRenderDetails> {
|
|
297
233
|
public readonly name = "web_search";
|
|
298
234
|
public readonly label = "Web Search";
|
|
299
235
|
public readonly description: string;
|
|
@@ -305,38 +241,32 @@ export class WebSearchTool implements AgentTool<typeof webSearchSchema, WebSearc
|
|
|
305
241
|
|
|
306
242
|
public async execute(
|
|
307
243
|
_toolCallId: string,
|
|
308
|
-
params:
|
|
244
|
+
params: SearchParams,
|
|
309
245
|
_signal?: AbortSignal,
|
|
310
|
-
_onUpdate?: AgentToolUpdateCallback<
|
|
246
|
+
_onUpdate?: AgentToolUpdateCallback<SearchRenderDetails>,
|
|
311
247
|
_context?: AgentToolContext,
|
|
312
|
-
): Promise<AgentToolResult<
|
|
313
|
-
return
|
|
248
|
+
): Promise<AgentToolResult<SearchRenderDetails>> {
|
|
249
|
+
return executeSearch(_toolCallId, params);
|
|
314
250
|
}
|
|
315
251
|
}
|
|
316
252
|
|
|
317
253
|
/** Web search tool as CustomTool (for TUI rendering support) */
|
|
318
|
-
export const webSearchCustomTool: CustomTool<typeof webSearchSchema,
|
|
254
|
+
export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRenderDetails> = {
|
|
319
255
|
name: "web_search",
|
|
320
256
|
label: "Web Search",
|
|
321
257
|
description: renderPromptTemplate(webSearchDescription),
|
|
322
258
|
parameters: webSearchSchema,
|
|
323
259
|
|
|
324
|
-
async execute(
|
|
325
|
-
toolCallId
|
|
326
|
-
params: WebSearchParams,
|
|
327
|
-
_onUpdate,
|
|
328
|
-
_ctx: CustomToolContext,
|
|
329
|
-
_signal?: AbortSignal,
|
|
330
|
-
) {
|
|
331
|
-
return executeWebSearch(toolCallId, params);
|
|
260
|
+
async execute(toolCallId: string, params: SearchParams, _onUpdate, _ctx: CustomToolContext, _signal?: AbortSignal) {
|
|
261
|
+
return executeSearch(toolCallId, params);
|
|
332
262
|
},
|
|
333
263
|
|
|
334
|
-
renderCall(args:
|
|
335
|
-
return
|
|
264
|
+
renderCall(args: SearchParams, theme: Theme) {
|
|
265
|
+
return renderSearchCall(args, theme);
|
|
336
266
|
},
|
|
337
267
|
|
|
338
268
|
renderResult(result, options: RenderResultOptions, theme: Theme) {
|
|
339
|
-
return
|
|
269
|
+
return renderSearchResult(result, options, theme);
|
|
340
270
|
},
|
|
341
271
|
};
|
|
342
272
|
|
|
@@ -582,19 +512,19 @@ Parameters:
|
|
|
582
512
|
};
|
|
583
513
|
|
|
584
514
|
/** All Exa-specific web search tools */
|
|
585
|
-
export const
|
|
515
|
+
export const exaSearchTools: CustomTool<any, ExaRenderDetails>[] = [
|
|
586
516
|
webSearchDeepTool,
|
|
587
517
|
webSearchCodeContextTool,
|
|
588
518
|
webSearchCrawlTool,
|
|
589
519
|
];
|
|
590
520
|
|
|
591
521
|
/** LinkedIn-specific tool (requires LinkedIn addon on Exa account) */
|
|
592
|
-
export const
|
|
522
|
+
export const linkedinSearchTools: CustomTool<any, ExaRenderDetails>[] = [webSearchLinkedinTool];
|
|
593
523
|
|
|
594
524
|
/** Company-specific tool (requires Company addon on Exa account) */
|
|
595
|
-
export const
|
|
525
|
+
export const companySearchTools: CustomTool<any, ExaRenderDetails>[] = [webSearchCompanyTool];
|
|
596
526
|
|
|
597
|
-
export interface
|
|
527
|
+
export interface SearchToolsOptions {
|
|
598
528
|
/** Enable LinkedIn search tool (requires Exa LinkedIn addon) */
|
|
599
529
|
enableLinkedin?: boolean;
|
|
600
530
|
/** Enable company research tool (requires Exa Company addon) */
|
|
@@ -610,19 +540,19 @@ export interface WebSearchToolsOptions {
|
|
|
610
540
|
* - With EXA_API_KEY + options.enableLinkedin: web_search_linkedin
|
|
611
541
|
* - With EXA_API_KEY + options.enableCompany: web_search_company
|
|
612
542
|
*/
|
|
613
|
-
export async function
|
|
543
|
+
export async function getSearchTools(options: SearchToolsOptions = {}): Promise<CustomTool<any, any>[]> {
|
|
614
544
|
const tools: CustomTool<any, any>[] = [webSearchCustomTool];
|
|
615
545
|
|
|
616
546
|
// Check for Exa API key
|
|
617
547
|
const exaKey = await findExaKey();
|
|
618
548
|
if (exaKey) {
|
|
619
|
-
tools.push(...
|
|
549
|
+
tools.push(...exaSearchTools);
|
|
620
550
|
|
|
621
551
|
if (options.enableLinkedin) {
|
|
622
|
-
tools.push(...
|
|
552
|
+
tools.push(...linkedinSearchTools);
|
|
623
553
|
}
|
|
624
554
|
if (options.enableCompany) {
|
|
625
|
-
tools.push(...
|
|
555
|
+
tools.push(...companySearchTools);
|
|
626
556
|
}
|
|
627
557
|
}
|
|
628
558
|
|
|
@@ -632,9 +562,13 @@ export async function getWebSearchTools(options: WebSearchToolsOptions = {}): Pr
|
|
|
632
562
|
/**
|
|
633
563
|
* Check if Exa-specific web search tools are available.
|
|
634
564
|
*/
|
|
635
|
-
export async function
|
|
565
|
+
export async function hasExaSearch(): Promise<boolean> {
|
|
636
566
|
const exaKey = await findExaKey();
|
|
637
567
|
return exaKey !== null;
|
|
638
568
|
}
|
|
639
569
|
|
|
640
|
-
export
|
|
570
|
+
export {
|
|
571
|
+
getSearchProvider,
|
|
572
|
+
setPreferredSearchProvider,
|
|
573
|
+
} from "./provider";
|
|
574
|
+
export type { SearchProviderId as SearchProvider, SearchResponse } from "./types";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AnthropicProvider } from "./providers/anthropic";
|
|
2
|
+
import type { SearchProvider } from "./providers/base";
|
|
3
|
+
import { CodexProvider } from "./providers/codex";
|
|
4
|
+
import { ExaProvider } from "./providers/exa";
|
|
5
|
+
import { GeminiProvider } from "./providers/gemini";
|
|
6
|
+
import { JinaProvider } from "./providers/jina";
|
|
7
|
+
import { PerplexityProvider } from "./providers/perplexity";
|
|
8
|
+
import type { SearchProviderId } from "./types";
|
|
9
|
+
|
|
10
|
+
export type { SearchParams } from "./providers/base";
|
|
11
|
+
export { SearchProvider } from "./providers/base";
|
|
12
|
+
|
|
13
|
+
const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
|
|
14
|
+
exa: new ExaProvider(),
|
|
15
|
+
jina: new JinaProvider(),
|
|
16
|
+
perplexity: new PerplexityProvider(),
|
|
17
|
+
anthropic: new AnthropicProvider(),
|
|
18
|
+
gemini: new GeminiProvider(),
|
|
19
|
+
codex: new CodexProvider(),
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
const SEARCH_PROVIDER_ORDER: SearchProviderId[] = ["exa", "jina", "perplexity", "anthropic", "gemini", "codex"];
|
|
23
|
+
|
|
24
|
+
export function getSearchProvider(provider: SearchProviderId): SearchProvider {
|
|
25
|
+
return SEARCH_PROVIDERS[provider];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Preferred provider set via settings (default: auto) */
|
|
29
|
+
let preferredProvId: SearchProviderId | "auto" = "auto";
|
|
30
|
+
|
|
31
|
+
/** Set the preferred web search provider from settings */
|
|
32
|
+
export function setPreferredSearchProvider(provider: SearchProviderId | "auto"): void {
|
|
33
|
+
preferredProvId = provider;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Determine which providers are configured (priority order) */
|
|
37
|
+
export async function resolveProviderChain(
|
|
38
|
+
preferredProvider: SearchProviderId | "auto" = preferredProvId,
|
|
39
|
+
): Promise<SearchProvider[]> {
|
|
40
|
+
const providers: SearchProvider[] = [];
|
|
41
|
+
|
|
42
|
+
if (preferredProvider !== "auto") {
|
|
43
|
+
if (await getSearchProvider(preferredProvider).isAvailable()) {
|
|
44
|
+
providers.push(getSearchProvider(preferredProvider));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const id of SEARCH_PROVIDER_ORDER) {
|
|
49
|
+
if (id === preferredProvider) continue;
|
|
50
|
+
|
|
51
|
+
const provider = getSearchProvider(id);
|
|
52
|
+
if (await provider.isAvailable()) {
|
|
53
|
+
providers.push(provider);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return providers;
|
|
58
|
+
}
|
|
@@ -11,11 +11,13 @@ import type {
|
|
|
11
11
|
AnthropicApiResponse,
|
|
12
12
|
AnthropicAuthConfig,
|
|
13
13
|
AnthropicCitation,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
SearchCitation,
|
|
15
|
+
SearchResponse,
|
|
16
|
+
SearchSource,
|
|
17
17
|
} from "../../../web/search/types";
|
|
18
|
-
import {
|
|
18
|
+
import { SearchProviderError } from "../../../web/search/types";
|
|
19
|
+
import type { SearchParams } from "./base";
|
|
20
|
+
import { SearchProvider } from "./base";
|
|
19
21
|
|
|
20
22
|
const DEFAULT_MODEL = "claude-haiku-4-5";
|
|
21
23
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
@@ -36,6 +38,10 @@ export interface AnthropicSearchParams {
|
|
|
36
38
|
query: string;
|
|
37
39
|
system_prompt?: string;
|
|
38
40
|
num_results?: number;
|
|
41
|
+
/** Maximum output tokens. Defaults to 4096. */
|
|
42
|
+
max_tokens?: number;
|
|
43
|
+
/** Sampling temperature (0–1). Lower = more focused/factual. */
|
|
44
|
+
temperature?: number;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
/**
|
|
@@ -74,13 +80,15 @@ function buildSystemBlocks(
|
|
|
74
80
|
* @param query - Search query from the user
|
|
75
81
|
* @param systemPrompt - Optional system prompt for guiding response style
|
|
76
82
|
* @returns Raw API response from Anthropic
|
|
77
|
-
* @throws {
|
|
83
|
+
* @throws {SearchProviderError} If the API request fails
|
|
78
84
|
*/
|
|
79
|
-
async function
|
|
85
|
+
async function callSearch(
|
|
80
86
|
auth: AnthropicAuthConfig,
|
|
81
87
|
model: string,
|
|
82
88
|
query: string,
|
|
83
89
|
systemPrompt?: string,
|
|
90
|
+
maxTokens?: number,
|
|
91
|
+
temperature?: number,
|
|
84
92
|
): Promise<AnthropicApiResponse> {
|
|
85
93
|
const url = buildAnthropicUrl(auth);
|
|
86
94
|
const headers = buildAnthropicHeaders(auth);
|
|
@@ -89,7 +97,7 @@ async function callWebSearch(
|
|
|
89
97
|
|
|
90
98
|
const body: Record<string, unknown> = {
|
|
91
99
|
model,
|
|
92
|
-
max_tokens: DEFAULT_MAX_TOKENS,
|
|
100
|
+
max_tokens: maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
93
101
|
messages: [{ role: "user", content: query }],
|
|
94
102
|
tools: [
|
|
95
103
|
{
|
|
@@ -99,6 +107,10 @@ async function callWebSearch(
|
|
|
99
107
|
],
|
|
100
108
|
};
|
|
101
109
|
|
|
110
|
+
if (temperature !== undefined) {
|
|
111
|
+
body.temperature = temperature;
|
|
112
|
+
}
|
|
113
|
+
|
|
102
114
|
if (systemBlocks && systemBlocks.length > 0) {
|
|
103
115
|
body.system = systemBlocks;
|
|
104
116
|
}
|
|
@@ -111,7 +123,7 @@ async function callWebSearch(
|
|
|
111
123
|
|
|
112
124
|
if (!response.ok) {
|
|
113
125
|
const errorText = await response.text();
|
|
114
|
-
throw new
|
|
126
|
+
throw new SearchProviderError(
|
|
115
127
|
"anthropic",
|
|
116
128
|
`Anthropic API error (${response.status}): ${errorText}`,
|
|
117
129
|
response.status,
|
|
@@ -158,15 +170,15 @@ function parsePageAge(pageAge: string | null | undefined): number | undefined {
|
|
|
158
170
|
}
|
|
159
171
|
|
|
160
172
|
/**
|
|
161
|
-
* Parses the Anthropic API response into a unified
|
|
173
|
+
* Parses the Anthropic API response into a unified SearchResponse.
|
|
162
174
|
* @param response - Raw API response containing content blocks
|
|
163
175
|
* @returns Normalized response with answer, sources, citations, and usage
|
|
164
176
|
*/
|
|
165
|
-
function parseResponse(response: AnthropicApiResponse):
|
|
177
|
+
function parseResponse(response: AnthropicApiResponse): SearchResponse {
|
|
166
178
|
const answerParts: string[] = [];
|
|
167
179
|
const searchQueries: string[] = [];
|
|
168
|
-
const sources:
|
|
169
|
-
const citations:
|
|
180
|
+
const sources: SearchSource[] = [];
|
|
181
|
+
const citations: SearchCitation[] = [];
|
|
170
182
|
|
|
171
183
|
for (const block of response.content) {
|
|
172
184
|
if (
|
|
@@ -228,7 +240,7 @@ function parseResponse(response: AnthropicApiResponse): WebSearchResponse {
|
|
|
228
240
|
* @returns Search response with synthesized answer, sources, and citations
|
|
229
241
|
* @throws {Error} If no Anthropic credentials are configured
|
|
230
242
|
*/
|
|
231
|
-
export async function searchAnthropic(params: AnthropicSearchParams): Promise<
|
|
243
|
+
export async function searchAnthropic(params: AnthropicSearchParams): Promise<SearchResponse> {
|
|
232
244
|
const auth = await findAnthropicAuth();
|
|
233
245
|
if (!auth) {
|
|
234
246
|
throw new Error(
|
|
@@ -237,7 +249,14 @@ export async function searchAnthropic(params: AnthropicSearchParams): Promise<We
|
|
|
237
249
|
}
|
|
238
250
|
|
|
239
251
|
const model = getModel();
|
|
240
|
-
const response = await
|
|
252
|
+
const response = await callSearch(
|
|
253
|
+
auth,
|
|
254
|
+
model,
|
|
255
|
+
params.query,
|
|
256
|
+
params.system_prompt,
|
|
257
|
+
params.max_tokens,
|
|
258
|
+
params.temperature,
|
|
259
|
+
);
|
|
241
260
|
|
|
242
261
|
const result = parseResponse(response);
|
|
243
262
|
|
|
@@ -248,3 +267,23 @@ export async function searchAnthropic(params: AnthropicSearchParams): Promise<We
|
|
|
248
267
|
|
|
249
268
|
return result;
|
|
250
269
|
}
|
|
270
|
+
|
|
271
|
+
/** Search provider for Anthropic Claude web search. */
|
|
272
|
+
export class AnthropicProvider extends SearchProvider {
|
|
273
|
+
readonly id = "anthropic";
|
|
274
|
+
readonly label = "Anthropic";
|
|
275
|
+
|
|
276
|
+
isAvailable() {
|
|
277
|
+
return findAnthropicAuth().then(Boolean);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
search(params: SearchParams): Promise<SearchResponse> {
|
|
281
|
+
return searchAnthropic({
|
|
282
|
+
query: params.query,
|
|
283
|
+
system_prompt: params.systemPrompt,
|
|
284
|
+
num_results: params.numSearchResults ?? params.limit,
|
|
285
|
+
max_tokens: params.maxOutputTokens,
|
|
286
|
+
temperature: params.temperature,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SearchProviderId, SearchResponse } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Shared web search parameters passed to providers. */
|
|
4
|
+
export interface SearchParams {
|
|
5
|
+
query: string;
|
|
6
|
+
limit?: number;
|
|
7
|
+
recency?: "day" | "week" | "month" | "year";
|
|
8
|
+
systemPrompt: string;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
maxOutputTokens?: number;
|
|
11
|
+
numSearchResults?: number;
|
|
12
|
+
temperature?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Base class for web search providers. */
|
|
16
|
+
export abstract class SearchProvider {
|
|
17
|
+
abstract readonly id: SearchProviderId;
|
|
18
|
+
abstract readonly label: string;
|
|
19
|
+
|
|
20
|
+
abstract isAvailable(): Promise<boolean> | boolean;
|
|
21
|
+
abstract search(params: SearchParams): Promise<SearchResponse>;
|
|
22
|
+
}
|