oscerd commented on code in PR #27264:
URL: https://github.com/apache/camel/pull/27264#discussion_r4176961858


##########
components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java:
##########
@@ -175,4 +176,28 @@ private static AiToolResult buildSuccessResult(AiToolSpec 
spec, Exchange exchang
                     String.format("Error executing tool '%s': %s", 
spec.getName(), e.getMessage()), e);
         }
     }
+
+    /**
+     * Builds the exchange used to invoke a route tool from the calling 
(agent) exchange. The caller's <em>context</em>
+     * is carried over - exchange properties (most importantly the 
authenticated caller's identity, so a tool route can
+     * be guarded on {@code exchangeProperty.subject} and the model cannot 
forge it) and variables - but the tool route
+     * is given a <em>clean message</em>: it receives only its own tool 
arguments (set as headers by
+     * {@link #execute(AiToolSpec, Map, Exchange)}), not the caller's body or 
inbound headers, and a tool that sets no
+     * body returns {@code No result} rather than echoing the caller's body 
back to the model. Changes the tool makes
+     * are isolated to this copy and do not leak back into the calling 
exchange. Every route-tool runtime
+     * (langchain4j-agent, openai, spring-ai-chat) builds the tool exchange 
this way, so an authorization check on an
+     * exchange property behaves identically across them (CAMEL-24832, 
CAMEL-23944).
+     *
+     * @param  callingExchange the exchange driving the agent
+     * @return                 an isolated copy, carrying the caller's 
properties and variables but a clean message, to
+     *                         pass to {@link #execute(AiToolSpec, Map, 
Exchange)}
+     */
+    public static Exchange createToolExchange(Exchange callingExchange) {
+        // copy carries properties and variables; then wipe the message so the 
tool route starts from its arguments
+        // only, not the caller's body/headers
+        Exchange toolExchange = ExchangeHelper.createCopy(callingExchange, 
true);
+        toolExchange.getMessage().setBody(null);

Review Comment:
   Thanks — applied your fix. `createToolExchange` now does 
`callingExchange.copy()` + `getExchangeExtension().setUnitOfWork(null)` and 
then clears the message, so the tool exchange runs in its **own** unit of work 
and with its **own** exchange id (the `ExchangeHelper` import is gone).
   
   That settles all three rows you found: the tool route's own `onCompletion` 
fires, parallel tool calls get distinct ids, and — the important one — an error 
handler's `useOriginalMessage()` now restores the tool's own clean message 
rather than the caller's, so the clean-message guarantee holds through a 
dead-letter path.
   
   Added a regression test 
`AiToolExecutorTest.createToolExchangeGivesTheToolRouteItsOwnUnitOfWork`: a 
tool route with 
`errorHandler(deadLetterChannel("mock:dlq").useOriginalMessage())` that throws 
— the DLQ message body is `null` (the tool's own clean original, not the 
caller's `caller-prompt`/headers) and the tool result is `No result`. The 
existing helper test now also asserts a distinct exchange id. Full 
camel-ai-tool suite (120) and camel-openai `McpToolCallExecutorTest` (12) stay 
green. The "The calling exchange" doc section and the 4.23 upgrade guide now 
state the tool route runs in its own unit of work and id.
   
   _Claude Code on behalf of oscerd_
   



##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/McpToolCallExecutor.java:
##########
@@ -109,7 +109,7 @@ protected void doStop() throws Exception {
      * @return           one result per tool call, in the same order
      * @throws Exception when a tool call fails and the configured strategy is 
to fail the exchange
      */
-    List<ToolResult> execute(List<ChatCompletionMessageToolCall> toolCalls) 
throws Exception {
+    List<ToolResult> execute(List<ChatCompletionMessageToolCall> toolCalls, 
Exchange callingExchange) throws Exception {

Review Comment:
   Added — `McpToolCallExecutor.execute` now documents `@param callingExchange` 
(the exchange driving the agent loop, copied into each route-tool invocation so 
the caller's context — properties, variables — reaches the tool route). Thanks 
for the persistence.
   
   _Claude Code on behalf of oscerd_
   



-- 
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