Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agentscope-dependencies-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
<quartz.version>2.5.2</quartz.version>
<spring.version>7.0.3</spring.version>
<spring-boot.version>4.0.3</spring-boot.version>
<nacos-client.version>3.1.1</nacos-client.version>
<nacos-client.version>3.2.0-BETA</nacos-client.version>
<json-schema-validator.version>3.0.0</json-schema-validator.version>
<jsonschema-generator.version>4.38.0</jsonschema-generator.version>
<snakeyaml.version>2.6</snakeyaml.version>
Expand Down
7 changes: 7 additions & 0 deletions agentscope-distribution/agentscope-all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>io.agentscope</groupId>
<artifactId>agentscope-extensions-nacos-skill</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>io.agentscope</groupId>
<artifactId>agentscope-extensions-skill-git-repository</artifactId>
Expand Down
6 changes: 6 additions & 0 deletions agentscope-distribution/agentscope-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.agentscope</groupId>
<artifactId>agentscope-extensions-nacos-skill</artifactId>
<version>${project.version}</version>
</dependency>

<!-- AgentScope Nacos Spring Boot Starter -->
<dependency>
<groupId>io.agentscope</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.agentscope</groupId>
<artifactId>agentscope-extensions-nacos</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>agentscope-extensions-nacos-skill</artifactId>
<name>AgentScope Java - Nacos A2A skill</name>
<description>agentscope-extensions-nacos-skill</description>

<dependencies>
<!-- Core library is needed at runtime for AgentSkillRepository, AgentSkill, etc. -->
<dependency>
<groupId>io.agentscope</groupId>
<artifactId>agentscope-core</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* 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}.
*
* <p>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.
*/
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<String> getAllSkillNames() {
throw new UnsupportedOperationException(
"getAllSkillNames is not implemented for NacosSkillRepository");
}

@Override
public List<AgentSkill> getAllSkills() {
throw new UnsupportedOperationException(
"getAllSkills is not implemented for NacosSkillRepository");
}

@Override
public boolean save(List<AgentSkill> 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
}
}
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<String, String> toResourceMap(Map<String, SkillResource> resourceMap) {
if (resourceMap == null || resourceMap.isEmpty()) {
return new HashMap<>();
}
Map<String, String> result = new HashMap<>(resourceMap.size());
for (Map.Entry<String, SkillResource> 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;
}
}
1 change: 1 addition & 0 deletions agentscope-extensions/agentscope-extensions-nacos/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<modules>
<module>agentscope-extensions-nacos-a2a</module>
<module>agentscope-extensions-nacos-prompt</module>
<module>agentscope-extensions-nacos-skill</module>
</modules>

</project>