diff --git a/agentscope-core/src/main/java/io/agentscope/core/formatter/gemini/GeminiMediaConverter.java b/agentscope-core/src/main/java/io/agentscope/core/formatter/gemini/GeminiMediaConverter.java index cdaca84256..8b23d46cd9 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/formatter/gemini/GeminiMediaConverter.java +++ b/agentscope-core/src/main/java/io/agentscope/core/formatter/gemini/GeminiMediaConverter.java @@ -128,7 +128,9 @@ private Part convertMediaBlockToInlineDataPart(Source source, String mediaType) /** * Read a file from URL/path as byte array. * - *

Supports both remote URLs (http://, https://) and local file paths. + *

Supports both remote URLs (http://, https://) and local file paths. Query string + * and fragment are stripped from local file paths so that paths like {@code + * /tmp/cat.png?token=abc} resolve to the actual file on disk. * * @param url File URL or path * @return File content as byte array @@ -146,8 +148,9 @@ private byte[] readFileAsBytes(String url) throws IOException { throw new IOException("Failed to download remote file: " + url, e); } } else { - // Local file path - Path path = Paths.get(url); + // Local file path — strip query string / fragment before resolving + String localPath = stripQueryAndFragment(url); + Path path = Paths.get(localPath); if (!Files.exists(path)) { throw new IOException("File not found: " + url); } @@ -155,6 +158,22 @@ private byte[] readFileAsBytes(String url) throws IOException { } } + /** + * Strips query string ({@code ?...}) and fragment ({@code #...}) from a URL/path string. + */ + private static String stripQueryAndFragment(String url) { + String path = url; + int fragmentIndex = path.indexOf('#'); + if (fragmentIndex >= 0) { + path = path.substring(0, fragmentIndex); + } + int queryIndex = path.indexOf('?'); + if (queryIndex >= 0) { + path = path.substring(0, queryIndex); + } + return path; + } + /** * Determine MIME type from file extension. * @@ -186,14 +205,19 @@ private String getMimeType(String url, String mediaType) { /** * Extract file extension from URL or path. * + *

Strips query string ({@code ?...}) and fragment ({@code #...}) before extracting + * the extension so that URLs like {@code http://example.com/cat.png?token=abc} produce + * {@code png} rather than {@code png?token=abc}. + * * @param url File URL or path * @return File extension in lowercase (without dot) */ private String extractExtension(String url) { - int lastDotIndex = url.lastIndexOf('.'); - if (lastDotIndex == -1 || lastDotIndex == url.length() - 1) { + String path = stripQueryAndFragment(url); + int lastDotIndex = path.lastIndexOf('.'); + if (lastDotIndex == -1 || lastDotIndex == path.length() - 1) { throw new IllegalArgumentException("Cannot extract file extension from: " + url); } - return url.substring(lastDotIndex + 1).toLowerCase(); + return path.substring(lastDotIndex + 1).toLowerCase(); } } diff --git a/agentscope-core/src/test/java/io/agentscope/core/formatter/gemini/GeminiMediaConverterTest.java b/agentscope-core/src/test/java/io/agentscope/core/formatter/gemini/GeminiMediaConverterTest.java index 90c520dd6c..81daa27085 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/formatter/gemini/GeminiMediaConverterTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/formatter/gemini/GeminiMediaConverterTest.java @@ -142,6 +142,45 @@ void testUnsupportedExtension() { assertThrows(RuntimeException.class, () -> converter.convertToInlineDataPart(block)); } + @Test + void testUrlWithQueryString() { + // Issue #1645: URL with query string should strip ?... before extracting extension + URLSource source = + URLSource.builder().url(tempImageFile.toString() + "?token=abc123").build(); + ImageBlock block = ImageBlock.builder().source(source).build(); + + // Should resolve to image/png and read the file successfully + Part result = converter.convertToInlineDataPart(block); + assertNotNull(result); + assertTrue(result.inlineData().isPresent()); + assertEquals("image/png", result.inlineData().get().mimeType().get()); + } + + @Test + void testUrlWithFragment() { + // Issue #1645: URL with fragment should strip #... before extracting extension + URLSource source = URLSource.builder().url(tempAudioFile.toString() + "#preview").build(); + AudioBlock block = AudioBlock.builder().source(source).build(); + + Part result = converter.convertToInlineDataPart(block); + assertNotNull(result); + assertTrue(result.inlineData().isPresent()); + assertEquals("audio/mp3", result.inlineData().get().mimeType().get()); + } + + @Test + void testUrlWithQueryStringAndFragment() { + // Issue #1645: URL with both query string and fragment + URLSource source = + URLSource.builder().url(tempImageFile.toString() + "?download=1#preview").build(); + ImageBlock block = ImageBlock.builder().source(source).build(); + + Part result = converter.convertToInlineDataPart(block); + assertNotNull(result); + assertTrue(result.inlineData().isPresent()); + assertEquals("image/png", result.inlineData().get().mimeType().get()); + } + @Test void testFileNotFound() { URLSource source = URLSource.builder().url("/nonexistent/file.png").build();