feat: 完善标准 Skill 包底座

- 标准化 SKILL.md、资源模型、校验规则与安全限额

- 支持流式内容存储和单、多 Skill ZIP 双向编解码
This commit is contained in:
2026-07-27 18:53:24 +08:00
parent 6fa93bd671
commit c48d9a9da6
53 changed files with 7809 additions and 666 deletions

View File

@@ -1,20 +1,59 @@
package com.easyagents.skill.codec;
import com.easyagents.skill.exception.SkillPackageException;
import com.easyagents.skill.model.Skill;
import com.easyagents.skill.model.SkillPackage;
import com.easyagents.skill.model.SkillPackageLayout;
import com.easyagents.skill.validation.SkillValidationReport;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
/**
* Skill 包导入接口。
* Skill 包双向流式编解码接口。
*/
public interface SkillPackageCodec {
/**
* 从 zip 输入流导入 Skill。
* 从 ZIP 输入流导入 Skill。
*
* @param inputStream zip 输入流
* @param inputStream ZIP 输入流
* @return Skill 列表
* @throws SkillPackageException ZIP 结构、内容或安全校验失败
* @deprecated 请使用 {@link #decode(InputStream, SkillPackageReadOptions)} 获取包形态、hash 和诊断。
*/
@Deprecated
List<Skill> importZip(InputStream inputStream);
/**
* 解码 Skill ZIP。
*
* <p>兼容默认实现委托旧导入接口;正式 Codec 应覆盖。</p>
*
* @param inputStream ZIP 输入流
* @param options 读取选项
* @return 解码结果
* @throws SkillPackageException ZIP 结构、内容、安全校验或资源存储失败
*/
default SkillPackageReadResult decode(InputStream inputStream, SkillPackageReadOptions options) {
List<Skill> skills = importZip(inputStream);
SkillPackageLayout layout = skills.size() > 1
? SkillPackageLayout.MULTI_DIRECTORY : SkillPackageLayout.SINGLE_DIRECTORY;
return new SkillPackageReadResult(new SkillPackage(layout, skills), new SkillValidationReport(), null);
}
/**
* 将 Skill 包编码为标准 ZIP。
*
* @param skillPackage Skill 包
* @param outputStream 输出流,不由本方法关闭
* @param options 写出选项
* @return 编码结果
* @throws SkillPackageException Skill 包不合法、资源不可读或 ZIP 写出失败
*/
default SkillPackageWriteResult encode(SkillPackage skillPackage, OutputStream outputStream,
SkillPackageWriteOptions options) {
throw new SkillPackageException("This SkillPackageCodec does not support encoding.");
}
}

View File

@@ -0,0 +1,18 @@
package com.easyagents.skill.codec;
/**
* Skill 包读取后的内容处理模式。
*/
public enum SkillPackageReadMode {
/**
* 校验通过后提交二进制内容;校验失败时抛出异常并回滚暂存内容。
*/
COMMIT_ON_VALID,
/**
* 返回可解析包的完整校验报告并回滚全部暂存内容,不提交二进制内容。
* 返回资源中的 contentRef 仅表示内容哈希身份,不保证读取结果返回后仍可打开。
*/
REPORT_ONLY
}

View File

@@ -0,0 +1,69 @@
package com.easyagents.skill.codec;
import com.easyagents.skill.model.SkillPackageLimits;
/**
* Skill 包读取选项。
*/
public final class SkillPackageReadOptions {
private final SkillPackageLimits limits;
private final SkillPackageReadMode mode;
/**
* 使用指定安全限额创建读取选项。
*
* @param limits 安全限额
*/
public SkillPackageReadOptions(SkillPackageLimits limits) {
this(limits, SkillPackageReadMode.COMMIT_ON_VALID);
}
/**
* 使用指定安全限额和内容处理模式创建读取选项。
*
* @param limits 安全限额
* @param mode 内容处理模式
*/
public SkillPackageReadOptions(SkillPackageLimits limits, SkillPackageReadMode mode) {
this.limits = limits == null ? SkillPackageLimits.defaults() : limits;
this.mode = mode == null ? SkillPackageReadMode.COMMIT_ON_VALID : mode;
}
/**
* 创建默认读取选项。
*
* @return 默认读取选项
*/
public static SkillPackageReadOptions defaults() {
return new SkillPackageReadOptions(SkillPackageLimits.defaults());
}
/**
* 创建只返回校验报告且不提交内容的读取选项。
* 二进制资源的 contentRef 仅表示内容哈希身份,不承诺可通过内容存储打开。
*
* @return 只读预检选项
*/
public static SkillPackageReadOptions reportOnly() {
return new SkillPackageReadOptions(SkillPackageLimits.defaults(), SkillPackageReadMode.REPORT_ONLY);
}
/**
* 获取安全限额。
*
* @return 安全限额
*/
public SkillPackageLimits getLimits() {
return limits;
}
/**
* 获取内容处理模式。
*
* @return 内容处理模式
*/
public SkillPackageReadMode getMode() {
return mode;
}
}

