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));
}
}