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

Croway pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.18.x by this push:
     new e3dc728b8594 CAMEL-23740: Fix WrappedFile converter for FTP/SFTP 
remote files
e3dc728b8594 is described below

commit e3dc728b8594f8381ba44e4c100dac1c09f755e6
Author: Croway <[email protected]>
AuthorDate: Thu Jun 11 17:11:29 2026 +0200

    CAMEL-23740: Fix WrappedFile converter for FTP/SFTP remote files
---
 .../agent/LangChain4jAgentConverter.java           |  66 ++++++++---
 .../agent/LangChain4jAgentConverterTest.java       | 122 +++++++++++++++++++++
 2 files changed, 174 insertions(+), 14 deletions(-)

diff --git 
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverter.java
 
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverter.java
index 07ec8ca41e80..eb5e0ddf3e92 100644
--- 
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverter.java
+++ 
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverter.java
@@ -34,6 +34,7 @@ import dev.langchain4j.data.pdf.PdfFile;
 import dev.langchain4j.data.video.Video;
 import org.apache.camel.Converter;
 import org.apache.camel.Exchange;
+import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.WrappedFile;
 import org.apache.camel.component.langchain4j.agent.api.AiAgentBody;
 import org.slf4j.Logger;
@@ -94,18 +95,9 @@ public final class LangChain4jAgentConverter {
      */
     @Converter
     public static AiAgentBody<?> toAiAgentBody(WrappedFile<?> wrappedFile, 
Exchange exchange) {
-        Object fileObj = wrappedFile.getFile();
-        if (fileObj == null) {
-            throw new IllegalArgumentException("WrappedFile contains null 
file");
-        }
-        if (!(fileObj instanceof File)) {
-            throw new IllegalArgumentException(
-                    "WrappedFile must contain a java.io.File instance, got: " 
+ fileObj.getClass().getName());
-        }
-
-        File file = (File) fileObj;
-        String mimeType = detectMimeType(file, exchange);
-        byte[] fileData = readFileBytes(file);
+        String fileName = resolveFileName(wrappedFile, exchange);
+        byte[] fileData = resolveFileData(wrappedFile, exchange);
+        String mimeType = detectMimeType(fileName, exchange);
         Content content = createContent(fileData, mimeType);
 
         String userMessage = exchange.getIn().getHeader(USER_MESSAGE, 
String.class);
@@ -121,6 +113,45 @@ public final class LangChain4jAgentConverter {
         return body;
     }
 
+    private static String resolveFileName(WrappedFile<?> wrappedFile, Exchange 
exchange) {
+        String headerName = exchange.getIn().getHeader(Exchange.FILE_NAME, 
String.class);
+        if (headerName != null) {
+            return headerName;
+        }
+        Object fileObj = wrappedFile.getFile();
+        if (fileObj instanceof File file) {
+            return file.getName();
+        }
+        Object body = wrappedFile.getBody();
+        if (body instanceof File file) {
+            return file.getName();
+        }
+        return null;
+    }
+
+    private static byte[] resolveFileData(WrappedFile<?> wrappedFile, Exchange 
exchange) {
+        Object fileObj = wrappedFile.getFile();
+        if (fileObj instanceof File file) {
+            return readFileBytes(file);
+        }
+        Object body = wrappedFile.getBody();
+        if (body instanceof File file) {
+            return readFileBytes(file);
+        }
+        if (body instanceof byte[] bytes) {
+            return bytes;
+        }
+        if (body != null) {
+            try {
+                return 
exchange.getContext().getTypeConverter().mandatoryConvertTo(byte[].class, 
exchange, body);
+            } catch (NoTypeConversionAvailableException e) {
+                throw new IllegalArgumentException(
+                        "WrappedFile body could not be converted to byte[]: " 
+ body.getClass().getName(), e);
+            }
+        }
+        throw new IllegalArgumentException("WrappedFile body is null");
+    }
+
     /**
      * Converts a {@code byte[]} to an {@link AiAgentBody} with the 
appropriate {@link Content} type.
      * <p>
@@ -240,7 +271,7 @@ public final class LangChain4jAgentConverter {
      * <li>Auto-detection from file extension</li>
      * </ol>
      */
-    private static String detectMimeType(File file, Exchange exchange) {
+    private static String detectMimeType(String fileName, Exchange exchange) {
         // Check agent-specific header first (highest priority)
         String mediaType = exchange.getIn().getHeader(MEDIA_TYPE, 
String.class);
         if (mediaType != null) {
@@ -253,8 +284,14 @@ public final class LangChain4jAgentConverter {
             return fileContentType;
         }
 
+        if (fileName == null) {
+            throw new IllegalArgumentException(
+                    "Cannot determine MIME type: no file name available. "
+                                               + "Set the 
CamelLangChain4jAgentMediaType, CamelFileContentType, or CamelFileName 
header.");
+        }
+
         // Auto-detect from file extension
-        return detectMimeTypeFromExtension(file.getName());
+        return detectMimeTypeFromExtension(fileName);
     }
 
     /**
@@ -405,4 +442,5 @@ public final class LangChain4jAgentConverter {
             throw new IllegalArgumentException("Failed to read file: " + 
file.getAbsolutePath(), e);
         }
     }
+
 }
diff --git 
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverterTest.java
 
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverterTest.java
new file mode 100644
index 000000000000..2c01e7091cae
--- /dev/null
+++ 
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverterTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.agent;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import dev.langchain4j.data.message.ImageContent;
+import dev.langchain4j.data.message.TextContent;
+import org.apache.camel.Exchange;
+import org.apache.camel.TypeConversionException;
+import org.apache.camel.component.file.GenericFile;
+import org.apache.camel.component.langchain4j.agent.api.AiAgentBody;
+import org.apache.camel.component.langchain4j.agent.api.Headers;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class LangChain4jAgentConverterTest extends CamelTestSupport {
+
+    @TempDir
+    Path tempDir;
+
+    @Test
+    void shouldConvertLocalFile() throws Exception {
+        Exchange exchange = 
context.getEndpoint("direct:test").createExchange();
+
+        File file = Files.writeString(tempDir.resolve("hello.txt"), "Hello 
file").toFile();
+
+        GenericFile<File> genericFile = new GenericFile<>();
+        genericFile.setFile(file);
+        genericFile.setFileName(file.getName());
+
+        AiAgentBody<?> body = context.getTypeConverter()
+                .convertTo(AiAgentBody.class, exchange, genericFile);
+
+        assertNotNull(body);
+        assertInstanceOf(TextContent.class, body.getContent());
+    }
+
+    @Test
+    void shouldConvertRemoteWrappedFileUsingFileNameHeader() {
+        Exchange exchange = 
context.getEndpoint("direct:test").createExchange();
+        exchange.getIn().setHeader(Exchange.FILE_NAME, "photo.png");
+
+        GenericFile<String> remoteFile = new GenericFile<>();
+        remoteFile.setFile("remote-handle");
+        remoteFile.setBody(new byte[] { 0x00, 0x01, 0x02 });
+
+        AiAgentBody<?> body = context.getTypeConverter()
+                .convertTo(AiAgentBody.class, exchange, remoteFile);
+
+        assertNotNull(body);
+        assertInstanceOf(ImageContent.class, body.getContent());
+    }
+
+    @Test
+    void shouldConvertRemoteWrappedFileWithMediaTypeHeader() {
+        Exchange exchange = 
context.getEndpoint("direct:test").createExchange();
+        exchange.getIn().setHeader(Headers.MEDIA_TYPE, "text/plain");
+
+        GenericFile<String> remoteFile = new GenericFile<>();
+        remoteFile.setFile("remote-handle");
+        remoteFile.setBody("Hello remote".getBytes());
+
+        AiAgentBody<?> body = context.getTypeConverter()
+                .convertTo(AiAgentBody.class, exchange, remoteFile);
+
+        assertNotNull(body);
+        assertInstanceOf(TextContent.class, body.getContent());
+    }
+
+    @Test
+    void shouldConvertRemoteWrappedFileWithInputStreamBody() {
+        Exchange exchange = 
context.getEndpoint("direct:test").createExchange();
+        exchange.getIn().setHeader(Exchange.FILE_NAME, "notes.txt");
+
+        GenericFile<String> remoteFile = new GenericFile<>();
+        remoteFile.setFile("remote-handle");
+        remoteFile.setBody(new ByteArrayInputStream("streamed 
content".getBytes()));
+
+        AiAgentBody<?> body = context.getTypeConverter()
+                .convertTo(AiAgentBody.class, exchange, remoteFile);
+
+        assertNotNull(body);
+        assertInstanceOf(TextContent.class, body.getContent());
+    }
+
+    @Test
+    void shouldFailForRemoteWrappedFileWithNoFileNameAndNoHeaders() {
+        Exchange exchange = 
context.getEndpoint("direct:test").createExchange();
+
+        GenericFile<String> remoteFile = new GenericFile<>();
+        remoteFile.setFile("remote-handle");
+        remoteFile.setBody("some data".getBytes());
+
+        assertThrows(
+                TypeConversionException.class,
+                () -> context.getTypeConverter()
+                        .convertTo(AiAgentBody.class, exchange, remoteFile));
+    }
+}

Reply via email to