dexto 1.5.7 → 1.5.8

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 (86) hide show
  1. package/dist/agents/coding-agent/coding-agent.yml +2 -0
  2. package/dist/analytics/events.d.ts +1 -1
  3. package/dist/analytics/events.d.ts.map +1 -1
  4. package/dist/cli/auth/constants.d.ts +4 -0
  5. package/dist/cli/auth/constants.d.ts.map +1 -1
  6. package/dist/cli/auth/constants.js +4 -0
  7. package/dist/cli/commands/auth/logout.js +2 -2
  8. package/dist/cli/commands/billing/status.d.ts +3 -1
  9. package/dist/cli/commands/billing/status.d.ts.map +1 -1
  10. package/dist/cli/commands/billing/status.js +23 -1
  11. package/dist/cli/commands/interactive-commands/prompt-commands.d.ts.map +1 -1
  12. package/dist/cli/commands/interactive-commands/prompt-commands.js +18 -2
  13. package/dist/cli/commands/list-agents.d.ts.map +1 -1
  14. package/dist/cli/commands/list-agents.js +3 -2
  15. package/dist/cli/commands/setup.d.ts +5 -5
  16. package/dist/cli/commands/setup.d.ts.map +1 -1
  17. package/dist/cli/commands/setup.js +766 -207
  18. package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
  19. package/dist/cli/ink-cli/InkCLIRefactored.js +11 -1
  20. package/dist/cli/ink-cli/components/BackgroundTasksPanel.d.ts +18 -0
  21. package/dist/cli/ink-cli/components/BackgroundTasksPanel.d.ts.map +1 -0
  22. package/dist/cli/ink-cli/components/BackgroundTasksPanel.js +48 -0
  23. package/dist/cli/ink-cli/components/ErrorBoundary.js +1 -1
  24. package/dist/cli/ink-cli/components/Footer.d.ts.map +1 -1
  25. package/dist/cli/ink-cli/components/Footer.js +4 -4
  26. package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
  27. package/dist/cli/ink-cli/components/ResourceAutocomplete.js +150 -41
  28. package/dist/cli/ink-cli/components/StatusBar.d.ts +3 -1
  29. package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
  30. package/dist/cli/ink-cli/components/StatusBar.js +17 -1
  31. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
  32. package/dist/cli/ink-cli/components/chat/MessageItem.js +9 -5
  33. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  34. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +2 -1
  35. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  36. package/dist/cli/ink-cli/components/modes/StaticCLI.js +2 -1
  37. package/dist/cli/ink-cli/components/overlays/ContextStatsOverlay.js +1 -1
  38. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts.map +1 -1
  39. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.js +8 -4
  40. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +1 -1
  41. package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.js +1 -1
  42. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts +1 -0
  43. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
  44. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +144 -41
  45. package/dist/cli/ink-cli/components/overlays/ToolBrowser.d.ts +1 -0
  46. package/dist/cli/ink-cli/components/overlays/ToolBrowser.d.ts.map +1 -1
  47. package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +281 -39
  48. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts +9 -1
  49. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts.map +1 -1
  50. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.js +35 -9
  51. package/dist/cli/ink-cli/containers/InputContainer.js +2 -2
  52. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  53. package/dist/cli/ink-cli/containers/OverlayContainer.js +134 -23
  54. package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts.map +1 -1
  55. package/dist/cli/ink-cli/hooks/useAgentEvents.js +61 -0
  56. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  57. package/dist/cli/ink-cli/hooks/useCLIState.js +3 -0
  58. package/dist/cli/ink-cli/hooks/useInputOrchestrator.d.ts.map +1 -1
  59. package/dist/cli/ink-cli/hooks/useInputOrchestrator.js +8 -0
  60. package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
  61. package/dist/cli/ink-cli/services/processStream.js +50 -2
  62. package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
  63. package/dist/cli/ink-cli/state/initialState.js +3 -0
  64. package/dist/cli/ink-cli/state/types.d.ts +9 -0
  65. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  66. package/dist/cli/ink-cli/utils/llm-provider-display.d.ts +3 -0
  67. package/dist/cli/ink-cli/utils/llm-provider-display.d.ts.map +1 -0
  68. package/dist/cli/ink-cli/utils/llm-provider-display.js +22 -0
  69. package/dist/cli/ink-cli/utils/messageFormatting.d.ts +4 -6
  70. package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
  71. package/dist/cli/ink-cli/utils/messageFormatting.js +77 -9
  72. package/dist/cli/utils/dexto-auth-check.d.ts +7 -7
  73. package/dist/cli/utils/dexto-auth-check.d.ts.map +1 -1
  74. package/dist/cli/utils/dexto-auth-check.js +16 -16
  75. package/dist/cli/utils/options.js +1 -1
  76. package/dist/cli/utils/provider-setup.d.ts +2 -2
  77. package/dist/cli/utils/provider-setup.d.ts.map +1 -1
  78. package/dist/cli/utils/provider-setup.js +10 -2
  79. package/dist/config/cli-overrides.js +1 -1
  80. package/dist/config/effective-llm.d.ts +4 -4
  81. package/dist/config/effective-llm.d.ts.map +1 -1
  82. package/dist/config/effective-llm.js +4 -4
  83. package/dist/index.js +12 -3
  84. package/dist/webui/assets/{index-Dl3mj53P.js → index-Cz2z7NQ8.js} +74 -74
  85. package/dist/webui/index.html +1 -1
  86. package/package.json +7 -7
