From cd1caa7e77df9491ed36760a0cfd609d22ba8261 Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 09:42:02 +0800 Subject: [PATCH 01/10] add nacos skill repository --- .../agentscope-extensions-nacos-skill/pom.xml | 61 +++++++ .../nacos/skill/NacosSkillRepository.java | 150 ++++++++++++++++++ .../NacosSkillToAgentSkillConverter.java | 69 ++++++++ 3 files changed, 280 insertions(+) create mode 100644 agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml create mode 100644 agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java create mode 100644 agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverter.java diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml new file mode 100644 index 000000000..4ba20ae67 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml @@ -0,0 +1,61 @@ + + + + + 4.0.0 + + io.agentscope + agentscope-extensions-nacos + ${revision} + ../pom.xml + + + agentscope-extensions-nacos-skill + AgentScope Java - Nacos A2A skill + agentscope-extensions-nacos-skill + + + + + io.agentscope + agentscope-core + + + + + com.alibaba.nacos + nacos-common + 3.2.0-BETA + + + + com.alibaba.nacos + nacos-api + 3.2.0-BETA + + + + com.alibaba.nacos + nacos-client + 3.2.0-BETA + + + + + diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java new file mode 100644 index 000000000..c38128316 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java @@ -0,0 +1,150 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed 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 io.agentscope.core.nacos.skill; + +import com.alibaba.nacos.api.ai.AiService; +import com.alibaba.nacos.api.ai.model.skills.Skill; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.common.utils.StringUtils; +import io.agentscope.core.skill.AgentSkill; +import io.agentscope.core.skill.repository.AgentSkillRepository; +import io.agentscope.core.skill.repository.AgentSkillRepositoryInfo; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Nacos-based implementation of {@link AgentSkillRepository}. + * + *

