refactor: 精简标准 Skill 包模型
- 统一使用 SKILL.md 与通用资源表达 - 删除仓储、专用资源类型和低价值兼容接口 - 保留安全 ZIP 编解码、校验和内容存储能力
This commit is contained in:
@@ -4,7 +4,6 @@ import com.easyagents.skill.exception.SkillPackageException;
|
||||
import com.easyagents.skill.exception.SkillValidationException;
|
||||
import com.easyagents.skill.model.Skill;
|
||||
import com.easyagents.skill.model.SkillDocument;
|
||||
import com.easyagents.skill.model.SkillMetadata;
|
||||
import com.easyagents.skill.model.SkillPackage;
|
||||
import com.easyagents.skill.model.SkillPackageLayout;
|
||||
import com.easyagents.skill.model.SkillPackageLimits;
|
||||
@@ -22,7 +21,6 @@ import com.easyagents.skill.validation.SkillValidationIssue;
|
||||
import com.easyagents.skill.validation.SkillValidationMode;
|
||||
import com.easyagents.skill.validation.SkillValidationReport;
|
||||
import com.easyagents.skill.validation.SkillValidationSeverity;
|
||||
import com.easyagents.skill.validation.SkillValidator;
|
||||
import com.easyagents.skill.validation.defaults.DefaultSkillValidator;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
@@ -62,7 +60,7 @@ import java.util.zip.ZipOutputStream;
|
||||
/**
|
||||
* 标准 Skill ZIP 的安全双向流式 Codec。
|
||||
*/
|
||||
public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
public class ZipSkillPackageCodec implements AutoCloseable {
|
||||
|
||||
private static final String DEFAULT_MEDIA_TYPE = "application/octet-stream";
|
||||
private static final int CENTRAL_DIRECTORY_HEADER_SIZE = 46;
|
||||
@@ -79,7 +77,6 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
private static final int UINT16_MAX = 0xFFFF;
|
||||
|
||||
private final SkillContentStore contentStore;
|
||||
private final SkillValidator additionalValidator;
|
||||
private final boolean ownsContentStore;
|
||||
|
||||
/**
|
||||
@@ -88,7 +85,7 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
* <p>该实例拥有默认内容存储,使用完毕后应调用 {@link #close()} 清理临时内容。</p>
|
||||
*/
|
||||
public ZipSkillPackageCodec() {
|
||||
this(new TemporaryFileSkillContentStore(), null, true);
|
||||
this(new TemporaryFileSkillContentStore(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,36 +94,17 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
* @param contentStore 二进制内容存储
|
||||
*/
|
||||
public ZipSkillPackageCodec(SkillContentStore contentStore) {
|
||||
this(contentStore, null, false);
|
||||
this(contentStore, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 ZIP Codec。
|
||||
*
|
||||
* @param contentStore 二进制内容存储
|
||||
* @param validator Skill 聚合校验器
|
||||
*/
|
||||
public ZipSkillPackageCodec(SkillContentStore contentStore, SkillValidator validator) {
|
||||
this(contentStore, requireValidator(validator), false);
|
||||
}
|
||||
|
||||
private ZipSkillPackageCodec(SkillContentStore contentStore, SkillValidator validator,
|
||||
boolean ownsContentStore) {
|
||||
private ZipSkillPackageCodec(SkillContentStore contentStore, boolean ownsContentStore) {
|
||||
if (contentStore == null) {
|
||||
throw new SkillPackageException("Skill content store is required.");
|
||||
}
|
||||
this.contentStore = contentStore;
|
||||
this.additionalValidator = validator;
|
||||
this.ownsContentStore = ownsContentStore;
|
||||
}
|
||||
|
||||
private static SkillValidator requireValidator(SkillValidator validator) {
|
||||
if (validator == null) {
|
||||
throw new SkillPackageException("Skill validator is required.");
|
||||
}
|
||||
return validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭 Codec 自有的默认临时内容存储。
|
||||
*
|
||||
@@ -139,19 +117,6 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用兼容入口导入 ZIP。
|
||||
*
|
||||
* @param inputStream ZIP 输入流
|
||||
* @return Skill 列表
|
||||
* @deprecated 请使用 {@link #decode(InputStream, SkillPackageReadOptions)}。
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public List<Skill> importZip(InputStream inputStream) {
|
||||
return decode(inputStream, SkillPackageReadOptions.defaults()).getSkillPackage().getSkills();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全解码 Skill ZIP。
|
||||
*
|
||||
@@ -159,7 +124,6 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
* @param options 读取选项
|
||||
* @return 解码结果
|
||||
*/
|
||||
@Override
|
||||
public SkillPackageReadResult decode(InputStream inputStream, SkillPackageReadOptions options) {
|
||||
if (inputStream == null) {
|
||||
throw packageError("ZIP_INPUT_REQUIRED", null, "ZIP input stream is required.");
|
||||
@@ -221,7 +185,6 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
* @param options 写出选项
|
||||
* @return 编码结果
|
||||
*/
|
||||
@Override
|
||||
public SkillPackageWriteResult encode(SkillPackage skillPackage, OutputStream outputStream,
|
||||
SkillPackageWriteOptions options) {
|
||||
if (skillPackage == null || skillPackage.getSkills() == null || skillPackage.getSkills().isEmpty()) {
|
||||
@@ -370,9 +333,7 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
throw validationPackageError(e,
|
||||
layout == SkillPackageLayout.ROOT_SKILL ? null : group.root);
|
||||
}
|
||||
Map<String, Object> frontmatter = document.getFrontmatter().getValues();
|
||||
String name = scalar(frontmatter.get("name"));
|
||||
String description = scalar(frontmatter.get("description"));
|
||||
String name = scalar(document.getFrontmatter().get("name"));
|
||||
|
||||
List<SkillResource> resources = new ArrayList<>();
|
||||
for (ArchiveFile file : group.files) {
|
||||
@@ -385,14 +346,9 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
resources.sort(Comparator.comparing(SkillResource::getPath));
|
||||
|
||||
Skill skill = new Skill();
|
||||
skill.setId(null);
|
||||
skill.setPackageRoot(layout == SkillPackageLayout.ROOT_SKILL ? name : group.root);
|
||||
skill.setName(name);
|
||||
skill.setDescription(description);
|
||||
skill.setMetadata(new SkillMetadata(frontmatter));
|
||||
skill.setDocument(document);
|
||||
skill.setResources(resources);
|
||||
SkillResources.refreshLegacyViews(skill);
|
||||
return skill;
|
||||
}
|
||||
|
||||
@@ -400,7 +356,7 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
StageTracker stages) throws IOException {
|
||||
SkillResourceKind kind = SkillResources.classify(file.relativePath);
|
||||
String mediaType = detectMediaType(file.relativePath);
|
||||
boolean text = SkillResources.isText(file.relativePath, kind, mediaType);
|
||||
boolean text = SkillResources.isText(file.relativePath, mediaType);
|
||||
long singleLimit = text ? limits.getMaxTextFileBytes() : limits.getMaxBinaryFileBytes();
|
||||
if (file.entry.getSize() > singleLimit) {
|
||||
throw packageError(text ? "TEXT_FILE_SIZE_LIMIT" : "BINARY_FILE_SIZE_LIMIT", file.fullPath,
|
||||
@@ -489,24 +445,16 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 Codec 不可绕过的标准安全校验,并追加调用方业务校验。
|
||||
* 执行 Codec 不可绕过的标准安全校验。
|
||||
*
|
||||
* @param skill Skill 聚合
|
||||
* @param limits 当前读写操作限额
|
||||
* @param mode 标准校验模式
|
||||
* @return 合并后的结构化报告
|
||||
* @return 结构化报告
|
||||
*/
|
||||
private SkillValidationReport validateForCodec(Skill skill, SkillPackageLimits limits,
|
||||
SkillValidationMode mode) {
|
||||
SkillValidationReport report = new DefaultSkillValidator(limits)
|
||||
.validateReport(skill, limits, mode);
|
||||
if (additionalValidator != null) {
|
||||
SkillValidationReport additionalReport = additionalValidator.getClass() == DefaultSkillValidator.class
|
||||
? additionalValidator.validateReport(skill, null, mode)
|
||||
: additionalValidator.validateReport(skill, limits, mode);
|
||||
report.merge(additionalReport);
|
||||
}
|
||||
return report;
|
||||
return new DefaultSkillValidator(limits).validateReport(skill, limits, mode);
|
||||
}
|
||||
|
||||
private List<OutputFile> prepareOutput(List<Skill> skills, SkillPackageLimits limits) {
|
||||
@@ -557,7 +505,7 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
files.add(OutputFile.text(skillPath, skillContent));
|
||||
totalSize = checkedOutputTotal(totalSize, skillSize, limits, skillPath);
|
||||
|
||||
List<SkillResource> resources = SkillResources.canonicalResources(skill);
|
||||
List<SkillResource> resources = new ArrayList<>(skill.getResources());
|
||||
resources.sort(Comparator.comparing(SkillResource::getPath));
|
||||
for (SkillResource resource : resources) {
|
||||
String path = skill.getName() + "/" + SkillPaths.normalize(resource.getPath());
|
||||
@@ -1533,22 +1481,27 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
}
|
||||
SkillPackageException rollbackFailure = null;
|
||||
for (int index = records.size() - 1; index >= 0; index--) {
|
||||
StageRecord record = records.get(index);
|
||||
if (record.cleaned) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
contentStore.rollback(records.get(index).stage);
|
||||
contentStore.rollback(record.stage);
|
||||
record.cleaned = true;
|
||||
} catch (RuntimeException cleanupError) {
|
||||
if (rollbackFailure == null) {
|
||||
rollbackFailure = new SkillPackageException(
|
||||
"SKILL_CONTENT_ROLLBACK_ERROR", records.get(index).path,
|
||||
"SKILL_CONTENT_ROLLBACK_ERROR", record.path,
|
||||
"Failed to rollback staged Skill package content.", cleanupError);
|
||||
} else {
|
||||
rollbackFailure.addSuppressed(cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
finalized = true;
|
||||
if (rollbackFailure != null) {
|
||||
throw rollbackFailure;
|
||||
}
|
||||
finalized = true;
|
||||
}
|
||||
|
||||
private void cleanupAfterFailure(Throwable primary) {
|
||||
@@ -1557,17 +1510,21 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
}
|
||||
for (int index = records.size() - 1; index >= 0; index--) {
|
||||
StageRecord record = records.get(index);
|
||||
if (record.cleaned) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (record.committedRef == null) {
|
||||
contentStore.rollback(record.stage);
|
||||
} else {
|
||||
contentStore.release(record.committedRef);
|
||||
}
|
||||
record.cleaned = true;
|
||||
} catch (RuntimeException cleanupError) {
|
||||
primary.addSuppressed(cleanupError);
|
||||
}
|
||||
}
|
||||
finalized = true;
|
||||
finalized = records.stream().allMatch(record -> record.cleaned);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1577,6 +1534,7 @@ public class ZipSkillPackageCodec implements SkillPackageCodec, AutoCloseable {
|
||||
private final SkillResource resource;
|
||||
private final String path;
|
||||
private String committedRef;
|
||||
private boolean cleaned;
|
||||
|
||||
private StageRecord(SkillContentStage stage, SkillResource resource, String path) {
|
||||
this.stage = stage;
|
||||
|
||||
Reference in New Issue
Block a user