@@ -12,14 +12,15 @@ import { Box, Text } from 'ink';
12
12
  import { useTerminalSize } from '../../hooks/useTerminalSize.js';
13
13
  import { writeToClipboard } from '../../utils/clipboardUtils.js';
14
14
  const MAX_VISIBLE_ITEMS = 12;
15
- const MAX_DETAIL_LINES = 15;
15
+ const LIST_ACTIONS = ['view', 'config', 'back'];
16
16
  /**
17
17
  * Tool browser with search and detail views
18
18
  */
19
- const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent }, ref) {
19
+ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent, sessionId }, ref) {
20
20
  const { columns, rows } = useTerminalSize();
21
21
  const [tools, setTools] = useState([]);
22
22
  const [isLoading, setIsLoading] = useState(false);
23
+ const [loadError, setLoadError] = useState(null);
23
24
  const [selectedIndex, setSelectedIndex] = useState(0);
24
25
  const [searchQuery, setSearchQuery] = useState('');
25
26
  const [scrollOffset, setScrollOffset] = useState(0);
@@ -27,16 +28,33 @@ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent
27
28
  const [selectedTool, setSelectedTool] = useState(null);
28
29
  const [detailScrollOffset, setDetailScrollOffset] = useState(0);
29
30
  const [copyFeedback, setCopyFeedback] = useState(null);
31
+ const [listActionsIndex, setListActionsIndex] = useState(0);
32
+ const [configIndex, setConfigIndex] = useState(0);
33
+ const [scopeToolName, setScopeToolName] = useState(null);
34
+ const [scopeNextEnabled, setScopeNextEnabled] = useState(true);
35
+ const [scopeTarget, setScopeTarget] = useState('session');
30
36
  const selectedIndexRef = useRef(selectedIndex);
31
37
  const viewModeRef = useRef(viewMode);
32
38
  const detailScrollOffsetRef = useRef(detailScrollOffset);
33
39
  const detailMaxScrollOffsetRef = useRef(0);
34
40
  const selectedToolRef = useRef(null);
41
+ const toolsRef = useRef([]);
42
+ const listActionsIndexRef = useRef(listActionsIndex);
43
+ const configIndexRef = useRef(configIndex);
44
+ const scopeTargetRef = useRef(scopeTarget);
45
+ const scopeNextEnabledRef = useRef(scopeNextEnabled);
46
+ const scopeToolNameRef = useRef(scopeToolName);
35
47
  // Keep refs in sync
36
48
  selectedIndexRef.current = selectedIndex;
37
49
  viewModeRef.current = viewMode;
38
50
  detailScrollOffsetRef.current = detailScrollOffset;
39
51
  selectedToolRef.current = selectedTool;
52
+ toolsRef.current = tools;
53
+ listActionsIndexRef.current = listActionsIndex;
54
+ configIndexRef.current = configIndex;
55
+ scopeTargetRef.current = scopeTarget;
56
+ scopeNextEnabledRef.current = scopeNextEnabled;
57
+ scopeToolNameRef.current = scopeToolName;
40
58
  // Fetch tools from agent
41
59
  useEffect(() => {
42
60
  if (!isVisible)
@@ -48,14 +66,21 @@ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent
48
66
  setScrollOffset(0);
49
67
  setViewMode('list');
50
68
  setSelectedTool(null);
69
+ setListActionsIndex(0);
70
+ setConfigIndex(0);
71
+ setLoadError(null);
51
72
  const fetchTools = async () => {
52
73
  try {
53
- const [allTools, mcpTools] = await Promise.all([
74
+ const [allTools, mcpTools, enabledTools, autoApprovedTools] = await Promise.all([
54
75
  agent.getAllTools(),
55
76
  agent.getAllMcpTools(),
77
+ agent.getEnabledTools(sessionId || undefined),
78
+ sessionId ? agent.getSessionAutoApproveTools(sessionId) : Promise.resolve([]),
56
79
  ]);
57
80
  const toolList = [];
58
81
  const mcpToolNames = new Set(Object.keys(mcpTools));
82
+ const enabledToolNames = new Set(Object.keys(enabledTools));
83
+ const autoApprovedToolNames = new Set(autoApprovedTools ?? []);
59
84
  for (const [toolName, toolInfo] of Object.entries(allTools)) {
60
85
  const isMcpTool = mcpToolNames.has(toolName) || toolName.startsWith('mcp--');
61
86
  // Extract server name from MCP tool name (format: mcp--serverName--toolName)
@@ -72,6 +97,8 @@ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent
72
97
  source: isMcpTool ? 'mcp' : 'internal',
73
98
  serverName,
74
99
  inputSchema: toolInfo.parameters,
100
+ isEnabled: enabledToolNames.has(toolName),
101
+ isAutoApproved: autoApprovedToolNames.has(toolName),
75
102
  });
76
103
  }
77
104
  // Sort: internal tools first, then MCP tools
@@ -84,12 +111,15 @@ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent
84
111
  if (!cancelled) {
85
112
  setTools(toolList);
86
113
  setIsLoading(false);
114
+ setLoadError(null);
87
115
  }
88
116
  }
89
117
  catch (error) {
90
118
  if (!cancelled) {
119
+ const message = error instanceof Error ? error.message : 'Unknown error';
91
120
  setTools([]);
92
121
  setIsLoading(false);
122
+ setLoadError(`Failed to load tools: ${message}`);
93
123
  }
94
124
  }
95
125
  };
@@ -97,7 +127,7 @@ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent
97
127
  return () => {
98
128
  cancelled = true;
99
129
  };
100
- }, [isVisible, agent]);
130
+ }, [isVisible, agent, sessionId]);
101
131
  // Filter tools based on search query
