diff --git a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaProducer.java b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaProducer.java index 39d6f1c..7c75a87 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaProducer.java +++ b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/JsonSchemaProducer.java @@ -459,6 +459,9 @@ public class JsonSchemaProducer { if (!retriever.validationUrl().isBlank()) { propertySchema.put("x-retriever-validation-url", retriever.validationUrl()); } + if (!retriever.definitionUrl().isBlank()) { + propertySchema.put("x-retriever-definition-url", retriever.definitionUrl()); + } applySubFlowValidationMetadata(propertySchema, ownerClass, entry.getKey()); } } diff --git a/src/main/java/it/cnr/isti/workflow/manager/configurations/annotations/FieldRetriever.java b/src/main/java/it/cnr/isti/workflow/manager/configurations/annotations/FieldRetriever.java index 928dc49..aa411d9 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/configurations/annotations/FieldRetriever.java +++ b/src/main/java/it/cnr/isti/workflow/manager/configurations/annotations/FieldRetriever.java @@ -20,4 +20,14 @@ public @interface FieldRetriever { boolean structuredData() default false; boolean requiresAuth() default false; String validationUrl() default ""; + + /** + * Where the editor can read the full definition behind a chosen value, as {@code /}. + * + *

Set it only when that endpoint answers with an id, a name, an optional description and the + * content to show: the editor renders it as read-only text, so a payload of another shape (a + * JSON schema, say) would be displayed as gibberish. Left empty, the editor offers no such view, + * which is the right answer for a value that is already all there is to know about it. + */ + String definitionUrl() default ""; } diff --git a/src/main/java/it/cnr/isti/workflow/manager/skills/SkillBinding.java b/src/main/java/it/cnr/isti/workflow/manager/skills/SkillBinding.java index 56bf79b..6b2fd9c 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/skills/SkillBinding.java +++ b/src/main/java/it/cnr/isti/workflow/manager/skills/SkillBinding.java @@ -18,7 +18,8 @@ import lombok.Builder; public record SkillBinding( @NotBlank @JsonProperty(required = false) - @FieldRetriever(name = "Skills", url = "/retriever/Skills/items") + @FieldRetriever(name = "Skills", url = "/retriever/Skills/items", + definitionUrl = "/retriever/Skills/definitions") @UiLabel("skill") @UiDescription("Local skill loaded from the static skill catalog.") String skillId) { diff --git a/src/test/java/it/cnr/isti/workflow/manager/controllers/BlocksControllerTest.java b/src/test/java/it/cnr/isti/workflow/manager/controllers/BlocksControllerTest.java index 816aea0..85f3f62 100644 --- a/src/test/java/it/cnr/isti/workflow/manager/controllers/BlocksControllerTest.java +++ b/src/test/java/it/cnr/isti/workflow/manager/controllers/BlocksControllerTest.java @@ -651,6 +651,28 @@ public class BlocksControllerTest { assertFalse(schema.path("properties").path("skills").has("x-ui-defaults-when-empty")); } + @Test + public void aSkillSaysWhereItsFullDefinitionCanBeRead() { + // A chosen skill is only an id on the node, and the instructions it carries are the whole + // reason to pick one. The editor offers to show them wherever a retriever-backed value + // declares this URL, so it stays a property of the binding rather than a rule about skills + // written into the editor. + BlockConfigurationDescriptor descriptor = blocksController + .getConfigurationDescriptorForType(LLMBlockType.TYPE); + JsonNode schema = (JsonNode) descriptor.schema(); + + JsonNode skillBinding = findDefinition(schema, "SkillBinding"); + assertNotNull(skillBinding, "SkillBinding should be a schema definition"); + assertEquals("/retriever/Skills/definitions", + skillBinding.path("properties").path("skillId").path("x-retriever-definition-url").asText()); + + // The MCP binding's own endpoint answers with a JSON schema, not a readable definition, so + // it must not claim one: the editor would render it as text. + JsonNode mcpBinding = findDefinition(schema, "MCPToolServerBinding"); + assertNotNull(mcpBinding, "MCPToolServerBinding should be a schema definition"); + assertFalse(mcpBinding.path("properties").path("serverName").has("x-retriever-definition-url")); + } + private JsonNode findDefinition(JsonNode schema, String name) { for (String container : new String[] { "definitions", "$defs", "sharedDefinitions" }) { JsonNode found = schema.path(container).path(name);