Skip to content

Commit cd1caa7

Browse files
committed
add nacos skill repository
1 parent e738d27 commit cd1caa7

3 files changed

Lines changed: 280 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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 A2A skill</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+
<!-- nacos-api provides AiService, Skill, NacosException, etc. -->
41+
<dependency>
42+
<groupId>com.alibaba.nacos</groupId>
43+
<artifactId>nacos-common</artifactId>
44+
<version>3.2.0-BETA</version>
45+
</dependency>
46+
47+
<dependency>
48+
<groupId>com.alibaba.nacos</groupId>
49+
<artifactId>nacos-api</artifactId>
50+
<version>3.2.0-BETA</version>
51+
</dependency>
52+
53+
<dependency>
54+
<groupId>com.alibaba.nacos</groupId>
55+
<artifactId>nacos-client</artifactId>
56+
<version>3.2.0-BETA</version>
57+
</dependency>
58+
59+
</dependencies>
60+
61+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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.List;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
29+
/**
30+
* Nacos-based implementation of {@link AgentSkillRepository}.
31+
*
32+
* <p>Reads skills from Nacos Config via {@link AiService#loadSkill(String)}. This implementation
33+
* currently supports only read operations: {@link #getSkill(String)}, {@link #skillExists(String)},
34+
* {@link #getRepositoryInfo()}, {@link #getSource()}, and {@link #isWriteable()}. List and write
35+
* operations are not implemented.
36+
*/
37+
public class NacosSkillRepository implements AgentSkillRepository {
38+
39+
private static final Logger log = LoggerFactory.getLogger(NacosSkillRepository.class);
40+
41+
private static final String REPO_TYPE = "nacos";
42+
private static final String LOCATION_PREFIX = "namespace:";
43+
44+
private final AiService aiService;
45+
private final String namespaceId;
46+
private final String source;
47+
private final String location;
48+
private boolean writeable;
49+
50+
/**
51+
* Creates a Nacos skill repository.
52+
*
53+
* @param aiService the Nacos AI service (must not be null)
54+
* @param namespaceId the Nacos namespace ID (null or blank is treated as default namespace)
55+
*/
56+
public NacosSkillRepository(AiService aiService, String namespaceId) {
57+
if (aiService == null) {
58+
throw new IllegalArgumentException("AiService cannot be null");
59+
}
60+
this.aiService = aiService;
61+
this.namespaceId = StringUtils.isBlank(namespaceId) ? "public" : namespaceId.trim();
62+
this.source = REPO_TYPE + ":" + this.namespaceId;
63+
this.location = LOCATION_PREFIX + this.namespaceId;
64+
this.writeable = false;
65+
log.info("NacosSkillRepository initialized for namespace: {}", this.namespaceId);
66+
}
67+
68+
@Override
69+
public AgentSkill getSkill(String name) {
70+
if (name == null || name.isBlank()) {
71+
throw new IllegalArgumentException("Skill name cannot be null or empty");
72+
}
73+
try {
74+
Skill nacosSkill = aiService.loadSkill(name.trim());
75+
if (nacosSkill == null) {
76+
throw new IllegalArgumentException("Skill not found: " + name);
77+
}
78+
return NacosSkillToAgentSkillConverter.toAgentSkill(nacosSkill, getSource());
79+
} catch (NacosException e) {
80+
if (e.getErrCode() == NacosException.NOT_FOUND) {
81+
throw new IllegalArgumentException("Skill not found: " + name, e);
82+
}
83+
throw new RuntimeException("Failed to load skill from Nacos: " + name, e);
84+
}
85+
}
86+
87+
@Override
88+
public boolean skillExists(String skillName) {
89+
if (skillName == null || skillName.isBlank()) {
90+
return false;
91+
}
92+
try {
93+
Skill skill = aiService.loadSkill(skillName.trim());
94+
return skill != null;
95+
} catch (NacosException e) {
96+
if (e.getErrCode() == NacosException.NOT_FOUND) {
97+
return false;
98+
}
99+
log.warn("Error checking skill existence for {}: {}", skillName, e.getMessage());
100+
return false;
101+
}
102+
}
103+
104+
@Override
105+
public AgentSkillRepositoryInfo getRepositoryInfo() {
106+
return new AgentSkillRepositoryInfo(REPO_TYPE, location, writeable);
107+
}
108+
109+
@Override
110+
public String getSource() {
111+
return source;
112+
}
113+
114+
@Override
115+
public void setWriteable(boolean writeable) {
116+
this.writeable = writeable;
117+
}
118+
119+
@Override
120+
public boolean isWriteable() {
121+
return writeable;
122+
}
123+
124+
// ---------- Unsupported operations (list and write) ----------
125+
126+
@Override
127+
public List<String> getAllSkillNames() {
128+
throw new UnsupportedOperationException("getAllSkillNames is not implemented for NacosSkillRepository");
129+
}
130+
131+
@Override
132+
public List<AgentSkill> getAllSkills() {
133+
throw new UnsupportedOperationException("getAllSkills is not implemented for NacosSkillRepository");
134+
}
135+
136+
@Override
137+
public boolean save(List<AgentSkill> skills, boolean force) {
138+
throw new UnsupportedOperationException("save is not implemented for NacosSkillRepository");
139+
}
140+
141+
@Override
142+
public boolean delete(String skillName) {
143+
throw new UnsupportedOperationException("delete is not implemented for NacosSkillRepository");
144+
}
145+
146+
@Override
147+
public void close() {
148+
// AiService lifecycle is managed by the caller; nothing to release here
149+
}
150+
}
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)