Skip to content

Commit 10c8e58

Browse files
authored
Add NacosSkillRepository (#902)
1 parent dd358b7 commit 10c8e58

File tree

11 files changed

+845
-1
lines changed

11 files changed

+845
-1
lines changed

agentscope-dependencies-bom/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
<quartz.version>2.5.2</quartz.version>
104104
<spring.version>7.0.3</spring.version>
105105
<spring-boot.version>4.0.3</spring-boot.version>
106-
<nacos-client.version>3.1.1</nacos-client.version>
106+
<nacos-client.version>3.2.0-BETA</nacos-client.version>
107107
<json-schema-validator.version>3.0.0</json-schema-validator.version>
108108
<jsonschema-generator.version>4.38.0</jsonschema-generator.version>
109109
<snakeyaml.version>2.6</snakeyaml.version>

agentscope-distribution/agentscope-all/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@
207207
<optional>true</optional>
208208
</dependency>
209209

210+
<dependency>
211+
<groupId>io.agentscope</groupId>
212+
<artifactId>agentscope-extensions-nacos-skill</artifactId>
213+
<scope>compile</scope>
214+
<optional>true</optional>
215+
</dependency>
216+
210217
<dependency>
211218
<groupId>io.agentscope</groupId>
212219
<artifactId>agentscope-extensions-skill-git-repository</artifactId>

agentscope-distribution/agentscope-bom/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,12 @@
282282
<version>${project.version}</version>
283283
</dependency>
284284

285+
<dependency>
286+
<groupId>io.agentscope</groupId>
287+
<artifactId>agentscope-extensions-nacos-skill</artifactId>
288+
<version>${project.version}</version>
289+
</dependency>
290+
285291
<!-- AgentScope Nacos Spring Boot Starter -->
286292
<dependency>
287293
<groupId>io.agentscope</groupId>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2024-2026 the original author or authors.
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
18+
<project xmlns="http://maven.apache.org/POM/4.0.0"
19+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
21+
<modelVersion>4.0.0</modelVersion>
22+
<parent>
23+
<groupId>io.agentscope</groupId>
24+
<artifactId>agentscope-extensions-nacos</artifactId>
25+
<version>${revision}</version>
26+
<relativePath>../pom.xml</relativePath>
27+
</parent>
28+
29+
<artifactId>agentscope-extensions-nacos-skill</artifactId>
30+
<name>AgentScope Java - Nacos Skill Repository</name>
31+
<description>agentscope-extensions-nacos-skill</description>
32+
33+
<dependencies>
34+
<!-- Core library is needed at runtime for AgentSkillRepository, AgentSkill, etc. -->
35+
<dependency>
36+
<groupId>io.agentscope</groupId>
37+
<artifactId>agentscope-core</artifactId>
38+
</dependency>
39+
40+
<dependency>
41+
<groupId>com.alibaba.nacos</groupId>
42+
<artifactId>nacos-client</artifactId>
43+
</dependency>
44+
45+
</dependencies>
46+
47+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright 2024-2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.agentscope.core.nacos.skill;
17+
18+
import com.alibaba.nacos.api.ai.AiService;
19+
import com.alibaba.nacos.api.ai.model.skills.Skill;
20+
import com.alibaba.nacos.api.exception.NacosException;
21+
import com.alibaba.nacos.common.utils.StringUtils;
22+
import io.agentscope.core.skill.AgentSkill;
23+
import io.agentscope.core.skill.repository.AgentSkillRepository;
24+
import io.agentscope.core.skill.repository.AgentSkillRepositoryInfo;
25+
import java.util.Collections;
26+
import java.util.List;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
30+
/**
31+
* Nacos-based implementation of {@link AgentSkillRepository}.
32+
*
33+
* <p>Reads skills from Nacos Config via {@code AiService.loadSkill(String)}. This implementation
34+
* supports read operations: {@link #getSkill(String)}, {@link #skillExists(String)}, {@link
35+
* #getRepositoryInfo()}, {@link #getSource()}, and {@link #isWriteable()}. List and write
36+
* operations ({@link #getAllSkillNames()}, {@link #getAllSkills()}, {@link #save(List, boolean)},
37+
* {@link #delete(String)})
38+
* are implemented as read-only no-ops: they return empty list or {@code false} and log a warning.
39+
*/
40+
public class NacosSkillRepository implements AgentSkillRepository {
41+
42+
private static final Logger log = LoggerFactory.getLogger(NacosSkillRepository.class);
43+
44+
private static final String REPO_TYPE = "nacos";
45+
private static final String LOCATION_PREFIX = "namespace:";
46+
47+
private final AiService aiService;
48+
private final String namespaceId;
49+
private final String source;
50+
private final String location;
51+
52+
/**
53+
* Creates a Nacos skill repository.
54+
*
55+
* @param aiService the Nacos AI service (must not be null)
56+
* @param namespaceId the Nacos namespace ID (null or blank is treated as default namespace)
57+
*/
58+
public NacosSkillRepository(AiService aiService, String namespaceId) {
59+
if (aiService == null) {
60+
throw new IllegalArgumentException("AiService cannot be null");
61+
}
62+
this.aiService = aiService;
63+
this.namespaceId = StringUtils.isBlank(namespaceId) ? "public" : namespaceId.trim();
64+
this.source = REPO_TYPE + ":" + this.namespaceId;
65+
this.location = LOCATION_PREFIX + this.namespaceId;
66+
log.info("NacosSkillRepository initialized for namespace: {}", this.namespaceId);
67+
}
68+
69+
@Override
70+
public AgentSkill getSkill(String name) {
71+
if (name == null || name.isBlank()) {
72+
throw new IllegalArgumentException("Skill name cannot be null or empty");
73+
}
74+
try {
75+
Skill nacosSkill = aiService.loadSkill(name.trim());
76+
if (nacosSkill == null) {
77+
throw new IllegalArgumentException("Skill not found: " + name);
78+
}
79+
return NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, getSource());
80+
} catch (NacosException e) {
81+
if (e.getErrCode() == NacosException.NOT_FOUND) {
82+
throw new IllegalArgumentException("Skill not found: " + name, e);
83+
}
84+
throw new RuntimeException("Failed to load skill from Nacos: " + name, e);
85+
}
86+
}
87+
88+
@Override
89+
public boolean skillExists(String skillName) {
90+
if (skillName == null || skillName.isBlank()) {
91+
return false;
92+
}
93+
try {
94+
Skill skill = aiService.loadSkill(skillName.trim());
95+
return skill != null;
96+
} catch (NacosException e) {
97+
if (e.getErrCode() == NacosException.NOT_FOUND) {
98+
return false;
99+
}
100+
log.warn("Error checking skill existence for {}: {}", skillName, e.getMessage());
101+
return false;
102+
}
103+
}
104+
105+
@Override
106+
public AgentSkillRepositoryInfo getRepositoryInfo() {
107+
return new AgentSkillRepositoryInfo(REPO_TYPE, location, false);
108+
}
109+
110+
@Override
111+
public String getSource() {
112+
return source;
113+
}
114+
115+
@Override
116+
public void setWriteable(boolean writeable) {
117+
log.warn("NacosSkillRepository is read-only, set writeable operation ignored");
118+
}
119+
120+
@Override
121+
public boolean isWriteable() {
122+
return false;
123+
}
124+
125+
// ---------- Read-only no-op operations (list and write) ----------
126+
127+
@Override
128+
public List<String> getAllSkillNames() {
129+
log.warn("NacosSkillRepository is read-only, getAllSkillNames returns empty list");
130+
return Collections.emptyList();
131+
}
132+
133+
@Override
134+
public List<AgentSkill> getAllSkills() {
135+
log.warn("NacosSkillRepository is read-only, getAllSkills returns empty list");
136+
return Collections.emptyList();
137+
}
138+
139+
@Override
140+
public boolean save(List<AgentSkill> skills, boolean force) {
141+
log.warn("NacosSkillRepository is read-only, save operation ignored");
142+
return false;
143+
}
144+
145+
@Override
146+
public boolean delete(String skillName) {
147+
log.warn("NacosSkillRepository is read-only, delete operation ignored");
148+
return false;
149+
}
150+
151+
@Override
152+
public void close() {
153+
// AiService lifecycle is managed by the caller; nothing to release here
154+
}
155+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2024-2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.agentscope.core.nacos.skill;
17+
18+
import com.alibaba.nacos.api.ai.model.skills.Skill;
19+
import com.alibaba.nacos.api.ai.model.skills.SkillResource;
20+
import io.agentscope.core.skill.AgentSkill;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
/**
25+
* Converts Nacos AI {@link Skill} to AgentScope {@link AgentSkill}.
26+
*/
27+
public final class NacosSkillToAgentSkillConverter {
28+
29+
private static final String NO_DESCRIPTION = "(no description)";
30+
private static final String NO_INSTRUCTION = "(no instruction)";
31+
32+
private NacosSkillToAgentSkillConverter() {}
33+
34+
/**
35+
* Converts a Nacos Skill to an AgentSkill.
36+
*
37+
* @param nacosSkill the Nacos Skill (must not be null)
38+
* @param source the source identifier for the resulting AgentSkill (e.g. "nacos:public")
39+
* @return the converted AgentSkill
40+
*/
41+
public static AgentSkill toAgentSkill(Skill nacosSkill, String source) {
42+
if (nacosSkill == null) {
43+
throw new IllegalArgumentException("Nacos Skill cannot be null");
44+
}
45+
String name = blankToDefault(nacosSkill.getName(), "unknown");
46+
String description = blankToDefault(nacosSkill.getDescription(), NO_DESCRIPTION);
47+
String skillContent = blankToDefault(nacosSkill.getInstruction(), NO_INSTRUCTION);
48+
Map<String, String> resources = toResourceMap(nacosSkill.getResource());
49+
return new AgentSkill(name, description, skillContent, resources, source);
50+
}
51+
52+
private static String blankToDefault(String value, String defaultValue) {
53+
return (value != null && !value.isBlank()) ? value.trim() : defaultValue;
54+
}
55+
56+
private static Map<String, String> toResourceMap(Map<String, SkillResource> resourceMap) {
57+
if (resourceMap == null || resourceMap.isEmpty()) {
58+
return new HashMap<>();
59+
}
60+
Map<String, String> result = new HashMap<>(resourceMap.size());
61+
for (Map.Entry<String, SkillResource> e : resourceMap.entrySet()) {
62+
String key = e.getKey() != null ? e.getKey() : "resource";
63+
SkillResource res = e.getValue();
64+
String content = (res != null && res.getContent() != null) ? res.getContent() : "";
65+
result.put(key, content);
66+
}
67+
return result;
68+
}
69+
}

0 commit comments

Comments
 (0)