This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 915e4829f647f7b227b2c496ac696c88e5c1b161
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Sep 8 16:29:24 2026 +0200

    CAMEL-24656: camel-jbang TUI - add an AI Tools row to the F2 Settings popup
    
    Cycles auto / core / full for the tool set the AI panel sends to the model,
    persisted as camel.tui.ai.tools like the /tools slash command. The panel
    re-reads the setting before each question so a change applies without a
    restart.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../modules/ROOT/pages/camel-jbang-tui.adoc        |  7 ++--
 .../camel/dsl/jbang/core/commands/tui/AiPanel.java |  6 ++-
 .../dsl/jbang/core/commands/tui/SettingsPopup.java | 39 ++++++++++++++++--
 .../jbang/core/commands/tui/SettingsPopupTest.java | 47 ++++++++++++++++++++--
 4 files changed, 89 insertions(+), 10 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
index 49380e035949..b5716f7eceb6 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
@@ -557,7 +557,8 @@ edit the default folder, *Enter* to save, and *Esc* to 
cancel.
 Settings are stored under `camel.tui.*` keys (`camel.tui.theme`, 
`camel.tui.startTab`,
 `camel.tui.selectTab`, `camel.tui.confirmActions`, `camel.tui.defaultFolder`,
 `camel.tui.panelPosition`, `camel.tui.panelSpace`,
-`camel.tui.shell.history`, `camel.tui.ai.promptHistory`) in the Camel CLI 
configuration file. Each key is read from and
+`camel.tui.shell.history`, `camel.tui.ai.provider`, `camel.tui.ai.model`, 
`camel.tui.ai.url`,
+`camel.tui.ai.tools`, `camel.tui.ai.promptHistory`) in the Camel CLI 
configuration file. Each key is read from and
 written back to the file where it currently lives: a key present in the local
 `./camel-cli.properties` is treated as a project-level override and stays 
local, while every
 other key defaults to the global `~/.camel-cli.properties`. This means a 
project can
@@ -822,8 +823,8 @@ tools (state, tables, logs, errors, diagrams, topology, 
processor details, catal
 spans, route control, sending messages, source files, navigation, log level 
and filters) to Ollama
 and to any provider on `localhost`, which roughly halves the prompt. Hosted 
providers get every
 tool, including the drawing, animation and automation tools. Use `/tools full` 
in the panel to send
-all tools to a local model too, `/tools core` to trim the set for a hosted 
one, or set
-`camel.tui.ai.tools` in `.camel-cli.properties`. Ollama requests also ask the 
server to keep the
+all tools to a local model too, `/tools core` to trim the set for a hosted 
one, pick *AI Tools* in
+*F2 -> Settings*, or set `camel.tui.ai.tools` in `.camel-cli.properties`. 
Ollama requests also ask the server to keep the
 model loaded for 30 minutes and use a 32k context window 
