@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.
Files changed (224) hide show
  1. package/CHANGELOG.md +1228 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/custom-tools.md +541 -0
  5. package/docs/extension-loading.md +1004 -0
  6. package/docs/hooks.md +867 -0
  7. package/docs/rpc.md +1040 -0
  8. package/docs/sdk.md +994 -0
  9. package/docs/session-tree-plan.md +441 -0
  10. package/docs/session.md +240 -0
  11. package/docs/skills.md +290 -0
  12. package/docs/theme.md +637 -0
  13. package/docs/tree.md +197 -0
  14. package/docs/tui.md +341 -0
  15. package/examples/README.md +21 -0
  16. package/examples/custom-tools/README.md +124 -0
  17. package/examples/custom-tools/hello/index.ts +20 -0
  18. package/examples/custom-tools/question/index.ts +84 -0
  19. package/examples/custom-tools/subagent/README.md +172 -0
  20. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  21. package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
  22. package/examples/custom-tools/subagent/agents/scout.md +50 -0
  23. package/examples/custom-tools/subagent/agents/worker.md +24 -0
  24. package/examples/custom-tools/subagent/agents.ts +156 -0
  25. package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
  26. package/examples/custom-tools/subagent/commands/implement.md +10 -0
  27. package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
  28. package/examples/custom-tools/subagent/index.ts +1002 -0
  29. package/examples/custom-tools/todo/index.ts +212 -0
  30. package/examples/hooks/README.md +56 -0
  31. package/examples/hooks/auto-commit-on-exit.ts +49 -0
  32. package/examples/hooks/confirm-destructive.ts +59 -0
  33. package/examples/hooks/custom-compaction.ts +116 -0
  34. package/examples/hooks/dirty-repo-guard.ts +52 -0
  35. package/examples/hooks/file-trigger.ts +41 -0
  36. package/examples/hooks/git-checkpoint.ts +53 -0
  37. package/examples/hooks/handoff.ts +150 -0
  38. package/examples/hooks/permission-gate.ts +34 -0
  39. package/examples/hooks/protected-paths.ts +30 -0
  40. package/examples/hooks/qna.ts +119 -0
  41. package/examples/hooks/snake.ts +343 -0
  42. package/examples/hooks/status-line.ts +40 -0
  43. package/examples/sdk/01-minimal.ts +22 -0
  44. package/examples/sdk/02-custom-model.ts +49 -0
  45. package/examples/sdk/03-custom-prompt.ts +44 -0
  46. package/examples/sdk/04-skills.ts +44 -0
  47. package/examples/sdk/05-tools.ts +90 -0
  48. package/examples/sdk/06-hooks.ts +61 -0
  49. package/examples/sdk/07-context-files.ts +36 -0
  50. package/examples/sdk/08-slash-commands.ts +42 -0
  51. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  52. package/examples/sdk/10-settings.ts +38 -0
  53. package/examples/sdk/11-sessions.ts +48 -0
  54. package/examples/sdk/12-full-control.ts +95 -0
  55. package/examples/sdk/README.md +154 -0
  56. package/package.json +81 -0
  57. package/src/cli/args.ts +246 -0
  58. package/src/cli/file-processor.ts +72 -0
  59. package/src/cli/list-models.ts +104 -0
  60. package/src/cli/plugin-cli.ts +650 -0
  61. package/src/cli/session-picker.ts +41 -0
  62. package/src/cli.ts +10 -0
  63. package/src/commands/init.md +20 -0
  64. package/src/config.ts +159 -0
  65. package/src/core/agent-session.ts +1900 -0
  66. package/src/core/auth-storage.ts +236 -0
  67. package/src/core/bash-executor.ts +196 -0
  68. package/src/core/compaction/branch-summarization.ts +343 -0
  69. package/src/core/compaction/compaction.ts +742 -0
  70. package/src/core/compaction/index.ts +7 -0
  71. package/src/core/compaction/utils.ts +154 -0
  72. package/src/core/custom-tools/index.ts +21 -0
  73. package/src/core/custom-tools/loader.ts +248 -0
  74. package/src/core/custom-tools/types.ts +169 -0
  75. package/src/core/custom-tools/wrapper.ts +28 -0
  76. package/src/core/exec.ts +129 -0
  77. package/src/core/export-html/index.ts +211 -0
  78. package/src/core/export-html/template.css +781 -0
  79. package/src/core/export-html/template.html +54 -0
  80. package/src/core/export-html/template.js +1185 -0
  81. package/src/core/export-html/vendor/highlight.min.js +1213 -0
  82. package/src/core/export-html/vendor/marked.min.js +6 -0
  83. package/src/core/hooks/index.ts +16 -0
  84. package/src/core/hooks/loader.ts +312 -0
  85. package/src/core/hooks/runner.ts +434 -0
  86. package/src/core/hooks/tool-wrapper.ts +99 -0
  87. package/src/core/hooks/types.ts +773 -0
  88. package/src/core/index.ts +52 -0
  89. package/src/core/mcp/client.ts +158 -0
  90. package/src/core/mcp/config.ts +154 -0
  91. package/src/core/mcp/index.ts +45 -0
  92. package/src/core/mcp/loader.ts +68 -0
  93. package/src/core/mcp/manager.ts +181 -0
  94. package/src/core/mcp/tool-bridge.ts +148 -0
  95. package/src/core/mcp/transports/http.ts +316 -0
  96. package/src/core/mcp/transports/index.ts +6 -0
  97. package/src/core/mcp/transports/stdio.ts +252 -0
  98. package/src/core/mcp/types.ts +220 -0
  99. package/src/core/messages.ts +189 -0
  100. package/src/core/model-registry.ts +317 -0
  101. package/src/core/model-resolver.ts +393 -0
  102. package/src/core/plugins/doctor.ts +59 -0
  103. package/src/core/plugins/index.ts +38 -0
  104. package/src/core/plugins/installer.ts +189 -0
  105. package/src/core/plugins/loader.ts +338 -0
  106. package/src/core/plugins/manager.ts +672 -0
  107. package/src/core/plugins/parser.ts +105 -0
  108. package/src/core/plugins/paths.ts +32 -0
  109. package/src/core/plugins/types.ts +190 -0
  110. package/src/core/sdk.ts +760 -0
  111. package/src/core/session-manager.ts +1128 -0
  112. package/src/core/settings-manager.ts +443 -0
  113. package/src/core/skills.ts +437 -0
  114. package/src/core/slash-commands.ts +248 -0
  115. package/src/core/system-prompt.ts +439 -0
  116. package/src/core/timings.ts +25 -0
  117. package/src/core/tools/ask.ts +211 -0
  118. package/src/core/tools/bash-interceptor.ts +120 -0
  119. package/src/core/tools/bash.ts +250 -0
  120. package/src/core/tools/context.ts +32 -0
  121. package/src/core/tools/edit-diff.ts +475 -0
  122. package/src/core/tools/edit.ts +208 -0
  123. package/src/core/tools/exa/company.ts +59 -0
  124. package/src/core/tools/exa/index.ts +64 -0
  125. package/src/core/tools/exa/linkedin.ts +59 -0
  126. package/src/core/tools/exa/logger.ts +56 -0
  127. package/src/core/tools/exa/mcp-client.ts +368 -0
  128. package/src/core/tools/exa/render.ts +196 -0
  129. package/src/core/tools/exa/researcher.ts +90 -0
  130. package/src/core/tools/exa/search.ts +337 -0
  131. package/src/core/tools/exa/types.ts +168 -0
  132. package/src/core/tools/exa/websets.ts +248 -0
  133. package/src/core/tools/find.ts +261 -0
  134. package/src/core/tools/grep.ts +555 -0
  135. package/src/core/tools/index.ts +202 -0
  136. package/src/core/tools/ls.ts +140 -0
  137. package/src/core/tools/lsp/client.ts +605 -0
  138. package/src/core/tools/lsp/config.ts +147 -0
  139. package/src/core/tools/lsp/edits.ts +101 -0
  140. package/src/core/tools/lsp/index.ts +804 -0
  141. package/src/core/tools/lsp/render.ts +447 -0
  142. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  143. package/src/core/tools/lsp/types.ts +463 -0
  144. package/src/core/tools/lsp/utils.ts +486 -0
  145. package/src/core/tools/notebook.ts +229 -0
  146. package/src/core/tools/path-utils.ts +61 -0
  147. package/src/core/tools/read.ts +240 -0
  148. package/src/core/tools/renderers.ts +540 -0
  149. package/src/core/tools/task/agents.ts +153 -0
  150. package/src/core/tools/task/artifacts.ts +114 -0
  151. package/src/core/tools/task/bundled-agents/browser.md +71 -0
  152. package/src/core/tools/task/bundled-agents/explore.md +82 -0
  153. package/src/core/tools/task/bundled-agents/plan.md +54 -0
  154. package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
  155. package/src/core/tools/task/bundled-agents/task.md +53 -0
  156. package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
  157. package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
  158. package/src/core/tools/task/bundled-commands/implement.md +11 -0
  159. package/src/core/tools/task/commands.ts +213 -0
  160. package/src/core/tools/task/discovery.ts +208 -0
  161. package/src/core/tools/task/executor.ts +367 -0
  162. package/src/core/tools/task/index.ts +388 -0
  163. package/src/core/tools/task/model-resolver.ts +115 -0
  164. package/src/core/tools/task/parallel.ts +38 -0
  165. package/src/core/tools/task/render.ts +232 -0
  166. package/src/core/tools/task/types.ts +99 -0
  167. package/src/core/tools/truncate.ts +265 -0
  168. package/src/core/tools/web-fetch.ts +2370 -0
  169. package/src/core/tools/web-search/auth.ts +193 -0
  170. package/src/core/tools/web-search/index.ts +537 -0
  171. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  172. package/src/core/tools/web-search/providers/exa.ts +302 -0
  173. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  174. package/src/core/tools/web-search/render.ts +182 -0
  175. package/src/core/tools/web-search/types.ts +180 -0
  176. package/src/core/tools/write.ts +99 -0
  177. package/src/index.ts +176 -0
  178. package/src/main.ts +464 -0
  179. package/src/migrations.ts +135 -0
  180. package/src/modes/index.ts +43 -0
  181. package/src/modes/interactive/components/armin.ts +382 -0
  182. package/src/modes/interactive/components/assistant-message.ts +86 -0
  183. package/src/modes/interactive/components/bash-execution.ts +196 -0
  184. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  185. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  186. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  187. package/src/modes/interactive/components/custom-editor.ts +122 -0
  188. package/src/modes/interactive/components/diff.ts +147 -0
  189. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  190. package/src/modes/interactive/components/footer.ts +381 -0
  191. package/src/modes/interactive/components/hook-editor.ts +117 -0
  192. package/src/modes/interactive/components/hook-input.ts +64 -0
  193. package/src/modes/interactive/components/hook-message.ts +96 -0
  194. package/src/modes/interactive/components/hook-selector.ts +91 -0
  195. package/src/modes/interactive/components/model-selector.ts +247 -0
  196. package/src/modes/interactive/components/oauth-selector.ts +120 -0
  197. package/src/modes/interactive/components/plugin-settings.ts +479 -0
  198. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  199. package/src/modes/interactive/components/session-selector.ts +204 -0
  200. package/src/modes/interactive/components/settings-selector.ts +453 -0
  201. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  202. package/src/modes/interactive/components/theme-selector.ts +62 -0
  203. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  204. package/src/modes/interactive/components/tool-execution.ts +675 -0
  205. package/src/modes/interactive/components/tree-selector.ts +866 -0
  206. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  207. package/src/modes/interactive/components/user-message.ts +18 -0
  208. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  209. package/src/modes/interactive/components/welcome.ts +183 -0
  210. package/src/modes/interactive/interactive-mode.ts +2516 -0
  211. package/src/modes/interactive/theme/dark.json +101 -0
  212. package/src/modes/interactive/theme/light.json +98 -0
  213. package/src/modes/interactive/theme/theme-schema.json +308 -0
  214. package/src/modes/interactive/theme/theme.ts +998 -0
  215. package/src/modes/print-mode.ts +128 -0
  216. package/src/modes/rpc/rpc-client.ts +527 -0
  217. package/src/modes/rpc/rpc-mode.ts +483 -0
  218. package/src/modes/rpc/rpc-types.ts +203 -0
  219. package/src/utils/changelog.ts +99 -0
  220. package/src/utils/clipboard.ts +265 -0
  221. package/src/utils/fuzzy.ts +108 -0
  222. package/src/utils/mime.ts +30 -0
  223. package/src/utils/shell.ts +276 -0
  224. 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
+ ];