102
132
  const filteredTools = useMemo(() => {
103
133
  if (!searchQuery.trim()) {
@@ -126,14 +156,118 @@ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent
126
156
  setScrollOffset(selectedIndex - MAX_VISIBLE_ITEMS + 1);
127
157
  }
128
158
  }, [selectedIndex, scrollOffset]);
129
- // Handle showing tool details
130
- const showToolDetails = (tool) => {
159
+ const openListActions = (tool) => {
160
+ setSelectedTool(tool);
161
+ setListActionsIndex(0);
162
+ setViewMode('list-actions');
163
+ };
164
+ const openToolDetails = (tool) => {
131
165
  setSelectedTool(tool);
132
166
  setViewMode('detail');
133
167
  setDetailScrollOffset(0);
134
168
  };
135
- // Handle going back to list
136
- const goBackToList = () => {
169
+ const openConfigMenu = (tool) => {
170
+ setSelectedTool(tool);
171
+ setConfigIndex(0);
172
+ setViewMode('config');
173
+ };
174
+ const openScopePrompt = (tool) => {
175
+ const nextEnabled = !tool.isEnabled;
176
+ const nextScope = sessionId ? 'session' : 'global';
177
+ setSelectedTool(tool);
178
+ setScopeToolName(tool.name);
179
+ setScopeNextEnabled(nextEnabled);
180
+ setScopeTarget(nextScope);
181
+ setViewMode('scope');
182
+ scopeToolNameRef.current = tool.name;
183
+ scopeNextEnabledRef.current = nextEnabled;
184
+ scopeTargetRef.current = nextScope;
185
+ };
186
+ const closeScopePrompt = () => {
187
+ setViewMode('config');
188
+ setScopeToolName(null);
189
+ };
190
+ const applyToolToggle = async (overrideTarget) => {
191
+ const targetToolName = scopeToolNameRef.current ?? scopeToolName ?? selectedToolRef.current?.name;
192
+ if (!targetToolName)
193
+ return;
194
+ const effectiveTarget = overrideTarget ?? scopeTargetRef.current;
195
+ const nextEnabled = scopeNextEnabledRef.current;
196
+ const previousTools = toolsRef.current;
197
+ const updatedTools = previousTools.map((tool) => {
198
+ if (tool.name !== targetToolName) {
199
+ return tool;
200
+ }
201
+ const updatedTool = { ...tool, isEnabled: nextEnabled };
202
+ if (!nextEnabled && tool.isAutoApproved) {
203
+ updatedTool.isAutoApproved = false;
204
+ }
205
+ return updatedTool;
206
+ });
207
+ setTools(updatedTools);
208
+ const updatedSelected = updatedTools.find((tool) => tool.name === targetToolName) ?? null;
209
+ setSelectedTool(updatedSelected);
210
+ const autoApprovedTools = updatedTools
211
+ .filter((tool) => tool.isAutoApproved)
212
+ .map((tool) => tool.name);
213
+ if (sessionId) {
214
+ agent.setSessionAutoApproveTools(sessionId, autoApprovedTools);
215
+ }
216
+ const disabledTools = updatedTools
217
+ .filter((tool) => !tool.isEnabled)
218
+ .map((tool) => tool.name);
219
+ if (effectiveTarget === 'session' && sessionId) {
220
+ agent.setSessionDisabledTools(sessionId, disabledTools);
221
+ }
222
+ else if (effectiveTarget === 'global') {
223
+ try {
224
+ const { updateAgentPreferences, saveAgentPreferences, agentPreferencesExist } = await import('@dexto/agent-management');
225
+ if (agentPreferencesExist(agent.config.agentId)) {
226
+ await updateAgentPreferences(agent.config.agentId, {
227
+ tools: { disabled: disabledTools },
228
+ });
229
+ }
230
+ else {
231
+ await saveAgentPreferences(agent.config.agentId, {
232
+ tools: { disabled: disabledTools },
233
+ });
234
+ }
235
+ agent.setGlobalDisabledTools(disabledTools);
236
+ }
237
+ catch (_error) {
238
+ // If we can't persist, still keep session state so user sees effect
239
+ if (sessionId) {
240
+ agent.setSessionDisabledTools(sessionId, disabledTools);
241
+ }
242
+ else {
243
+ setTools(previousTools);
244
+ setSelectedTool(previousTools.find((tool) => tool.name === targetToolName) ?? null);
245
+ }
246
+ }
247
+ }
248
+ closeScopePrompt();
249
+ };
250
+ const toggleAutoApprove = () => {
251
+ if (!sessionId)
252
+ return;
253
+ const updatedTools = toolsRef.current.map((tool) => tool.name === selectedToolRef.current?.name
254
+ ? { ...tool, isAutoApproved: !tool.isAutoApproved }
255
+ : tool);
256
+ const updatedSelected = updatedTools.find((tool) => tool.name === selectedToolRef.current?.name);
257
+ setTools(updatedTools);
258
+ setSelectedTool(updatedSelected ?? null);
259
+ const autoApprovedTools = updatedTools
260
+ .filter((tool) => tool.isAutoApproved)
261
+ .map((tool) => tool.name);
262
+ agent.setSessionAutoApproveTools(sessionId, autoApprovedTools);
263
+ };
264
+ const closeConfigMenu = () => {
265
+ setViewMode('list-actions');
266
+ };
267
+ const closeDetailView = () => {
268
+ setViewMode('list-actions');
269
+ };
270
+ const closeListActions = () => {
137
271
  setViewMode('list');
138
272
  setSelectedTool(null);
139
273
  };
@@ -142,13 +276,69 @@ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent
142
276
  handleInput: (input, key) => {
143
277
  if (!isVisible)
144
278
  return false;
145
- // In detail view (use ref to get latest value)
279
+ // Scope selection view
280
+ if (viewModeRef.current === 'scope') {
281
+ if (key.escape || key.backspace || key.delete) {
282
+ closeScopePrompt();
283
+ return true;
284
+ }
285
+ if (key.upArrow || key.downArrow) {
286
+ setScopeTarget((prev) => (prev === 'session' ? 'global' : 'session'));
287
+ return true;
288
+ }
289
+ if (key.return) {
290
+ if (scopeTargetRef.current === 'session' && !sessionId) {
291
+ setScopeTarget('global');
292
+ scopeTargetRef.current = 'global';
293
+ void applyToolToggle('global');
294
+ return true;
295
+ }
296
+ void applyToolToggle();
297
+ return true;
298
+ }
299
+ return true;
300
+ }
301
+ // Config menu view
302
+ if (viewModeRef.current === 'config') {
303
+ if (key.escape || key.backspace || key.delete) {
304
+ closeConfigMenu();
305
+ return true;
306
+ }
307
+ if (key.upArrow) {
308
+ const nextIndex = (configIndexRef.current - 1 + 3) % 3;
309
+ setConfigIndex(nextIndex);
310
+ return true;
311
+ }
312
+ if (key.downArrow) {
313
+ const nextIndex = (configIndexRef.current + 1) % 3;
314
+ setConfigIndex(nextIndex);
315
+ return true;
316
+ }
317
+ if (key.return) {
318
+ const tool = selectedToolRef.current;
319
+ if (tool) {
320
+ if (configIndexRef.current === 0) {
321
+ openScopePrompt(tool);
322
+ }
323
+ else if (configIndexRef.current === 1) {
324
+ if (sessionId && tool.isEnabled) {
325
+ toggleAutoApprove();
326
+ }
327
+ }
328
+ else {
329
+ closeConfigMenu();
330
+ }
331
+ }
332
+ return true;
333
+ }
334
+ return true;
335
+ }
336
+ // Detail view
146
337
  if (viewModeRef.current === 'detail') {
147
338
  if (key.escape || key.backspace || key.delete) {
148
- goBackToList();
339
+ closeDetailView();
149
340
  return true;
150
341
  }
151
- // Handle scrolling in detail view - check refs before setState to avoid flicker
152
342
  if (key.upArrow) {
153
343
  if (detailScrollOffsetRef.current > 0) {
154
344
  setDetailScrollOffset((prev) => prev - 1);
@@ -178,10 +368,53 @@ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent
178
368
  }
179
369
  return true;
180
370
  }
181
- return true; // Consume all input in detail view
371
+ if (key.return) {
372
+ const tool = selectedToolRef.current;
373
+ if (tool) {
374
+ openConfigMenu(tool);
375
+ }
376
+ return true;
377
+ }
378
+ return true;
379
+ }
380
+ // List action menu view
381
+ if (viewModeRef.current === 'list-actions') {
382
+ if (key.escape || key.backspace || key.delete) {
383
+ closeListActions();
384
+ return true;
385
+ }
386
+ if (key.upArrow) {
387
+ const nextIndex = (listActionsIndexRef.current - 1 + LIST_ACTIONS.length) %
388
+ LIST_ACTIONS.length;
389
+ setListActionsIndex(nextIndex);
390
+ return true;
391
+ }
392
+ if (key.downArrow) {
393
+ const nextIndex = (listActionsIndexRef.current + 1) % LIST_ACTIONS.length;
394
+ setListActionsIndex(nextIndex);
395
+ return true;
396
+ }
397
+ if (key.return) {
398
+ const tool = selectedToolRef.current;
399
+ const action = LIST_ACTIONS[listActionsIndexRef.current];
400
+ if (!tool) {
401
+ closeListActions();
402
+ return true;
403
+ }
404
+ if (action === 'view') {
405
+ openToolDetails(tool);
406
+ }
407
+ else if (action === 'config') {
408
+ openConfigMenu(tool);
409
+ }
410
+ else {
411
+ closeListActions();
412
+ }
413
+ return true;
414
+ }
415
+ return true;
182
416
  }
183
417
  // In list view
184
- // Escape closes
185
418
  if (key.escape) {
186
419
  onClose();
187
420
  return true;
@@ -224,33 +457,39 @@ const ToolBrowser = forwardRef(function ToolBrowser({ isVisible, onClose, agent
224
457
  if (key.return && itemsLength > 0) {
225
458
  const tool = filteredTools[selectedIndexRef.current];
226
459
  if (tool) {
227
- showToolDetails(tool);
460
+ openListActions(tool);
228
461
  return true;
229
462
  }
230
463
  }
231
464
  return false;
232
465
  },
233
- }), [isVisible, filteredTools, onClose, viewMode]);
466
+ }), [isVisible, filteredTools, onClose, sessionId]);
234
467
  if (!isVisible)
