diff --git a/core/pom.xml b/core/pom.xml
index caaf149fb..cf0fc9a0f 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -154,6 +154,12 @@
truth
test
+
+ com.google.jimfs
+ jimfs
+ 1.3.2
+ test
+
org.mockito
mockito-core
diff --git a/core/src/main/java/com/google/adk/skills/LocalSkillSource.java b/core/src/main/java/com/google/adk/skills/LocalSkillSource.java
index 8cb465521..8ed2a91be 100644
--- a/core/src/main/java/com/google/adk/skills/LocalSkillSource.java
+++ b/core/src/main/java/com/google/adk/skills/LocalSkillSource.java
@@ -23,6 +23,7 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.nio.file.Files.isDirectory;
+import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Maybe;
@@ -37,6 +38,8 @@
/** Loads skills from the local file system. */
public final class LocalSkillSource extends AbstractSkillSource {
+ private static final Joiner PATH_JOINER = Joiner.on('/');
+
private final Path skillsBasePath;
public LocalSkillSource(Path skillsBasePath) {
@@ -71,7 +74,9 @@ public Single> listResources(String skillName, String reso
return paths
.filter(Files::isRegularFile)
.map(skillDir::relativize)
- .map(Path::toString)
+ // Join the path's name elements with '/' on every OS, not the platform
+ // separator, to match ClassPathSkillSource and InMemorySkillSource.
+ .map(relativePath -> PATH_JOINER.join(relativePath))
.collect(toImmutableList());
}
})
diff --git a/core/src/test/java/com/google/adk/skills/LocalSkillSourceTest.java b/core/src/test/java/com/google/adk/skills/LocalSkillSourceTest.java
index d66cfe283..5d6a629cd 100644
--- a/core/src/test/java/com/google/adk/skills/LocalSkillSourceTest.java
+++ b/core/src/test/java/com/google/adk/skills/LocalSkillSourceTest.java
@@ -23,7 +23,10 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteSource;
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
import java.io.IOException;
+import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.Rule;
@@ -96,6 +99,27 @@ public void testListResources() throws IOException {
assertThat(resources).containsExactly("assets/file1.txt", "assets/subdir/file2.txt");
}
+ @Test
+ public void testListResources_windowsFileSystem() throws IOException {
+ // Use Windows paths on any host so Ubuntu CI covers separator normalization.
+ try (FileSystem fileSystem = Jimfs.newFileSystem(Configuration.windows())) {
+ Path skillsBase = fileSystem.getPath("C:\\skills");
+ Path assetsDir = skillsBase.resolve("my-skill").resolve("assets");
+ Files.createDirectories(assetsDir.resolve("subdir"));
+ Files.writeString(assetsDir.resolve("file1.txt"), "resource content");
+ Files.writeString(assetsDir.resolve("subdir").resolve("file2.txt"), "resource content");
+
+ SkillSource source = new LocalSkillSource(skillsBase);
+ ImmutableList resources = source.listResources("my-skill", "assets").blockingGet();
+
+ assertThat(resources).containsExactly("assets/file1.txt", "assets/subdir/file2.txt");
+ for (String resourcePath : resources) {
+ ByteSource resource = source.loadResource("my-skill", resourcePath).blockingGet();
+ assertThat(new String(resource.read(), UTF_8)).isEqualTo("resource content");
+ }
+ }
+ }
+
@Test
public void testListResources_notDirectory() throws IOException {
Path skillsBase = tempFolder.getRoot().toPath().resolve("skills");