ToolSelector.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. import { c as _c } from "react/compiler-runtime";
  2. import figures from 'figures';
  3. import React, { useCallback, useMemo, useState } from 'react';
  4. import { mcpInfoFromString } from 'src/services/mcp/mcpStringUtils.js';
  5. import { isMcpTool } from 'src/services/mcp/utils.js';
  6. import type { Tool, Tools } from 'src/Tool.js';
  7. import { filterToolsForAgent } from 'src/tools/AgentTool/agentToolUtils.js';
  8. import { AGENT_TOOL_NAME } from 'src/tools/AgentTool/constants.js';
  9. import { BashTool } from 'src/tools/BashTool/BashTool.js';
  10. import { ExitPlanModeV2Tool } from 'src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.js';
  11. import { FileEditTool } from 'src/tools/FileEditTool/FileEditTool.js';
  12. import { FileReadTool } from 'src/tools/FileReadTool/FileReadTool.js';
  13. import { FileWriteTool } from 'src/tools/FileWriteTool/FileWriteTool.js';
  14. import { GlobTool } from 'src/tools/GlobTool/GlobTool.js';
  15. import { GrepTool } from 'src/tools/GrepTool/GrepTool.js';
  16. import { ListMcpResourcesTool } from 'src/tools/ListMcpResourcesTool/ListMcpResourcesTool.js';
  17. import { NotebookEditTool } from 'src/tools/NotebookEditTool/NotebookEditTool.js';
  18. import { ReadMcpResourceTool } from 'src/tools/ReadMcpResourceTool/ReadMcpResourceTool.js';
  19. import { TaskOutputTool } from 'src/tools/TaskOutputTool/TaskOutputTool.js';
  20. import { TaskStopTool } from 'src/tools/TaskStopTool/TaskStopTool.js';
  21. import { TodoWriteTool } from 'src/tools/TodoWriteTool/TodoWriteTool.js';
  22. import { TungstenTool } from 'src/tools/TungstenTool/TungstenTool.js';
  23. import { WebFetchTool } from 'src/tools/WebFetchTool/WebFetchTool.js';
  24. import { WebSearchTool } from 'src/tools/WebSearchTool/WebSearchTool.js';
  25. import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
  26. import { Box, Text } from '../../ink.js';
  27. import { useKeybinding } from '../../keybindings/useKeybinding.js';
  28. import { count } from '../../utils/array.js';
  29. import { plural } from '../../utils/stringUtils.js';
  30. import { Divider } from '../design-system/Divider.js';
  31. type Props = {
  32. tools: Tools;
  33. initialTools: string[] | undefined;
  34. onComplete: (selectedTools: string[] | undefined) => void;
  35. onCancel?: () => void;
  36. };
  37. type ToolBucket = {
  38. name: string;
  39. toolNames: Set<string>;
  40. isMcp?: boolean;
  41. };
  42. type ToolBuckets = {
  43. READ_ONLY: ToolBucket;
  44. EDIT: ToolBucket;
  45. EXECUTION: ToolBucket;
  46. MCP: ToolBucket;
  47. OTHER: ToolBucket;
  48. };
  49. function getToolBuckets(): ToolBuckets {
  50. return {
  51. READ_ONLY: {
  52. name: 'Read-only tools',
  53. toolNames: new Set([GlobTool.name, GrepTool.name, ExitPlanModeV2Tool.name, FileReadTool.name, WebFetchTool.name, TodoWriteTool.name, WebSearchTool.name, TaskStopTool.name, TaskOutputTool.name, ListMcpResourcesTool.name, ReadMcpResourceTool.name])
  54. },
  55. EDIT: {
  56. name: 'Edit tools',
  57. toolNames: new Set([FileEditTool.name, FileWriteTool.name, NotebookEditTool.name])
  58. },
  59. EXECUTION: {
  60. name: 'Execution tools',
  61. toolNames: new Set([BashTool.name, ("external" as string) === 'ant' ? TungstenTool.name : undefined].filter(n => n !== undefined))
  62. },
  63. MCP: {
  64. name: 'MCP tools',
  65. toolNames: new Set(),
  66. // Dynamic - no static list
  67. isMcp: true
  68. },
  69. OTHER: {
  70. name: 'Other tools',
  71. toolNames: new Set() // Dynamic - catch-all for uncategorized tools
  72. }
  73. };
  74. }
  75. // Helper to get MCP server buckets dynamically
  76. function getMcpServerBuckets(tools: Tools): Array<{
  77. serverName: string;
  78. tools: Tools;
  79. }> {
  80. const serverMap = new Map<string, Tool[]>();
  81. tools.forEach(tool => {
  82. if (isMcpTool(tool)) {
  83. const mcpInfo = mcpInfoFromString(tool.name);
  84. if (mcpInfo?.serverName) {
  85. const existing = serverMap.get(mcpInfo.serverName) || [];
  86. existing.push(tool);
  87. serverMap.set(mcpInfo.serverName, existing);
  88. }
  89. }
  90. });
  91. return Array.from(serverMap.entries()).map(([serverName, tools]) => ({
  92. serverName,
  93. tools
  94. })).sort((a, b) => a.serverName.localeCompare(b.serverName));
  95. }
  96. export function ToolSelector(t0) {
  97. const $ = _c(69);
  98. const {
  99. tools,
  100. initialTools,
  101. onComplete,
  102. onCancel
  103. } = t0;
  104. let t1;
  105. if ($[0] !== tools) {
  106. t1 = filterToolsForAgent({
  107. tools,
  108. isBuiltIn: false,
  109. isAsync: false
  110. });
  111. $[0] = tools;
  112. $[1] = t1;
  113. } else {
  114. t1 = $[1];
  115. }
  116. const customAgentTools = t1;
  117. let t2;
  118. if ($[2] !== customAgentTools || $[3] !== initialTools) {
  119. t2 = !initialTools || initialTools.includes("*") ? customAgentTools.map(_temp) : initialTools;
  120. $[2] = customAgentTools;
  121. $[3] = initialTools;
  122. $[4] = t2;
  123. } else {
  124. t2 = $[4];
  125. }
  126. const expandedInitialTools = t2;
  127. const [selectedTools, setSelectedTools] = useState(expandedInitialTools);
  128. const [focusIndex, setFocusIndex] = useState(0);
  129. const [showIndividualTools, setShowIndividualTools] = useState(false);
  130. let t3;
  131. if ($[5] !== customAgentTools) {
  132. t3 = new Set(customAgentTools.map(_temp2));
  133. $[5] = customAgentTools;
  134. $[6] = t3;
  135. } else {
  136. t3 = $[6];
  137. }
  138. const toolNames = t3;
  139. let t4;
  140. if ($[7] !== selectedTools || $[8] !== toolNames) {
  141. let t5;
  142. if ($[10] !== toolNames) {
  143. t5 = name => toolNames.has(name);
  144. $[10] = toolNames;
  145. $[11] = t5;
  146. } else {
  147. t5 = $[11];
  148. }
  149. t4 = selectedTools.filter(t5);
  150. $[7] = selectedTools;
  151. $[8] = toolNames;
  152. $[9] = t4;
  153. } else {
  154. t4 = $[9];
  155. }
  156. const validSelectedTools = t4;
  157. let t5;
  158. if ($[12] !== validSelectedTools) {
  159. t5 = new Set(validSelectedTools);
  160. $[12] = validSelectedTools;
  161. $[13] = t5;
  162. } else {
  163. t5 = $[13];
  164. }
  165. const selectedSet = t5;
  166. const isAllSelected = validSelectedTools.length === customAgentTools.length && customAgentTools.length > 0;
  167. let t6;
  168. if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
  169. t6 = toolName => {
  170. if (!toolName) {
  171. return;
  172. }
  173. setSelectedTools(current => current.includes(toolName) ? current.filter(t_1 => t_1 !== toolName) : [...current, toolName]);
  174. };
  175. $[14] = t6;
  176. } else {
  177. t6 = $[14];
  178. }
  179. const handleToggleTool = t6;
  180. let t7;
  181. if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
  182. t7 = (toolNames_0, select) => {
  183. setSelectedTools(current_0 => {
  184. if (select) {
  185. const toolsToAdd = toolNames_0.filter(t_2 => !current_0.includes(t_2));
  186. return [...current_0, ...toolsToAdd];
  187. } else {
  188. return current_0.filter(t_3 => !toolNames_0.includes(t_3));
  189. }
  190. });
  191. };
  192. $[15] = t7;
  193. } else {
  194. t7 = $[15];
  195. }
  196. const handleToggleTools = t7;
  197. let t8;
  198. if ($[16] !== customAgentTools || $[17] !== onComplete || $[18] !== validSelectedTools) {
  199. t8 = () => {
  200. const allToolNames = customAgentTools.map(_temp3);
  201. const areAllToolsSelected = validSelectedTools.length === allToolNames.length && allToolNames.every(name_0 => validSelectedTools.includes(name_0));
  202. const finalTools = areAllToolsSelected ? undefined : validSelectedTools;
  203. onComplete(finalTools);
  204. };
  205. $[16] = customAgentTools;
  206. $[17] = onComplete;
  207. $[18] = validSelectedTools;
  208. $[19] = t8;
  209. } else {
  210. t8 = $[19];
  211. }
  212. const handleConfirm = t8;
  213. let buckets;
  214. if ($[20] !== customAgentTools) {
  215. const toolBuckets = getToolBuckets();
  216. buckets = {
  217. readOnly: [] as Tool[],
  218. edit: [] as Tool[],
  219. execution: [] as Tool[],
  220. mcp: [] as Tool[],
  221. other: [] as Tool[]
  222. };
  223. customAgentTools.forEach(tool => {
  224. if (isMcpTool(tool)) {
  225. buckets.mcp.push(tool);
  226. } else {
  227. if (toolBuckets.READ_ONLY.toolNames.has(tool.name)) {
  228. buckets.readOnly.push(tool);
  229. } else {
  230. if (toolBuckets.EDIT.toolNames.has(tool.name)) {
  231. buckets.edit.push(tool);
  232. } else {
  233. if (toolBuckets.EXECUTION.toolNames.has(tool.name)) {
  234. buckets.execution.push(tool);
  235. } else {
  236. if (tool.name !== AGENT_TOOL_NAME) {
  237. buckets.other.push(tool);
  238. }
  239. }
  240. }
  241. }
  242. }
  243. });
  244. $[20] = customAgentTools;
  245. $[21] = buckets;
  246. } else {
  247. buckets = $[21];
  248. }
  249. const toolsByBucket = buckets;
  250. let t9;
  251. if ($[22] !== selectedSet) {
  252. t9 = bucketTools => {
  253. const selected = count(bucketTools, (t_5: Tool) => selectedSet.has(t_5.name));
  254. const needsSelection = selected < bucketTools.length;
  255. return () => {
  256. const toolNames_1 = bucketTools.map(_temp4);
  257. handleToggleTools(toolNames_1, needsSelection);
  258. };
  259. };
  260. $[22] = selectedSet;
  261. $[23] = t9;
  262. } else {
  263. t9 = $[23];
  264. }
  265. const createBucketToggleAction = t9;
  266. let navigableItems;
  267. if ($[24] !== createBucketToggleAction || $[25] !== customAgentTools || $[26] !== focusIndex || $[27] !== handleConfirm || $[28] !== isAllSelected || $[29] !== selectedSet || $[30] !== showIndividualTools || $[31] !== toolsByBucket.edit || $[32] !== toolsByBucket.execution || $[33] !== toolsByBucket.mcp || $[34] !== toolsByBucket.other || $[35] !== toolsByBucket.readOnly) {
  268. navigableItems = [];
  269. navigableItems.push({
  270. id: "continue",
  271. label: "Continue",
  272. action: handleConfirm,
  273. isContinue: true
  274. });
  275. let t10;
  276. if ($[37] !== customAgentTools || $[38] !== isAllSelected) {
  277. t10 = () => {
  278. const allToolNames_0 = customAgentTools.map(_temp5);
  279. handleToggleTools(allToolNames_0, !isAllSelected);
  280. };
  281. $[37] = customAgentTools;
  282. $[38] = isAllSelected;
  283. $[39] = t10;
  284. } else {
  285. t10 = $[39];
  286. }
  287. navigableItems.push({
  288. id: "bucket-all",
  289. label: `${isAllSelected ? figures.checkboxOn : figures.checkboxOff} All tools`,
  290. action: t10
  291. });
  292. const toolBuckets_0 = getToolBuckets();
  293. const bucketConfigs = [{
  294. id: "bucket-readonly",
  295. name: toolBuckets_0.READ_ONLY.name,
  296. tools: toolsByBucket.readOnly
  297. }, {
  298. id: "bucket-edit",
  299. name: toolBuckets_0.EDIT.name,
  300. tools: toolsByBucket.edit
  301. }, {
  302. id: "bucket-execution",
  303. name: toolBuckets_0.EXECUTION.name,
  304. tools: toolsByBucket.execution
  305. }, {
  306. id: "bucket-mcp",
  307. name: toolBuckets_0.MCP.name,
  308. tools: toolsByBucket.mcp
  309. }, {
  310. id: "bucket-other",
  311. name: toolBuckets_0.OTHER.name,
  312. tools: toolsByBucket.other
  313. }];
  314. bucketConfigs.forEach(t11 => {
  315. const {
  316. id,
  317. name: name_1,
  318. tools: bucketTools_0
  319. } = t11;
  320. if (bucketTools_0.length === 0) {
  321. return;
  322. }
  323. const selected_0 = count(bucketTools_0, (t_8: Tool) => selectedSet.has(t_8.name));
  324. const isFullySelected = selected_0 === bucketTools_0.length;
  325. navigableItems.push({
  326. id,
  327. label: `${isFullySelected ? figures.checkboxOn : figures.checkboxOff} ${name_1}`,
  328. action: createBucketToggleAction(bucketTools_0)
  329. });
  330. });
  331. const toggleButtonIndex = navigableItems.length;
  332. let t12;
  333. if ($[40] !== focusIndex || $[41] !== showIndividualTools || $[42] !== toggleButtonIndex) {
  334. t12 = () => {
  335. setShowIndividualTools(!showIndividualTools);
  336. if (showIndividualTools && focusIndex > toggleButtonIndex) {
  337. setFocusIndex(toggleButtonIndex);
  338. }
  339. };
  340. $[40] = focusIndex;
  341. $[41] = showIndividualTools;
  342. $[42] = toggleButtonIndex;
  343. $[43] = t12;
  344. } else {
  345. t12 = $[43];
  346. }
  347. navigableItems.push({
  348. id: "toggle-individual",
  349. label: showIndividualTools ? "Hide advanced options" : "Show advanced options",
  350. action: t12,
  351. isToggle: true
  352. });
  353. const mcpServerBuckets = getMcpServerBuckets(customAgentTools);
  354. if (showIndividualTools) {
  355. if (mcpServerBuckets.length > 0) {
  356. navigableItems.push({
  357. id: "mcp-servers-header",
  358. label: "MCP Servers:",
  359. action: _temp6,
  360. isHeader: true
  361. });
  362. mcpServerBuckets.forEach(t13 => {
  363. const {
  364. serverName,
  365. tools: serverTools
  366. } = t13;
  367. const selected_1 = count(serverTools, t_9 => selectedSet.has(t_9.name));
  368. const isFullySelected_0 = selected_1 === serverTools.length;
  369. navigableItems.push({
  370. id: `mcp-server-${serverName}`,
  371. label: `${isFullySelected_0 ? figures.checkboxOn : figures.checkboxOff} ${serverName} (${serverTools.length} ${plural(serverTools.length, "tool")})`,
  372. action: () => {
  373. const toolNames_2 = serverTools.map(_temp7);
  374. handleToggleTools(toolNames_2, !isFullySelected_0);
  375. }
  376. });
  377. });
  378. navigableItems.push({
  379. id: "tools-header",
  380. label: "Individual Tools:",
  381. action: _temp8,
  382. isHeader: true
  383. });
  384. }
  385. customAgentTools.forEach(tool_0 => {
  386. let displayName = tool_0.name;
  387. if (tool_0.name.startsWith("mcp__")) {
  388. const mcpInfo = mcpInfoFromString(tool_0.name);
  389. displayName = mcpInfo ? `${mcpInfo.toolName} (${mcpInfo.serverName})` : tool_0.name;
  390. }
  391. navigableItems.push({
  392. id: `tool-${tool_0.name}`,
  393. label: `${selectedSet.has(tool_0.name) ? figures.checkboxOn : figures.checkboxOff} ${displayName}`,
  394. action: () => handleToggleTool(tool_0.name)
  395. });
  396. });
  397. }
  398. $[24] = createBucketToggleAction;
  399. $[25] = customAgentTools;
  400. $[26] = focusIndex;
  401. $[27] = handleConfirm;
  402. $[28] = isAllSelected;
  403. $[29] = selectedSet;
  404. $[30] = showIndividualTools;
  405. $[31] = toolsByBucket.edit;
  406. $[32] = toolsByBucket.execution;
  407. $[33] = toolsByBucket.mcp;
  408. $[34] = toolsByBucket.other;
  409. $[35] = toolsByBucket.readOnly;
  410. $[36] = navigableItems;
  411. } else {
  412. navigableItems = $[36];
  413. }
  414. let t10;
  415. if ($[44] !== initialTools || $[45] !== onCancel || $[46] !== onComplete) {
  416. t10 = () => {
  417. if (onCancel) {
  418. onCancel();
  419. } else {
  420. onComplete(initialTools);
  421. }
  422. };
  423. $[44] = initialTools;
  424. $[45] = onCancel;
  425. $[46] = onComplete;
  426. $[47] = t10;
  427. } else {
  428. t10 = $[47];
  429. }
  430. const handleCancel = t10;
  431. let t11;
  432. if ($[48] === Symbol.for("react.memo_cache_sentinel")) {
  433. t11 = {
  434. context: "Confirmation"
  435. };
  436. $[48] = t11;
  437. } else {
  438. t11 = $[48];
  439. }
  440. useKeybinding("confirm:no", handleCancel, t11);
  441. let t12;
  442. if ($[49] !== focusIndex || $[50] !== navigableItems) {
  443. t12 = e => {
  444. if (e.key === "return") {
  445. e.preventDefault();
  446. const item = navigableItems[focusIndex];
  447. if (item && !item.isHeader) {
  448. item.action();
  449. }
  450. } else {
  451. if (e.key === "up") {
  452. e.preventDefault();
  453. let newIndex = focusIndex - 1;
  454. while (newIndex > 0 && navigableItems[newIndex]?.isHeader) {
  455. newIndex--;
  456. }
  457. setFocusIndex(Math.max(0, newIndex));
  458. } else {
  459. if (e.key === "down") {
  460. e.preventDefault();
  461. let newIndex_0 = focusIndex + 1;
  462. while (newIndex_0 < navigableItems.length - 1 && navigableItems[newIndex_0]?.isHeader) {
  463. newIndex_0++;
  464. }
  465. setFocusIndex(Math.min(navigableItems.length - 1, newIndex_0));
  466. }
  467. }
  468. }
  469. };
  470. $[49] = focusIndex;
  471. $[50] = navigableItems;
  472. $[51] = t12;
  473. } else {
  474. t12 = $[51];
  475. }
  476. const handleKeyDown = t12;
  477. const t13 = focusIndex === 0 ? "suggestion" : undefined;
  478. const t14 = focusIndex === 0;
  479. const t15 = focusIndex === 0 ? `${figures.pointer} ` : " ";
  480. let t16;
  481. if ($[52] !== t13 || $[53] !== t14 || $[54] !== t15) {
  482. t16 = <Text color={t13} bold={t14}>{t15}[ Continue ]</Text>;
  483. $[52] = t13;
  484. $[53] = t14;
  485. $[54] = t15;
  486. $[55] = t16;
  487. } else {
  488. t16 = $[55];
  489. }
  490. let t17;
  491. if ($[56] === Symbol.for("react.memo_cache_sentinel")) {
  492. t17 = <Divider width={40} />;
  493. $[56] = t17;
  494. } else {
  495. t17 = $[56];
  496. }
  497. let t18;
  498. if ($[57] !== navigableItems) {
  499. t18 = navigableItems.slice(1);
  500. $[57] = navigableItems;
  501. $[58] = t18;
  502. } else {
  503. t18 = $[58];
  504. }
  505. let t19;
  506. if ($[59] !== focusIndex || $[60] !== t18) {
  507. t19 = t18.map((item_0, index) => {
  508. const isCurrentlyFocused = index + 1 === focusIndex;
  509. const isToggleButton = item_0.isToggle;
  510. const isHeader = item_0.isHeader;
  511. return <React.Fragment key={item_0.id}>{isToggleButton && <Divider width={40} />}{isHeader && index > 0 && <Box marginTop={1} />}<Text color={isHeader ? undefined : isCurrentlyFocused ? "suggestion" : undefined} dimColor={isHeader} bold={isToggleButton && isCurrentlyFocused}>{isHeader ? "" : isCurrentlyFocused ? `${figures.pointer} ` : " "}{isToggleButton ? `[ ${item_0.label} ]` : item_0.label}</Text></React.Fragment>;
  512. });
  513. $[59] = focusIndex;
  514. $[60] = t18;
  515. $[61] = t19;
  516. } else {
  517. t19 = $[61];
  518. }
  519. const t20 = isAllSelected ? "All tools selected" : `${selectedSet.size} of ${customAgentTools.length} tools selected`;
  520. let t21;
  521. if ($[62] !== t20) {
  522. t21 = <Box marginTop={1} flexDirection="column"><Text dimColor={true}>{t20}</Text></Box>;
  523. $[62] = t20;
  524. $[63] = t21;
  525. } else {
  526. t21 = $[63];
  527. }
  528. let t22;
  529. if ($[64] !== handleKeyDown || $[65] !== t16 || $[66] !== t19 || $[67] !== t21) {
  530. t22 = <Box flexDirection="column" marginTop={1} tabIndex={0} autoFocus={true} onKeyDown={handleKeyDown}>{t16}{t17}{t19}{t21}</Box>;
  531. $[64] = handleKeyDown;
  532. $[65] = t16;
  533. $[66] = t19;
  534. $[67] = t21;
  535. $[68] = t22;
  536. } else {
  537. t22 = $[68];
  538. }
  539. return t22;
  540. }
  541. function _temp8() {}
  542. function _temp7(t_10) {
  543. return t_10.name;
  544. }
  545. function _temp6() {}
  546. function _temp5(t_7) {
  547. return t_7.name;
  548. }
  549. function _temp4(t_6) {
  550. return t_6.name;
  551. }
  552. function _temp3(t_4) {
  553. return t_4.name;
  554. }
  555. function _temp2(t_0) {
  556. return t_0.name;
  557. }
  558. function _temp(t) {
  559. return t.name;
  560. }