Croway opened a new pull request, #28683:
URL: https://github.com/apache/camel/pull/28683

   _Claude Code on behalf of Croway_
   
   [CAMEL-26400](https://issues.apache.org/jira/browse/CAMEL-26400)
   
   ## Problem
   
   `camel_eval_expression` takes only `expression`, `language`, `body` and 
`name`, so it cannot evaluate an expression the
   way the route does:
   
   - the XPath language evaluates to nodes by default, so a function such as 
`string()`, `local-name()`, `boolean()` or
     `count()` fails with `Can not convert #STRING to a NodeList!`; in a route 
it runs with `resultType: java.lang.String`
     or as a predicate;
   - predicate mode is guessed for simple only, so an XPath or jsonpath 
predicate is always evaluated as an expression;
   - the scratch exchange has no headers or variables, so `headers.x` (groovy) 
or `variable("x")` (jq) is null, and a
     `headers` argument is silently ignored;
   - a local XPath that works shows the class of the node list 
(`DTMNodeList@60b333fd`), because the local path answers
     `toString()` of the value, while the integration converts it to String.
   
   ## Fix
   
   - `camel_eval_expression` (shared tool and MCP wrapper) gets four optional 
arguments:
     - `headers` and `variables`: a JSON object, whose values keep their JSON 
type (so the rows of a query work for a
       groovy `headers.rows[0].total`), or `key=value` lines as 
`camel_runtime_send` takes headers (same parser). The MCP
       wrapper declares them as `Object`, so the schema takes any JSON: a 
client can send an object or a string;
     - `predicate`: evaluate as a predicate in any language; when absent, 
simple keeps its guess and the other languages
       are expressions;
     - `resultType`: a class name passed to the language as its `resultType` 
option, as the reifiers pass it
       (`createExpression(text, new Object[] { type })`); the context's class 
resolver resolves it, so `String` and
       `Integer` work as in a route.
   - `ExpressionEvaluator` takes a `Request` record. Locally the headers and 
variables are set on the scratch exchange,
     and the value is converted to String by the context's type converter 
(falling back to `toString()`), as the
     integration does, so a node list shows its XML.
   - In the integration: the eval action gets the headers and variables in its 
`{key, value}` form, `predicate` and
     `resultType`. `EvalLanguageDevConsole` has a new `resultType` option (text 
and JSON), and
     `LocalCliConnector.doActionEvalTask` passes it on and keeps the JSON type 
of a header or variable value (it read
     every value with `getString`). An integration before Camel 4.24 gets a 
list or object value as JSON text, and the
     answer notes that it ignores `resultType`.
   - An XPath that fails with `Can not convert #STRING / #BOOLEAN / #NUMBER to 
a NodeList` gets a `hint` with the
     argument to pass and the route form, e.g. `this XPath returns a string, 
not nodes: pass resultType java.lang.String
     (in a route: xpath: {expression: "local-name(...)", resultType: 
java.lang.String})`; `predicate=true` for a boolean,
     `java.lang.Integer` or `java.lang.Double` for a number. In the integration 
the failed eval action is a tool error
     (CAMEL-25398), so there the hint is appended to the error message.
   
   | Call | Before | After |
   |---|---|---|
   | xpath `boolean(/*[local-name()='x'])` | error `Can not convert #BOOLEAN to 
a NodeList!` | same error, with the hint `pass predicate=true` |
   | same, `predicate: true` | (no such argument) | `true` |
   | xpath `local-name(/*[local-name()='x']/*[1])` | error `#STRING to a 
NodeList!` | same error, with the hint `pass resultType java.lang.String` |
   | same, `resultType: java.lang.String` | (no such argument) | `y` |
   | xpath `/x/y` on `<x><y>1</y></x>` | 
`com.sun.org.apache.xml.internal.dtm.ref.DTMNodeList@60b333fd` | `<y>1</y>` |
   | groovy `headers.totalOrdersRows[0].total_orders as long`, `headers: 
{"totalOrdersRows": [{"total_orders": 21}]}` | `Cannot invoke method getAt() on 
null object` | the value |
   | jq `(variable("requestBodyJson") \| fromjson).products[]`, `variables: 
{"requestBodyJson": "..."}` | `null (null) only strings can be parsed` | the 
list |
   | `${header.foo}`, `headers: {"foo": "bar"}` | ok, no result (argument 
ignored) | `bar` |
   | `${header.foo}`, `headers: "foo=bar"` | ok, no result | `bar` |
   
   ## Testing
   
   New tests (each fails on `origin/main`: the camel-jbang-core and MCP ones do 
not compile there, as the arguments
   and `ExpressionEvaluator.Request` are new; the console and connector ones 
were run against the main code of their
   module and fail):
   
   - `ExpressionEvaluatorRequestTest` (local): xpath `boolean()` errors with 
the `predicate=true` hint and gives `true`
     with `predicate=true`; `local-name()` gives the `resultType` hint and `y` 
with `java.lang.String` (and `String`);
     `count()` gets the number hint and `2` with `java.lang.Integer`; `/x/y[1]` 
shows `<y>1</y>`; an unknown resultType
     says what to pass; a predicate notes that resultType is not used; groovy 
reads the rows of a JSON header; jq reads a
     variable with `fromjson`; key=value headers and variables; invalid headers 
say why; the registry tool takes the
     four arguments as text. camel-xpath, camel-groovy and camel-jq are test 
dependencies of camel-jbang-core for this.
   - `EvalInTheIntegrationTest` (in-process, through the test integration and 
the real `EvalLanguageDevConsole`): the
     action carries headers and variables as `{key, value}`, `predicate` and 
`resultType`; a JSON list keeps its type;
     an integration of Camel 4.23.0 (status file) gets the list as JSON text 
and the note on resultType; a failed XPath
     of the integration gets the hint in the tool error.
   - `EvalLanguageDevConsoleTest.testEvalWithResultType`: `${body}` of `0042` 
with `java.lang.Integer` (and `Integer`)
     gives `42`, in JSON and text; an unknown class fails with its name. Fails 
on main (`0042`).
   - `LocalCliConnectorEvalActionTest`: the eval action passes resultType, and 
a list header and a number variable keep
     their type (`${header.rows.size()} ${variable.count}` gives `1 7`). Both 
fail on main (`0042`, and a
     `ClassCastException` on `getString` of the list).
   - MCP `AuthoringToolsTest`: the wrapper passes a JSON object header, 
key=value variables, `predicate` and
     `resultType`; `jsonText` serializes an object argument.
   
   Runs (`mvnd -P apache-snapshots test` over 
`core/camel-console,dsl/camel-cli-connector,dsl/camel-jbang/camel-jbang-core,dsl/camel-jbang/camel-jbang-mcp`):
   
   | Module | Tests | Result |
   |---|---|---|
   | camel-console (`EvalLanguageDevConsoleTest`) | 7 | pass |
   | camel-cli-connector (`LocalCliConnector*Test`) | 29 | pass |
   | camel-jbang-core (`commands/ai/**`, `common/*`, `AskToolsTest`) | 1214 | 
pass (2 skipped) |
   | camel-jbang-mcp (whole module) | 507 | pass |
   | camel-jbang-plugin-tui (`AiPanel*Test`, `TuiToolRegistry*Test`, 
`McpFacadeConfirmTest`, `TuiMcpServer*Test`) | 202 | pass (1 skipped) |
   
   `AiPanelPromptBudgetTest`: the four arguments cost ~114 tokens in every 
prefix (core 5119 -> 5233, core with groups
   5898 -> 6011, full 10731 -> 10844, semantic core 6248 -> 6361). Main was 
within 2 tokens of two budgets, so the
   budgets are raised with a comment.
   
   End to end, with the MCP server runner jar built from the branch (stdio 
client):
   
   - local: every call of the table above gives the answer in the After column;
   - in a running Camel 4.23.0 integration (`camel run`, selected by name): the 
XPath errors carry the hint, and for
     resultType the note that 4.23.0 ignores it; `predicate: true` gives 
`true`; a JSON object header and key=value
     variables give `x-y`; a list header arrives as JSON text; `resultType` 
with `0042` gives `0042` with the note.
   - the input schema of `headers` and `variables` has no `type` (the wrapper 
declares `Object`), so a client can send
     a JSON object or a string; both work.
   
   ## Notes for reviewers
   
   - `headers` and `variables` are `Object` in the MCP wrapper, so a JSON 
object reaches the tool as a map and is not
     checked by the string sanitising of `McpSecurityInterceptor` (length 
limit, control characters), the same as the
     `edits` of `camel_edit_file`. The values only become headers and variables 
of the one evaluation.
   - The local value now goes through the type converter, as in the 
integration: a jq result is shown indented
     (`[ "A", "B" ]` where it was `["A","B"]`), and other object results can 
read differently than their `toString()`.
   - camel-xpath, camel-groovy and camel-jq are test dependencies of 
camel-jbang-core, so a change in one of them also
     runs the camel-jbang-core tests on CI.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to