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 0f6476e73420 CAMEL-23739: camel-openai - support WrappedFile, byte[] 
and InputStream bodies for vision models
0f6476e73420 is described below

commit 0f6476e734207ba9cc7a74c489c7ad2a12a8d129
Author: croway <[email protected]>
AuthorDate: Thu Jun 11 19:54:57 2026 +0200

    CAMEL-23739: camel-openai - support WrappedFile, byte[] and InputStream 
bodies for vision models
---
 .../apache/camel/catalog/components/openai.json    |  31 +--
 components/camel-ai/camel-openai/pom.xml           |   5 +
 .../org/apache/camel/component/openai/openai.json  |  31 +--
 .../src/main/docs/openai-component.adoc            |  49 ++++-
 .../camel/component/openai/MimeTypeHelper.java     | 119 ++++++++++
 .../camel/component/openai/OpenAIConstants.java    |   5 +
 .../camel/component/openai/OpenAIProducer.java     | 105 ++++++---
 .../openai/OpenAIVisionBodyTypesMockTest.java      | 243 +++++++++++++++++++++
 .../endpoint/dsl/OpenAIEndpointBuilderFactory.java |  15 ++
 9 files changed, 542 insertions(+), 61 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
index ba3fbf984932..384b1446fa93 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
@@ -42,21 +42,22 @@
     "CamelOpenAIStreaming": { "index": 7, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "Boolean", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Whether to stream the response back incrementally", 
"constantName": "org.apache.camel.component.openai.OpenAIConstants#STREAMING" },
     "CamelOpenAIOutputClass": { "index": 8, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Class", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The Java class to use for structured output parsing", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#OUTPUT_CLASS" },
     "CamelOpenAIJsonSchema": { "index": 9, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The JSON schema to use for structured output 
validation", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#JSON_SCHEMA" },
-    "CamelOpenAIResponseModel": { "index": 10, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The model used for the completion 
response", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#RESPONSE_MODEL" },
-    "CamelOpenAIResponseId": { "index": 11, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The unique identifier for the completion response", 
"constantName": "org.apache.camel.component.openai.OpenAIConstants#RESPONSE_ID" 
},
-    "CamelOpenAIFinishReason": { "index": 12, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The reason the completion finished (e.g., stop, length, 
content_filter)", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#FINISH_REASON" },
-    "CamelOpenAIPromptTokens": { "index": 13, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The number of tokens used in the prompt", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#PROMPT_TOKENS" },
-    "CamelOpenAICompletionTokens": { "index": 14, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The number of tokens used in the 
completion", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#COMPLETION_TOKENS" },
-    "CamelOpenAITotalTokens": { "index": 15, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The total number of tokens used (prompt completion)", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#TOTAL_TOKENS" },
-    "CamelOpenAIResponse": { "index": 16, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": 
"com.openai.models.ChatCompletion", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The complete OpenAI 
response object", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#RESPONSE" },
-    "CamelOpenAIEmbeddingModel": { "index": 17, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The model to use for embeddings", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_MODEL" },
-    "CamelOpenAIEmbeddingDimensions": { "index": 18, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Number of output dimensions", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_DIMENSIONS" },
-    "CamelOpenAIEmbeddingResponseModel": { "index": 19, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The embedding model used in the 
response", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_RESPONSE_MODEL" },
-    "CamelOpenAIEmbeddingCount": { "index": 20, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Number of embeddings returned", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_COUNT" },
-    "CamelOpenAIEmbeddingVectorSize": { "index": 21, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Vector dimensions of the embeddings", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_VECTOR_SIZE" },
-    "CamelOpenAIReferenceEmbedding": { "index": 22, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "List<Float>", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Reference embedding vector 
for similarity comparison", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#REFERENCE_EMBEDDING" },
-    "CamelOpenAISimilarityScore": { "index": 23, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Calculated cosine similarity score (0.0 
to 1.0)", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#SIMILARITY_SCORE" },
-    "CamelOpenAIOriginalText": { "index": 24, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String or 
List<String>", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Original text content when embeddings 
operation is used", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#ORIGINAL_TEXT" }
+    "CamelOpenAIMediaType": { "index": 10, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The MIME type of the message body when sending a file or 
binary content (File, WrappedFile, byte or InputStream) to the model. Takes 
precedence over component content-type headers and automatic MIME type 
detection", "constantName": "org.apa [...]
+    "CamelOpenAIResponseModel": { "index": 11, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The model used for the completion 
response", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#RESPONSE_MODEL" },
+    "CamelOpenAIResponseId": { "index": 12, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The unique identifier for the completion response", 
"constantName": "org.apache.camel.component.openai.OpenAIConstants#RESPONSE_ID" 
},
+    "CamelOpenAIFinishReason": { "index": 13, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The reason the completion finished (e.g., stop, length, 
content_filter)", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#FINISH_REASON" },
+    "CamelOpenAIPromptTokens": { "index": 14, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The number of tokens used in the prompt", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#PROMPT_TOKENS" },
+    "CamelOpenAICompletionTokens": { "index": 15, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The number of tokens used in the 
completion", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#COMPLETION_TOKENS" },
+    "CamelOpenAITotalTokens": { "index": 16, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The total number of tokens used (prompt completion)", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#TOTAL_TOKENS" },
+    "CamelOpenAIResponse": { "index": 17, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": 
"com.openai.models.ChatCompletion", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The complete OpenAI 
response object", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#RESPONSE" },
+    "CamelOpenAIEmbeddingModel": { "index": 18, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The model to use for embeddings", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_MODEL" },
+    "CamelOpenAIEmbeddingDimensions": { "index": 19, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Number of output dimensions", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_DIMENSIONS" },
+    "CamelOpenAIEmbeddingResponseModel": { "index": 20, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The embedding model used in the 
response", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_RESPONSE_MODEL" },
+    "CamelOpenAIEmbeddingCount": { "index": 21, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Number of embeddings returned", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_COUNT" },
+    "CamelOpenAIEmbeddingVectorSize": { "index": 22, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Vector dimensions of the embeddings", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_VECTOR_SIZE" },
+    "CamelOpenAIReferenceEmbedding": { "index": 23, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "List<Float>", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Reference embedding vector 
for similarity comparison", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#REFERENCE_EMBEDDING" },
+    "CamelOpenAISimilarityScore": { "index": 24, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Calculated cosine similarity score (0.0 
to 1.0)", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#SIMILARITY_SCORE" },
+    "CamelOpenAIOriginalText": { "index": 25, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String or 
List<String>", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Original text content when embeddings 
operation is used", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#ORIGINAL_TEXT" }
   },
   "properties": {
     "operation": { "index": 0, "kind": "path", "displayName": "Operation", 
"group": "producer", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.openai.OpenAIOperations", "enum": [ 
"chat-completion", "embeddings" ], "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The operation to perform: 
'chat-completion' or 'embeddings'" },
diff --git a/components/camel-ai/camel-openai/pom.xml 
b/components/camel-ai/camel-openai/pom.xml
index 40a370122994..c9f7ff2d6113 100644
--- a/components/camel-ai/camel-openai/pom.xml
+++ b/components/camel-ai/camel-openai/pom.xml
@@ -52,6 +52,11 @@
             <artifactId>camel-test-junit5</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-file</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter</artifactId>
diff --git 
a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
 
b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
index ba3fbf984932..384b1446fa93 100644
--- 
a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
+++ 
b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
@@ -42,21 +42,22 @@
     "CamelOpenAIStreaming": { "index": 7, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": "Boolean", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Whether to stream the response back incrementally", 
"constantName": "org.apache.camel.component.openai.OpenAIConstants#STREAMING" },
     "CamelOpenAIOutputClass": { "index": 8, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Class", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The Java class to use for structured output parsing", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#OUTPUT_CLASS" },
     "CamelOpenAIJsonSchema": { "index": 9, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The JSON schema to use for structured output 
validation", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#JSON_SCHEMA" },
-    "CamelOpenAIResponseModel": { "index": 10, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The model used for the completion 
response", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#RESPONSE_MODEL" },
-    "CamelOpenAIResponseId": { "index": 11, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The unique identifier for the completion response", 
"constantName": "org.apache.camel.component.openai.OpenAIConstants#RESPONSE_ID" 
},
-    "CamelOpenAIFinishReason": { "index": 12, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The reason the completion finished (e.g., stop, length, 
content_filter)", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#FINISH_REASON" },
-    "CamelOpenAIPromptTokens": { "index": 13, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The number of tokens used in the prompt", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#PROMPT_TOKENS" },
-    "CamelOpenAICompletionTokens": { "index": 14, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The number of tokens used in the 
completion", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#COMPLETION_TOKENS" },
-    "CamelOpenAITotalTokens": { "index": 15, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The total number of tokens used (prompt completion)", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#TOTAL_TOKENS" },
-    "CamelOpenAIResponse": { "index": 16, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": 
"com.openai.models.ChatCompletion", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The complete OpenAI 
response object", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#RESPONSE" },
-    "CamelOpenAIEmbeddingModel": { "index": 17, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The model to use for embeddings", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_MODEL" },
-    "CamelOpenAIEmbeddingDimensions": { "index": 18, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Number of output dimensions", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_DIMENSIONS" },
-    "CamelOpenAIEmbeddingResponseModel": { "index": 19, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The embedding model used in the 
response", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_RESPONSE_MODEL" },
-    "CamelOpenAIEmbeddingCount": { "index": 20, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Number of embeddings returned", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_COUNT" },
-    "CamelOpenAIEmbeddingVectorSize": { "index": 21, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Vector dimensions of the embeddings", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_VECTOR_SIZE" },
-    "CamelOpenAIReferenceEmbedding": { "index": 22, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "List<Float>", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Reference embedding vector 
for similarity comparison", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#REFERENCE_EMBEDDING" },
-    "CamelOpenAISimilarityScore": { "index": 23, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Calculated cosine similarity score (0.0 
to 1.0)", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#SIMILARITY_SCORE" },
-    "CamelOpenAIOriginalText": { "index": 24, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String or 
List<String>", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Original text content when embeddings 
operation is used", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#ORIGINAL_TEXT" }
+    "CamelOpenAIMediaType": { "index": 10, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The MIME type of the message body when sending a file or 
binary content (File, WrappedFile, byte or InputStream) to the model. Takes 
precedence over component content-type headers and automatic MIME type 
detection", "constantName": "org.apa [...]
+    "CamelOpenAIResponseModel": { "index": 11, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The model used for the completion 
response", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#RESPONSE_MODEL" },
+    "CamelOpenAIResponseId": { "index": 12, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The unique identifier for the completion response", 
"constantName": "org.apache.camel.component.openai.OpenAIConstants#RESPONSE_ID" 
},
+    "CamelOpenAIFinishReason": { "index": 13, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The reason the completion finished (e.g., stop, length, 
content_filter)", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#FINISH_REASON" },
+    "CamelOpenAIPromptTokens": { "index": 14, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The number of tokens used in the prompt", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#PROMPT_TOKENS" },
+    "CamelOpenAICompletionTokens": { "index": 15, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The number of tokens used in the 
completion", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#COMPLETION_TOKENS" },
+    "CamelOpenAITotalTokens": { "index": 16, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "Integer", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The total number of tokens used (prompt completion)", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#TOTAL_TOKENS" },
+    "CamelOpenAIResponse": { "index": 17, "kind": "header", "displayName": "", 
"group": "producer", "label": "", "required": false, "javaType": 
"com.openai.models.ChatCompletion", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The complete OpenAI 
response object", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#RESPONSE" },
+    "CamelOpenAIEmbeddingModel": { "index": 18, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The model to use for embeddings", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_MODEL" },
+    "CamelOpenAIEmbeddingDimensions": { "index": 19, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Number of output dimensions", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_DIMENSIONS" },
+    "CamelOpenAIEmbeddingResponseModel": { "index": 20, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "The embedding model used in the 
response", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_RESPONSE_MODEL" },
+    "CamelOpenAIEmbeddingCount": { "index": 21, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Number of embeddings returned", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_COUNT" },
+    "CamelOpenAIEmbeddingVectorSize": { "index": 22, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Vector dimensions of the embeddings", 
"constantName": 
"org.apache.camel.component.openai.OpenAIConstants#EMBEDDING_VECTOR_SIZE" },
+    "CamelOpenAIReferenceEmbedding": { "index": 23, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "List<Float>", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Reference embedding vector 
for similarity comparison", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#REFERENCE_EMBEDDING" },
+    "CamelOpenAISimilarityScore": { "index": 24, "kind": "header", 
"displayName": "", "group": "producer", "label": "", "required": false, 
"javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Calculated cosine similarity score (0.0 
to 1.0)", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#SIMILARITY_SCORE" },
+    "CamelOpenAIOriginalText": { "index": 25, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String or 
List<String>", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Original text content when embeddings 
operation is used", "constantName": 
"org.apache.camel.component.openai.OpenAIConstants#ORIGINAL_TEXT" }
   },
   "properties": {
     "operation": { "index": 0, "kind": "path", "displayName": "Operation", 
"group": "producer", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.openai.OpenAIOperations", "enum": [ 
"chat-completion", "embeddings" ], "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The operation to perform: 
'chat-completion' or 'embeddings'" },
diff --git 
a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc 
b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
index afe483f31765..e0613be791d7 100644
--- a/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
+++ b/components/camel-ai/camel-openai/src/main/docs/openai-component.adoc
@@ -113,13 +113,35 @@ from("file:prompts?noop=true")
 [source,java]
 ----
 from("file:images?noop=true")
-    .to("openai:chat-completion?model=gpt-4.1-mini?userMessage=Describe what 
you see in this image")
+    .to("openai:chat-completion?model=gpt-4.1-mini&userMessage=Describe what 
you see in this image")
+    .log("Response: ${body}");
+----
+
+Image input also works with bodies produced by remote file and cloud storage 
components, such as FTP/SFTP (`WrappedFile`), AWS S3, Azure Blob Storage or 
MinIO (`byte[]` or `InputStream`). The MIME type is detected from the 
component's content-type header or the file name:
+
+.Usage example:
+[source,java]
+----
+from("aws2-s3:my-bucket")
+    .to("openai:chat-completion?model=gpt-4.1-mini&userMessage=Describe what 
you see in this image")
+    .log("Response: ${body}");
+----
+
+When no content-type header is available, set the `CamelOpenAIMediaType` 
header explicitly:
+
+.Usage example:
+[source,java]
+----
+from("direct:image")
+    .setHeader("CamelOpenAIMediaType", constant("image/png"))
+    .setHeader("CamelOpenAIUserMessage", constant("Describe what you see in 
this image"))
+    .to("openai:chat-completion?model=gpt-4.1-mini")
     .log("Response: ${body}");
 ----
 
 [NOTE]
 ====
-When using image files, the userMessage is required. Supported image formats 
are detected by MIME type (e.g., `image/png`, `image/jpeg`, `image/gif`, 
`image/webp`).
+When using image input, the userMessage is required. Supported image formats 
are detected by MIME type (e.g., `image/png`, `image/jpeg`, `image/gif`, 
`image/webp`).
 ====
 
 === Streaming Response
@@ -238,13 +260,30 @@ from("direct:local")
 The component accepts the following types of input in the message body:
 
 1. *String*: The prompt text is taken directly from the body
-2. *File*: Used for file-based prompts. The component handles two types of 
files:
-   * *Text files* (MIME type starting with `text/`): The file content is read 
and used as the prompt. If userMessage endpoint option or 
`CamelOpenAIUserMessage` is set, it overrides the file content
+2. *File*, *Path* or *WrappedFile* (the body type produced by the file, FTP, 
SFTP and SMB components): Used for file-based prompts. The component handles 
two types of files:
+   * *Text files* (MIME type starting with `text/`, plus `application/xml` and 
`application/json`): The file content is read and used as the prompt. If 
userMessage endpoint option or `CamelOpenAIUserMessage` is set, it overrides 
the file content
    * *Image files* (MIME type starting with `image/`): The file is encoded as 
a base64 data URL and sent to vision-capable models. The userMessage is 
**required** when using image files
+3. *byte[]* or *InputStream* (the body types produced by cloud storage 
components such as AWS S3, Azure Blob Storage, Google Cloud Storage and MinIO): 
When the detected MIME type is an image, the content is encoded as a base64 
data URL and sent to vision-capable models (userMessage is **required**). 
Otherwise, the content is converted to a String and used as the prompt
+
+=== MIME Type Detection
+
+For `File`, `Path` and locally backed `WrappedFile` bodies, the MIME type is 
resolved in the following order:
+
+1. The `CamelOpenAIMediaType` header
+2. The `CamelFileContentType` header
+3. The file name extension, using the Camel built-in MIME type table (e.g., 
`.png`, `.jpg`, `.gif`, `.webp`, `.txt`, `.csv`, `.md`, `.xml`, `.json`)
+
+For `byte[]`, `InputStream` and remote `WrappedFile` bodies, the MIME type is 
resolved in the following order:
+
+1. The `CamelOpenAIMediaType` header
+2. Cloud storage content-type headers: `CamelAwsS3ContentType`, 
`CamelAzureStorageBlobContentType`, `CamelAzureStorageDataLakeContentType`, 
`CamelGoogleCloudStorageContentType`, `CamelMinioContentType`, 
`CamelIBMCOSContentType`
+3. The `Content-Type` header
+4. The `CamelFileContentType` header
+5. The extension of the file name in the `CamelFileName` header
 
 [NOTE]
 ====
-When using `File` input, the component uses `Files.probeContentType()` to 
detect the file type. Ensure your system has proper MIME type detection 
configured.
+Set the `CamelOpenAIMediaType` header to override the MIME type detection, for 
example when the payload has no content-type metadata or the detection picks 
the wrong type.
 ====
 
 == Output Handling
diff --git 
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/MimeTypeHelper.java
 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/MimeTypeHelper.java
new file mode 100644
index 000000000000..2d404f4d9b6e
--- /dev/null
+++ 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/MimeTypeHelper.java
@@ -0,0 +1,119 @@
+/*
+ * 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.openai;
+
+import java.io.File;
+import java.util.Locale;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+
+/**
+ * Resolves the MIME type of file and binary message bodies sent to 
vision-capable models.
+ */
+final class MimeTypeHelper {
+
+    /**
+     * Content-type headers set by file-based and cloud storage components, 
checked in order.
+     */
+    private static final String[] CONTENT_TYPE_HEADERS = {
+            "CamelAwsS3ContentType", // AWS S3
+            "CamelAzureStorageBlobContentType", // Azure Blob Storage
+            "CamelAzureStorageDataLakeContentType", // Azure Data Lake Storage
+            "CamelGoogleCloudStorageContentType", // Google Cloud Storage
+            "CamelMinioContentType", // Minio (S3-compatible)
+            "CamelIBMCOSContentType" // IBM Cloud Object Storage
+    };
+
+    private MimeTypeHelper() {
+    }
+
+    /**
+     * Resolves the MIME type of a local file. Priority: the {@code 
CamelOpenAIMediaType} header, the
+     * {@code CamelFileContentType} header, and finally the file name 
extension.
+     */
+    static String resolveForFile(Message in, File file) {
+        String mime = headerMimeType(in, OpenAIConstants.MEDIA_TYPE);
+        if (mime == null) {
+            mime = headerMimeType(in, Exchange.FILE_CONTENT_TYPE);
+        }
+        if (mime == null) {
+            mime = fromFileName(file.getName());
+        }
+        return mime;
+    }
+
+    /**
+     * Resolves the MIME type of a binary body (byte[], InputStream or a 
remote WrappedFile) where no local file is
+     * available. Priority: the {@code CamelOpenAIMediaType} header, cloud 
storage content-type headers,
+     * {@code Content-Type}, {@code CamelFileContentType}, and finally the 
extension of the {@code CamelFileName}
+     * header.
+     */
+    static String resolveForBinary(Message in) {
+        String mime = headerMimeType(in, OpenAIConstants.MEDIA_TYPE);
+        for (int i = 0; mime == null && i < CONTENT_TYPE_HEADERS.length; i++) {
+            mime = headerMimeType(in, CONTENT_TYPE_HEADERS[i]);
+        }
+        if (mime == null) {
+            mime = headerMimeType(in, Exchange.CONTENT_TYPE);
+        }
+        if (mime == null) {
+            mime = headerMimeType(in, Exchange.FILE_CONTENT_TYPE);
+        }
+        if (mime == null) {
+            String fileName = in.getHeader(Exchange.FILE_NAME, String.class);
+            if (fileName != null) {
+                mime = fromFileName(fileName);
+            }
+        }
+        return mime;
+    }
+
+    static boolean isImage(String mime) {
+        return mime != null && mime.startsWith("image/");
+    }
+
+    static boolean isText(String mime) {
+        if (mime == null) {
+            return false;
+        }
+        // XML and JSON are textual formats usable as prompt text, but map to 
application/* MIME types
+        return mime.startsWith("text/") || "application/xml".equals(mime) || 
"application/json".equals(mime);
+    }
+
+    private static String headerMimeType(Message in, String header) {
+        String value = in.getHeader(header, String.class);
+        if (value == null || value.isBlank()) {
+            return null;
+        }
+        // strip parameters such as "; charset=utf-8"
+        int semicolon = value.indexOf(';');
+        return semicolon > 0 ? value.substring(0, semicolon).trim() : 
value.trim();
+    }
+
+    private static String fromFileName(String fileName) {
+        String mime = 
org.apache.camel.util.MimeTypeHelper.probeMimeType(fileName);
+        if (mime == null) {
+            // markdown is not in the camel-util MIME type table
+            String name = fileName.toLowerCase(Locale.ROOT);
+            if (name.endsWith(".md") || name.endsWith(".markdown")) {
+                mime = "text/markdown";
+            }
+        }
+        return mime;
+    }
+}
diff --git 
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java
 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java
index 8e40ef78908e..090d58ccab9c 100644
--- 
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java
+++ 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java
@@ -46,6 +46,11 @@ public final class OpenAIConstants {
     public static final String OUTPUT_CLASS = "CamelOpenAIOutputClass";
     @Metadata(description = "The JSON schema to use for structured output 
validation", javaType = "String")
     public static final String JSON_SCHEMA = "CamelOpenAIJsonSchema";
+    @Metadata(description = "The MIME type of the message body when sending a 
file or binary content (File, WrappedFile, "
+                            + "byte[] or InputStream) to the model. Takes 
precedence over component content-type headers "
+                            + "and automatic MIME type detection",
+              javaType = "String")
+    public static final String MEDIA_TYPE = "CamelOpenAIMediaType";
 
     // Output Headers
     @Metadata(description = "The model used for the completion response", 
javaType = "String")
diff --git 
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
index 3aa022c5b295..48ce7a57d55b 100644
--- 
a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
+++ 
b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIProducer.java
@@ -17,8 +17,8 @@
 package org.apache.camel.component.openai;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -46,6 +46,7 @@ import com.openai.models.completions.CompletionUsage;
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
+import org.apache.camel.WrappedFile;
 import org.apache.camel.spi.Synchronization;
 import org.apache.camel.support.DefaultAsyncProducer;
 import org.apache.camel.support.ResourceHelper;
@@ -244,8 +245,10 @@ public class OpenAIProducer extends DefaultAsyncProducer {
             userPrompt = config.getUserMessage();
         }
 
-        if (body instanceof File) {
+        if (body instanceof WrappedFile || body instanceof File || body 
instanceof Path) {
             return buildFileMessage(in, userPrompt, config);
+        } else if (body instanceof byte[] || body instanceof InputStream) {
+            return buildBinaryMessage(in, userPrompt, config);
         } else {
             return buildTextMessage(in, userPrompt, config);
         }
@@ -261,15 +264,28 @@ public class OpenAIProducer extends DefaultAsyncProducer {
 
     private ChatCompletionMessageParam buildFileMessage(Message in, String 
userPrompt, OpenAIConfiguration config)
             throws Exception {
-        File inputFile = in.getBody(File.class);
-        Path path = inputFile.toPath();
-        String mime = Files.probeContentType(path);
-
-        if (mime != null && mime.startsWith("text/")) {
+        Object body = in.getBody();
+        File inputFile = null;
+        if (body instanceof WrappedFile<?> wrappedFile && 
wrappedFile.getFile() instanceof File file) {
+            // local file-based components (camel-file) expose the underlying 
java.io.File
+            inputFile = file;
+        } else if (body instanceof File file) {
+            inputFile = file;
+        } else if (body instanceof Path path) {
+            inputFile = path.toFile();
+        }
+
+        // for remote file-based components (FTP, SFTP, ...) there is no local 
java.io.File, so the
+        // MIME type is detected from headers and the file name only, before 
reading any content
+        String mime = inputFile != null
+                ? MimeTypeHelper.resolveForFile(in, inputFile) : 
MimeTypeHelper.resolveForBinary(in);
+
+        if (MimeTypeHelper.isText(mime)) {
             // Handle text files - read content and use buildTextMessage logic
             String prompt = userPrompt;
             if (prompt == null || prompt.isEmpty()) {
-                prompt = new String(Files.readAllBytes(path), 
StandardCharsets.UTF_8);
+                // the type converter reads the content honoring the charset 
configured on file-based endpoints
+                prompt = in.getBody(String.class);
             }
 
             if (prompt == null || prompt.isEmpty()) {
@@ -277,25 +293,48 @@ public class OpenAIProducer extends DefaultAsyncProducer {
                         "File content or user message configuration must 
contain the prompt text");
             }
             return createTextMessage(prompt);
-        } else if (mime != null && mime.startsWith("image/")) {
-            // Handle image files - require user prompt and combine with image
-            if (userPrompt == null || userPrompt.isEmpty()) {
-                throw new IllegalArgumentException("User message configuration 
must be set when using image File body");
-            }
+        } else if (MimeTypeHelper.isImage(mime)) {
+            byte[] image = inputFile != null ? 
Files.readAllBytes(inputFile.toPath()) : readBodyBytes(in);
+            return createImageMessage(image, mime, userPrompt);
+        } else {
+            throw unsupportedMimeType(mime,
+                    inputFile != null ? inputFile.getName() : 
in.getHeader(Exchange.FILE_NAME, String.class));
+        }
+    }
 
-            ChatCompletionContentPart imageContentPart = 
createImageContentPart(inputFile, mime);
-            ChatCompletionContentPart textContentPart = 
createTextContentPart(userPrompt);
+    private ChatCompletionMessageParam buildBinaryMessage(Message in, String 
userPrompt, OpenAIConfiguration config)
+            throws Exception {
+        String mime = MimeTypeHelper.resolveForBinary(in);
+        if (MimeTypeHelper.isImage(mime)) {
+            return createImageMessage(readBodyBytes(in), mime, userPrompt);
+        }
+        // not an image: keep the previous behavior and treat the payload as 
text
+        return buildTextMessage(in, userPrompt, config);
+    }
 
-            return ChatCompletionMessageParam.ofUser(
-                    ChatCompletionUserMessageParam.builder()
-                            
.content(ChatCompletionUserMessageParam.Content.ofArrayOfContentParts(
-                                    List.of(textContentPart, 
imageContentPart)))
-                            .build());
-        } else {
-            throw new IllegalArgumentException("Only text and image files are 
supported");
+    private byte[] readBodyBytes(Message in) throws IOException {
+        Object body = in.getBody();
+        if (body instanceof byte[] bytes) {
+            return bytes;
+        }
+        InputStream is = in.getBody(InputStream.class);
+        if (is == null) {
+            throw new IllegalArgumentException(
+                    "Cannot read message body as InputStream: " + (body != 
null ? body.getClass().getName() : "null"));
+        }
+        try (is) {
+            return is.readAllBytes();
         }
     }
 
+    private IllegalArgumentException unsupportedMimeType(String mime, String 
fileName) {
+        return new IllegalArgumentException(
+                "Only text and image files are supported. Detected MIME type: 
" + mime
+                                            + (fileName != null ? " for file: 
" + fileName : "")
+                                            + ". Set the " + 
OpenAIConstants.MEDIA_TYPE
+                                            + " header to override the MIME 
type detection");
+    }
+
     private ChatCompletionMessageParam createTextMessage(String prompt) {
         return ChatCompletionMessageParam.ofUser(
                 ChatCompletionUserMessageParam.builder()
@@ -317,10 +356,24 @@ public class OpenAIProducer extends DefaultAsyncProducer {
                         .build());
     }
 
-    private ChatCompletionContentPart createImageContentPart(File inputFile, 
String mime) throws Exception {
-        Path path = inputFile.toPath();
-        byte[] img = Files.readAllBytes(path);
-        String dataUrl = "data:" + mime + ";base64," + 
Base64.getEncoder().encodeToString(img);
+    private ChatCompletionMessageParam createImageMessage(byte[] image, String 
mime, String userPrompt) {
+        // image input requires a user prompt to combine with the image
+        if (userPrompt == null || userPrompt.isEmpty()) {
+            throw new IllegalArgumentException("User message configuration 
must be set when using an image body");
+        }
+
+        ChatCompletionContentPart imageContentPart = 
createImageContentPart(image, mime);
+        ChatCompletionContentPart textContentPart = 
createTextContentPart(userPrompt);
+
+        return ChatCompletionMessageParam.ofUser(
+                ChatCompletionUserMessageParam.builder()
+                        
.content(ChatCompletionUserMessageParam.Content.ofArrayOfContentParts(
+                                List.of(textContentPart, imageContentPart)))
+                        .build());
+    }
+
+    private ChatCompletionContentPart createImageContentPart(byte[] image, 
String mime) {
+        String dataUrl = "data:" + mime + ";base64," + 
Base64.getEncoder().encodeToString(image);
 
         return ChatCompletionContentPart.ofImageUrl(
                 ChatCompletionContentPartImage.builder()
diff --git 
a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIVisionBodyTypesMockTest.java
 
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIVisionBodyTypesMockTest.java
new file mode 100644
index 000000000000..10a4f286b573
--- /dev/null
+++ 
b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIVisionBodyTypesMockTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.openai;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Base64;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.infra.openai.mock.OpenAIMock;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests vision model input with the different body types produced by 
file-based and cloud storage components:
+ * WrappedFile/GenericFile, byte[] and InputStream (CAMEL-23739).
+ */
+public class OpenAIVisionBodyTypesMockTest extends CamelTestSupport {
+
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    private static final byte[] PNG_BYTES = {
+            (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, (byte) 
0xFF, 0x00, 0x01, 0x02 };
+    private static final byte[] JPEG_BYTES = {
+            (byte) 0xFF, (byte) 0xD8, (byte) 0xFF, (byte) 0xE0, (byte) 0xCA, 
(byte) 0xFE, 0x00, 0x42 };
+
+    private final Path imagesDir = createTempDir();
+
+    @RegisterExtension
+    public OpenAIMock openAIMock = new OpenAIMock().builder()
+            .when("describe-png")
+            .assertRequest(request -> assertImageDataUrl(request, "image/png", 
PNG_BYTES))
+            .replyWith("png response")
+            .end()
+            .when("describe-jpeg")
+            .assertRequest(request -> assertImageDataUrl(request, 
"image/jpeg", JPEG_BYTES))
+            .replyWith("jpeg response")
+            .end()
+            .when("describe-webp")
+            .assertRequest(request -> assertImageDataUrl(request, 
"image/webp", PNG_BYTES))
+            .replyWith("webp response")
+            .end()
+            .when("describe-avif")
+            .assertRequest(request -> assertImageDataUrl(request, 
"image/avif", PNG_BYTES))
+            .replyWith("avif response")
+            .end()
+            .when("hello bytes")
+            .replyWith("text response")
+            .end()
+            .when("prompt from file")
+            .replyWith("file text response")
+            .end()
+            .when("<note>xml prompt</note>")
+            .replyWith("xml text response")
+            .end()
+            .build();
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:chat")
+                        
.to("openai:chat-completion?model=gpt-5&apiKey=dummy&baseUrl=" + 
openAIMock.getBaseUrl()
+                            + "/v1");
+
+                // consumes GenericFile bodies, the typical vision use case 
from the file component
+                from("file:" + imagesDir + "?noop=true&initialDelay=0")
+                        
.to("openai:chat-completion?model=gpt-5&apiKey=dummy&userMessage=describe-png&baseUrl="
+                            + openAIMock.getBaseUrl() + "/v1")
+                        .to("mock:fileResult");
+            }
+        };
+    }
+
+    @Test
+    void genericFileBodyFromFileConsumerIsSentAsImage() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:fileResult");
+        mock.expectedBodiesReceived("png response");
+
+        Files.write(imagesDir.resolve("picture.png"), PNG_BYTES);
+
+        mock.assertIsSatisfied();
+    }
+
+    @Test
+    void byteArrayBodyWithCloudContentTypeHeaderIsSentAsImage() {
+        Exchange result = template.request("direct:chat", e -> {
+            e.getIn().setBody(PNG_BYTES);
+            e.getIn().setHeader("CamelAwsS3ContentType", "image/png");
+            e.getIn().setHeader(OpenAIConstants.USER_MESSAGE, "describe-png");
+        });
+        assertEquals("png response", 
result.getMessage().getBody(String.class));
+    }
+
+    @Test
+    void inputStreamBodyWithContentTypeHeaderIsSentAsImage() {
+        Exchange result = template.request("direct:chat", e -> {
+            e.getIn().setBody(new ByteArrayInputStream(JPEG_BYTES));
+            e.getIn().setHeader(Exchange.CONTENT_TYPE, "image/jpeg");
+            e.getIn().setHeader(OpenAIConstants.USER_MESSAGE, "describe-jpeg");
+        });
+        assertEquals("jpeg response", 
result.getMessage().getBody(String.class));
+    }
+
+    @Test
+    void mediaTypeHeaderOverridesOtherContentTypeHeaders() {
+        Exchange result = template.request("direct:chat", e -> {
+            e.getIn().setBody(PNG_BYTES);
+            e.getIn().setHeader(Exchange.CONTENT_TYPE, 
"application/octet-stream");
+            e.getIn().setHeader(OpenAIConstants.MEDIA_TYPE, "image/webp");
+            e.getIn().setHeader(OpenAIConstants.USER_MESSAGE, "describe-webp");
+        });
+        assertEquals("webp response", 
result.getMessage().getBody(String.class));
+    }
+
+    @Test
+    void byteArrayBodyWithFileNameHeaderUsesExtensionDetection() {
+        Exchange result = template.request("direct:chat", e -> {
+            e.getIn().setBody(PNG_BYTES);
+            e.getIn().setHeader(Exchange.FILE_NAME, "photo.png");
+            e.getIn().setHeader(OpenAIConstants.USER_MESSAGE, "describe-png");
+        });
+        assertEquals("png response", 
result.getMessage().getBody(String.class));
+    }
+
+    @Test
+    void byteArrayBodyWithoutMimeInfoIsTreatedAsText() {
+        Exchange result = template.request("direct:chat",
+                e -> e.getIn().setBody("hello 
bytes".getBytes(StandardCharsets.UTF_8)));
+        assertEquals("text response", 
result.getMessage().getBody(String.class));
+    }
+
+    @Test
+    void textFileBodyContentIsUsedAsPrompt() throws Exception {
+        Path textFile = Files.createTempFile("camel-openai-prompt", ".txt");
+        Files.writeString(textFile, "prompt from file");
+
+        Exchange result = template.request("direct:chat", e -> 
e.getIn().setBody(textFile.toFile()));
+        assertEquals("file text response", 
result.getMessage().getBody(String.class));
+    }
+
+    @Test
+    void avifFileNameExtensionIsDetectedFromMimeTypeTable() {
+        Exchange result = template.request("direct:chat", e -> {
+            e.getIn().setBody(PNG_BYTES);
+            e.getIn().setHeader(Exchange.FILE_NAME, "photo.avif");
+            e.getIn().setHeader(OpenAIConstants.USER_MESSAGE, "describe-avif");
+        });
+        assertEquals("avif response", 
result.getMessage().getBody(String.class));
+    }
+
+    @Test
+    void xmlFileBodyContentIsUsedAsPrompt() throws Exception {
+        Path xmlFile = Files.createTempFile("camel-openai-prompt", ".xml");
+        Files.writeString(xmlFile, "<note>xml prompt</note>");
+
+        Exchange result = template.request("direct:chat", e -> 
e.getIn().setBody(xmlFile.toFile()));
+        assertEquals("xml text response", 
result.getMessage().getBody(String.class));
+    }
+
+    @Test
+    void imageBodyWithoutUserMessageFails() {
+        Exchange result = template.request("direct:chat", e -> {
+            e.getIn().setBody(PNG_BYTES);
+            e.getIn().setHeader(Exchange.CONTENT_TYPE, "image/png");
+        });
+        Exception exception = result.getException();
+        assertInstanceOf(IllegalArgumentException.class, exception);
+        assertTrue(exception.getMessage().contains("User message"));
+    }
+
+    @Test
+    void unsupportedFileTypeFails() throws Exception {
+        Path binFile = Files.createTempFile("camel-openai-unsupported", 
".bin");
+        Files.write(binFile, PNG_BYTES);
+
+        Exchange result = template.request("direct:chat", e -> {
+            e.getIn().setBody(binFile.toFile());
+            e.getIn().setHeader(OpenAIConstants.USER_MESSAGE, "describe-png");
+        });
+        Exception exception = result.getException();
+        assertInstanceOf(IllegalArgumentException.class, exception);
+        assertTrue(exception.getMessage().contains("Only text and image files 
are supported"));
+    }
+
+    private static void assertImageDataUrl(String request, String 
expectedMime, byte[] expectedBytes) {
+        try {
+            JsonNode root = OBJECT_MAPPER.readTree(request);
+            JsonNode messages = root.path("messages");
+            JsonNode lastMessage = messages.get(messages.size() - 1);
+            String url = null;
+            for (JsonNode part : lastMessage.path("content")) {
+                if ("image_url".equals(part.path("type").asText())) {
+                    url = part.path("image_url").path("url").asText();
+                }
+            }
+            assertNotNull(url, "Expected an image_url content part in the 
request");
+            String prefix = "data:" + expectedMime + ";base64,";
+            assertTrue(url.startsWith(prefix), "Expected data URL with MIME 
type " + expectedMime);
+            assertArrayEquals(expectedBytes, 
Base64.getDecoder().decode(url.substring(prefix.length())));
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private static Path createTempDir() {
+        try {
+            return Files.createTempDirectory("camel-openai-vision-test");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+}
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
index 3809ee39d3c2..a133e09f7937 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
@@ -715,6 +715,21 @@ public interface OpenAIEndpointBuilderFactory {
         public String openAIJsonSchema() {
             return "CamelOpenAIJsonSchema";
         }
+        /**
+         * The MIME type of the message body when sending a file or binary
+         * content (File, WrappedFile, byte or InputStream) to the model. Takes
+         * precedence over component content-type headers and automatic MIME
+         * type detection.
+         * 
+         * The option is a: {@code String} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code OpenAIMediaType}.
+         */
+        public String openAIMediaType() {
+            return "CamelOpenAIMediaType";
+        }
         /**
          * The model used for the completion response.
          * 

Reply via email to