235
468
  return null;
236
469
  if (isLoading) {
237
470
  return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "Loading tools..." }) }));
238
471
  }
239
- // Detail view
472
+ if (viewMode === 'list-actions' && selectedTool) {
473
+ return (_jsx(ToolActionsMenu, { tool: selectedTool, columns: columns, listActionsIndex: listActionsIndex }));
474
+ }
240
475
  if (viewMode === 'detail' && selectedTool) {
241
- // Calculate max visible lines based on terminal height
242
- const maxVisibleLines = Math.min(18, Math.max(5, rows - 6)); // Cap at 18 to reduce flicker
476
+ const maxVisibleLines = Math.min(18, Math.max(5, rows - 6));
243
477
  return (_jsx(ToolDetailView, { tool: selectedTool, columns: columns, scrollOffset: detailScrollOffset, maxVisibleLines: maxVisibleLines, maxScrollOffsetRef: detailMaxScrollOffsetRef, copyFeedback: copyFeedback }));
244
478
  }
479
+ if (viewMode === 'config' && selectedTool) {
480
+ return (_jsx(ToolSettingsView, { tool: selectedTool, columns: columns, configIndex: configIndex, sessionAvailable: Boolean(sessionId) }));
481
+ }
482
+ if (viewMode === 'scope' && selectedTool) {
483
+ return (_jsx(ToolScopeView, { tool: selectedTool, columns: columns, scopeTarget: scopeTarget, scopeNextEnabled: scopeNextEnabled, sessionAvailable: Boolean(sessionId) }));
484
+ }
245
485
  // List view
