@oh-my-pi/pi-coding-agent 1.337.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 +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- package/src/utils/tools-manager.ts +274 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Web Search Tool
|
|
3
|
+
*
|
|
4
|
+
* Single tool supporting Anthropic, Perplexity, and Exa providers with
|
|
5
|
+
* provider-specific parameters exposed conditionally.
|
|
6
|
+
*
|
|
7
|
+
* When EXA_API_KEY is available, additional specialized tools are exposed:
|
|
8
|
+
* - web_search_deep: Natural language web search with synthesized results
|
|
9
|
+
* - web_search_code_context: Search code snippets, docs, and examples
|
|
10
|
+
* - web_search_crawl: Extract content from specific URLs
|
|
11
|
+
* - web_search_linkedin: Search LinkedIn profiles and companies
|
|
12
|
+
* - web_search_company: Comprehensive company research
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
16
|
+
import { Type } from "@sinclair/typebox";
|
|
17
|
+
import type { Theme } from "../../../modes/interactive/theme/theme.js";
|
|
18
|
+
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../custom-tools/types.js";
|
|
19
|
+
import { callExaTool, findApiKey as findExaKey, formatSearchResults, isSearchResponse } from "../exa/mcp-client.js";
|
|
20
|
+
import { renderExaCall, renderExaResult } from "../exa/render.js";
|
|
21
|
+
import type { ExaRenderDetails } from "../exa/types.js";
|
|
22
|
+
import { searchAnthropic } from "./providers/anthropic.js";
|
|
23
|
+
import { searchExa } from "./providers/exa.js";
|
|
24
|
+
import { findApiKey as findPerplexityKey, searchPerplexity } from "./providers/perplexity.js";
|
|
25
|
+
import { formatAge, renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./render.js";
|
|
26
|
+
import type { WebSearchProvider, WebSearchResponse } from "./types.js";
|
|
27
|
+
|
|
28
|
+
/** Web search parameters schema */
|
|
29
|
+
export const webSearchSchema = Type.Object({
|
|
30
|
+
// Common
|
|
31
|
+
query: Type.String({ description: "Search query" }),
|
|
32
|
+
provider: Type.Optional(
|
|
33
|
+
Type.Union([Type.Literal("exa"), Type.Literal("anthropic"), Type.Literal("perplexity")], {
|
|
34
|
+
description: "Search provider (auto-detected if omitted based on API keys)",
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
num_results: Type.Optional(Type.Number({ description: "Maximum number of results to return" })),
|
|
38
|
+
|
|
39
|
+
// Common (Anthropic & Perplexity)
|
|
40
|
+
system_prompt: Type.Optional(
|
|
41
|
+
Type.String({
|
|
42
|
+
description: "System prompt to guide response style",
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
max_tokens: Type.Optional(
|
|
46
|
+
Type.Number({
|
|
47
|
+
description: "Maximum tokens in response, 1-16384, default 4096 (Anthropic only)",
|
|
48
|
+
minimum: 1,
|
|
49
|
+
maximum: 16384,
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
|
|
53
|
+
// Perplexity-specific
|
|
54
|
+
model: Type.Optional(
|
|
55
|
+
Type.Union([Type.Literal("sonar"), Type.Literal("sonar-pro")], {
|
|
56
|
+
description: "Perplexity model - sonar (fast) or sonar-pro (comprehensive research)",
|
|
57
|
+
}),
|
|
58
|
+
),
|
|
59
|
+
search_recency_filter: Type.Optional(
|
|
60
|
+
Type.Union([Type.Literal("day"), Type.Literal("week"), Type.Literal("month"), Type.Literal("year")], {
|
|
61
|
+
description: "Filter results by recency (Perplexity only)",
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
search_domain_filter: Type.Optional(
|
|
65
|
+
Type.Array(Type.String(), {
|
|
66
|
+
description: "Domain filter - include domains, prefix with - to exclude (Perplexity only)",
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
search_context_size: Type.Optional(
|
|
70
|
+
Type.Union([Type.Literal("low"), Type.Literal("medium"), Type.Literal("high")], {
|
|
71
|
+
description: "Context size for cost control (Perplexity only)",
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
return_related_questions: Type.Optional(
|
|
75
|
+
Type.Boolean({
|
|
76
|
+
description: "Include follow-up question suggestions, default true (Perplexity only)",
|
|
77
|
+
}),
|
|
78
|
+
),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export type WebSearchParams = {
|
|
82
|
+
query: string;
|
|
83
|
+
provider?: "exa" | "anthropic" | "perplexity";
|
|
84
|
+
num_results?: number;
|
|
85
|
+
// Anthropic
|
|
86
|
+
system_prompt?: string;
|
|
87
|
+
max_tokens?: number;
|
|
88
|
+
// Perplexity
|
|
89
|
+
model?: "sonar" | "sonar-pro";
|
|
90
|
+
search_recency_filter?: "day" | "week" | "month" | "year";
|
|
91
|
+
search_domain_filter?: string[];
|
|
92
|
+
search_context_size?: "low" | "medium" | "high";
|
|
93
|
+
return_related_questions?: boolean;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** Detect provider based on available API keys (priority: exa > perplexity > anthropic) */
|
|
97
|
+
async function detectProvider(): Promise<WebSearchProvider> {
|
|
98
|
+
// Exa takes highest priority if key exists
|
|
99
|
+
const exaKey = await findExaKey();
|
|
100
|
+
if (exaKey) return "exa";
|
|
101
|
+
|
|
102
|
+
// Perplexity second priority
|
|
103
|
+
const perplexityKey = await findPerplexityKey();
|
|
104
|
+
if (perplexityKey) return "perplexity";
|
|
105
|
+
|
|
106
|
+
// Default to Anthropic
|
|
107
|
+
return "anthropic";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Format response for LLM consumption */
|
|
111
|
+
function formatForLLM(response: WebSearchResponse): string {
|
|
112
|
+
const parts: string[] = [];
|
|
113
|
+
|
|
114
|
+
// Add synthesized answer
|
|
115
|
+
if (response.answer) {
|
|
116
|
+
parts.push(response.answer);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add sources
|
|
120
|
+
if (response.sources.length > 0) {
|
|
121
|
+
parts.push("\n## Sources");
|
|
122
|
+
for (const [i, src] of response.sources.entries()) {
|
|
123
|
+
const age = formatAge(src.ageSeconds) || src.publishedDate;
|
|
124
|
+
const agePart = age ? ` (${age})` : "";
|
|
125
|
+
parts.push(`[${i + 1}] ${src.title}${agePart}\n ${src.url}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Add related questions (Perplexity)
|
|
130
|
+
if (response.relatedQuestions && response.relatedQuestions.length > 0) {
|
|
131
|
+
parts.push("\n## Related Questions");
|
|
132
|
+
for (const q of response.relatedQuestions) {
|
|
133
|
+
parts.push(`- ${q}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return parts.join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Execute web search */
|
|
141
|
+
async function executeWebSearch(
|
|
142
|
+
_toolCallId: string,
|
|
143
|
+
params: WebSearchParams,
|
|
144
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; details: WebSearchRenderDetails }> {
|
|
145
|
+
try {
|
|
146
|
+
const provider = params.provider ?? (await detectProvider());
|
|
147
|
+
|
|
148
|
+
let response: WebSearchResponse;
|
|
149
|
+
if (provider === "exa") {
|
|
150
|
+
response = await searchExa({
|
|
151
|
+
query: params.query,
|
|
152
|
+
num_results: params.num_results,
|
|
153
|
+
});
|
|
154
|
+
} else if (provider === "anthropic") {
|
|
155
|
+
response = await searchAnthropic({
|
|
156
|
+
query: params.query,
|
|
157
|
+
system_prompt: params.system_prompt,
|
|
158
|
+
max_tokens: params.max_tokens,
|
|
159
|
+
num_results: params.num_results,
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
response = await searchPerplexity({
|
|
163
|
+
query: params.query,
|
|
164
|
+
model: params.model,
|
|
165
|
+
system_prompt: params.system_prompt,
|
|
166
|
+
search_recency_filter: params.search_recency_filter,
|
|
167
|
+
search_domain_filter: params.search_domain_filter,
|
|
168
|
+
search_context_size: params.search_context_size,
|
|
169
|
+
return_related_questions: params.return_related_questions,
|
|
170
|
+
num_results: params.num_results,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const text = formatForLLM(response);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: "text" as const, text }],
|
|
178
|
+
details: { response },
|
|
179
|
+
};
|
|
180
|
+
} catch (error) {
|
|
181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
184
|
+
details: { response: { provider: "anthropic", sources: [] }, error: message },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const WEB_SEARCH_DESCRIPTION = `Allows Pi to search the web and use the results to inform responses
|
|
190
|
+
- Provides up-to-date information for current events and recent data
|
|
191
|
+
- Returns search result information formatted as search result blocks, including links as markdown hyperlinks
|
|
192
|
+
- Use this tool for accessing information beyond Claude's knowledge cutoff
|
|
193
|
+
- Searches are performed automatically within a single API call
|
|
194
|
+
|
|
195
|
+
Common: system_prompt (guides response style)
|
|
196
|
+
Anthropic-specific: max_tokens
|
|
197
|
+
Perplexity-specific: model (sonar/sonar-pro), search_recency_filter, search_domain_filter, search_context_size, return_related_questions
|
|
198
|
+
Exa-specific: num_results`;
|
|
199
|
+
|
|
200
|
+
/** Web search tool as AgentTool (for allTools export) */
|
|
201
|
+
export const webSearchTool: AgentTool<typeof webSearchSchema> = {
|
|
202
|
+
name: "web_search",
|
|
203
|
+
label: "Web Search",
|
|
204
|
+
description: WEB_SEARCH_DESCRIPTION,
|
|
205
|
+
parameters: webSearchSchema,
|
|
206
|
+
execute: async (toolCallId, params) => {
|
|
207
|
+
return executeWebSearch(toolCallId, params as WebSearchParams);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/** Web search tool as CustomTool (for TUI rendering support) */
|
|
212
|
+
export const webSearchCustomTool: CustomTool<typeof webSearchSchema, WebSearchRenderDetails> = {
|
|
213
|
+
name: "web_search",
|
|
214
|
+
label: "Web Search",
|
|
215
|
+
description: WEB_SEARCH_DESCRIPTION,
|
|
216
|
+
parameters: webSearchSchema,
|
|
217
|
+
|
|
218
|
+
async execute(
|
|
219
|
+
toolCallId: string,
|
|
220
|
+
params: WebSearchParams,
|
|
221
|
+
_onUpdate,
|
|
222
|
+
_ctx: CustomToolContext,
|
|
223
|
+
_signal?: AbortSignal,
|
|
224
|
+
) {
|
|
225
|
+
return executeWebSearch(toolCallId, params);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
renderCall(args: WebSearchParams, theme: Theme) {
|
|
229
|
+
return renderWebSearchCall(args, theme);
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
renderResult(result, options: RenderResultOptions, theme: Theme) {
|
|
233
|
+
return renderWebSearchResult(result, options, theme);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/** Factory function for backward compatibility */
|
|
238
|
+
export function createWebSearchTool(_cwd: string): AgentTool<typeof webSearchSchema> {
|
|
239
|
+
return webSearchTool;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// Exa-specific tools (available when EXA_API_KEY is present)
|
|
244
|
+
// ============================================================================
|
|
245
|
+
|
|
246
|
+
/** Schema for deep search */
|
|
247
|
+
const webSearchDeepSchema = Type.Object({
|
|
248
|
+
query: Type.String({ description: "Research query" }),
|
|
249
|
+
type: Type.Optional(
|
|
250
|
+
Type.Union([Type.Literal("keyword"), Type.Literal("neural"), Type.Literal("auto")], {
|
|
251
|
+
description: "Search type - neural (semantic), keyword (exact), or auto",
|
|
252
|
+
}),
|
|
253
|
+
),
|
|
254
|
+
include_domains: Type.Optional(
|
|
255
|
+
Type.Array(Type.String(), { description: "Only include results from these domains" }),
|
|
256
|
+
),
|
|
257
|
+
exclude_domains: Type.Optional(Type.Array(Type.String(), { description: "Exclude results from these domains" })),
|
|
258
|
+
start_published_date: Type.Optional(
|
|
259
|
+
Type.String({ description: "Filter results published after this date (ISO 8601)" }),
|
|
260
|
+
),
|
|
261
|
+
end_published_date: Type.Optional(
|
|
262
|
+
Type.String({ description: "Filter results published before this date (ISO 8601)" }),
|
|
263
|
+
),
|
|
264
|
+
num_results: Type.Optional(
|
|
265
|
+
Type.Number({ description: "Maximum results (default: 10, max: 100)", minimum: 1, maximum: 100 }),
|
|
266
|
+
),
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
/** Schema for code context search */
|
|
270
|
+
const webSearchCodeContextSchema = Type.Object({
|
|
271
|
+
query: Type.String({ description: "Code or technical search query" }),
|
|
272
|
+
code_context: Type.Optional(Type.String({ description: "Additional context about what you're looking for" })),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
/** Schema for URL crawling */
|
|
276
|
+
const webSearchCrawlSchema = Type.Object({
|
|
277
|
+
url: Type.String({ description: "URL to crawl and extract content from" }),
|
|
278
|
+
text: Type.Optional(Type.Boolean({ description: "Include full page text content (default: false)" })),
|
|
279
|
+
highlights: Type.Optional(Type.Boolean({ description: "Include highlighted relevant snippets (default: false)" })),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
/** Schema for LinkedIn search */
|
|
283
|
+
const webSearchLinkedinSchema = Type.Object({
|
|
284
|
+
query: Type.String({ description: 'LinkedIn search query (e.g., "Software Engineer at OpenAI")' }),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
/** Schema for company research */
|
|
288
|
+
const webSearchCompanySchema = Type.Object({
|
|
289
|
+
company_name: Type.String({ description: "Name of the company to research" }),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
/** Helper to execute Exa tool and format response */
|
|
293
|
+
async function executeExaTool(
|
|
294
|
+
mcpToolName: string,
|
|
295
|
+
params: Record<string, unknown>,
|
|
296
|
+
toolName: string,
|
|
297
|
+
): Promise<{ content: Array<{ type: "text"; text: string }>; details: ExaRenderDetails }> {
|
|
298
|
+
try {
|
|
299
|
+
const apiKey = await findExaKey();
|
|
300
|
+
if (!apiKey) {
|
|
301
|
+
return {
|
|
302
|
+
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
303
|
+
details: { error: "EXA_API_KEY not found", toolName },
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const response = await callExaTool(mcpToolName, params, apiKey);
|
|
308
|
+
|
|
309
|
+
if (isSearchResponse(response)) {
|
|
310
|
+
const formatted = formatSearchResults(response);
|
|
311
|
+
return {
|
|
312
|
+
content: [{ type: "text" as const, text: formatted }],
|
|
313
|
+
details: { response, toolName },
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
content: [{ type: "text" as const, text: JSON.stringify(response, null, 2) }],
|
|
319
|
+
details: { raw: response, toolName },
|
|
320
|
+
};
|
|
321
|
+
} catch (error) {
|
|
322
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
323
|
+
return {
|
|
324
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
325
|
+
details: { error: message, toolName },
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Deep search - AI-synthesized research with multiple sources */
|
|
331
|
+
export const webSearchDeepTool: CustomTool<typeof webSearchDeepSchema, ExaRenderDetails> = {
|
|
332
|
+
name: "web_search_deep",
|
|
333
|
+
label: "Deep Search",
|
|
334
|
+
description: `Natural language web search with synthesized results (requires Exa).
|
|
335
|
+
|
|
336
|
+
Performs AI-powered deep research that synthesizes information from multiple sources.
|
|
337
|
+
Best for complex research queries that need comprehensive answers.
|
|
338
|
+
|
|
339
|
+
Parameters:
|
|
340
|
+
- query: Research query (required)
|
|
341
|
+
- type: Search type - neural (semantic), keyword (exact), or auto
|
|
342
|
+
- include_domains/exclude_domains: Domain filters
|
|
343
|
+
- start/end_published_date: Date range filter (ISO 8601)
|
|
344
|
+
- num_results: Maximum results (default: 10)`,
|
|
345
|
+
parameters: webSearchDeepSchema,
|
|
346
|
+
|
|
347
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
348
|
+
return executeExaTool("deep_search_exa", params as Record<string, unknown>, "web_search_deep");
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
renderCall(args, theme) {
|
|
352
|
+
return renderExaCall(args as Record<string, unknown>, "Deep Search", theme);
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
renderResult(result, options, theme) {
|
|
356
|
+
return renderExaResult(result, options, theme);
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/** Code context search - optimized for code snippets and documentation */
|
|
361
|
+
export const webSearchCodeContextTool: CustomTool<typeof webSearchCodeContextSchema, ExaRenderDetails> = {
|
|
362
|
+
name: "web_search_code_context",
|
|
363
|
+
label: "Code Search",
|
|
364
|
+
description: `Search code snippets, documentation, and technical examples (requires Exa).
|
|
365
|
+
|
|
366
|
+
Optimized for finding:
|
|
367
|
+
- Code examples and snippets
|
|
368
|
+
- API documentation
|
|
369
|
+
- Technical tutorials
|
|
370
|
+
- Stack Overflow answers
|
|
371
|
+
- GitHub code references
|
|
372
|
+
|
|
373
|
+
Parameters:
|
|
374
|
+
- query: Code or technical search query (required)
|
|
375
|
+
- code_context: Additional context about what you're looking for`,
|
|
376
|
+
parameters: webSearchCodeContextSchema,
|
|
377
|
+
|
|
378
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
379
|
+
return executeExaTool("get_code_context_exa", params as Record<string, unknown>, "web_search_code_context");
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
renderCall(args, theme) {
|
|
383
|
+
return renderExaCall(args as Record<string, unknown>, "Code Search", theme);
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
renderResult(result, options, theme) {
|
|
387
|
+
return renderExaResult(result, options, theme);
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/** URL crawl - extract content from specific URLs */
|
|
392
|
+
export const webSearchCrawlTool: CustomTool<typeof webSearchCrawlSchema, ExaRenderDetails> = {
|
|
393
|
+
name: "web_search_crawl",
|
|
394
|
+
label: "Crawl URL",
|
|
395
|
+
description: `Extract content from a specific URL (requires Exa).
|
|
396
|
+
|
|
397
|
+
Fetches and extracts content from a URL with optional text and highlights.
|
|
398
|
+
Useful when you have a specific URL and want its content.
|
|
399
|
+
|
|
400
|
+
Parameters:
|
|
401
|
+
- url: URL to crawl (required)
|
|
402
|
+
- text: Include full page text content (default: false)
|
|
403
|
+
- highlights: Include highlighted snippets (default: false)`,
|
|
404
|
+
parameters: webSearchCrawlSchema,
|
|
405
|
+
|
|
406
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
407
|
+
return executeExaTool("crawling_exa", params as Record<string, unknown>, "web_search_crawl");
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
renderCall(args, theme) {
|
|
411
|
+
const url = (args as { url: string }).url;
|
|
412
|
+
return renderExaCall({ query: url }, "Crawl URL", theme);
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
renderResult(result, options, theme) {
|
|
416
|
+
return renderExaResult(result, options, theme);
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
/** LinkedIn search - search LinkedIn profiles and companies */
|
|
421
|
+
export const webSearchLinkedinTool: CustomTool<typeof webSearchLinkedinSchema, ExaRenderDetails> = {
|
|
422
|
+
name: "web_search_linkedin",
|
|
423
|
+
label: "LinkedIn Search",
|
|
424
|
+
description: `Search LinkedIn for people, companies, and professional content (requires Exa + LinkedIn addon).
|
|
425
|
+
|
|
426
|
+
Returns LinkedIn profiles, company pages, posts, and professional content.
|
|
427
|
+
|
|
428
|
+
Examples:
|
|
429
|
+
- "Software Engineer at OpenAI"
|
|
430
|
+
- "Y Combinator companies"
|
|
431
|
+
- "CEO fintech startup San Francisco"
|
|
432
|
+
|
|
433
|
+
Parameters:
|
|
434
|
+
- query: LinkedIn search query (required)`,
|
|
435
|
+
parameters: webSearchLinkedinSchema,
|
|
436
|
+
|
|
437
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
438
|
+
return executeExaTool("linkedin_search_exa", params as Record<string, unknown>, "web_search_linkedin");
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
renderCall(args, theme) {
|
|
442
|
+
return renderExaCall(args as Record<string, unknown>, "LinkedIn Search", theme);
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
renderResult(result, options, theme) {
|
|
446
|
+
return renderExaResult(result, options, theme);
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
/** Company research - comprehensive company information */
|
|
451
|
+
export const webSearchCompanyTool: CustomTool<typeof webSearchCompanySchema, ExaRenderDetails> = {
|
|
452
|
+
name: "web_search_company",
|
|
453
|
+
label: "Company Research",
|
|
454
|
+
description: `Comprehensive company research (requires Exa + Company addon).
|
|
455
|
+
|
|
456
|
+
Returns detailed company information including:
|
|
457
|
+
- Company overview and description
|
|
458
|
+
- Recent news and announcements
|
|
459
|
+
- Key people and leadership
|
|
460
|
+
- Funding and financial information
|
|
461
|
+
- Products and services
|
|
462
|
+
|
|
463
|
+
Parameters:
|
|
464
|
+
- company_name: Name of the company to research (required)`,
|
|
465
|
+
parameters: webSearchCompanySchema,
|
|
466
|
+
|
|
467
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
468
|
+
return executeExaTool("company_research_exa", params as Record<string, unknown>, "web_search_company");
|
|
469
|
+
},
|
|
470
|
+
|
|
471
|
+
renderCall(args, theme) {
|
|
472
|
+
const name = (args as { company_name: string }).company_name;
|
|
473
|
+
return renderExaCall({ query: name }, "Company Research", theme);
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
renderResult(result, options, theme) {
|
|
477
|
+
return renderExaResult(result, options, theme);
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
/** All Exa-specific web search tools */
|
|
482
|
+
export const exaWebSearchTools: CustomTool<any, ExaRenderDetails>[] = [
|
|
483
|
+
webSearchDeepTool,
|
|
484
|
+
webSearchCodeContextTool,
|
|
485
|
+
webSearchCrawlTool,
|
|
486
|
+
];
|
|
487
|
+
|
|
488
|
+
/** LinkedIn-specific tool (requires LinkedIn addon on Exa account) */
|
|
489
|
+
export const linkedinWebSearchTools: CustomTool<any, ExaRenderDetails>[] = [webSearchLinkedinTool];
|
|
490
|
+
|
|
491
|
+
/** Company-specific tool (requires Company addon on Exa account) */
|
|
492
|
+
export const companyWebSearchTools: CustomTool<any, ExaRenderDetails>[] = [webSearchCompanyTool];
|
|
493
|
+
|
|
494
|
+
export interface WebSearchToolsOptions {
|
|
495
|
+
/** Enable LinkedIn search tool (requires Exa LinkedIn addon) */
|
|
496
|
+
enableLinkedin?: boolean;
|
|
497
|
+
/** Enable company research tool (requires Exa Company addon) */
|
|
498
|
+
enableCompany?: boolean;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Get all available web search tools based on API key availability.
|
|
503
|
+
*
|
|
504
|
+
* Returns:
|
|
505
|
+
* - Always: web_search (unified, works with Anthropic/Perplexity/Exa)
|
|
506
|
+
* - With EXA_API_KEY: web_search_deep, web_search_code_context, web_search_crawl
|
|
507
|
+
* - With EXA_API_KEY + options.enableLinkedin: web_search_linkedin
|
|
508
|
+
* - With EXA_API_KEY + options.enableCompany: web_search_company
|
|
509
|
+
*/
|
|
510
|
+
export async function getWebSearchTools(options: WebSearchToolsOptions = {}): Promise<CustomTool<any, any>[]> {
|
|
511
|
+
const tools: CustomTool<any, any>[] = [webSearchCustomTool];
|
|
512
|
+
|
|
513
|
+
// Check for Exa API key
|
|
514
|
+
const exaKey = await findExaKey();
|
|
515
|
+
if (exaKey) {
|
|
516
|
+
tools.push(...exaWebSearchTools);
|
|
517
|
+
|
|
518
|
+
if (options.enableLinkedin) {
|
|
519
|
+
tools.push(...linkedinWebSearchTools);
|
|
520
|
+
}
|
|
521
|
+
if (options.enableCompany) {
|
|
522
|
+
tools.push(...companyWebSearchTools);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return tools;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Check if Exa-specific web search tools are available.
|
|
531
|
+
*/
|
|
532
|
+
export async function hasExaWebSearch(): Promise<boolean> {
|
|
533
|
+
const exaKey = await findExaKey();
|
|
534
|
+
return exaKey !== null;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export type { WebSearchProvider, WebSearchResponse } from "./types.js";
|