Reads skills from Nacos Config via {@link AiService#loadSkill(String)}. This implementation + * currently supports only read operations: {@link #getSkill(String)}, {@link #skillExists(String)}, + * {@link #getRepositoryInfo()}, {@link #getSource()}, and {@link #isWriteable()}. List and write + * operations are not implemented. + */ +public class NacosSkillRepository implements AgentSkillRepository { + + private static final Logger log = LoggerFactory.getLogger(NacosSkillRepository.class); + + private static final String REPO_TYPE = "nacos"; + private static final String LOCATION_PREFIX = "namespace:"; + + private final AiService aiService; + private final String namespaceId; + private final String source; + private final String location; + private boolean writeable; + + /** + * Creates a Nacos skill repository. + * + * @param aiService the Nacos AI service (must not be null) + * @param namespaceId the Nacos namespace ID (null or blank is treated as default namespace) + */ + public NacosSkillRepository(AiService aiService, String namespaceId) { + if (aiService == null) { + throw new IllegalArgumentException("AiService cannot be null"); + } + this.aiService = aiService; + this.namespaceId = StringUtils.isBlank(namespaceId) ? "public" : namespaceId.trim(); + this.source = REPO_TYPE + ":" + this.namespaceId; + this.location = LOCATION_PREFIX + this.namespaceId; + this.writeable = false; + log.info("NacosSkillRepository initialized for namespace: {}", this.namespaceId); + } + + @Override + public AgentSkill getSkill(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Skill name cannot be null or empty"); + } + try { + Skill nacosSkill = aiService.loadSkill(name.trim()); + if (nacosSkill == null) { + throw new IllegalArgumentException("Skill not found: " + name); + } + return NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, getSource()); + } catch (NacosException e) { + if (e.getErrCode() == NacosException.NOT_FOUND) { + throw new IllegalArgumentException("Skill not found: " + name, e); + } + throw new RuntimeException("Failed to load skill from Nacos: " + name, e); + } + } + + @Override + public boolean skillExists(String skillName) { + if (skillName == null || skillName.isBlank()) { + return false; + } + try { + Skill skill = aiService.loadSkill(skillName.trim()); + return skill != null; + } catch (NacosException e) { + if (e.getErrCode() == NacosException.NOT_FOUND) { + return false; + } + log.warn("Error checking skill existence for {}: {}", skillName, e.getMessage()); + return false; + } + } + + @Override + public AgentSkillRepositoryInfo getRepositoryInfo() { + return new AgentSkillRepositoryInfo(REPO_TYPE, location, writeable); + } + + @Override + public String getSource() { + return source; + } + + @Override + public void setWriteable(boolean writeable) { + this.writeable = writeable; + } + + @Override + public boolean isWriteable() { + return writeable; + } + + // ---------- Unsupported operations (list and write) ---------- + + @Override + public List getAllSkillNames() { + throw new UnsupportedOperationException("getAllSkillNames is not implemented for NacosSkillRepository"); + } + + @Override + public List getAllSkills() { + throw new UnsupportedOperationException("getAllSkills is not implemented for NacosSkillRepository"); + } + + @Override + public boolean save(List skills, boolean force) { + throw new UnsupportedOperationException("save is not implemented for NacosSkillRepository"); + } + + @Override + public boolean delete(String skillName) { + throw new UnsupportedOperationException("delete is not implemented for NacosSkillRepository"); + } + + @Override + public void close() { + // AiService lifecycle is managed by the caller; nothing to release here + } +} diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverter.java b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverter.java new file mode 100644 index 000000000..bf3c1dd56 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed 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 io.agentscope.core.nacos.skill; + +import com.alibaba.nacos.api.ai.model.skills.Skill; +import com.alibaba.nacos.api.ai.model.skills.SkillResource; +import io.agentscope.core.skill.AgentSkill; +import java.util.HashMap; +import java.util.Map; + +/** + * Converts Nacos AI {@link Skill} to AgentScope {@link AgentSkill}. + */ +public final class NacosSkillToAgentSkillConverter { + + private static final String NO_DESCRIPTION = "(no description)"; + private static final String NO_INSTRUCTION = "(no instruction)"; + + private NacosSkillToAgentSkillConverter() {} + + /** + * Converts a Nacos Skill to an AgentSkill. + * + * @param nacosSkill the Nacos Skill (must not be null) + * @param source the source identifier for the resulting AgentSkill (e.g. "nacos:public") + * @return the converted AgentSkill + */ + public static AgentSkill toAgentSkill(Skill nacosSkill, String source) { + if (nacosSkill == null) { + throw new IllegalArgumentException("Nacos Skill cannot be null"); + } + String name = blankToDefault(nacosSkill.getName(), "unknown"); + String description = blankToDefault(nacosSkill.getDescription(), NO_DESCRIPTION); + String skillContent = blankToDefault(nacosSkill.getInstruction(), NO_INSTRUCTION); + Map resources = toResourceMap(nacosSkill.getResource()); + return new AgentSkill(name, description, skillContent, resources, source); + } + + private static String blankToDefault(String value, String defaultValue) { + return (value != null && !value.isBlank()) ? value.trim() : defaultValue; + } + + private static Map toResourceMap(Map resourceMap) { + if (resourceMap == null || resourceMap.isEmpty()) { + return new HashMap<>(); + } + Map result = new HashMap<>(resourceMap.size()); + for (Map.Entry e : resourceMap.entrySet()) { + String key = e.getKey() != null ? e.getKey() : "resource"; + SkillResource res = e.getValue(); + String content = (res != null && res.getContent() != null) ? res.getContent() : ""; + result.put(key, content); + } + return result; + } +} From a66e7f333191ced22a9a9af7bdb998f483b6df15 Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 09:49:01 +0800 Subject: [PATCH 02/10] add nacos skill repository --- agentscope-extensions/agentscope-extensions-nacos/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/agentscope-extensions/agentscope-extensions-nacos/pom.xml b/agentscope-extensions/agentscope-extensions-nacos/pom.xml index 8ba5f51a7..6d218e575 100644 --- a/agentscope-extensions/agentscope-extensions-nacos/pom.xml +++ b/agentscope-extensions/agentscope-extensions-nacos/pom.xml @@ -34,6 +34,7 @@ agentscope-extensions-nacos-a2a agentscope-extensions-nacos-prompt + agentscope-extensions-nacos-skill From 288b2af4fccef0452fc58e26c1726fdee462f36d Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 10:05:26 +0800 Subject: [PATCH 03/10] add nacos skill repository --- .../core/nacos/skill/NacosSkillRepository.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java index c38128316..ff94a07b8 100644 --- a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java @@ -125,12 +125,14 @@ public boolean isWriteable() { @Override public List getAllSkillNames() { - throw new UnsupportedOperationException("getAllSkillNames is not implemented for NacosSkillRepository"); + throw new UnsupportedOperationException( + "getAllSkillNames is not implemented for NacosSkillRepository"); } @Override public List getAllSkills() { - throw new UnsupportedOperationException("getAllSkills is not implemented for NacosSkillRepository"); + throw new UnsupportedOperationException( + "getAllSkills is not implemented for NacosSkillRepository"); } @Override @@ -140,7 +142,8 @@ public boolean save(List skills, boolean force) { @Override public boolean delete(String skillName) { - throw new UnsupportedOperationException("delete is not implemented for NacosSkillRepository"); + throw new UnsupportedOperationException( + "delete is not implemented for NacosSkillRepository"); } @Override From e64dfb9acc1c029c0d28f6177afc42806a9c86c4 Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 10:22:32 +0800 Subject: [PATCH 04/10] edit pom --- agentscope-distribution/agentscope-all/pom.xml | 7 +++++++ agentscope-distribution/agentscope-bom/pom.xml | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/agentscope-distribution/agentscope-all/pom.xml b/agentscope-distribution/agentscope-all/pom.xml index 99383a174..f2a519789 100644 --- a/agentscope-distribution/agentscope-all/pom.xml +++ b/agentscope-distribution/agentscope-all/pom.xml @@ -207,6 +207,13 @@ true + + io.agentscope + agentscope-extensions-nacos-skill + compile + true + + io.agentscope agentscope-extensions-skill-git-repository diff --git a/agentscope-distribution/agentscope-bom/pom.xml b/agentscope-distribution/agentscope-bom/pom.xml index fe5608371..82770f14e 100644 --- a/agentscope-distribution/agentscope-bom/pom.xml +++ b/agentscope-distribution/agentscope-bom/pom.xml @@ -282,6 +282,12 @@ ${project.version} + + io.agentscope + agentscope-extensions-nacos-skill + ${project.version} + + io.agentscope From b66477f33ed3dc07565b5de29e9e33bfc76c2137 Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 10:41:53 +0800 Subject: [PATCH 05/10] edit pom --- agentscope-dependencies-bom/pom.xml | 2 +- .../agentscope-extensions-nacos-skill/pom.xml | 14 -------------- .../core/nacos/skill/NacosSkillRepository.java | 2 +- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/agentscope-dependencies-bom/pom.xml b/agentscope-dependencies-bom/pom.xml index 36517105c..351cdefbd 100644 --- a/agentscope-dependencies-bom/pom.xml +++ b/agentscope-dependencies-bom/pom.xml @@ -103,7 +103,7 @@ 2.5.2 7.0.3 4.0.3 - 3.1.1 + 3.2.0-BETA 3.0.0 4.38.0 2.6 diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml index 4ba20ae67..a67a4bdd6 100644 --- a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml @@ -37,23 +37,9 @@ agentscope-core - - - com.alibaba.nacos - nacos-common - 3.2.0-BETA - - - - com.alibaba.nacos - nacos-api - 3.2.0-BETA - - com.alibaba.nacos nacos-client - 3.2.0-BETA diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java index ff94a07b8..41d8518ef 100644 --- a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java @@ -29,7 +29,7 @@ /** * Nacos-based implementation of {@link AgentSkillRepository}. * - *

Reads skills from Nacos Config via {@link AiService#loadSkill(String)}. This implementation + *

Reads skills from Nacos Config via {@code AiService.loadSkill(String)}. This implementation * currently supports only read operations: {@link #getSkill(String)}, {@link #skillExists(String)}, * {@link #getRepositoryInfo()}, {@link #getSource()}, and {@link #isWriteable()}. List and write * operations are not implemented. From b47ec4def96434ef170d8e8c241f5cd456730bad Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 10:46:48 +0800 Subject: [PATCH 06/10] set mvnd --no-daemon --- .github/workflows/maven-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml index 9654c1cb6..cbb242db6 100644 --- a/.github/workflows/maven-ci.yml +++ b/.github/workflows/maven-ci.yml @@ -105,12 +105,12 @@ jobs: if: runner.os == 'Linux' run: | export PATH="$HOME/.mvnd/bin:$PATH" - mvnd -B clean verify + mvnd --no-daemon -B clean verify - name: Build and Test with Coverage [Windows] if: runner.os == 'Windows' run: | - & "~\.mvnd\bin\mvnd.cmd" -B clean verify + & "~\.mvnd\bin\mvnd.cmd" --no-daemon -B clean verify - name: Upload coverage reports to Codecov if: runner.os == 'Linux' From d3923517db0827b8b8929ea4ffd126a3d3d07db9 Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 10:49:06 +0800 Subject: [PATCH 07/10] delete mvnd --no-daemon --- .github/workflows/maven-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml index cbb242db6..9654c1cb6 100644 --- a/.github/workflows/maven-ci.yml +++ b/.github/workflows/maven-ci.yml @@ -105,12 +105,12 @@ jobs: if: runner.os == 'Linux' run: | export PATH="$HOME/.mvnd/bin:$PATH" - mvnd --no-daemon -B clean verify + mvnd -B clean verify - name: Build and Test with Coverage [Windows] if: runner.os == 'Windows' run: | - & "~\.mvnd\bin\mvnd.cmd" --no-daemon -B clean verify + & "~\.mvnd\bin\mvnd.cmd" -B clean verify - name: Upload coverage reports to Codecov if: runner.os == 'Linux' From 70569b08553e1f352c21a9f75c108d9fc6798e11 Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 16:01:05 +0800 Subject: [PATCH 08/10] add agent-skill.md --- docs/en/task/agent-skill.md | 14 ++++++++++++++ docs/zh/task/agent-skill.md | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/docs/en/task/agent-skill.md b/docs/en/task/agent-skill.md index f09536af1..4f7436fec 100644 --- a/docs/en/task/agent-skill.md +++ b/docs/en/task/agent-skill.md @@ -301,6 +301,20 @@ Resource structure: Place multiple skill subdirectories under `src/main/resource > Note: `JarSkillRepositoryAdapter` is deprecated. Use `ClasspathSkillRepository` instead. +#### Nacos Repository (Read-Only) + +Pulls or subscribes to Skills from Nacos via a pre-built `AiService` (or Nacos connection config). The Agent fetches Skills from Nacos at runtime in real time, with support for change subscription and automatic awareness. Suitable for online scenarios that need to stay in sync with Nacos. + +```java +// Create Nacos skill repository with a pre-built AiService +try (NacosSkillRepository repository = new NacosSkillRepository(aiService, "namespace")) { + AgentSkill skill = repository.getSkill("data-analysis"); + boolean exists = repository.skillExists("data-analysis"); +} catch //... +``` + +> Note: Add the `agentscope-extensions-nacos-skill` dependency. + ### Performance Optimization Recommendations 1. **Control SKILL.md Size**: Keep under 5k tokens, recommended 1.5-2k tokens diff --git a/docs/zh/task/agent-skill.md b/docs/zh/task/agent-skill.md index 7dfdc85d2..9e4f1af29 100644 --- a/docs/zh/task/agent-skill.md +++ b/docs/zh/task/agent-skill.md @@ -295,6 +295,32 @@ try (ClasspathSkillRepository repository = new ClasspathSkillRepository("skills" > 注意: `JarSkillRepositoryAdapter` 已废弃,请使用 `ClasspathSkillRepository`。 +#### Nacos 仓库 (只读) + +通过已构建的 `AiService`(或 Nacos 连接配置)从 Nacos 拉取或订阅 Skill,Agent 运行时从 Nacos 实时获取,支持变更订阅与自动感知,适合需要与 Nacos 保持同步的在线场景。 + +```java +// 使用已构建的 AiService 创建 Nacos 技能仓库 +try (NacosSkillRepository repository = new NacosSkillRepository(aiService, "namespace")) { + AgentSkill skill = repository.getSkill("data-analysis"); + boolean exists = repository.skillExists("data-analysis"); +} catch //... +``` + +**特性说明**: +- 支持按名称拉取 Skill:`getSkill(String)`、`skillExists(String)` +- 每次获取均从 Nacos 读取最新配置,实现运行时实时同步 +- 当前仅支持读操作,不支持 `getAllSkillNames`、`getAllSkills`、`save`、`delete` +- 需引入以下依赖: + +```xml + + io.agentscope + agentscope-extensions-nacos-skill + ${agentscope.version} + +``` + ### 性能优化建议 1. **控制 SKILL.md 大小**: 保持在 5k tokens 以内,建议 1.5-2k tokens From fc6f591f682c2c5968c227dec176ebaf8b037dd6 Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 19:13:10 +0800 Subject: [PATCH 09/10] modify according to copilot's recommendations --- .../agentscope-extensions-nacos-skill/pom.xml | 2 +- .../nacos/skill/NacosSkillRepository.java | 34 +-- .../nacos/skill/NacosSkillRepositoryTest.java | 257 +++++++++++++++++ .../NacosSkillToAgentSkillConverterTest.java | 264 ++++++++++++++++++ docs/zh/task/agent-skill.md | 14 +- 5 files changed, 541 insertions(+), 30 deletions(-) create mode 100644 agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillRepositoryTest.java create mode 100644 agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverterTest.java diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml index a67a4bdd6..92cb72aa1 100644 --- a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/pom.xml @@ -27,7 +27,7 @@ agentscope-extensions-nacos-skill - AgentScope Java - Nacos A2A skill + AgentScope Java - Nacos Skill Repository agentscope-extensions-nacos-skill diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java index 41d8518ef..468894619 100644 --- a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/main/java/io/agentscope/core/nacos/skill/NacosSkillRepository.java @@ -22,6 +22,7 @@ import io.agentscope.core.skill.AgentSkill; import io.agentscope.core.skill.repository.AgentSkillRepository; import io.agentscope.core.skill.repository.AgentSkillRepositoryInfo; +import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,9 +31,11 @@ * Nacos-based implementation of {@link AgentSkillRepository}. * *

Reads skills from Nacos Config via {@code AiService.loadSkill(String)}. This implementation - * currently supports only read operations: {@link #getSkill(String)}, {@link #skillExists(String)}, - * {@link #getRepositoryInfo()}, {@link #getSource()}, and {@link #isWriteable()}. List and write - * operations are not implemented. + * supports read operations: {@link #getSkill(String)}, {@link #skillExists(String)}, {@link + * #getRepositoryInfo()}, {@link #getSource()}, and {@link #isWriteable()}. List and write + * operations ({@link #getAllSkillNames()}, {@link #getAllSkills()}, {@link #save(List, boolean)}, + * {@link #delete(String)}) + * are implemented as read-only no-ops: they return empty list or {@code false} and log a warning. */ public class NacosSkillRepository implements AgentSkillRepository { @@ -45,7 +48,6 @@ public class NacosSkillRepository implements AgentSkillRepository { private final String namespaceId; private final String source; private final String location; - private boolean writeable; /** * Creates a Nacos skill repository. @@ -61,7 +63,6 @@ public NacosSkillRepository(AiService aiService, String namespaceId) { this.namespaceId = StringUtils.isBlank(namespaceId) ? "public" : namespaceId.trim(); this.source = REPO_TYPE + ":" + this.namespaceId; this.location = LOCATION_PREFIX + this.namespaceId; - this.writeable = false; log.info("NacosSkillRepository initialized for namespace: {}", this.namespaceId); } @@ -103,7 +104,7 @@ public boolean skillExists(String skillName) { @Override public AgentSkillRepositoryInfo getRepositoryInfo() { - return new AgentSkillRepositoryInfo(REPO_TYPE, location, writeable); + return new AgentSkillRepositoryInfo(REPO_TYPE, location, false); } @Override @@ -113,37 +114,38 @@ public String getSource() { @Override public void setWriteable(boolean writeable) { - this.writeable = writeable; + log.warn("NacosSkillRepository is read-only, set writeable operation ignored"); } @Override public boolean isWriteable() { - return writeable; + return false; } - // ---------- Unsupported operations (list and write) ---------- + // ---------- Read-only no-op operations (list and write) ---------- @Override public List getAllSkillNames() { - throw new UnsupportedOperationException( - "getAllSkillNames is not implemented for NacosSkillRepository"); + log.warn("NacosSkillRepository is read-only, getAllSkillNames returns empty list"); + return Collections.emptyList(); } @Override public List getAllSkills() { - throw new UnsupportedOperationException( - "getAllSkills is not implemented for NacosSkillRepository"); + log.warn("NacosSkillRepository is read-only, getAllSkills returns empty list"); + return Collections.emptyList(); } @Override public boolean save(List skills, boolean force) { - throw new UnsupportedOperationException("save is not implemented for NacosSkillRepository"); + log.warn("NacosSkillRepository is read-only, save operation ignored"); + return false; } @Override public boolean delete(String skillName) { - throw new UnsupportedOperationException( - "delete is not implemented for NacosSkillRepository"); + log.warn("NacosSkillRepository is read-only, delete operation ignored"); + return false; } @Override diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillRepositoryTest.java b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillRepositoryTest.java new file mode 100644 index 000000000..fdbe57784 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillRepositoryTest.java @@ -0,0 +1,257 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed 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 io.agentscope.core.nacos.skill; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.alibaba.nacos.api.ai.AiService; +import com.alibaba.nacos.api.ai.model.skills.Skill; +import com.alibaba.nacos.api.exception.NacosException; +import io.agentscope.core.skill.AgentSkill; +import io.agentscope.core.skill.repository.AgentSkillRepositoryInfo; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Unit tests for {@link NacosSkillRepository}. + */ +@ExtendWith(MockitoExtension.class) +class NacosSkillRepositoryTest { + + @Mock private AiService aiService; + + private NacosSkillRepository repository; + + @BeforeEach + void setUp() { + repository = new NacosSkillRepository(aiService, "public"); + } + + @Test + @DisplayName("Should throw when AiService is null") + void testConstructorWithNullAiService() { + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> new NacosSkillRepository(null, "public")); + assertEquals("AiService cannot be null", e.getMessage()); + } + + @Test + @DisplayName("Should throw when getSkill is called with null name") + void testGetSkillWithNullName() { + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> repository.getSkill(null)); + assertEquals("Skill name cannot be null or empty", e.getMessage()); + } + + @Test + @DisplayName("Should throw when getSkill is called with blank name") + void testGetSkillWithBlankName() { + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> repository.getSkill(" ")); + assertEquals("Skill name cannot be null or empty", e.getMessage()); + } + + @Test + @DisplayName("Should throw when getSkill is called with empty name") + void testGetSkillWithEmptyName() { + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, () -> repository.getSkill("")); + assertEquals("Skill name cannot be null or empty", e.getMessage()); + } + + @Test + @DisplayName("Should throw when skill not found (loadSkill returns null)") + void testGetSkillWhenLoadSkillReturnsNull() throws NacosException { + when(aiService.loadSkill("missing-skill")).thenReturn(null); + + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> repository.getSkill("missing-skill")); + assertEquals("Skill not found: missing-skill", e.getMessage()); + } + + @Test + @DisplayName("Should throw IllegalArgumentException when NacosException is NOT_FOUND") + void testGetSkillWhenNacosExceptionNotFound() throws NacosException { + NacosException nacosEx = new NacosException(NacosException.NOT_FOUND, "not found"); + when(aiService.loadSkill("missing-skill")).thenThrow(nacosEx); + + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> repository.getSkill("missing-skill")); + assertEquals("Skill not found: missing-skill", e.getMessage()); + assertEquals(nacosEx, e.getCause()); + } + + @Test + @DisplayName("Should throw RuntimeException for other NacosException") + void testGetSkillWhenOtherNacosException() throws NacosException { + NacosException nacosEx = new NacosException(500, "server error"); + when(aiService.loadSkill("my-skill")).thenThrow(nacosEx); + + RuntimeException e = + assertThrows(RuntimeException.class, () -> repository.getSkill("my-skill")); + assertEquals("Failed to load skill from Nacos: my-skill", e.getMessage()); + assertEquals(nacosEx, e.getCause()); + } + + @Test + @DisplayName("Should return AgentSkill when skill is found") + void testGetSkillSuccess() throws NacosException { + Skill nacosSkill = mockNacosSkill("test-skill", "A test skill", "Do something"); + when(aiService.loadSkill("test-skill")).thenReturn(nacosSkill); + + AgentSkill result = repository.getSkill("test-skill"); + + assertNotNull(result); + assertEquals("test-skill", result.getName()); + assertEquals("A test skill", result.getDescription()); + assertEquals("Do something", result.getSkillContent()); + assertEquals("nacos:public", result.getSource()); + } + + @Test + @DisplayName("Should trim skill name when calling loadSkill") + void testGetSkillTrimsName() throws NacosException { + Skill nacosSkill = mockNacosSkill("trimmed", "Desc", "Content"); + when(aiService.loadSkill("trimmed")).thenReturn(nacosSkill); + + repository.getSkill(" trimmed "); + + verify(aiService).loadSkill("trimmed"); + } + + @Test + @DisplayName("Should return false for skillExists with null") + void testSkillExistsWithNull() { + assertFalse(repository.skillExists(null)); + } + + @Test + @DisplayName("Should return false for skillExists with blank") + void testSkillExistsWithBlank() { + assertFalse(repository.skillExists(" ")); + } + + @Test + @DisplayName("Should return true when skill exists") + void testSkillExistsWhenFound() throws NacosException { + when(aiService.loadSkill("exists")).thenReturn(mock(Skill.class)); + + assertTrue(repository.skillExists("exists")); + } + + @Test + @DisplayName("Should return false when skill not found") + void testSkillExistsWhenNotFound() throws NacosException { + when(aiService.loadSkill("missing")).thenReturn(null); + + assertFalse(repository.skillExists("missing")); + } + + @Test + @DisplayName("Should return false when NacosException NOT_FOUND") + void testSkillExistsWhenNacosNotFound() throws NacosException { + when(aiService.loadSkill("missing")) + .thenThrow(new NacosException(NacosException.NOT_FOUND, "not found")); + + assertFalse(repository.skillExists("missing")); + } + + @Test + @DisplayName("Should return correct repository info") + void testGetRepositoryInfo() { + AgentSkillRepositoryInfo info = repository.getRepositoryInfo(); + + assertNotNull(info); + assertEquals("nacos", info.getType()); + assertEquals("namespace:public", info.getLocation()); + assertFalse(info.isWritable()); + } + + @Test + @DisplayName("Should return correct source") + void testGetSource() { + assertEquals("nacos:public", repository.getSource()); + } + + @Test + @DisplayName("Should use default namespace when namespaceId is blank") + void testDefaultNamespace() { + try (NacosSkillRepository repo = new NacosSkillRepository(aiService, null)) { + assertEquals("nacos:public", repo.getSource()); + assertEquals("namespace:public", repo.getRepositoryInfo().getLocation()); + } + } + + @Test + @DisplayName("Should always return false for isWriteable") + void testIsWriteable() { + assertFalse(repository.isWriteable()); + repository.setWriteable(true); + assertFalse(repository.isWriteable()); + } + + @Test + @DisplayName("Should return empty list for getAllSkillNames") + void testGetAllSkillNames() { + assertTrue(repository.getAllSkillNames().isEmpty()); + } + + @Test + @DisplayName("Should return empty list for getAllSkills") + void testGetAllSkills() { + assertTrue(repository.getAllSkills().isEmpty()); + } + + @Test + @DisplayName("Should return false for save") + void testSave() { + assertFalse(repository.save(List.of(), false)); + } + + @Test + @DisplayName("Should return false for delete") + void testDelete() { + assertFalse(repository.delete("any-skill")); + } + + private static Skill mockNacosSkill(String name, String description, String instruction) { + Skill skill = mock(Skill.class); + when(skill.getName()).thenReturn(name); + when(skill.getDescription()).thenReturn(description); + when(skill.getInstruction()).thenReturn(instruction); + when(skill.getResource()).thenReturn(null); + return skill; + } +} diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverterTest.java b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverterTest.java new file mode 100644 index 000000000..4ecbb7f50 --- /dev/null +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverterTest.java @@ -0,0 +1,264 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed 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 io.agentscope.core.nacos.skill; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.alibaba.nacos.api.ai.model.skills.Skill; +import com.alibaba.nacos.api.ai.model.skills.SkillResource; +import io.agentscope.core.skill.AgentSkill; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Unit tests for {@link NacosSkillToAgentSkillConverter}. + */ +@ExtendWith(MockitoExtension.class) +class NacosSkillToAgentSkillConverterTest { + + @Mock private Skill nacosSkill; + + @Test + @DisplayName("Should throw when Nacos Skill is null") + void testToAgentSkillWithNullSkill() { + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> NacosSkillToAgentSkillConverter.toAgentSkill(null, "nacos:public")); + assertEquals("Nacos Skill cannot be null", e.getMessage()); + } + + @Test + @DisplayName("Should use default 'unknown' when name is null") + void testDefaultNameWhenNull() { + when(nacosSkill.getName()).thenReturn(null); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(null); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("unknown", result.getName()); + } + + @Test + @DisplayName("Should use default 'unknown' when name is blank") + void testDefaultNameWhenBlank() { + when(nacosSkill.getName()).thenReturn(" "); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(null); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("unknown", result.getName()); + } + + @Test + @DisplayName("Should use default '(no description)' when description is null") + void testDefaultDescriptionWhenNull() { + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn(null); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(null); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("(no description)", result.getDescription()); + } + + @Test + @DisplayName("Should use default '(no description)' when description is blank") + void testDefaultDescriptionWhenBlank() { + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn(""); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(null); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("(no description)", result.getDescription()); + } + + @Test + @DisplayName("Should use default '(no instruction)' when instruction is null") + void testDefaultInstructionWhenNull() { + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn(null); + when(nacosSkill.getResource()).thenReturn(null); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("(no instruction)", result.getSkillContent()); + } + + @Test + @DisplayName("Should use default '(no instruction)' when instruction is blank") + void testDefaultInstructionWhenBlank() { + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn(" "); + when(nacosSkill.getResource()).thenReturn(null); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("(no instruction)", result.getSkillContent()); + } + + @Test + @DisplayName("Should trim name, description, and instruction") + void testTrimsFields() { + when(nacosSkill.getName()).thenReturn(" my-skill "); + when(nacosSkill.getDescription()).thenReturn(" desc "); + when(nacosSkill.getInstruction()).thenReturn(" content "); + when(nacosSkill.getResource()).thenReturn(null); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("my-skill", result.getName()); + assertEquals("desc", result.getDescription()); + assertEquals("content", result.getSkillContent()); + } + + @Test + @DisplayName("Should return empty resources when resource map is null") + void testNullResourceMap() { + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(null); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertNotNull(result.getResources()); + assertTrue(result.getResources().isEmpty()); + } + + @Test + @DisplayName("Should return empty resources when resource map is empty") + void testEmptyResourceMap() { + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(new HashMap<>()); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertTrue(result.getResources().isEmpty()); + } + + @Test + @DisplayName("Should map resources correctly") + void testResourceMapping() { + SkillResource res1 = mock(SkillResource.class); + when(res1.getContent()).thenReturn("content1"); + SkillResource res2 = mock(SkillResource.class); + when(res2.getContent()).thenReturn("content2"); + + Map resourceMap = new HashMap<>(); + resourceMap.put("ref/guide.md", res1); + resourceMap.put("examples/sample.txt", res2); + + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(resourceMap); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:ns1"); + + assertEquals(2, result.getResources().size()); + assertEquals("content1", result.getResource("ref/guide.md")); + assertEquals("content2", result.getResource("examples/sample.txt")); + } + + @Test + @DisplayName("Should use 'resource' as key when resource key is null") + void testNullResourceKey() { + SkillResource res = mock(SkillResource.class); + when(res.getContent()).thenReturn("content"); + Map resourceMap = new HashMap<>(); + resourceMap.put(null, res); + + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(resourceMap); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("content", result.getResource("resource")); + } + + @Test + @DisplayName("Should use empty string when SkillResource content is null") + void testNullResourceContent() { + SkillResource res = mock(SkillResource.class); + when(res.getContent()).thenReturn(null); + Map resourceMap = new HashMap<>(); + resourceMap.put("key", res); + + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(resourceMap); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("", result.getResource("key")); + } + + @Test + @DisplayName("Should use empty string when SkillResource is null") + void testNullSkillResource() { + Map resourceMap = new HashMap<>(); + resourceMap.put("key", null); + + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(resourceMap); + + AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + + assertEquals("", result.getResource("key")); + } + + @Test + @DisplayName("Should pass source to AgentSkill") + void testSourcePassedToAgentSkill() { + when(nacosSkill.getName()).thenReturn("my-skill"); + when(nacosSkill.getDescription()).thenReturn("desc"); + when(nacosSkill.getInstruction()).thenReturn("instruction"); + when(nacosSkill.getResource()).thenReturn(null); + + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:custom-ns"); + + assertEquals("nacos:custom-ns", result.getSource()); + } +} diff --git a/docs/zh/task/agent-skill.md b/docs/zh/task/agent-skill.md index 9e4f1af29..2f876aad3 100644 --- a/docs/zh/task/agent-skill.md +++ b/docs/zh/task/agent-skill.md @@ -307,19 +307,7 @@ try (NacosSkillRepository repository = new NacosSkillRepository(aiService, "name } catch //... ``` -**特性说明**: -- 支持按名称拉取 Skill:`getSkill(String)`、`skillExists(String)` -- 每次获取均从 Nacos 读取最新配置,实现运行时实时同步 -- 当前仅支持读操作,不支持 `getAllSkillNames`、`getAllSkills`、`save`、`delete` -- 需引入以下依赖: - -```xml - - io.agentscope - agentscope-extensions-nacos-skill - ${agentscope.version} - -``` +> 注意: 需引入 `agentscope-extensions-nacos-skill` 依赖 ### 性能优化建议 From 488c33785c7789ac5f61667fbb95c3e4e2d8434d Mon Sep 17 00:00:00 2001 From: "chenxinyu.cxy" Date: Tue, 10 Mar 2026 19:21:32 +0800 Subject: [PATCH 10/10] modify according to copilot's recommendations --- .../nacos/skill/NacosSkillRepositoryTest.java | 6 ++-- .../NacosSkillToAgentSkillConverterTest.java | 36 ++++++++++++------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillRepositoryTest.java b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillRepositoryTest.java index fdbe57784..e0cf8923c 100644 --- a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillRepositoryTest.java +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillRepositoryTest.java @@ -94,8 +94,7 @@ void testGetSkillWhenLoadSkillReturnsNull() throws NacosException { IllegalArgumentException e = assertThrows( - IllegalArgumentException.class, - () -> repository.getSkill("missing-skill")); + IllegalArgumentException.class, () -> repository.getSkill("missing-skill")); assertEquals("Skill not found: missing-skill", e.getMessage()); } @@ -107,8 +106,7 @@ void testGetSkillWhenNacosExceptionNotFound() throws NacosException { IllegalArgumentException e = assertThrows( - IllegalArgumentException.class, - () -> repository.getSkill("missing-skill")); + IllegalArgumentException.class, () -> repository.getSkill("missing-skill")); assertEquals("Skill not found: missing-skill", e.getMessage()); assertEquals(nacosEx, e.getCause()); } diff --git a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverterTest.java b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverterTest.java index 4ecbb7f50..1cdd6af16 100644 --- a/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverterTest.java +++ b/agentscope-extensions/agentscope-extensions-nacos/agentscope-extensions-nacos-skill/src/test/java/io/agentscope/core/nacos/skill/NacosSkillToAgentSkillConverterTest.java @@ -60,7 +60,8 @@ void testDefaultNameWhenNull() { when(nacosSkill.getInstruction()).thenReturn("instruction"); when(nacosSkill.getResource()).thenReturn(null); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("unknown", result.getName()); } @@ -73,7 +74,8 @@ void testDefaultNameWhenBlank() { when(nacosSkill.getInstruction()).thenReturn("instruction"); when(nacosSkill.getResource()).thenReturn(null); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("unknown", result.getName()); } @@ -86,7 +88,8 @@ void testDefaultDescriptionWhenNull() { when(nacosSkill.getInstruction()).thenReturn("instruction"); when(nacosSkill.getResource()).thenReturn(null); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("(no description)", result.getDescription()); } @@ -99,7 +102,8 @@ void testDefaultDescriptionWhenBlank() { when(nacosSkill.getInstruction()).thenReturn("instruction"); when(nacosSkill.getResource()).thenReturn(null); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("(no description)", result.getDescription()); } @@ -112,7 +116,8 @@ void testDefaultInstructionWhenNull() { when(nacosSkill.getInstruction()).thenReturn(null); when(nacosSkill.getResource()).thenReturn(null); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("(no instruction)", result.getSkillContent()); } @@ -125,7 +130,8 @@ void testDefaultInstructionWhenBlank() { when(nacosSkill.getInstruction()).thenReturn(" "); when(nacosSkill.getResource()).thenReturn(null); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("(no instruction)", result.getSkillContent()); } @@ -138,7 +144,8 @@ void testTrimsFields() { when(nacosSkill.getInstruction()).thenReturn(" content "); when(nacosSkill.getResource()).thenReturn(null); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("my-skill", result.getName()); assertEquals("desc", result.getDescription()); @@ -153,7 +160,8 @@ void testNullResourceMap() { when(nacosSkill.getInstruction()).thenReturn("instruction"); when(nacosSkill.getResource()).thenReturn(null); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertNotNull(result.getResources()); assertTrue(result.getResources().isEmpty()); @@ -167,7 +175,8 @@ void testEmptyResourceMap() { when(nacosSkill.getInstruction()).thenReturn("instruction"); when(nacosSkill.getResource()).thenReturn(new HashMap<>()); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertTrue(result.getResources().isEmpty()); } @@ -209,7 +218,8 @@ void testNullResourceKey() { when(nacosSkill.getInstruction()).thenReturn("instruction"); when(nacosSkill.getResource()).thenReturn(resourceMap); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("content", result.getResource("resource")); } @@ -227,7 +237,8 @@ void testNullResourceContent() { when(nacosSkill.getInstruction()).thenReturn("instruction"); when(nacosSkill.getResource()).thenReturn(resourceMap); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("", result.getResource("key")); } @@ -243,7 +254,8 @@ void testNullSkillResource() { when(nacosSkill.getInstruction()).thenReturn("instruction"); when(nacosSkill.getResource()).thenReturn(resourceMap); - AgentSkill result = NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); + AgentSkill result = + NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, "nacos:public"); assertEquals("", result.getResource("key")); }