Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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