246
486
  const visibleTools = filteredTools.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS);
247
- // Show counts based on filtered results, not total
248
487
  const filteredInternalCount = filteredTools.filter((t) => t.source === 'internal').length;
249
488
  const filteredMcpCount = filteredTools.filter((t) => t.source === 'mcp').length;
250
- return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "cyan", bold: true, children: "Tool Browser" }), _jsxs(Text, { color: "gray", children: [' ', "(", filteredTools.length, " tools: ", filteredInternalCount, " internal,", ' ', filteredMcpCount, " MCP)"] })] }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "\u2191\u2193 navigate, Enter view details, Esc close" }) }), _jsxs(Box, { paddingX: 0, paddingY: 0, marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Search: " }), _jsx(Text, { color: searchQuery ? 'white' : 'gray', children: searchQuery || 'Type to filter...' }), _jsx(Text, { color: "cyan", children: "\u258C" })] }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: '─'.repeat(Math.min(60, columns - 2)) }) }), filteredTools.length === 0 ? (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "No tools match your search" }) })) : (visibleTools.map((tool, visibleIndex) => {
489
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "cyan", bold: true, children: "Tool Browser" }), _jsxs(Text, { color: "gray", children: [' ', "(", filteredTools.length, " tools: ", filteredInternalCount, " internal,", ' ', filteredMcpCount, " MCP)"] })] }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "\u2191\u2193 navigate \u00B7 Enter options \u00B7 Esc close" }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "Type to search \u00B7 Backspace to delete" }) }), loadError && (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "red", children: loadError }) })), _jsxs(Box, { paddingX: 0, paddingY: 0, marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Search: " }), _jsx(Text, { color: searchQuery ? 'white' : 'gray', children: searchQuery || 'Type to filter...' }), _jsx(Text, { color: "cyan", children: "\u258C" })] }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: '─'.repeat(Math.min(60, columns - 2)) }) }), filteredTools.length === 0 ? (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "No tools match your search" }) })) : (visibleTools.map((tool, visibleIndex) => {
251
490
  const actualIndex = scrollOffset + visibleIndex;
252
491
  const isSelected = actualIndex === selectedIndex;
253
- return (_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: isSelected ? 'cyan' : 'white', children: isSelected ? '▶ ' : ' ' }), _jsx(Text, { color: isSelected ? 'cyan' : 'white', bold: isSelected, children: truncateText(tool.name, 35) }), _jsxs(Text, { color: tool.source === 'internal' ? 'magenta' : 'blue', children: [' ', "[", tool.source === 'internal' ? 'Internal' : 'MCP', "]"] }), tool.serverName && _jsxs(Text, { color: "gray", children: [" (", tool.serverName, ")"] })] }, tool.name));
492
+ return (_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: isSelected ? 'cyan' : 'white', children: isSelected ? '▶ ' : ' ' }), _jsx(Text, { color: isSelected ? 'cyan' : 'white', bold: isSelected, children: truncateText(tool.name, 35) }), _jsxs(Text, { color: tool.source === 'internal' ? 'magenta' : 'blue', children: [' ', "[", tool.source === 'internal' ? 'Internal' : 'MCP', "]"] }), tool.serverName && _jsxs(Text, { color: "gray", children: [" (", tool.serverName, ")"] }), _jsxs(Text, { color: tool.isEnabled ? 'green' : 'red', children: [' ', tool.isEnabled ? 'Enabled' : 'Disabled'] }), tool.isAutoApproved && _jsx(Text, { color: "yellow", children: " [auto-approved]" })] }, tool.name));
254
493
  })), filteredTools.length > MAX_VISIBLE_ITEMS && (_jsx(Box, { paddingX: 0, paddingY: 0, marginTop: 1, children: _jsxs(Text, { color: "gray", children: [scrollOffset > 0 ? '↑ more above' : '', scrollOffset > 0 && scrollOffset + MAX_VISIBLE_ITEMS < filteredTools.length
255
494
  ? ' | '
256
495
  : '', scrollOffset + MAX_VISIBLE_ITEMS < filteredTools.length
@@ -299,36 +538,29 @@ function buildDetailLineData(tool, maxWidth) {
299
538
  });
300
539
  // Parameter description (wrapped)
301
540
  if (description) {
302
- const paramDescLines = wrapText(description, maxWidth - 6).split('\n');
303
- for (const paramDescLine of paramDescLines) {
304
- lines.push({ type: 'param-desc', text: paramDescLine });
541
+ const descLines = wrapText(description, maxWidth - 2).split('\n');
542
+ for (const descLine of descLines) {
543
+ lines.push({ type: 'param-desc', text: descLine });
305
544
  }
306
545
  }
307
546
  // Enum values
308
- if (enumValues) {
547
+ if (enumValues && enumValues.length > 0) {
309
548
  lines.push({ type: 'param-enum', values: enumValues });
310
549
  }
311
- // Empty line between parameters
312
- lines.push({ type: 'empty' });
313
550
  }
314
551
  }
315
- else {
316
- // Empty line before "no parameters"
317
- lines.push({ type: 'empty' });
318
- lines.push({ type: 'param-desc', text: 'No parameters' });
319
- }
320
552
  }
321
553
  return lines;
322
554
  }
323
555
  /**
324
- * Render a single detail line from data
556
+ * Render a detail line based on type
325
557
  */
326
- function renderDetailLine(line, index) {
558
+ function renderDetailLine(line, _index) {
327
559
  switch (line.type) {
328
560
  case 'title':
329
561
  return (_jsx(Text, { color: "cyan", bold: true, children: line.text }));
330
562
  case 'source':
331
- return (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Source: " }), _jsx(Text, { color: line.source === 'internal' ? 'magenta' : 'blue', children: line.source === 'internal' ? 'Internal' : 'MCP' }), line.serverName && _jsxs(Text, { color: "gray", children: [" (server: ", line.serverName, ")"] })] }));
563
+ return (_jsxs(Text, { color: line.source === 'internal' ? 'magenta' : 'blue', children: [line.source === 'internal' ? 'Internal Tool' : 'MCP Tool', line.serverName ? ` (${line.serverName})` : ''] }));
332
564
  case 'empty':
333
565
  return _jsx(Text, { children: " " });
334
566
  case 'header':
@@ -343,9 +575,9 @@ function renderDetailLine(line, index) {
343
575
  return (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: "gray", children: ["Allowed: ", line.values.join(' | ')] })] }));
344
576
  }
345
577
  }
346
- /**
347
- * Tool detail view component with scrolling support
348
- */
578
+ function ToolActionsMenu({ tool, columns, listActionsIndex, }) {
579
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "cyan", bold: true, children: "Tool Options" }), _jsxs(Text, { color: "gray", children: [" ", tool.name] })] }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: listActionsIndex === 0 ? 'cyan' : 'gray', children: [listActionsIndex === 0 ? '▶ ' : ' ', "View details"] }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: listActionsIndex === 1 ? 'cyan' : 'gray', children: [listActionsIndex === 1 ? '▶ ' : ' ', "Edit tool settings"] }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: listActionsIndex === 2 ? 'cyan' : 'gray', children: [listActionsIndex === 2 ? '▶ ' : ' ', "Back"] }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "\u2191\u2193 select \u00B7 Enter confirm \u00B7 Esc back" }) })] }));
580
+ }
349
581
  function ToolDetailView({ tool, columns, scrollOffset, maxVisibleLines, maxScrollOffsetRef, copyFeedback, }) {
350
582
  const maxWidth = Math.min(80, columns - 4);
351
583
  // Build plain data for lines (memoized)
@@ -358,7 +590,7 @@ function ToolDetailView({ tool, columns, scrollOffset, maxVisibleLines, maxScrol
358
590
  // Calculate scroll indicators
359
591
  const hasMoreAbove = clampedOffset > 0;
360
592
  const hasMoreBelow = clampedOffset + maxVisibleLines < totalLines;
361
- return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "cyan", bold: true, children: "Tool Details" }), _jsx(Text, { color: "gray", children: " - \u2191\u2193 scroll, c copy schema, Esc back" }), copyFeedback && (_jsxs(Text, { color: "green", bold: true, children: [' ', copyFeedback] }))] }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: '─'.repeat(Math.min(60, columns - 2)) }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: hasMoreAbove ? `↑ ${clampedOffset} more above` : ' ' }) }), Array.from({ length: maxVisibleLines }, (_, i) => {
593
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "cyan", bold: true, children: "Tool Details" }), _jsx(Text, { color: "gray", children: " - \u2191\u2193 scroll, c copy schema, Enter settings, Esc back" }), copyFeedback && (_jsxs(Text, { color: "green", bold: true, children: [' ', copyFeedback] }))] }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: '─'.repeat(Math.min(60, columns - 2)) }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: hasMoreAbove ? `↑ ${clampedOffset} more above` : ' ' }) }), Array.from({ length: maxVisibleLines }, (_, i) => {
362
594
  const absoluteIndex = clampedOffset + i;
363
595
  const line = lineData[absoluteIndex];
364
596
  return (_jsx(Box, { paddingX: 0, paddingY: 0, children: line ? renderDetailLine(line, absoluteIndex) : _jsx(Text, { children: " " }) }, i));
@@ -366,6 +598,16 @@ function ToolDetailView({ tool, columns, scrollOffset, maxVisibleLines, maxScrol
366
598
  ? `↓ ${totalLines - clampedOffset - maxVisibleLines} more below`
367
599
  : ' ' }) })] }));