View File

@@ -0,0 +1,43 @@
package com.easyagents.skill.codec;
import com.easyagents.skill.model.SkillPackage;
import com.easyagents.skill.validation.SkillValidationReport;
/**
* Skill 包解码结果。
*/
public final class SkillPackageReadResult {
private final SkillPackage skillPackage;
private final SkillValidationReport validationReport;
private final String packageHash;
/**
* 创建解码结果。
*
* @param skillPackage Skill 包
* @param validationReport 校验报告
* @param packageHash 输入 ZIP SHA-256
*/
public SkillPackageReadResult(SkillPackage skillPackage, SkillValidationReport validationReport,
String packageHash) {
this.skillPackage = skillPackage;
this.validationReport = validationReport;
this.packageHash = packageHash;
}
/** @return Skill 包 */
public SkillPackage getSkillPackage() {
return skillPackage;
}
/** @return 结构化校验报告 */
public SkillValidationReport getValidationReport() {
return validationReport;
}
/** @return 输入 ZIP SHA-256 */
public String getPackageHash() {
return packageHash;
}
}

View File

@@ -0,0 +1,38 @@
package com.easyagents.skill.codec;
import com.easyagents.skill.model.SkillPackageLimits;
/**
* Skill 包写出选项。
*/
public final class SkillPackageWriteOptions {
private final SkillPackageLimits limits;
/**
* 使用指定安全限额创建写出选项。
*
* @param limits 安全限额
*/
public SkillPackageWriteOptions(SkillPackageLimits limits) {
this.limits = limits == null ? SkillPackageLimits.defaults() : limits;
}
/**
* 创建默认写出选项。
*
* @return 默认写出选项
*/
public static SkillPackageWriteOptions defaults() {
return new SkillPackageWriteOptions(SkillPackageLimits.defaults());
}
/**
* 获取安全限额。
*
* @return 安全限额
*/
public SkillPackageLimits getLimits() {
return limits;
}
}

View File

@@ -0,0 +1,61 @@
package com.easyagents.skill.codec;
import com.easyagents.skill.model.SkillPackageLayout;
/**
* Skill 包编码结果。
*/
public final class SkillPackageWriteResult {
private final String packageHash;
private final long size;
private final int entryCount;
private final SkillPackageLayout layout;
/**
* 创建编码结果。
*
* @param packageHash 输出 ZIP SHA-256
* @param size 输出字节数
* @param entryCount 输出 entry 数
*/
public SkillPackageWriteResult(String packageHash, long size, int entryCount) {
this(packageHash, size, entryCount, SkillPackageLayout.SINGLE_DIRECTORY);
}
/**
* 创建带包形态的编码结果。
*
* @param packageHash 输出 ZIP SHA-256
* @param size 输出字节数
* @param entryCount 输出 entry 数
* @param layout 输出包形态
*/
public SkillPackageWriteResult(String packageHash, long size, int entryCount,
SkillPackageLayout layout) {
this.packageHash = packageHash;
this.size = size;
this.entryCount = entryCount;
this.layout = layout == null ? SkillPackageLayout.SINGLE_DIRECTORY : layout;
}
/** @return 输出 ZIP SHA-256 */
public String getPackageHash() {
return packageHash;
}
/** @return 输出字节数 */
public long getSize() {
return size;
}
/** @return 输出 entry 数 */
public int getEntryCount() {
return entryCount;
}
/** @return 输出包形态 */
public SkillPackageLayout getLayout() {
return layout;
}
}