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
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import com.alibaba.nacos.api.ai.model.skills.Skill;
import com.alibaba.nacos.api.ai.model.skills.SkillMeta;
import com.alibaba.nacos.api.ai.model.skills.SkillSummary;
import com.alibaba.nacos.api.ai.model.skills.SkillUploadPrecheckRequest;
import com.alibaba.nacos.api.ai.model.skills.SkillUploadPrecheckResult;
import com.alibaba.nacos.api.annotation.NacosApi;
import com.alibaba.nacos.api.annotation.Since;
import com.alibaba.nacos.api.common.ApiType;
Expand All @@ -56,11 +58,14 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import static com.alibaba.nacos.ai.constant.Constants.Skills.ADMIN_PATH;
Expand Down Expand Up @@ -196,6 +201,7 @@ public Result<String> uploadSkill(HttpServletRequest request,
defaultValue = "false") boolean overwrite,
@RequestParam(value = "targetVersion", required = false) String targetVersion,
@RequestParam(value = "commitMsg", required = false) String commitMsg,
@RequestParam(value = "uploadAction", required = false) String uploadAction,
@RequestParam("file") MultipartFile file) throws NacosException {
namespaceId = NamespaceUtil.processNamespaceParameter(namespaceId);
byte[] zipBytes = SkillRequestUtil.validateAndExtractZipBytes(file);
Expand All @@ -205,11 +211,41 @@ public Result<String> uploadSkill(HttpServletRequest request,
.overwrite(overwrite)
.targetVersion(targetVersion)
.commitMsg(commitMsg)
.uploadAction(uploadAction)
.build();
String skillName = skillOperationService.uploadSkillFromZip(uploadRequest);
return Result.success(skillName);
}

/**
* Batch precheck multiple skill uploads from client-parsed metadata.
*
* @param request HTTP servlet request
* @param precheckRequests list of upload precheck requests
* @return list of precheck results in the same order as input
* @throws NacosException if precheck failed unexpectedly
*/
@Since("3.2.3")
@PostMapping(value = "/upload/batch/precheck")
@Secured(action = ActionTypes.WRITE, signType = SignType.AI, apiType = ApiType.ADMIN_API)
@ExtractorManager.Extractor(httpExtractor = ExtractorManager.DefaultHttpExtractor.class)
public Result<List<SkillUploadPrecheckResult>> batchPrecheckUploadSkill(
HttpServletRequest request,
@RequestBody(required = false) List<SkillUploadPrecheckRequest> precheckRequests)
throws NacosException {
if (precheckRequests == null || precheckRequests.isEmpty()) {
return Result.success(Collections.emptyList());
}
for (SkillUploadPrecheckRequest req : precheckRequests) {
if (req != null) {
req.setNamespaceId(
NamespaceUtil.processNamespaceParameter(req.getNamespaceId()));
}
}
return Result.success(
skillOperationService.batchPrecheckUploadSkill(precheckRequests));
}

/**
* Batch upload multiple skills from a single zip file. The zip must contain one-level subdirectories,
* each with its own SKILL.md. Uses best-effort strategy.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,19 @@ public class SkillResourceOperator implements AiResourceOperator {

private static final String CONFLICT_TYPE_WORKING_VERSION = "working_version";

private static final String CONFLICT_TYPE_REVIEWING_VERSION = "reviewing_version";

private static final String CONFLICT_TYPE_EXISTING = "existing";

private static final String METADATA_ARTIFACT_URL = "artifactUrl";

private static final String METADATA_SOURCE = "source";

private static final String WORKING_VERSION_SKIP_MESSAGE =
"Skipped because a working version (editing/reviewing) already exists.";
"Skipped because an editing draft already exists.";

private static final String REVIEWING_VERSION_SKIP_MESSAGE =
"Skipped because a reviewing version already exists.";

private final SkillOperationService skillOperationService;

Expand Down Expand Up @@ -86,11 +91,18 @@ public AiResourceImportValidationItem validate(String namespaceId,
return result;
}
ResourceVersionInfo info = AiResourceManager.requireVersionInfo(meta);
if (hasWorkingVersion(info) && !overwriteExisting) {
if (hasReviewingVersion(info)) {
result.setStatus(AiResourceImportValidationStatus.CONFLICT);
result.setConflictType(CONFLICT_TYPE_REVIEWING_VERSION);
result.setErrors(Collections.singletonList(
"There is already a reviewing version, cannot import until review finishes."));
return result;
}
if (hasEditingVersion(info) && !overwriteExisting) {
result.setStatus(AiResourceImportValidationStatus.CONFLICT);
result.setConflictType(CONFLICT_TYPE_WORKING_VERSION);
result.setErrors(Collections.singletonList(
"There is already a working version (editing/reviewing), enable overwrite to import."));
"There is already an editing draft, enable overwrite to import."));
return result;
}
result.setStatus(AiResourceImportValidationStatus.WARNING);
Expand All @@ -104,7 +116,11 @@ public AiResourceImportResultItem importResource(String namespaceId,
AiResourceImportArtifact artifact, boolean overwriteExisting) throws NacosException {
Skill skill = parseSkill(namespaceId, artifact);
AiResource meta = resourceManager.findMeta(namespaceId, skill.getName(), resourceType());
if (!overwriteExisting && hasWorkingVersion(AiResourceManager.requireVersionInfo(meta))) {
ResourceVersionInfo info = meta == null ? null : AiResourceManager.requireVersionInfo(meta);
if (hasReviewingVersion(info)) {
return skippedReviewingVersionItem(artifact, skill);
}
if (!overwriteExisting && hasEditingVersion(info)) {
return skippedWorkingVersionItem(artifact, skill);
}
String version = resolveVersion(artifact);
Expand All @@ -124,6 +140,17 @@ public AiResourceImportResultItem importResource(String namespaceId,
return result;
}

private AiResourceImportResultItem skippedReviewingVersionItem(
AiResourceImportArtifact artifact, Skill skill) {
AiResourceImportResultItem result = new AiResourceImportResultItem();
result.setExternalId(artifact.getExternalId());
result.setResourceName(skill.getName());
result.setVersion(resolveVersion(artifact));
result.setStatus(AiResourceImportResultStatus.SKIPPED);
result.setWarnings(Collections.singletonList(REVIEWING_VERSION_SKIP_MESSAGE));
return result;
}

private AiResourceImportResultItem skippedWorkingVersionItem(
AiResourceImportArtifact artifact, Skill skill) {
AiResourceImportResultItem result = new AiResourceImportResultItem();
Expand Down Expand Up @@ -157,9 +184,12 @@ private String resolveSource(AiResourceImportArtifact artifact) {
return metadata.get(METADATA_SOURCE);
}

private boolean hasWorkingVersion(ResourceVersionInfo info) {
return StringUtils.isNotBlank(info.getEditingVersion())
|| StringUtils.isNotBlank(info.getReviewingVersion());
private boolean hasEditingVersion(ResourceVersionInfo info) {
return info != null && StringUtils.isNotBlank(info.getEditingVersion());
}

private boolean hasReviewingVersion(ResourceVersionInfo info) {
return info != null && StringUtils.isNotBlank(info.getReviewingVersion());
}

private String resolveExistingWarning(ResourceVersionInfo info) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
import com.alibaba.nacos.api.ai.model.skills.SkillBasicInfo;
import com.alibaba.nacos.api.ai.model.skills.SkillMeta;
import com.alibaba.nacos.api.ai.model.skills.SkillSummary;
import com.alibaba.nacos.api.ai.model.skills.SkillUploadPrecheckRequest;
import com.alibaba.nacos.api.ai.model.skills.SkillUploadPrecheckResult;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.model.Page;

import java.util.List;
import java.util.Map;

/**
Expand All @@ -44,6 +47,16 @@ public interface SkillOperationService {
*/
String uploadSkillFromZip(SkillUploadRequest request) throws NacosException;

/**
* Batch precheck multiple skill uploads.
*
* @param requests list of precheck requests
* @return list of precheck results (same order as input)
* @throws NacosException if precheck failed unexpectedly
*/
List<SkillUploadPrecheckResult> batchPrecheckUploadSkill(
List<SkillUploadPrecheckRequest> requests) throws NacosException;

/**
* Batch upload multiple skills from a single zip archive. The zip must contain one-level subdirectories,
* each with its own SKILL.md. Uses best-effort strategy: processes all skills individually, returning
Expand Down
Loading
Loading