Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions agentscope-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@
<artifactId>json-schema-validator</artifactId>
</dependency>

<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
Expand Down
135 changes: 115 additions & 20 deletions agentscope-core/src/main/java/io/agentscope/core/skill/AgentSkill.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

Expand Down Expand Up @@ -62,8 +63,7 @@
* @see io.agentscope.core.skill.util.MarkdownSkillParser
*/
public class AgentSkill {
private final String name;
private final String description;
private final Map<String, Object> metadata;
private final String skillContent;
private final Map<String, String> resources;
private final String source;
Expand Down Expand Up @@ -104,16 +104,37 @@ public AgentSkill(
String skillContent,
Map<String, String> resources,
String source) {
if (name == null || name.isEmpty() || description == null || description.isEmpty()) {
throw new IllegalArgumentException(
"The skill must have `name` and `description` fields.");
}
this(createMetadata(name, description), skillContent, resources, source);
}

/**
* Creates an AgentSkill with explicit metadata.
*
* <p>The metadata must include non-empty string values for {@code name} and
* {@code description}. The metadata map is copied and stored as immutable.
*
* @param metadata Skill metadata including required {@code name} and {@code description}
* @param skillContent The skill implementation or instructions (must not be null or empty)
* @param resources Supporting resources referenced by the skill (can be null)
* @param source Source identifier for the skill (null defaults to "custom")
* @throws IllegalArgumentException if metadata is invalid or skillContent is null or empty
*/
public AgentSkill(
Map<String, Object> metadata,
String skillContent,
Map<String, String> resources,
String source) {
String name = getRequiredMetadataString(metadata, "name");
String description = getRequiredMetadataString(metadata, "description");
if (skillContent == null || skillContent.isEmpty()) {
throw new IllegalArgumentException("The skill must have content");
}

this.name = name;
this.description = description;
LinkedHashMap<String, Object> metadataCopy = new LinkedHashMap<>(metadata);
metadataCopy.put("name", name);
metadataCopy.put("description", description);

this.metadata = Collections.unmodifiableMap(metadataCopy);
this.skillContent = skillContent;
this.resources = resources != null ? new HashMap<>(resources) : new HashMap<>();
this.source = source != null ? source : "custom";
Expand All @@ -125,7 +146,7 @@ public AgentSkill(
* @return The skill name (never null)
*/
public String getName() {
return name;
return (String) metadata.get("name");
}

/**
Expand All @@ -134,7 +155,26 @@ public String getName() {
* @return The skill description (never null)
*/
public String getDescription() {
return description;
return (String) metadata.get("description");
}

/**
* Gets the skill metadata.
*
* @return The immutable metadata map (never null, may be empty except required fields)
*/
public Map<String, Object> getMetadata() {
return metadata;
}

/**
* Gets a metadata value by key.
*
* @param key The metadata key
* @return The metadata value, or null if not found
*/
public Object getMetadataValue(String key) {
return metadata.get(key);
}

/**
Expand Down Expand Up @@ -193,7 +233,7 @@ public Set<String> getResourcePaths() {
* @return Unique skill identifier (never null)
*/
public String getSkillId() {
return name + "_" + source;
return getName() + "_" + source;
}

/**
Expand Down Expand Up @@ -224,9 +264,9 @@ public static Builder builder() {
@Override
public String toString() {
return "AgentSkill{name='"
+ name
+ getName()
+ "', description='"
+ description
+ getDescription()
+ "', source='"
+ source
+ "'}";
Expand Down Expand Up @@ -256,8 +296,7 @@ public String toString() {
* }</pre>
*/
public static class Builder {
private String name;
private String description;
private Map<String, Object> metadata;
private String skillContent;
private Map<String, String> resources;
private String source;
Expand All @@ -266,6 +305,7 @@ public static class Builder {
* Creates an empty builder.
*/
private Builder() {
this.metadata = new LinkedHashMap<>();
this.resources = new HashMap<>();
}

Expand All @@ -275,8 +315,7 @@ private Builder() {
* @param baseSkill The skill to copy values from
*/
private Builder(AgentSkill baseSkill) {
this.name = baseSkill.name;
this.description = baseSkill.description;
this.metadata = new LinkedHashMap<>(baseSkill.metadata);
this.skillContent = baseSkill.skillContent;
this.resources = new HashMap<>(baseSkill.resources);
this.source = baseSkill.source;
Expand All @@ -289,7 +328,7 @@ private Builder(AgentSkill baseSkill) {
* @return This builder
*/
public Builder name(String name) {
this.name = name;
this.metadata.put("name", name);
return this;
}

Expand All @@ -300,7 +339,42 @@ public Builder name(String name) {
* @return This builder
*/
public Builder description(String description) {
this.description = description;
this.metadata.put("description", description);
return this;
}

/**
* Replaces all metadata with a new map.
*
* @param metadata The new metadata map
* @return This builder
*/
public Builder metadata(Map<String, Object> metadata) {
this.metadata =
metadata != null ? new LinkedHashMap<>(metadata) : new LinkedHashMap<>();
return this;
}

/**
* Adds or updates a single metadata entry.
*
* @param key The metadata key
* @param value The metadata value
* @return This builder
*/
public Builder putMetadata(String key, Object value) {
this.metadata.put(key, value);
return this;
}

/**
* Removes a metadata entry.
*
* @param key The metadata key to remove
* @return This builder
*/
public Builder removeMetadata(String key) {
this.metadata.remove(key);
return this;
}

Expand Down Expand Up @@ -377,7 +451,28 @@ public Builder source(String source) {
* @throws IllegalArgumentException if required fields are missing
*/
public AgentSkill build() {
return new AgentSkill(name, description, skillContent, resources, source);
return new AgentSkill(metadata, skillContent, resources, source);
}
}

private static Map<String, Object> createMetadata(String name, String description) {
LinkedHashMap<String, Object> metadata = new LinkedHashMap<>();
metadata.put("name", name);
metadata.put("description", description);
return metadata;
}

private static String getRequiredMetadataString(Map<String, Object> metadata, String key) {
if (metadata == null) {
throw new IllegalArgumentException(
"The skill must have `name` and `description` fields.");
}

Object value = metadata.get(key);
if (!(value instanceof String stringValue) || stringValue.isEmpty()) {
throw new IllegalArgumentException(
"The skill must have `name` and `description` fields.");
}
return stringValue;
}
}
Loading
Loading