368
600
  }
601
+ function ToolSettingsView({ tool, columns, configIndex, sessionAvailable, }) {
602
+ const autoApproveDisabled = !tool.isEnabled;
603
+ const autoApproveUnavailable = !sessionAvailable || autoApproveDisabled;
604
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "cyan", bold: true, children: "Tool Settings" }), _jsxs(Text, { color: "gray", children: [" ", tool.name] })] }), _jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "gray", children: "Status: " }), _jsx(Text, { color: tool.isEnabled ? 'green' : 'red', children: tool.isEnabled ? 'Enabled' : 'Disabled' }), _jsx(Text, { color: "gray", children: " \u00B7 Auto-approve: " }), _jsx(Text, { color: tool.isAutoApproved ? 'green' : 'red', children: tool.isAutoApproved ? 'On' : 'Off' }), _jsx(Text, { color: "gray", children: " (session)" })] }), autoApproveDisabled && (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "yellow", children: "Enable the tool to allow auto-approve." }) })), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: configIndex === 0 ? 'cyan' : 'gray', children: [configIndex === 0 ? '▶ ' : ' ', tool.isEnabled ? 'Disable tool' : 'Enable tool'] }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: configIndex === 1 ? (autoApproveUnavailable ? 'yellow' : 'cyan') : 'gray', children: [configIndex === 1 ? '▶ ' : ' ', tool.isAutoApproved
605
+ ? 'Disable auto-approve (session)'
606
+ : 'Enable auto-approve (session)'] }) }), !sessionAvailable && (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "yellow", children: "Auto-approve requires an active session." }) })), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: configIndex === 2 ? 'cyan' : 'gray', children: [configIndex === 2 ? '▶ ' : ' ', "Back"] }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "\u2191\u2193 select \u00B7 Enter confirm \u00B7 Esc back" }) })] }));
607
+ }
608
+ function ToolScopeView({ tool, columns, scopeTarget, scopeNextEnabled, sessionAvailable, }) {
609
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsxs(Box, { paddingX: 0, paddingY: 0, children: [_jsx(Text, { color: "cyan", bold: true, children: scopeNextEnabled ? 'Enable Tool' : 'Disable Tool' }), _jsxs(Text, { color: "gray", children: [" ", tool.name] })] }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "Apply to:" }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: scopeTarget === 'session' ? 'cyan' : 'gray', children: [scopeTarget === 'session' ? '▶ ' : ' ', "Session (default)"] }) }), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: scopeTarget === 'global' ? 'cyan' : 'gray', children: [scopeTarget === 'global' ? '▶ ' : ' ', "Global (persisted)"] }) }), !sessionAvailable && scopeTarget === 'session' && (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "yellow", children: "No active session; switching to global." }) })), _jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "\u2191\u2193 choose scope \u00B7 Enter confirm \u00B7 Esc back" }) })] }));
610
+ }
369
611
  /**
370
612
  * Truncate text to max length with ellipsis
371
613
  */
