@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,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exa TUI Rendering
|
|
3
|
+
*
|
|
4
|
+
* Tree-based rendering with collapsed/expanded states for Exa search results.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
|
+
import { Text } from "@oh-my-pi/pi-tui";
|
|
9
|
+
import type { Theme } from "../../../modes/interactive/theme/theme.js";
|
|
10
|
+
import type { RenderResultOptions } from "../../custom-tools/types.js";
|
|
11
|
+
import { logViewError } from "./logger.js";
|
|
12
|
+
import type { ExaRenderDetails } from "./types.js";
|
|
13
|
+
|
|
14
|
+
// Tree formatting constants
|
|
15
|
+
const TREE_MID = "├─";
|
|
16
|
+
const TREE_END = "└─";
|
|
17
|
+
const TREE_PIPE = "│";
|
|
18
|
+
const TREE_SPACE = " ";
|
|
19
|
+
const TREE_HOOK = "⎿";
|
|
20
|
+
|
|
21
|
+
/** Truncate text to max length with ellipsis */
|
|
22
|
+
function truncate(text: string, maxLen: number): string {
|
|
23
|
+
if (text.length <= maxLen) return text;
|
|
24
|
+
return `${text.slice(0, maxLen - 1)}…`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Extract domain from URL */
|
|
28
|
+
function getDomain(url: string): string {
|
|
29
|
+
try {
|
|
30
|
+
const u = new URL(url);
|
|
31
|
+
return u.hostname.replace(/^www\./, "");
|
|
32
|
+
} catch {
|
|
33
|
+
return url;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Get first N lines of text as preview */
|
|
38
|
+
function getPreviewLines(text: string, maxLines: number, maxLineLen: number): string[] {
|
|
39
|
+
const lines = text.split("\n").filter((l) => l.trim());
|
|
40
|
+
return lines.slice(0, maxLines).map((l) => truncate(l.trim(), maxLineLen));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Render Exa result with tree-based layout */
|
|
44
|
+
export function renderExaResult(
|
|
45
|
+
result: { content: Array<{ type: string; text?: string }>; details?: ExaRenderDetails },
|
|
46
|
+
options: RenderResultOptions,
|
|
47
|
+
theme: Theme,
|
|
48
|
+
): Component {
|
|
49
|
+
const { expanded } = options;
|
|
50
|
+
const details = result.details;
|
|
51
|
+
|
|
52
|
+
// Handle error case
|
|
53
|
+
if (details?.error) {
|
|
54
|
+
logViewError("Exa render error", { error: details.error, toolName: details.toolName });
|
|
55
|
+
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const response = details?.response;
|
|
59
|
+
if (!response) {
|
|
60
|
+
// Non-search response: show raw result
|
|
61
|
+
if (details?.raw) {
|
|
62
|
+
const rawText = typeof details.raw === "string" ? details.raw : JSON.stringify(details.raw, null, 2);
|
|
63
|
+
const preview = expanded ? rawText : truncate(rawText, 200);
|
|
64
|
+
const toolLabel = details?.toolName ?? "Exa";
|
|
65
|
+
return new Text(
|
|
66
|
+
`${theme.fg("success", "●")} ${theme.fg("toolTitle", toolLabel)}\n ${theme.fg("dim", TREE_PIPE)} ${preview}`,
|
|
67
|
+
0,
|
|
68
|
+
0,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return new Text(theme.fg("error", "No response data"), 0, 0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const results = response.results ?? [];
|
|
75
|
+
const resultCount = results.length;
|
|
76
|
+
const cost = response.costDollars?.total;
|
|
77
|
+
const time = response.searchTime;
|
|
78
|
+
|
|
79
|
+
// Build header: ● Exa Search · N results · $X.XX · Xs
|
|
80
|
+
const icon = resultCount > 0 ? theme.fg("success", "●") : theme.fg("warning", "●");
|
|
81
|
+
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
82
|
+
const toolLabel = details?.toolName ?? "Exa Search";
|
|
83
|
+
|
|
84
|
+
let headerParts = `${icon} ${theme.fg("toolTitle", toolLabel)} · ${theme.fg(
|
|
85
|
+
"dim",
|
|
86
|
+
`${resultCount} result${resultCount !== 1 ? "s" : ""}`,
|
|
87
|
+
)}`;
|
|
88
|
+
|
|
89
|
+
if (cost !== undefined) {
|
|
90
|
+
headerParts += ` · ${theme.fg("muted", `$${cost.toFixed(4)}`)}`;
|
|
91
|
+
}
|
|
92
|
+
if (time !== undefined) {
|
|
93
|
+
headerParts += ` · ${theme.fg("muted", `${time.toFixed(2)}s`)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let text = headerParts + expandHint;
|
|
97
|
+
|
|
98
|
+
if (!expanded) {
|
|
99
|
+
// Collapsed view: show 3-line preview from first result
|
|
100
|
+
if (resultCount > 0) {
|
|
101
|
+
const first = results[0];
|
|
102
|
+
const previewText = first.text ?? first.title ?? "";
|
|
103
|
+
const previewLines = getPreviewLines(previewText, 3, 100);
|
|
104
|
+
|
|
105
|
+
for (const line of previewLines) {
|
|
106
|
+
text += `\n ${theme.fg("dim", TREE_PIPE)} ${theme.fg("dim", line)}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const totalLines = previewText.split("\n").filter((l) => l.trim()).length;
|
|
110
|
+
if (totalLines > 3) {
|
|
111
|
+
text += `\n ${theme.fg("dim", TREE_PIPE)} ${theme.fg("muted", `… ${totalLines - 3} more lines`)}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (resultCount > 1) {
|
|
115
|
+
text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg(
|
|
116
|
+
"muted",
|
|
117
|
+
`${resultCount - 1} more result${resultCount !== 2 ? "s" : ""}`,
|
|
118
|
+
)}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
// Expanded view: full results tree
|
|
123
|
+
if (resultCount > 0) {
|
|
124
|
+
text += `\n ${theme.fg("dim", TREE_PIPE)}`;
|
|
125
|
+
text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("accent", "Results")}`;
|
|
126
|
+
|
|
127
|
+
for (let i = 0; i < results.length; i++) {
|
|
128
|
+
const res = results[i];
|
|
129
|
+
const isLast = i === results.length - 1;
|
|
130
|
+
const branch = isLast ? TREE_END : TREE_MID;
|
|
131
|
+
const cont = isLast ? TREE_SPACE : TREE_PIPE;
|
|
132
|
+
|
|
133
|
+
// Title + domain
|
|
134
|
+
const title = truncate(res.title ?? "Untitled", 60);
|
|
135
|
+
const domain = res.url ? getDomain(res.url) : "";
|
|
136
|
+
const domainPart = domain ? theme.fg("dim", ` (${domain})`) : "";
|
|
137
|
+
|
|
138
|
+
text += `\n ${theme.fg("dim", TREE_SPACE)} ${theme.fg("dim", branch)} ${theme.fg(
|
|
139
|
+
"accent",
|
|
140
|
+
title,
|
|
141
|
+
)}${domainPart}`;
|
|
142
|
+
|
|
143
|
+
// URL
|
|
144
|
+
if (res.url) {
|
|
145
|
+
text += `\n ${theme.fg("dim", cont)} ${theme.fg("dim", TREE_HOOK)} ${theme.fg("mdLinkUrl", res.url)}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Author
|
|
149
|
+
if (res.author) {
|
|
150
|
+
text += `\n ${theme.fg("dim", cont)} ${theme.fg("muted", `Author: ${res.author}`)}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Published date
|
|
154
|
+
if (res.publishedDate) {
|
|
155
|
+
text += `\n ${theme.fg("dim", cont)} ${theme.fg("muted", `Published: ${res.publishedDate}`)}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Text content
|
|
159
|
+
if (res.text) {
|
|
160
|
+
const textLines = res.text.split("\n").filter((l) => l.trim());
|
|
161
|
+
const displayLines = textLines.slice(0, 5); // Show first 5 lines
|
|
162
|
+
for (const line of displayLines) {
|
|
163
|
+
text += `\n ${theme.fg("dim", cont)} ${truncate(line.trim(), 90)}`;
|
|
164
|
+
}
|
|
165
|
+
if (textLines.length > 5) {
|
|
166
|
+
text += `\n ${theme.fg("dim", cont)} ${theme.fg("muted", `… ${textLines.length - 5} more lines`)}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Highlights
|
|
171
|
+
if (res.highlights?.length) {
|
|
172
|
+
text += `\n ${theme.fg("dim", cont)} ${theme.fg("accent", "Highlights:")}`;
|
|
173
|
+
for (let j = 0; j < Math.min(res.highlights.length, 3); j++) {
|
|
174
|
+
const h = res.highlights[j];
|
|
175
|
+
text += `\n ${theme.fg("dim", cont)} ${theme.fg("muted", `• ${truncate(h, 80)}`)}`;
|
|
176
|
+
}
|
|
177
|
+
if (res.highlights.length > 3) {
|
|
178
|
+
text += `\n ${theme.fg("dim", cont)} ${theme.fg("muted", `… ${res.highlights.length - 3} more`)}`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return new Text(text, 0, 0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Render Exa call (query/args preview) */
|
|
189
|
+
export function renderExaCall(args: Record<string, unknown>, toolName: string, theme: Theme): Component {
|
|
190
|
+
const query = typeof args.query === "string" ? truncate(args.query, 80) : "";
|
|
191
|
+
const numResults = typeof args.num_results === "number" ? args.num_results : undefined;
|
|
192
|
+
const detail = numResults ? theme.fg("dim", ` (${numResults} results)`) : "";
|
|
193
|
+
|
|
194
|
+
const text = `${theme.fg("toolTitle", toolName)} ${theme.fg("muted", query)}${detail}`;
|
|
195
|
+
return new Text(text, 0, 0);
|
|
196
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exa Researcher Tools
|
|
3
|
+
*
|
|
4
|
+
* Async research tasks with polling for completion.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Type } from "@sinclair/typebox";
|
|
8
|
+
import type { CustomTool } from "../../custom-tools/types.js";
|
|
9
|
+
import { callExaTool, findApiKey } from "./mcp-client.js";
|
|
10
|
+
import type { ExaRenderDetails } from "./types.js";
|
|
11
|
+
|
|
12
|
+
const researcherStartTool: CustomTool<any, ExaRenderDetails> = {
|
|
13
|
+
name: "exa_researcher_start",
|
|
14
|
+
label: "Start Deep Research",
|
|
15
|
+
description:
|
|
16
|
+
"Start an asynchronous deep research task using Exa's researcher. Returns a task_id for polling completion.",
|
|
17
|
+
parameters: Type.Object({
|
|
18
|
+
query: Type.String({ description: "Research query to investigate" }),
|
|
19
|
+
depth: Type.Optional(
|
|
20
|
+
Type.Number({
|
|
21
|
+
description: "Research depth (1-5, default: 3)",
|
|
22
|
+
minimum: 1,
|
|
23
|
+
maximum: 5,
|
|
24
|
+
}),
|
|
25
|
+
),
|
|
26
|
+
breadth: Type.Optional(
|
|
27
|
+
Type.Number({
|
|
28
|
+
description: "Research breadth (1-5, default: 3)",
|
|
29
|
+
minimum: 1,
|
|
30
|
+
maximum: 5,
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
}),
|
|
34
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
35
|
+
try {
|
|
36
|
+
const apiKey = await findApiKey();
|
|
37
|
+
if (!apiKey) {
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
40
|
+
details: { error: "EXA_API_KEY not found", toolName: "exa_researcher_start" },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const result = await callExaTool("deep_researcher_start", params as Record<string, unknown>, apiKey);
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
46
|
+
details: { raw: result, toolName: "exa_researcher_start" },
|
|
47
|
+
};
|
|
48
|
+
} catch (error) {
|
|
49
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
52
|
+
details: { error: message, toolName: "exa_researcher_start" },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const researcherPollTool: CustomTool<any, ExaRenderDetails> = {
|
|
59
|
+
name: "exa_researcher_poll",
|
|
60
|
+
label: "Poll Research Status",
|
|
61
|
+
description:
|
|
62
|
+
"Poll the status of an asynchronous research task. Returns status (pending|running|completed|failed) and result if completed.",
|
|
63
|
+
parameters: Type.Object({
|
|
64
|
+
task_id: Type.String({ description: "Task ID returned from exa_researcher_start" }),
|
|
65
|
+
}),
|
|
66
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
67
|
+
try {
|
|
68
|
+
const apiKey = await findApiKey();
|
|
69
|
+
if (!apiKey) {
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
72
|
+
details: { error: "EXA_API_KEY not found", toolName: "exa_researcher_poll" },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const result = await callExaTool("deep_researcher_check", params as Record<string, unknown>, apiKey);
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
78
|
+
details: { raw: result, toolName: "exa_researcher_poll" },
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
84
|
+
details: { error: message, toolName: "exa_researcher_poll" },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const researcherTools: CustomTool<any, ExaRenderDetails>[] = [researcherStartTool, researcherPollTool];
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exa Search Tools
|
|
3
|
+
*
|
|
4
|
+
* Basic neural/keyword search, deep research, code search, and URL crawling.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Type } from "@sinclair/typebox";
|
|
8
|
+
import type { CustomTool } from "../../custom-tools/types.js";
|
|
9
|
+
import type { ExaRenderDetails } from "./types.js";
|
|
10
|
+
|
|
11
|
+
/** exa_search - Basic neural/keyword search */
|
|
12
|
+
const exaSearchTool: CustomTool<any, ExaRenderDetails> = {
|
|
13
|
+
name: "exa_search",
|
|
14
|
+
label: "Exa Search",
|
|
15
|
+
description: `Search the web using Exa's neural or keyword search.
|
|
16
|
+
|
|
17
|
+
Returns structured search results with optional text content and highlights.
|
|
18
|
+
|
|
19
|
+
Parameters:
|
|
20
|
+
- query: Search query (required)
|
|
21
|
+
- type: Search type - "neural" (semantic), "keyword" (exact), or "auto" (default: auto)
|
|
22
|
+
- include_domains: Array of domains to include in results
|
|
23
|
+
- exclude_domains: Array of domains to exclude from results
|
|
24
|
+
- start_published_date: Filter results published after this date (ISO 8601)
|
|
25
|
+
- end_published_date: Filter results published before this date (ISO 8601)
|
|
26
|
+
- use_autoprompt: Let Exa optimize your query automatically (default: true)
|
|
27
|
+
- text: Include page text content in results (default: false, costs more)
|
|
28
|
+
- highlights: Include highlighted relevant snippets (default: false)
|
|
29
|
+
- num_results: Maximum number of results to return (default: 10, max: 100)`,
|
|
30
|
+
|
|
31
|
+
parameters: Type.Object({
|
|
32
|
+
query: Type.String({ description: "Search query" }),
|
|
33
|
+
type: Type.Optional(
|
|
34
|
+
Type.Union([Type.Literal("keyword"), Type.Literal("neural"), Type.Literal("auto")], {
|
|
35
|
+
description: "Search type - neural (semantic), keyword (exact), or auto",
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
include_domains: Type.Optional(
|
|
39
|
+
Type.Array(Type.String(), {
|
|
40
|
+
description: "Only include results from these domains",
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
exclude_domains: Type.Optional(
|
|
44
|
+
Type.Array(Type.String(), {
|
|
45
|
+
description: "Exclude results from these domains",
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
start_published_date: Type.Optional(
|
|
49
|
+
Type.String({
|
|
50
|
+
description: "Filter results published after this date (ISO 8601 format)",
|
|
51
|
+
}),
|
|
52
|
+
),
|
|
53
|
+
end_published_date: Type.Optional(
|
|
54
|
+
Type.String({
|
|
55
|
+
description: "Filter results published before this date (ISO 8601 format)",
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
use_autoprompt: Type.Optional(
|
|
59
|
+
Type.Boolean({
|
|
60
|
+
description: "Let Exa optimize your query automatically (default: true)",
|
|
61
|
+
}),
|
|
62
|
+
),
|
|
63
|
+
text: Type.Optional(
|
|
64
|
+
Type.Boolean({
|
|
65
|
+
description: "Include page text content in results (costs more, default: false)",
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
highlights: Type.Optional(
|
|
69
|
+
Type.Boolean({
|
|
70
|
+
description: "Include highlighted relevant snippets (default: false)",
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
73
|
+
num_results: Type.Optional(
|
|
74
|
+
Type.Number({
|
|
75
|
+
description: "Maximum number of results to return (default: 10, max: 100)",
|
|
76
|
+
minimum: 1,
|
|
77
|
+
maximum: 100,
|
|
78
|
+
}),
|
|
79
|
+
),
|
|
80
|
+
}),
|
|
81
|
+
|
|
82
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
83
|
+
try {
|
|
84
|
+
// Dynamic imports to avoid circular dependencies
|
|
85
|
+
const { findApiKey, callExaTool, formatSearchResults, isSearchResponse } = await import("./mcp-client.js");
|
|
86
|
+
|
|
87
|
+
const apiKey = await findApiKey();
|
|
88
|
+
if (!apiKey) {
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
91
|
+
details: { error: "EXA_API_KEY not found", toolName: "exa_search" },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const response = await callExaTool("web_search", params, apiKey);
|
|
95
|
+
|
|
96
|
+
if (isSearchResponse(response)) {
|
|
97
|
+
const formatted = formatSearchResults(response);
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: "text" as const, text: formatted }],
|
|
100
|
+
details: { response, toolName: "exa_search" },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "text" as const, text: JSON.stringify(response, null, 2) }],
|
|
106
|
+
details: { raw: response, toolName: "exa_search" },
|
|
107
|
+
};
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
return {
|
|
111
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
112
|
+
details: { error: message, toolName: "exa_search" },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/** exa_search_deep - AI-synthesized deep research */
|
|
119
|
+
const exaSearchDeepTool: CustomTool<any, ExaRenderDetails> = {
|
|
120
|
+
name: "exa_search_deep",
|
|
121
|
+
label: "Exa Deep Search",
|
|
122
|
+
description: `Perform AI-synthesized deep research using Exa.
|
|
123
|
+
|
|
124
|
+
Returns comprehensive research with synthesized answers and multiple sources.
|
|
125
|
+
|
|
126
|
+
Similar parameters to exa_search, optimized for research depth.`,
|
|
127
|
+
|
|
128
|
+
parameters: Type.Object({
|
|
129
|
+
query: Type.String({ description: "Research query" }),
|
|
130
|
+
type: Type.Optional(
|
|
131
|
+
Type.Union([Type.Literal("keyword"), Type.Literal("neural"), Type.Literal("auto")], {
|
|
132
|
+
description: "Search type - neural (semantic), keyword (exact), or auto",
|
|
133
|
+
}),
|
|
134
|
+
),
|
|
135
|
+
include_domains: Type.Optional(
|
|
136
|
+
Type.Array(Type.String(), {
|
|
137
|
+
description: "Only include results from these domains",
|
|
138
|
+
}),
|
|
139
|
+
),
|
|
140
|
+
exclude_domains: Type.Optional(
|
|
141
|
+
Type.Array(Type.String(), {
|
|
142
|
+
description: "Exclude results from these domains",
|
|
143
|
+
}),
|
|
144
|
+
),
|
|
145
|
+
start_published_date: Type.Optional(
|
|
146
|
+
Type.String({
|
|
147
|
+
description: "Filter results published after this date (ISO 8601 format)",
|
|
148
|
+
}),
|
|
149
|
+
),
|
|
150
|
+
end_published_date: Type.Optional(
|
|
151
|
+
Type.String({
|
|
152
|
+
description: "Filter results published before this date (ISO 8601 format)",
|
|
153
|
+
}),
|
|
154
|
+
),
|
|
155
|
+
use_autoprompt: Type.Optional(
|
|
156
|
+
Type.Boolean({
|
|
157
|
+
description: "Let Exa optimize your query automatically (default: true)",
|
|
158
|
+
}),
|
|
159
|
+
),
|
|
160
|
+
text: Type.Optional(
|
|
161
|
+
Type.Boolean({
|
|
162
|
+
description: "Include page text content in results (costs more, default: false)",
|
|
163
|
+
}),
|
|
164
|
+
),
|
|
165
|
+
highlights: Type.Optional(
|
|
166
|
+
Type.Boolean({
|
|
167
|
+
description: "Include highlighted relevant snippets (default: false)",
|
|
168
|
+
}),
|
|
169
|
+
),
|
|
170
|
+
num_results: Type.Optional(
|
|
171
|
+
Type.Number({
|
|
172
|
+
description: "Maximum number of results to return (default: 10, max: 100)",
|
|
173
|
+
minimum: 1,
|
|
174
|
+
maximum: 100,
|
|
175
|
+
}),
|
|
176
|
+
),
|
|
177
|
+
}),
|
|
178
|
+
|
|
179
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
180
|
+
try {
|
|
181
|
+
const { findApiKey, callExaTool, formatSearchResults, isSearchResponse } = await import("./mcp-client.js");
|
|
182
|
+
|
|
183
|
+
const apiKey = await findApiKey();
|
|
184
|
+
if (!apiKey) {
|
|
185
|
+
return {
|
|
186
|
+
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
187
|
+
details: { error: "EXA_API_KEY not found", toolName: "exa_search_deep" },
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const response = await callExaTool("deep_search_exa", params, apiKey);
|
|
191
|
+
|
|
192
|
+
if (isSearchResponse(response)) {
|
|
193
|
+
const formatted = formatSearchResults(response);
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text" as const, text: formatted }],
|
|
196
|
+
details: { response, toolName: "exa_search_deep" },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
content: [{ type: "text" as const, text: JSON.stringify(response, null, 2) }],
|
|
202
|
+
details: { raw: response, toolName: "exa_search_deep" },
|
|
203
|
+
};
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
208
|
+
details: { error: message, toolName: "exa_search_deep" },
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** exa_search_code - Code-focused search */
|
|
215
|
+
const exaSearchCodeTool: CustomTool<any, ExaRenderDetails> = {
|
|
216
|
+
name: "exa_search_code",
|
|
217
|
+
label: "Exa Code Search",
|
|
218
|
+
description: `Search for code examples and technical documentation using Exa.
|
|
219
|
+
|
|
220
|
+
Optimized for finding code snippets, API documentation, and technical content.
|
|
221
|
+
|
|
222
|
+
Parameters:
|
|
223
|
+
- query: Code or technical search query (required)
|
|
224
|
+
- code_context: Additional context about what you're looking for`,
|
|
225
|
+
|
|
226
|
+
parameters: Type.Object({
|
|
227
|
+
query: Type.String({ description: "Code or technical search query" }),
|
|
228
|
+
code_context: Type.Optional(
|
|
229
|
+
Type.String({
|
|
230
|
+
description: "Additional context about what you're looking for",
|
|
231
|
+
}),
|
|
232
|
+
),
|
|
233
|
+
}),
|
|
234
|
+
|
|
235
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
236
|
+
try {
|
|
237
|
+
const { findApiKey, callExaTool, formatSearchResults, isSearchResponse } = await import("./mcp-client.js");
|
|
238
|
+
|
|
239
|
+
const apiKey = await findApiKey();
|
|
240
|
+
if (!apiKey) {
|
|
241
|
+
return {
|
|
242
|
+
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
243
|
+
details: { error: "EXA_API_KEY not found", toolName: "exa_search_code" },
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const response = await callExaTool("get_code_context_exa", params, apiKey);
|
|
247
|
+
|
|
248
|
+
if (isSearchResponse(response)) {
|
|
249
|
+
const formatted = formatSearchResults(response);
|
|
250
|
+
return {
|
|
251
|
+
content: [{ type: "text" as const, text: formatted }],
|
|
252
|
+
details: { response, toolName: "exa_search_code" },
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
content: [{ type: "text" as const, text: JSON.stringify(response, null, 2) }],
|
|
258
|
+
details: { raw: response, toolName: "exa_search_code" },
|
|
259
|
+
};
|
|
260
|
+
} catch (error) {
|
|
261
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
262
|
+
return {
|
|
263
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
264
|
+
details: { error: message, toolName: "exa_search_code" },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/** exa_crawl - URL content extraction */
|
|
271
|
+
const exaCrawlTool: CustomTool<any, ExaRenderDetails> = {
|
|
272
|
+
name: "exa_crawl",
|
|
273
|
+
label: "Exa Crawl",
|
|
274
|
+
description: `Extract content from a specific URL using Exa.
|
|
275
|
+
|
|
276
|
+
Returns the page content with optional text and highlights.
|
|
277
|
+
|
|
278
|
+
Parameters:
|
|
279
|
+
- url: URL to crawl (required)
|
|
280
|
+
- text: Include full page text content (default: false)
|
|
281
|
+
- highlights: Include highlighted relevant snippets (default: false)`,
|
|
282
|
+
|
|
283
|
+
parameters: Type.Object({
|
|
284
|
+
url: Type.String({ description: "URL to crawl and extract content from" }),
|
|
285
|
+
text: Type.Optional(
|
|
286
|
+
Type.Boolean({
|
|
287
|
+
description: "Include full page text content (default: false)",
|
|
288
|
+
}),
|
|
289
|
+
),
|
|
290
|
+
highlights: Type.Optional(
|
|
291
|
+
Type.Boolean({
|
|
292
|
+
description: "Include highlighted relevant snippets (default: false)",
|
|
293
|
+
}),
|
|
294
|
+
),
|
|
295
|
+
}),
|
|
296
|
+
|
|
297
|
+
async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
|
|
298
|
+
try {
|
|
299
|
+
const { findApiKey, callExaTool, formatSearchResults, isSearchResponse } = await import("./mcp-client.js");
|
|
300
|
+
|
|
301
|
+
const apiKey = await findApiKey();
|
|
302
|
+
if (!apiKey) {
|
|
303
|
+
return {
|
|
304
|
+
content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
|
|
305
|
+
details: { error: "EXA_API_KEY not found", toolName: "exa_crawl" },
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const response = await callExaTool("crawling_exa", params, apiKey);
|
|
309
|
+
|
|
310
|
+
if (isSearchResponse(response)) {
|
|
311
|
+
const formatted = formatSearchResults(response);
|
|
312
|
+
return {
|
|
313
|
+
content: [{ type: "text" as const, text: formatted }],
|
|
314
|
+
details: { response, toolName: "exa_crawl" },
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
content: [{ type: "text" as const, text: JSON.stringify(response, null, 2) }],
|
|
320
|
+
details: { raw: response, toolName: "exa_crawl" },
|
|
321
|
+
};
|
|
322
|
+
} catch (error) {
|
|
323
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
324
|
+
return {
|
|
325
|
+
content: [{ type: "text" as const, text: `Error: ${message}` }],
|
|
326
|
+
details: { error: message, toolName: "exa_crawl" },
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
export const searchTools: CustomTool<any, ExaRenderDetails>[] = [
|
|
333
|
+
exaSearchTool,
|
|
334
|
+
exaSearchDeepTool,
|
|
335
|
+
exaSearchCodeTool,
|
|
336
|
+
exaCrawlTool,
|
|
337
|
+
];
|