feat: 增加技能管理模块试验性功能,等待优化
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
package com.easyagents.skill.codec;
|
||||
|
||||
import com.easyagents.skill.exception.SkillPackageException;
|
||||
import com.easyagents.skill.model.Skill;
|
||||
import com.easyagents.skill.store.SkillContentStore;
|
||||
import com.easyagents.skill.store.memory.InMemorySkillContentStore;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* ZipSkillPackageCodec 单元测试。
|
||||
*/
|
||||
public class ZipSkillPackageCodecTest {
|
||||
|
||||
/**
|
||||
* 导入包含一个 Skill 文件夹的 zip。
|
||||
*/
|
||||
@Test
|
||||
public void importOneSkillFolder() {
|
||||
List<Skill> skills = importZip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-a/references/rules/a.md", "# Rule",
|
||||
"skill-a/scripts/run.py", "print('ok')",
|
||||
"skill-a/assets/images/logo.png", "png-data"
|
||||
));
|
||||
|
||||
Assert.assertEquals(1, skills.size());
|
||||
Skill skill = skills.get(0);
|
||||
Assert.assertEquals("skill-a", skill.getId());
|
||||
Assert.assertEquals("Skill A", skill.getName());
|
||||
Assert.assertEquals("Desc A", skill.getDescription());
|
||||
Assert.assertEquals(1, skill.getReferences().size());
|
||||
Assert.assertEquals("references/rules/a.md", skill.getReferences().get(0).getPath());
|
||||
Assert.assertEquals(1, skill.getScripts().size());
|
||||
Assert.assertEquals(1, skill.getAssets().size());
|
||||
Assert.assertEquals("assets/images/logo.png", skill.getAssets().get(0).getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入包含多个 Skill 文件夹的 zip。
|
||||
*/
|
||||
@Test
|
||||
public void importMultipleSkillFolders() {
|
||||
List<Skill> skills = importZip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-b/SKILL.md", skillMd("Skill B", "Desc B")
|
||||
));
|
||||
|
||||
Assert.assertEquals(2, skills.size());
|
||||
Assert.assertEquals("skill-a", skills.get(0).getId());
|
||||
Assert.assertEquals("skill-b", skills.get(1).getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝 zip 根目录直接包含 SKILL.md。
|
||||
*/
|
||||
@Test(expected = SkillPackageException.class)
|
||||
public void rejectRootSkillMd() {
|
||||
importZip(files("SKILL.md", skillMd("Skill", "Desc")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝未知顶级目录。
|
||||
*/
|
||||
@Test(expected = SkillPackageException.class)
|
||||
public void rejectUnknownTopLevelDirectory() {
|
||||
importZip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-a/unknown/a.md", "# Unknown"
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝 references 下的非 md 文件。
|
||||
*/
|
||||
@Test(expected = SkillPackageException.class)
|
||||
public void rejectNonMarkdownReference() {
|
||||
importZip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-a/references/a.txt", "text"
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝不支持的脚本扩展名。
|
||||
*/
|
||||
@Test(expected = SkillPackageException.class)
|
||||
public void rejectUnsupportedScript() {
|
||||
importZip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-a/scripts/run.rb", "puts 'ok'"
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* assets 允许任意扩展名。
|
||||
*/
|
||||
@Test
|
||||
public void allowArbitraryAssetExtension() {
|
||||
List<Skill> skills = importZip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-a/assets/model.custom", "asset"
|
||||
));
|
||||
|
||||
Assert.assertEquals(1, skills.get(0).getAssets().size());
|
||||
Assert.assertEquals("application/octet-stream", skills.get(0).getAssets().get(0).getMediaType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略 macOS 系统文件和空目录。
|
||||
*/
|
||||
@Test
|
||||
public void ignoreSystemFilesAndEmptyDirectories() {
|
||||
Map<String, String> files = files(
|
||||
"__MACOSX/._x", "ignored",
|
||||
"skill-a/.DS_Store", "ignored",
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A")
|
||||
);
|
||||
List<Skill> skills = new ZipSkillPackageCodec().importZip(new ByteArrayInputStream(zip(files, "skill-a/references/")));
|
||||
|
||||
Assert.assertEquals(1, skills.size());
|
||||
Assert.assertEquals("skill-a", skills.get(0).getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 嵌套 references 和 assets 路径正常导入。
|
||||
*/
|
||||
@Test
|
||||
public void allowNestedManagedDirectories() {
|
||||
List<Skill> skills = importZip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-a/references/rules/a.md", "# Nested",
|
||||
"skill-a/assets/images/logo.png", "logo"
|
||||
));
|
||||
|
||||
Assert.assertEquals("references/rules/a.md", skills.get(0).getReferences().get(0).getPath());
|
||||
Assert.assertEquals("assets/images/logo.png", skills.get(0).getAssets().get(0).getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* 资产字节写入内容存储。
|
||||
*/
|
||||
@Test
|
||||
public void storeAssetContent() {
|
||||
InMemorySkillContentStore store = new InMemorySkillContentStore();
|
||||
List<Skill> skills = new ZipSkillPackageCodec(store).importZip(new ByteArrayInputStream(zip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-a/assets/data.bin", "abc"
|
||||
))));
|
||||
|
||||
String contentRef = skills.get(0).getAssets().get(0).getContentRef();
|
||||
Assert.assertTrue(store.exists(contentRef));
|
||||
Assert.assertArrayEquals("abc".getBytes(StandardCharsets.UTF_8), store.readAllBytes(contentRef));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入失败时不写入资产内容。
|
||||
*/
|
||||
@Test
|
||||
public void failedImportDoesNotWriteAssetContent() {
|
||||
CountingContentStore store = new CountingContentStore();
|
||||
|
||||
try {
|
||||
new ZipSkillPackageCodec(store).importZip(new ByteArrayInputStream(zip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-a/assets/data.bin", "abc",
|
||||
"skill-a/unknown/a.md", "# Unknown"
|
||||
))));
|
||||
Assert.fail("Import should fail.");
|
||||
} catch (SkillPackageException expected) {
|
||||
Assert.assertEquals(0, store.putCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多 Skill 包中后续 Skill 失败时,不提前写入前面 Skill 的资产。
|
||||
*/
|
||||
@Test
|
||||
public void failedMultiSkillImportDoesNotWriteEarlierAssetContent() {
|
||||
CountingContentStore store = new CountingContentStore();
|
||||
|
||||
try {
|
||||
new ZipSkillPackageCodec(store).importZip(new ByteArrayInputStream(zip(files(
|
||||
"skill-a/SKILL.md", skillMd("Skill A", "Desc A"),
|
||||
"skill-a/assets/data.bin", "abc",
|
||||
"skill-b/references/a.md", "# Missing SKILL.md"
|
||||
))));
|
||||
Assert.fail("Import should fail.");
|
||||
} catch (SkillPackageException expected) {
|
||||
Assert.assertEquals(0, store.putCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝嵌套 frontmatter。
|
||||
*/
|
||||
@Test(expected = SkillPackageException.class)
|
||||
public void rejectNestedFrontmatter() {
|
||||
importZip(files(
|
||||
"skill-a/SKILL.md", "---\nname: Skill A\ndescription: Desc A\nconfig:\n mode: strict\n---\n# Skill A\n"
|
||||
));
|
||||
}
|
||||
|
||||
private static List<Skill> importZip(Map<String, String> files) {
|
||||
return new ZipSkillPackageCodec().importZip(new ByteArrayInputStream(zip(files)));
|
||||
}
|
||||
|
||||
private static Map<String, String> files(String... keyValues) {
|
||||
Map<String, String> files = new LinkedHashMap<>();
|
||||
for (int i = 0; i < keyValues.length; i += 2) {
|
||||
files.put(keyValues[i], keyValues[i + 1]);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private static byte[] zip(Map<String, String> files, String... directories) {
|
||||
try {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(bytes)) {
|
||||
for (String directory : directories) {
|
||||
zipOutputStream.putNextEntry(new ZipEntry(directory));
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
for (Map.Entry<String, String> file : files.entrySet()) {
|
||||
zipOutputStream.putNextEntry(new ZipEntry(file.getKey()));
|
||||
zipOutputStream.write(file.getValue().getBytes(StandardCharsets.UTF_8));
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String skillMd(String name, String description) {
|
||||
return "---\nname: " + name + "\ndescription: " + description + "\n---\n# " + name + "\n";
|
||||
}
|
||||
|
||||
private static final class CountingContentStore implements SkillContentStore {
|
||||
|
||||
private int putCount;
|
||||
|
||||
@Override
|
||||
public String put(byte[] bytes) {
|
||||
putCount++;
|
||||
return "sha256:" + com.easyagents.skill.util.SkillHashes.sha256Hex(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream open(String contentRef) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readAllBytes(String contentRef) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String contentRef) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.easyagents.skill.repository.memory;
|
||||
|
||||
import com.easyagents.skill.model.Skill;
|
||||
import com.easyagents.skill.model.SkillDescriptor;
|
||||
import com.easyagents.skill.model.SkillReference;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* InMemorySkillRepository 单元测试。
|
||||
*/
|
||||
public class InMemorySkillRepositoryTest {
|
||||
|
||||
/**
|
||||
* 覆盖保存、读取、描述、列表、删除和存在性判断。
|
||||
*/
|
||||
@Test
|
||||
public void crudSkill() {
|
||||
InMemorySkillRepository repository = new InMemorySkillRepository();
|
||||
Skill skill = skill();
|
||||
|
||||
repository.save(skill);
|
||||
|
||||
Assert.assertTrue(repository.exists("skill-a"));
|
||||
Assert.assertTrue(repository.get("skill-a").isPresent());
|
||||
Assert.assertTrue(repository.getDescriptor("skill-a").isPresent());
|
||||
Assert.assertEquals(1, repository.listDescriptors().size());
|
||||
|
||||
repository.delete("skill-a");
|
||||
|
||||
Assert.assertFalse(repository.exists("skill-a"));
|
||||
Assert.assertFalse(repository.get("skill-a").isPresent());
|
||||
}
|
||||
|
||||
/**
|
||||
* descriptor 不携带 references/scripts/assets 内容。
|
||||
*/
|
||||
@Test
|
||||
public void descriptorDoesNotExposeResourceContent() {
|
||||
InMemorySkillRepository repository = new InMemorySkillRepository();
|
||||
repository.save(skill());
|
||||
|
||||
Optional<SkillDescriptor> descriptor = repository.getDescriptor("skill-a");
|
||||
|
||||
Assert.assertTrue(descriptor.isPresent());
|
||||
Assert.assertEquals("skill-a", descriptor.get().getId());
|
||||
Assert.assertEquals("Skill A", descriptor.get().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取返回副本,避免外部修改仓储内部状态。
|
||||
*/
|
||||
@Test
|
||||
public void getReturnsCopy() {
|
||||
InMemorySkillRepository repository = new InMemorySkillRepository();
|
||||
repository.save(skill());
|
||||
|
||||
Skill loaded = repository.get("skill-a").get();
|
||||
loaded.setName("Changed");
|
||||
loaded.getReferences().get(0).setContent("changed");
|
||||
|
||||
Skill reloaded = repository.get("skill-a").get();
|
||||
Assert.assertEquals("Skill A", reloaded.getName());
|
||||
Assert.assertEquals("# A", reloaded.getReferences().get(0).getContent());
|
||||
}
|
||||
|
||||
private static Skill skill() {
|
||||
Skill skill = new Skill();
|
||||
skill.setId("skill-a");
|
||||
skill.setName("Skill A");
|
||||
skill.setDescription("Desc A");
|
||||
skill.setSkillContent("---\nname: Skill A\ndescription: Desc A\n---\n# Skill A\n");
|
||||
|
||||
SkillReference reference = new SkillReference();
|
||||
reference.setPath("references/a.md");
|
||||
reference.setName("a.md");
|
||||
reference.setContent("# A");
|
||||
skill.getReferences().add(reference);
|
||||
return skill;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.easyagents.skill.store.memory;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* InMemorySkillContentStore 单元测试。
|
||||
*/
|
||||
public class InMemorySkillContentStoreTest {
|
||||
|
||||
/**
|
||||
* put 返回 sha256 内容引用,且相同内容引用相同。
|
||||
*/
|
||||
@Test
|
||||
public void putReturnsStableSha256Ref() {
|
||||
InMemorySkillContentStore store = new InMemorySkillContentStore();
|
||||
|
||||
String firstRef = store.put("abc".getBytes(StandardCharsets.UTF_8));
|
||||
String secondRef = store.put("abc".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
Assert.assertTrue(firstRef.startsWith("sha256:"));
|
||||
Assert.assertEquals(firstRef, secondRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* open、readAllBytes 和 exists 正常工作。
|
||||
*
|
||||
* @throws Exception 读取流失败时抛出
|
||||
*/
|
||||
@Test
|
||||
public void openReadAndExists() throws Exception {
|
||||
InMemorySkillContentStore store = new InMemorySkillContentStore();
|
||||
byte[] bytes = "abc".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
String contentRef = store.put(bytes);
|
||||
|
||||
Assert.assertTrue(store.exists(contentRef));
|
||||
Assert.assertArrayEquals(bytes, store.readAllBytes(contentRef));
|
||||
try (InputStream inputStream = store.open(contentRef)) {
|
||||
Assert.assertArrayEquals(bytes, inputStream.readAllBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.easyagents.skill.validation.defaults;
|
||||
|
||||
import com.easyagents.skill.exception.SkillValidationException;
|
||||
import com.easyagents.skill.factory.SkillFactory;
|
||||
import com.easyagents.skill.model.*;
|
||||
import com.easyagents.skill.util.SkillHashes;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* DefaultSkillValidator 单元测试。
|
||||
*/
|
||||
public class DefaultSkillValidatorTest {
|
||||
|
||||
private final DefaultSkillValidator validator = new DefaultSkillValidator();
|
||||
|
||||
/**
|
||||
* 缺失 SKILL.md 内容时失败。
|
||||
*/
|
||||
@Test(expected = SkillValidationException.class)
|
||||
public void rejectMissingSkillContent() {
|
||||
Skill skill = validSkill();
|
||||
skill.setSkillContent(null);
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缺失名称时失败。
|
||||
*/
|
||||
@Test(expected = SkillValidationException.class)
|
||||
public void rejectMissingName() {
|
||||
Skill skill = validSkill();
|
||||
skill.setName("");
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缺失描述时失败。
|
||||
*/
|
||||
@Test(expected = SkillValidationException.class)
|
||||
public void rejectMissingDescription() {
|
||||
Skill skill = validSkill();
|
||||
skill.setDescription("");
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段名称必须与 SKILL.md frontmatter 一致。
|
||||
*/
|
||||
@Test(expected = SkillValidationException.class)
|
||||
public void rejectNameMismatchWithFrontmatter() {
|
||||
Skill skill = validSkill();
|
||||
skill.setName("Changed");
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 元数据必须与 SKILL.md frontmatter 一致。
|
||||
*/
|
||||
@Test(expected = SkillValidationException.class)
|
||||
public void rejectMetadataMismatchWithFrontmatter() {
|
||||
Skill skill = validSkill();
|
||||
skill.getMetadata().put("extra", "x");
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝嵌套 frontmatter。
|
||||
*/
|
||||
@Test(expected = SkillValidationException.class)
|
||||
public void rejectNestedFrontmatter() {
|
||||
Skill skill = validSkill();
|
||||
skill.setSkillContent("---\nname: Skill A\ndescription: Desc A\nconfig:\n mode: strict\n---\n# Skill A\n");
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绝对路径失败。
|
||||
*/
|
||||
@Test(expected = SkillValidationException.class)
|
||||
public void rejectAbsolutePath() {
|
||||
Skill skill = validSkill();
|
||||
SkillReference reference = reference("/references/a.md", "# A");
|
||||
skill.getReferences().add(reference);
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* ../ 路径失败。
|
||||
*/
|
||||
@Test(expected = SkillValidationException.class)
|
||||
public void rejectParentPath() {
|
||||
Skill skill = validSkill();
|
||||
skill.getReferences().add(reference("references/../a.md", "# A"));
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重复路径失败。
|
||||
*/
|
||||
@Test(expected = SkillValidationException.class)
|
||||
public void rejectDuplicatePath() {
|
||||
Skill skill = validSkill();
|
||||
skill.getReferences().add(reference("references/a.md", "# A"));
|
||||
skill.getReferences().add(reference("references/a.md", "# B"));
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不因为文件大小较大而失败。
|
||||
*/
|
||||
@Test
|
||||
public void allowLargeRecordedSize() {
|
||||
Skill skill = validSkill();
|
||||
SkillReference reference = reference("references/a.md", "# A");
|
||||
reference.setSize(Long.MAX_VALUE);
|
||||
skill.getReferences().add(reference);
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 允许空内容文件,只校验 hash 和 size 记录。
|
||||
*/
|
||||
@Test
|
||||
public void allowEmptyReferenceAndScriptContent() {
|
||||
Skill skill = validSkill();
|
||||
skill.getReferences().add(reference("references/empty.md", ""));
|
||||
skill.getScripts().add(script("scripts/empty.sh", ""));
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验通过完整 Skill。
|
||||
*/
|
||||
@Test
|
||||
public void validateCompleteSkill() {
|
||||
Skill skill = validSkill();
|
||||
skill.getReferences().add(reference("references/a.md", "# A"));
|
||||
skill.getScripts().add(script("scripts/run.sh", "echo ok"));
|
||||
skill.getAssets().add(asset("assets/a.bin", "abc"));
|
||||
|
||||
validator.validate(skill);
|
||||
}
|
||||
|
||||
private static Skill validSkill() {
|
||||
return SkillFactory.create("skill-a", "---\nname: Skill A\ndescription: Desc A\n---\n# Skill A\n");
|
||||
}
|
||||
|
||||
private static SkillReference reference(String path, String content) {
|
||||
SkillReference reference = new SkillReference();
|
||||
reference.setPath(path);
|
||||
reference.setName("a.md");
|
||||
reference.setContent(content);
|
||||
reference.setContentHash(SkillHashes.sha256Hex(content.getBytes(StandardCharsets.UTF_8)));
|
||||
reference.setSize(content.getBytes(StandardCharsets.UTF_8).length);
|
||||
return reference;
|
||||
}
|
||||
|
||||
private static SkillScript script(String path, String content) {
|
||||
SkillScript script = new SkillScript();
|
||||
script.setPath(path);
|
||||
script.setLanguage(SkillScriptLanguage.fromPath(path));
|
||||
script.setContent(content);
|
||||
script.setContentHash(SkillHashes.sha256Hex(content.getBytes(StandardCharsets.UTF_8)));
|
||||
script.setSize(content.getBytes(StandardCharsets.UTF_8).length);
|
||||
return script;
|
||||
}
|
||||
|
||||
private static SkillAsset asset(String path, String content) {
|
||||
byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
|
||||
String hash = SkillHashes.sha256Hex(bytes);
|
||||
SkillAsset asset = new SkillAsset();
|
||||
asset.setPath(path);
|
||||
asset.setName("a.bin");
|
||||
asset.setMediaType("application/octet-stream");
|
||||
asset.setContentRef("sha256:" + hash);
|
||||
asset.setContentHash(hash);
|
||||
asset.setSize(bytes.length);
|
||||
return asset;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user