@@ -20,9 +20,17 @@ export declare function getProviderConfig(provider: CustomModelProvider): Provid
20
20
  export declare function getProviderLabel(provider: CustomModelProvider): string;
21
21
  /**
22
22
  * Get all available provider types.
23
- * Filters out 'dexto' when the feature flag is disabled.
23
+ * Filters out 'dexto-nova' when the feature flag is disabled.
24
24
  */
25
25
  export declare function getAvailableProviders(): CustomModelProvider[];
26
+ /**
27
+ * Maps a provider-selection menu index to the selected provider, using the same
28
+ * provider ordering as the UI.
29
+ *
30
+ * This avoids subtle bugs when the displayed ordering differs from the base
31
+ * CUSTOM_MODEL_PROVIDERS array (e.g. feature-flagged reordering).
32
+ */
33
+ export declare function getProviderByMenuIndex(index: number): CustomModelProvider | undefined;
26
34
  /**
27
35
  * Check if a provider has async validation.
28
36
  */
@@ -1 +1 @@
1
- {"version":3,"file":"provider-config.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAe,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAQhF,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AA4D7D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,mBAAmB,EAAE,cAAc,CAybxE,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,cAAc,CAE/E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,CAGtE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,mBAAmB,EAAE,CAG7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAEzE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACpC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB"}
1
+ {"version":3,"file":"provider-config.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAe,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAQhF,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AA4D7D;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,mBAAmB,EAAE,cAAc,CAgcxE,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,cAAc,CAE/E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,CAGtE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,mBAAmB,EAAE,CAW7D;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAIrF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAEzE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACpC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB"}
@@ -120,6 +120,7 @@ export const PROVIDER_CONFIGS = {
120
120
  validate: validators.slashFormat,
121
121
  },
122
122
  { ...DISPLAY_NAME_STEP, placeholder: 'e.g., Claude 3.5 Sonnet' },
123
+ MAX_INPUT_TOKENS_STEP,
123
124
  REASONING_EFFORT_STEP,
