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


The following commit(s) were added to refs/heads/main by this push:
     new 03d7471dd78a CAMEL-26932: camel-jbang-mcp - camel_version_list works, 
and camel_get_files reads a large file in parts (#29912)
03d7471dd78a is described below

commit 03d7471dd78afe05408e869e352c9357de975ef3
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 14 19:22:52 2026 +0100

    CAMEL-26932: camel-jbang-mcp - camel_version_list works, and 
camel_get_files reads a large file in parts (#29912)
    
    camel_version_list always failed with a NullPointerException: the tool 
creates
    VersionList itself, and since the aesh port its @Mixin fields are only set 
when a
    command line is parsed. VersionList now creates its option groups in their
    declarations, as Run does. The tool says openWorldHint=true, as it 
downloads the
    release lists from Maven Central.
    
    camel_get_files put the whole file in the answer: a 3.6 MB file was a 4.5 MB
    answer. A small file is still returned whole and as it is; a larger one 
comes in
    parts of at most 2000 lines and 256 KB, from the new offset argument and up 
to
    the new limit argument, and the answer says which lines it has and the 
offset of
    the next part.
    
    CAMEL-26932: camel-jbang - offset and limit of camel_get_files are 
integers, and the AI panel prompt budgets allow for them
    
    The two arguments made the prompt of the TUI AI panel about 20 tokens 
longer than
    its budgets (AiPanelPromptBudgetTest): their descriptions are shorter now, 
and the
    budgets are raised with the measured sizes, as the test asks.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../modules/ROOT/pages/camel-jbang-mcp.adoc        |  4 +-
 .../dsl/jbang/core/commands/ai/AuthoringTools.java | 91 +++++++++++++++++++++-
 .../jbang/core/commands/version/VersionList.java   |  5 +-
 .../jbang/core/commands/ai/AuthoringToolsTest.java | 57 ++++++++++++++
 .../core/commands/version/VersionListTest.java     | 10 +++
 .../jbang/core/commands/mcp/AuthoringTools.java    |  9 ++-
 .../dsl/jbang/core/commands/mcp/VersionTools.java  |  3 +-
 .../core/commands/mcp/AuthoringToolsRootsTest.java | 14 ++--
 .../core/commands/mcp/AuthoringToolsTest.java      |  7 +-
 .../core/commands/tui/AiPanelPromptBudgetTest.java | 12 ++-
 10 files changed, 190 insertions(+), 22 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index 4268638e9381..0b9e05a87d17 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -417,7 +417,9 @@ passed as the file name, is answered with the argument the 
source goes in.
   files up front (`routeFiles`, `configFiles`) and, when an integration is 
selected, which file and line each of its
   routes was loaded from, mapped from the runtime's location (a jar entry of 
an exported project, a classpath or
   file resource) onto the source under `src/main`. With `file`, a path 
relative to the directory as the list names
-  it, the content of that file. The `.camel` folders are not listed, but 
counted (`consumedFiles`): a file consumer
+  it, the content of that file: all of a small file as it is, else at most 
2000 lines and 256 KB, from the line
+  `offset` (from 1) and up to `limit` lines, with the answer saying which 
lines it has and the `offset` to read
+  on from. The `.camel` folders are not listed, but counted (`consumedFiles`): 
a file consumer
   moves each file it reads into `.camel` (unless `noop=true`), so a data 
folder whose files were all read is not
   reported as empty.
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
index 9d721b4cad35..2630c1834ccc 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.ai;
 
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.FileVisitOption;
@@ -242,12 +243,15 @@ public final class AuthoringTools {
                                                 + "from; with file (a path 
relative to the directory, as listed) its content.")
                 .param("directory", "string", DIRECTORY_DESC, false)
                 .param("file", "string", FILE_PATH_DESC + " to read; omitted 
lists the files", false)
+                .param("offset", "integer", "First line (from 1)", false)
+                .param("limit", "integer", "Lines to read (default " + 
READ_LINES + ")", false)
                 .core(true)
                 .executor((ctx, args) -> {
                     String file = args.get("file");
                     if (file != null && !file.isBlank()) {
                         FileTarget target = fileTarget(ctx, 
args.get("directory"), file, FileAccess.READ);
-                        return readFile(target.directory(), 
target.file()).toJson();
+                        return readFile(target.directory(), target.file(), 
lineArg(args, "offset"), lineArg(args, "limit"))
+                                .toJson();
                     }
                     Path dir = ctx.resolveDirectory(args.get("directory"));
                     JsonObject result = listFiles(dir);
@@ -2314,8 +2318,41 @@ public final class AuthoringTools {
         return result;
     }
 
+    /** The lines camel_get_files reads of a file by default. */
+    static final int READ_LINES = 2000;
+
+    /**
+     * The most characters of a file in one answer of camel_get_files: a 3.6 
MB file was an answer of 4.5 MB, about a
+     * million tokens (CAMEL-26932).
+     */
+    static final int READ_CHARS = 256 * 1024;
+
+    private static int lineArg(Map<String, String> args, String name) {
+        String value = args.get(name);
+        if (value == null || value.isBlank()) {
+            return 0;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException e) {
+            throw new ToolExecutionException(name + " must be a number of 
lines: " + value);
+        }
+    }
+
     /** One file of a project directory with its content, as {@code 
camel_get_files} reads it. */
     public static JsonObject readFile(Path dir, String file) {
+        return readFile(dir, file, 0, 0);
+    }
+
+    /**
+     * One file of a project directory with its content, as {@code 
camel_get_files} reads it: the whole file when it is
+     * small, else the lines from the offset (from 1), at most the limit 
({@value #READ_LINES} by default) and
+     * {@value #READ_CHARS} characters, with what was left out and the offset 
to read on from.
+     *
+     * @param offset the first line, from 1; 0 for the first
+     * @param limit  how many lines; 0 for {@value #READ_LINES}
+     */
+    public static JsonObject readFile(Path dir, String file, int offset, int 
limit) {
         Path path = resolveFile(dir, file);
         if (!Files.isRegularFile(path)) {
             String elsewhere = elsewhere(dir, path, "; ");
@@ -2331,7 +2368,57 @@ public final class AuthoringTools {
         result.put("directory", dir.toString());
         result.put("size", formatSize(size(path)));
         result.put("type", fileType(file));
-        result.put("content", read(path));
+        int from = Math.max(1, offset);
+        int max = limit > 0 ? limit : READ_LINES;
+        StringBuilder window = new StringBuilder();
+        int lines = 0;
+        int to = from - 1;
+        boolean cutLine = false;
+        boolean full = false;
+        try (BufferedReader reader = Files.newBufferedReader(path, 
StandardCharsets.UTF_8)) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                lines++;
+                if (lines < from || full) {
+                    continue;
+                }
+                if (lines - from >= max || window.length() + line.length() + 1 
> READ_CHARS) {
+                    if (window.isEmpty()) {
+                        // one line longer than an answer may be
+                        window.append(line, 0, Math.min(line.length(), 
READ_CHARS)).append('\n');
+                        to = lines;
+                        cutLine = line.length() > READ_CHARS;
+                    }
+                    full = true;
+                    continue;
+                }
+                window.append(line).append('\n');
+                to = lines;
+            }
+        } catch (IOException e) {
+            throw new ToolExecutionException("Cannot read " + path + ": " + 
e.getMessage());
+        }
+        if (from == 1 && to == lines && !cutLine) {
+            // all of it, as it is in the file (its line endings, and the end 
without a newline)
+            result.put("content", read(path));
+            return result;
+        }
+        result.put("content", window.toString());
+        result.put("lines", lines);
+        if (to < from) {
+            result.put("message", "The file has " + lines + " lines, so there 
are none from line " + from);
+            return result;
+        }
+        result.put("from", from);
+        result.put("to", to);
+        StringBuilder message = new StringBuilder("The content is lines " + 
from + " to " + to + " of " + lines);
+        if (cutLine) {
+            message.append(" (line ").append(to).append(" is cut after 
").append(READ_CHARS).append(" characters)");
+        }
+        if (to < lines) {
+            message.append("; call camel_get_files with offset=").append(to + 
1).append(" for the next lines");
+        }
+        result.put("message", message.toString());
         return result;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionList.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionList.java
index b582e28e0ca4..8042c5a714b4 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionList.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionList.java
@@ -124,8 +124,9 @@ public class VersionList extends CamelCommand {
     @Option(name = "sort", description = "Sort by (version, date, or days)", 
defaultValue = "version")
     public String sort;
 
+    // set also when the command is created without its command line, as the 
MCP server does
     @Mixin
-    public MavenResolverMixin mavenResolver;
+    public MavenResolverMixin mavenResolver = new MavenResolverMixin();
 
     @Option(name = "vendor", description = "Vendor of Apache Camel 
distribution to use when filtering versions")
     public String vendor;
@@ -156,7 +157,7 @@ public class VersionList extends CamelCommand {
     public boolean jsonOutput;
 
     @Mixin
-    QuarkusExtensionRegistryMixin quarkusExtensionRegistry;
+    QuarkusExtensionRegistryMixin quarkusExtensionRegistry = new 
QuarkusExtensionRegistryMixin();
 
     public VersionList(CamelJBangMain main) {
         super(main);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsTest.java
index 27f651ce818d..0f7c71a2b00a 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringToolsTest.java
@@ -149,6 +149,63 @@ class AuthoringToolsTest {
                 Map.of("directory", dir.toString(), "file", "missing.yaml")));
     }
 
+    @Test
+    void aLargeFileIsReadInParts(@TempDir Path dir) throws IOException {
+        // a 3.6 MB file was an answer of 4.5 MB, about a million tokens 
(CAMEL-26932)
+        StringBuilder sb = new StringBuilder();
+        for (int i = 1; i <= 5000; i++) {
+            sb.append("line ").append(i).append('\n');
+        }
+        Files.writeString(dir.resolve("big.log"), sb.toString());
+
+        JsonObject first = call("camel_get_files", new ToolContext(), 
Map.of("directory", dir.toString(), "file", "big.log"));
+        assertTrue(first.getString("content").startsWith("line 1\nline 2\n"));
+        assertTrue(first.getString("content").endsWith("line 2000\n"));
+        assertEquals(5000, first.getInteger("lines"));
+        assertEquals(1, first.getInteger("from"));
+        assertEquals(2000, first.getInteger("to"));
+        assertEquals("The content is lines 1 to 2000 of 5000; call 
camel_get_files with offset=2001 for the next lines",
+                first.getString("message"));
+
+        JsonObject part = call("camel_get_files", new ToolContext(),
+                Map.of("directory", dir.toString(), "file", "big.log", 
"offset", 4999, "limit", 10));
+        assertEquals("line 4999\nline 5000\n", part.getString("content"));
+        assertEquals("The content is lines 4999 to 5000 of 5000", 
part.getString("message"));
+
+        JsonObject past = call("camel_get_files", new ToolContext(),
+                Map.of("directory", dir.toString(), "file", "big.log", 
"offset", 6000));
+        assertEquals("", past.getString("content"));
+        assertEquals("The file has 5000 lines, so there are none from line 
6000", past.getString("message"));
+
+        assertThrows(ToolExecutionException.class, () -> 
ToolRegistry.execute("camel_get_files", new ToolContext(),
+                Map.of("directory", dir.toString(), "file", "big.log", 
"offset", "ten")));
+    }
+
+    @Test
+    void aSmallFileIsReadAsItIs(@TempDir Path dir) throws IOException {
+        // line endings and no newline at the end, as they are in the file
+        Files.writeString(dir.resolve("crlf.properties"), "a=1\r\nb=2");
+
+        JsonObject file = call("camel_get_files", new ToolContext(),
+                Map.of("directory", dir.toString(), "file", 
"crlf.properties"));
+
+        assertEquals("a=1\r\nb=2", file.getString("content"));
+        assertNull(file.get("message"));
+        assertNull(file.get("lines"));
+    }
+
+    @Test
+    void aLineLongerThanAnAnswerIsCut(@TempDir Path dir) throws IOException {
+        Files.writeString(dir.resolve("one.json"), 
"x".repeat(AuthoringTools.READ_CHARS + 10) + "\nsecond\n");
+
+        JsonObject file = call("camel_get_files", new ToolContext(), 
Map.of("directory", dir.toString(), "file", "one.json"));
+
+        assertEquals(AuthoringTools.READ_CHARS + 1, 
file.getString("content").length());
+        assertEquals("The content is lines 1 to 1 of 2 (line 1 is cut after " 
+ AuthoringTools.READ_CHARS
+                     + " characters); call camel_get_files with offset=2 for 
the next lines",
+                file.getString("message"));
+    }
+
     /** A file consumer moves the files it read into .camel, which the listing 
skips: the folder looked empty. */
     @Test
     void filesAFileConsumerMovedIntoDotCamelAreCounted(@TempDir Path dir) 
throws IOException {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionListTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionListTest.java
index e698d1e04716..17f92b4c1d6b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionListTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionListTest.java
@@ -30,6 +30,16 @@ import org.junit.jupiter.api.Test;
 
 class VersionListTest extends CamelCommandBaseTestSupport {
 
+    @Test
+    void aVersionListCreatedWithoutItsCommandLineHasItsOptionGroups() {
+        // the MCP server creates the command itself: camel_version_list 
failed with a NullPointerException
+        VersionList versionList = new VersionList(new 
CamelJBangMain().withPrinter(printer));
+
+        Assertions.assertThat(versionList.mavenResolver).isNotNull();
+        Assertions.assertThat(versionList.mavenResolver.download()).isTrue();
+        
Assertions.assertThat(versionList.quarkusExtensionRegistry).isNotNull();
+    }
+
     @Test
     void springbootVersionIsAvailable() throws Exception {
         VersionList versionList = new VersionList(new 
CamelJBangMain().withPrinter(printer));
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringTools.java
index ca07981350f4..de114e097042 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringTools.java
@@ -191,8 +191,13 @@ public class AuthoringTools {
             @ToolArg(description = DIRECTORY_DESC, required = false) String 
directory,
             @ToolArg(description = "File path relative to the directory, e.g. 
src/main/resources/camel/foo.camel.yaml,"
                                    + " to read; omitted lists the files",
-                     required = false) String file) {
-        return call("camel_get_files", args("directory", directory, "file", 
file), null, clientRoot(roots));
+                     required = false) String file,
+            @ToolArg(description = "The first line of the file to read, from 1 
(default 1). A large file comes in parts:"
+                                   + " the answer says the offset of the next 
part",
+                     required = false) Integer offset,
+            @ToolArg(description = "How many lines of the file to read 
(default 2000)", required = false) Integer limit) {
+        return call("camel_get_files", args("directory", directory, "file", 
file, "offset", offset, "limit", limit), null,
+                clientRoot(roots));
     }
 
     @ToolGuardrails(input = McpArgumentGuardrail.class, output = 
McpArgumentGuardrail.class)
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
index 53d740948ed7..4ddb56f9eeb2 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
@@ -44,7 +44,8 @@ public class VersionTools {
      * Tool to list available Camel versions for a specific runtime.
      */
     @ToolGuardrails(input = McpArgumentGuardrail.class, output = 
McpArgumentGuardrail.class)
-    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+    // it downloads the release lists from Maven Central
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = true),
           description = "List available Camel versions for a specific runtime 
(main, spring-boot, quarkus). " +
                         "Returns version information including release date, 
JDK requirements, and LTS status.")
     public VersionListResult camel_version_list(
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringToolsRootsTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringToolsRootsTest.java
index 52b86dcb0437..909b89515acd 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringToolsRootsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringToolsRootsTest.java
@@ -78,7 +78,7 @@ class AuthoringToolsRootsTest {
     void aRelativeFileIsReadAndWrittenInTheClientsRoot() throws Exception {
         FakeRoots roots = FakeRoots.answering(List.of(new Root("project", 
workspace.toUri().toString())));
 
-        JsonObject read = tools.camel_get_files(roots, null, 
"camel/main-route.yaml");
+        JsonObject read = tools.camel_get_files(roots, null, 
"camel/main-route.yaml", null, null);
         assertThat(read.getString("content")).isEqualTo(ROUTE);
         JsonObject validated = tools.camel_validate_source(null, roots, null, 
"camel/main-route.yaml", null, null);
         
assertThat(validated.getBoolean("valid")).as(validated.toJson()).isTrue();
@@ -99,9 +99,9 @@ class AuthoringToolsRootsTest {
         Path outside = workspace.resolve("other.yaml");
         Files.writeString(outside, ROUTE, StandardCharsets.UTF_8);
 
-        assertThatThrownBy(() -> tools.camel_get_files(roots, null, 
outside.toString()))
+        assertThatThrownBy(() -> tools.camel_get_files(roots, null, 
outside.toString(), null, null))
                 
.isInstanceOf(ToolCallException.class).hasMessageContaining("is not inside the 
directory");
-        assertThatThrownBy(() -> tools.camel_get_files(roots, null, 
"../other.yaml"))
+        assertThatThrownBy(() -> tools.camel_get_files(roots, null, 
"../other.yaml", null, null))
                 
.isInstanceOf(ToolCallException.class).hasMessageContaining("file must stay 
inside the directory");
     }
 
@@ -109,9 +109,9 @@ class AuthoringToolsRootsTest {
     void theRootsAreAskedForOnlyWhenTheCallNamesNoDirectory() {
         FakeRoots roots = FakeRoots.answering(List.of(new Root("project", 
workspace.toUri().toString())));
 
-        tools.camel_get_files(roots, workspace.toString(), 
"camel/main-route.yaml");
+        tools.camel_get_files(roots, workspace.toString(), 
"camel/main-route.yaml", null, null);
         assertThat(roots.asked).hasValue(0);
-        tools.camel_get_files(roots, null, 
workspace.resolve("camel/main-route.yaml").toString());
+        tools.camel_get_files(roots, null, 
workspace.resolve("camel/main-route.yaml").toString(), null, null);
         assertThat(roots.asked).as("an absolute file inside the 
root").hasValue(1);
     }
 
@@ -121,7 +121,7 @@ class AuthoringToolsRootsTest {
             throw new AssertionError("a client that did not declare roots is 
not asked for them");
         });
 
-        assertThatThrownBy(() -> tools.camel_get_files(roots, null, 
"camel/main-route.yaml"))
+        assertThatThrownBy(() -> tools.camel_get_files(roots, null, 
"camel/main-route.yaml", null, null))
                 
.isInstanceOf(ToolCallException.class).hasMessageStartingWith("directory is 
required");
         assertThat(roots.asked).hasValue(0);
     }
@@ -139,7 +139,7 @@ class AuthoringToolsRootsTest {
         FakeRoots silent = new FakeRoots(true, () -> 
Uni.createFrom().nothing());
 
         long start = System.nanoTime();
-        assertThatThrownBy(() -> tools.camel_get_files(silent, null, 
"camel/main-route.yaml"))
+        assertThatThrownBy(() -> tools.camel_get_files(silent, null, 
"camel/main-route.yaml", null, null))
                 
.isInstanceOf(ToolCallException.class).hasMessageStartingWith("directory is 
required");
         assertThat(System.nanoTime() - 
start).isLessThan(AuthoringTools.ROOTS_WAIT.plusSeconds(10).toNanos());
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringToolsTest.java
index c7e1b4f6a3b2..bdefe36df6ca 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringToolsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringToolsTest.java
@@ -103,12 +103,13 @@ class AuthoringToolsTest {
         assertThat(invalid.getString("status")).isEqualTo("invalid");
         assertThat(Files.readString(dir.resolve("demo.camel.yaml"), 
StandardCharsets.UTF_8)).isEqualTo(route);
 
-        JsonObject listed = tools.camel_get_files(null, dir.toString(), null);
+        JsonObject listed = tools.camel_get_files(null, dir.toString(), null, 
null, null);
         assertThat(listed.getInteger("totalFiles")).isEqualTo(1);
-        assertThat(tools.camel_get_files(null, dir.toString(), 
"demo.camel.yaml").getString("content")).isEqualTo(route);
+        assertThat(tools.camel_get_files(null, dir.toString(), 
"demo.camel.yaml", null, null).getString("content"))
+                .isEqualTo(route);
         assertThat(tools.camel_validate_source(null, null, dir.toString(), 
"demo.camel.yaml", null, null).getBoolean("valid"))
                 .isTrue();
-        assertThatThrownBy(() -> tools.camel_get_files(null, null, null))
+        assertThatThrownBy(() -> tools.camel_get_files(null, null, null, null, 
null))
                 
.isInstanceOf(ToolCallException.class).hasMessageContaining("directory is 
required");
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelPromptBudgetTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelPromptBudgetTest.java
index 16403c755bae..d3d8512a4d59 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelPromptBudgetTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AiPanelPromptBudgetTest.java
@@ -76,7 +76,9 @@ class AiPanelPromptBudgetTest {
     // raised from 10600 for tui_diagram_zoom, measured ~10731 (~168 for the 
tool): a model asked to zoom or fold the
     // diagram does it as the keys do; full mode only, its schema cut to the 
bone
     // raised from 10750 with the core budget for camel_eval_expression 
(CAMEL-26400), measured ~10844
-    static final int FULL_BUDGET_TOKENS = 10_900;
+    // raised from 10900 for the offset and limit arguments of camel_get_files 
(CAMEL-26932), measured ~10922: a large
+    // file comes in parts, as a 3.6 MB file was an answer of about a million 
tokens
+    static final int FULL_BUDGET_TOKENS = 10_950;
     /** Measured ~5.5k tokens for 28 tools: the core set (~4.7k) plus every 
tool group (CAMEL-24834). */
     // the SQL group adds tui_execute_sql and tui_update_row (~385 tokens), 
each group one guidance line in the prompt
     // (~130 for all three); an integration rarely has all of them, and the 
groups only load for the integration that
@@ -86,7 +88,8 @@ class AiPanelPromptBudgetTest {
     // it is building
     // raised from 5750 with the core budget for camel_catalog_doc 
(CAMEL-25541), measured ~5820
     // raised from 5900 with the core budget for camel_eval_expression 
(CAMEL-26400), measured ~6011
-    static final int CORE_WITH_GROUPS_BUDGET_TOKENS = 6_050;
+    // raised from 6050 for the offset and limit arguments of camel_get_files 
(CAMEL-26932), measured ~6069
+    static final int CORE_WITH_GROUPS_BUDGET_TOKENS = 6_100;
 
     record Prefix(String mode, int tools, long promptChars, long toolChars) {
 
@@ -192,8 +195,9 @@ class AiPanelPromptBudgetTest {
             System.out.println("AI panel semantic prefix: " + prefix);
             // core raised from 6100 with the core budget for 
camel_catalog_doc (CAMEL-25541), measured ~6170; full
             // raised from 11000 for tui_diagram_zoom, measured ~11068; both 
raised by 150 with the core budget for
-            // camel_eval_expression (CAMEL-26400), measured ~6361 and ~11182
-            assertTrue(prefix.totalTokens() <= 
(AiPanel.TOOL_MODE_CORE.equals(mode) ? 6400 : 11250));
+            // camel_eval_expression (CAMEL-26400), measured ~6361 and ~11182; 
both raised by 50 for the offset and
+            // limit arguments of camel_get_files (CAMEL-26932), measured 
~6419 and ~11259
+            assertTrue(prefix.totalTokens() <= 
(AiPanel.TOOL_MODE_CORE.equals(mode) ? 6450 : 11300));
         }
     }
 

Reply via email to