(`OLLAMA_CONTEXT_LENGTH` overrides it),
 so follow-up questions reuse the cached prompt instead of reloading the model.
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
index 1aa3722125bf..0ec870f14ef1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanel.java
@@ -964,7 +964,11 @@ class AiPanel {
         thinkingStartTime = System.currentTimeMillis();
         thinking.set(true);
 
-        // rebuild tools in case mcpFacade was wired after init
+        // re-read the tool mode so a change made in F2 -> Settings applies to 
the next question, and rebuild the
+        // tools in case mcpFacade was wired after init
+        if (!testingClientInjected) {
+            toolMode = normalizeToolMode(TuiSettings.load().getAiTools());
+        }
         tools = buildTuiToolDefinitions();
         String systemPrompt = buildSystemPrompt();
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopup.java
index 67e0d02e3505..aea9a61533c3 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopup.java
@@ -61,13 +61,16 @@ class SettingsPopup {
     private static final int ROW_AI_PROVIDER = 13;
     private static final int ROW_AI_MODEL = 14;
     private static final int ROW_AI_URL = 15;
-    private static final int ROW_AI_PROMPT_HISTORY = 16;
-    private static final int ROW_COUNT = 17;
+    private static final int ROW_AI_TOOLS = 16;
+    private static final int ROW_AI_PROMPT_HISTORY = 17;
+    private static final int ROW_COUNT = 18;
 
     private static final String[] LOG_PIN_OPTIONS = { "off", "25", "50", "75" 
};
     private static final String[] RATE_PER_OPTIONS = { "seconds", "minutes" };
     private static final String[] PANEL_POSITION_OPTIONS = { "bottom", "top" };
     private static final String[] PANEL_SPACE_OPTIONS = { "move", "overlay" };
+    private static final String[] AI_TOOLS_OPTIONS
+            = { AiPanel.TOOL_MODE_AUTO, AiPanel.TOOL_MODE_CORE, 
AiPanel.TOOL_MODE_FULL };
     private static final List<String> AI_PROVIDERS = buildAiProviderList();
 
     private static List<String> buildAiProviderList() {
@@ -93,6 +96,7 @@ class SettingsPopup {
     private int confirmActionsIndex;
     private int validateOnSaveIndex;
     private int aiProviderIndex;
+    private int aiToolsIndex;
     private TextInputState folderInput;
     private TextInputState proxyHostInput;
     private TextInputState proxyPortInput;
@@ -181,6 +185,9 @@ class SettingsPopup {
         aiProviderIndex = providerIdx >= 0 ? providerIdx : 
AI_PROVIDERS.indexOf("auto");
         aiModelInput = new TextInputState(settings.getAiModel() != null ? 
settings.getAiModel() : "");
         aiUrlInput = new TextInputState(settings.getAiUrl() != null ? 
settings.getAiUrl() : "");
+        String currentTools = AiPanel.normalizeToolMode(settings.getAiTools());
+        int toolsIdx = List.of(AI_TOOLS_OPTIONS).indexOf(currentTools != null 
? currentTools : AiPanel.TOOL_MODE_AUTO);
+        aiToolsIndex = Math.max(0, toolsIdx);
         aiPromptHistoryInput = new TextInputState(
                 settings.getAiPromptHistory() != null ? 
settings.getAiPromptHistory() : "");
         selectedRow = ROW_THEME;
@@ -323,6 +330,14 @@ class SettingsPopup {
             handleTextInput(ke, aiUrlInput);
             return true;
         }
+        if (selectedRow == ROW_AI_TOOLS) {
+            if (ke.isChar(' ') || ke.isRight()) {
+                aiToolsIndex = (aiToolsIndex + 1) % AI_TOOLS_OPTIONS.length;
+            } else if (ke.isLeft()) {
+                aiToolsIndex = (aiToolsIndex - 1 + AI_TOOLS_OPTIONS.length) % 
AI_TOOLS_OPTIONS.length;
+            }
+            return true;
+        }
         if (selectedRow == ROW_AI_PROMPT_HISTORY) {
             handleTextInput(ke, aiPromptHistoryInput);
             return true;
@@ -368,6 +383,8 @@ class SettingsPopup {
         settings.setAiProvider(AI_PROVIDERS.get(aiProviderIndex));
         settings.setAiModel(stripControlChars(aiModelInput.text().trim()));
         settings.setAiUrl(stripControlChars(aiUrlInput.text().trim()));
+        String aiToolsValue = AI_TOOLS_OPTIONS[aiToolsIndex];
+        settings.setAiTools(AiPanel.TOOL_MODE_AUTO.equals(aiToolsValue) ? null 
: aiToolsValue);
         
settings.setAiPromptHistory(stripControlChars(aiPromptHistoryInput.text().trim()));
         settings.save();
         if (Theme.mode().equals(selectedThemeId)) {
@@ -495,6 +512,10 @@ class SettingsPopup {
         renderTextInput(frame, innerX + labelW, rowY, fieldW, aiUrlInput, 
selectedRow == ROW_AI_URL, "(auto)");
         rowY++;
 
+        renderLabel(frame, innerX, rowY, labelW, "AI Tools:", selectedRow == 
ROW_AI_TOOLS);
+        renderValue(frame, innerX + labelW, rowY, fieldW, aiToolsLabel(), 
selectedRow == ROW_AI_TOOLS);
+        rowY++;
+
         renderLabel(frame, innerX, rowY, labelW, "AI History:", selectedRow == 
ROW_AI_PROMPT_HISTORY);
         renderTextInput(frame, innerX + labelW, rowY, fieldW, 
aiPromptHistoryInput,
                 selectedRow == ROW_AI_PROMPT_HISTORY, "(100)");
@@ -505,7 +526,7 @@ class SettingsPopup {
                 || selectedRow == ROW_LOG_PIN || selectedRow == ROW_RATE_PER
                 || selectedRow == ROW_PANEL_POSITION || selectedRow == 
ROW_PANEL_SPACE
                 || selectedRow == ROW_CONFIRM_ACTIONS || selectedRow == 
ROW_VALIDATE_ON_SAVE
-                || selectedRow == ROW_AI_PROVIDER) {
+                || selectedRow == ROW_AI_PROVIDER || selectedRow == 
ROW_AI_TOOLS) {
             hint(spans, "Space", "cycle");
         }
         hint(spans, "Enter", "save");
@@ -637,6 +658,18 @@ class SettingsPopup {
         return AI_PROVIDERS.get(aiProviderIndex);
     }
 
+    String selectedAiTools() {
+        return AI_TOOLS_OPTIONS[aiToolsIndex];
+    }
+
+    private String aiToolsLabel() {
+        return switch (AI_TOOLS_OPTIONS[aiToolsIndex]) {
+            case AiPanel.TOOL_MODE_CORE -> "core (troubleshooting only)";
+            case AiPanel.TOOL_MODE_FULL -> "full (all tools)";
+            default -> "auto (core if local model)";
+        };
+    }
+
     String aiModelText() {
         return aiModelInput != null ? aiModelInput.text() : "";
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopupTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopupTest.java
index efba01b08d02..7dc071e4d1c4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopupTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SettingsPopupTest.java
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.parallel.Isolated;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -216,6 +217,46 @@ class SettingsPopupTest {
         assertEquals("https://example.test";, persisted.getAiUrl());
     }
 
+    @Test
+    void aiToolsRowCyclesModesAndPersistsNonDefault(@TempDir Path tempDir) {
+        useHome(tempDir);
+        SettingsPopup popup = new SettingsPopup();
+        popup.setTabEntries(tabs());
+        popup.open();
+
+        // navigate to AI Tools (row 16)
+        for (int i = 0; i < 16; i++) {
+            popup.handleKeyEvent(key(KeyCode.DOWN));
+        }
+        assertEquals(16, popup.selectedRow());
+        assertEquals("auto", popup.selectedAiTools());
+
+        popup.handleKeyEvent(KeyEvent.ofChar(' '));
+        assertEquals("core", popup.selectedAiTools());
+        popup.handleKeyEvent(key(KeyCode.RIGHT));
+        assertEquals("full", popup.selectedAiTools());
+        popup.handleKeyEvent(key(KeyCode.RIGHT));
+        assertEquals("auto", popup.selectedAiTools());
+        popup.handleKeyEvent(key(KeyCode.LEFT));
+        assertEquals("full", popup.selectedAiTools());
+
+        popup.handleKeyEvent(key(KeyCode.ENTER));
+        assertEquals("full", TuiSettings.load().getAiTools());
+
+        // auto is the default and is not written to the settings file
+        SettingsPopup reopened = new SettingsPopup();
+        reopened.setTabEntries(tabs());
+        reopened.open();
+        assertEquals("full", reopened.selectedAiTools());
+        for (int i = 0; i < 16; i++) {
+            reopened.handleKeyEvent(key(KeyCode.DOWN));
+        }
+        reopened.handleKeyEvent(key(KeyCode.RIGHT));
+        assertEquals("auto", reopened.selectedAiTools());
+        reopened.handleKeyEvent(key(KeyCode.ENTER));
+        assertNull(TuiSettings.load().getAiTools());
+    }
+
     @Test
     void historyFieldsPersistValues(@TempDir Path tempDir) {
         useHome(tempDir);
@@ -233,11 +274,11 @@ class SettingsPopupTest {
         }
         assertEquals("50", popup.shellHistoryText());
 
-        // navigate to AI Prompt History (row 14)
-        for (int i = 0; i < 4; i++) {
+        // navigate to AI Prompt History (row 17)
+        for (int i = 0; i < 5; i++) {
             popup.handleKeyEvent(key(KeyCode.DOWN));
         }
-        assertEquals(16, popup.selectedRow());
+        assertEquals(17, popup.selectedRow());
         for (char c : "200".toCharArray()) {
             popup.handleKeyEvent(KeyEvent.ofChar(c));
         }

Reply via email to