124
125
  {
125
126
  ...API_KEY_STEP,
@@ -134,6 +135,9 @@ export const PROVIDER_CONFIGS = {
134
135
  if (values.displayName?.trim()) {
135
136
  model.displayName = values.displayName.trim();
136
137
  }
138
+ if (values.maxInputTokens?.trim()) {
139
+ model.maxInputTokens = parseInt(values.maxInputTokens, 10);
140
+ }
137
141
  if (values.reasoningEffort?.trim()) {
138
142
  model.reasoningEffort =
139
143
  values.reasoningEffort.toLowerCase();
@@ -423,9 +427,9 @@ export const PROVIDER_CONFIGS = {
423
427
  docsUrl: 'https://docs.dexto.ai/docs/guides/supported-llm-providers#google-vertex-ai',
424
428
  },
425
429
  },
426
- dexto: {
427
- displayName: 'Dexto',
428
- description: 'Access 100+ models with Dexto credits',
430
+ 'dexto-nova': {
431
+ displayName: 'Dexto Nova',
432
+ description: 'Access 100+ models with Nova credits',
429
433
  steps: [
430
434
  {
431
435
  field: 'name',
@@ -446,6 +450,9 @@ export const PROVIDER_CONFIGS = {
446
450
  if (values.displayName?.trim()) {
447
451
  model.displayName = values.displayName.trim();
448
452
  }
453
+ if (values.maxInputTokens?.trim()) {
454
+ model.maxInputTokens = parseInt(values.maxInputTokens, 10);
455
+ }
449
456
  if (values.reasoningEffort?.trim()) {
450
457
  model.reasoningEffort =
451
458
  values.reasoningEffort.toLowerCase();
@@ -455,7 +462,7 @@ export const PROVIDER_CONFIGS = {
455
462
  asyncValidation: {
456
463
  field: 'name',
457
464
  validate: async (modelId) => {
458
- // Reuse OpenRouter validation since Dexto uses OpenRouter model IDs
465
+ // Reuse OpenRouter validation since Dexto Nova uses OpenRouter model IDs
459
466
  let status = lookupOpenRouterModel(modelId);
460
467
  // If cache is stale/empty, try to refresh
461
468
  if (status === 'unknown') {
@@ -469,14 +476,14 @@ export const PROVIDER_CONFIGS = {
469
476
  }
470
477
  }
471
478
  if (status === 'invalid') {
472
- return `Model '${modelId}' not found. Dexto uses OpenRouter model IDs - check https://openrouter.ai/models`;
479
+ return `Model '${modelId}' not found. Dexto Nova uses OpenRouter model IDs - check https://openrouter.ai/models`;
473
480
  }
474
481
  return null;
475
482
  },
476
483
  },
477
484
  setupInfo: {
478
- title: 'Dexto Setup',
479
- description: 'Add OpenRouter-format models that use your Dexto credits. Requires login: run `dexto login` first.',
485
+ title: 'Dexto Nova Setup',
486
+ description: 'Add OpenRouter-format models that use your Dexto Nova credits. Requires login: run `dexto login` first.',
480
487
  docsUrl: 'https://openrouter.ai/models',
481
488
  },
482
489
  },
@@ -497,11 +504,30 @@ export function getProviderLabel(provider) {
497
504
  }
498
505
  /**
499
506
  * Get all available provider types.
500
- * Filters out 'dexto' when the feature flag is disabled.
507
+ * Filters out 'dexto-nova' when the feature flag is disabled.
501
508
  */
502
509
  export function getAvailableProviders() {
503
510
  const dextoEnabled = isDextoAuthEnabled();
504
- return CUSTOM_MODEL_PROVIDERS.filter((provider) => provider !== 'dexto' || dextoEnabled);
511
+ const providers = CUSTOM_MODEL_PROVIDERS.filter((provider) => provider !== 'dexto-nova' || dextoEnabled);
512
+ if (!dextoEnabled) {
513
+ return providers;
514
+ }
515
+ // When enabled, put Dexto Nova first for better UX.
516
+ const withoutDexto = providers.filter((p) => p !== 'dexto-nova');
517
+ return ['dexto-nova', ...withoutDexto];
518
+ }
519
+ /**
520
+ * Maps a provider-selection menu index to the selected provider, using the same
521
+ * provider ordering as the UI.
522
+ *
523
+ * This avoids subtle bugs when the displayed ordering differs from the base
524
+ * CUSTOM_MODEL_PROVIDERS array (e.g. feature-flagged reordering).
525
+ */
526
+ export function getProviderByMenuIndex(index) {
527
+ const providers = getAvailableProviders();
528
+ if (index < 0 || index >= providers.length)
529
+ return undefined;
530
+ return providers[index];
505
531
  }
506
532
  /**
507
533
  * Check if a provider has async validation.
@@ -417,7 +417,7 @@ export const InputContainer = forwardRef(function InputContainer({ buffer, input
417
417
  {
418
418
  id: generateMessageId('error'),
419
419
  role: 'system',
420
- content: `❌ Error: ${error instanceof Error ? error.message : String(error)}`,
420
+ content: `Error: ${error instanceof Error ? error.message : String(error)}`,
421
421
  timestamp: new Date(),
422
422
  },
423
423
  ]);
@@ -545,7 +545,7 @@ export const InputContainer = forwardRef(function InputContainer({ buffer, input
545
545
  {
546
546
  id: generateMessageId('error'),
547
547
  role: 'system',
548
- content: `❌ Error: ${error instanceof Error ? error.message : String(error)}`,
548
+ content: `Error: ${error instanceof Error ? error.message : String(error)}`,
549
549
  timestamp: new Date(),
550
550
  },
551
551
  ]);